web-dev-qa-db-ja.com

動的Oracle Where句の作成

動的クエリを使用してユーザー入力に基づいて選択ステートメントを実行するアプリケーションに取り組んでいます。DBAとセキュリティについて話し合った後、動的選択ステートメントをストアドプロシージャに変換することを望んでいます。

MSSQLを使用して動的SQLを構築しましたが、それをOracle SQLに変換する方法がわかりません。

CREATE PROCEDURE GetCustomer
@FirstN nvarchar(20) = NULL,
@LastN nvarchar(20) = NULL,
@CUserName nvarchar(10) = NULL, 
@CID nvarchar(15) = NULL as
DECLARE @sql nvarchar(4000),
SELECT @sql = 'C_FirstName, C_LastName, C_UserName, C_UserID ' + 
'FROM CUSTOMER ' +
'WHERE 1=1 ' +

IF @FirstN  IS NOT NULL
SELECT @sql = @sql + ' AND C_FirstName like @FirstN '
IF @LastN  IS NOT NULL 
SELECT @sql = @sql + ' AND C_LastName like @LastN '
IF @CUserName IS NOT NULL
SELECT @sql = @sql + ' AND C_UserName like @CUserName '
IF @CID IS NOT NULL 
SELECT @sql = @sql + ' AND C_UserID like @CID '
EXEC sp_executesql @sql, N'@C_FirstName nvarchar(20), @C_LastName nvarchar(20), @CUserName nvarchar(10), @CID nvarchar(15)',
                   @FirstN, @LastN, @CUserName, @CID

* SQLインジェクションを防ぎたいので、文字列を一緒に追加したくないだけであることに注意してください

**私は.netで私のアプリケーション用にこの動的クエリを作成するための別のクラスを構築しました。すべてを処理し、SQLインジェクションを防ぐためのほぼ1000行のコードがありますが、DBAは、ストアドプロシージャが入力を制御できるようにして、出力。

7

これはあなたにアイデアを与えるかもしれません:

create table Customer (
  c_firstname varchar2(50),
  c_lastname  varchar2(50),
  c_userid    varchar2(50)
);

insert into Customer values ('Micky' , 'Mouse', 'mm');
insert into Customer values ('Donald', 'Duck' , 'dd');
insert into Customer values ('Peter' , 'Pan'  , 'pp');

create or replace function GetCustomer(
  FirstN    varchar2 := null,
  LastN     varchar2 := null,
  CID       varchar2 := null
) return sys_refcursor
as
  stmt varchar2(4000);
  ret sys_refcursor;
begin
  stmt := 'select * from Customer where 1=1';

  if  FirstN is not null then
      stmt := stmt || ' and c_firstname like ''%' || FirstN || '%''';
  end if;

  if  LastN is not null then
      stmt := stmt || ' and c_lastname like ''%' || LastN  || '%''';
  end if;

  if  CID is not null then
      stmt := stmt || ' and c_userid like ''%' || CID || '%''';
  end if;

  dbms_output.put_line(stmt);

  open ret for stmt;
  return ret;
end;
/

その後、SQL * Plusで:

set serveroutput on size 100000 format wrapped

declare
  c sys_refcursor;
  fn Customer.c_firstname%type;
  ln Customer.c_lastname %type;
  id Customer.c_userid   %type;
begin
  c := GetCustomer(LastN => 'u');

  fetch c into fn, ln, id;
  while  c%found loop
      dbms_output.put_line('First Name: ' || fn);
      dbms_output.put_line('Last Name:  ' || ln);
      dbms_output.put_line('user id:    ' || id);

      fetch c into fn, ln, id;
  end loop;

  close c;
end;
/

編集:コメントは正しく、手順はSQLインジェクションの対象になります。したがって、それを防ぐために、次の変更された手順のようなバインド変数を使用できます。

create or replace function GetCustomer(
  FirstN    varchar2 := null,
  LastN     varchar2 := null,
  CID       varchar2 := null
) return sys_refcursor
as
  stmt varchar2(4000);
  ret  sys_refcursor;

  type parameter_t is table of varchar2(50);
  parameters parameter_t := parameter_t();
begin
  stmt := 'select * from Customer where 1=1';

  if  FirstN is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || FirstN || '%';
      stmt := stmt || ' and c_firstname like :' || parameters.count;
  end if;

  if  LastN is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || LastN || '%';
      stmt := stmt || ' and c_lastname like :' || parameters.count;
  end if;

  if  CID is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || CID || '%';
      stmt := stmt || ' and c_userid like :' || parameters.count;
  end if;


  if    parameters.count = 0 then
        open ret for stmt;
  elsif parameters.count = 1 then
        open ret for stmt using parameters(1);
  elsif parameters.count = 2 then
        open ret for stmt using parameters(1), parameters(2);
  elsif parameters.count = 3 then
        open ret for stmt using parameters(1), parameters(2), parameters(3);
  else  raise_application_error(-20800, 'Too many parameters');
  end   if;

  return ret;
end;
/

注意してください、今、入力が何であれ、selectステートメントはselect ... from ... where 1=1 and col1 like :1 and col2 :2 ...これは明らかにはるかに安全です。

7

特定のwhere条件が存在しないときに適用されないという理由だけで、動的SQLは必ずしも必要ではありません。

SELECT 
    C_FirstName, C_LastName, C_UserName, C_UserID 
FROM 
    CUSTOMER
WHERE 
    (FirstN IS NULL OR C_FirstName LIKE FirstN)
    AND (LastN IS NULL OR C_LastName LIKE LastN)
    AND (CUserName IS NULL OR C_UserName LIKE CUserName)
    AND (CID IS NULL OR C_UserID LIKE CID)

このコードをパッケージ内のストアドプロシージャに配置することは、優れたアイデアです。

Oracleは、ストアドプロシージャとパッケージをすばやく理解できる優れたドキュメントをいくつか提供しています。 コンセプトガイド から始めてOracleの仕組みを理解してから、 SQL言語リファレンスPL/SQL言語)に進んでください。参照 現在のタスクに関連する情報。

6
Leigh Riffel

これは独立した回答ではありませんが、バインド変数を使用したRenéNyffeneggerのコードに対する追加の説明です。

ソースは、このコードがSQLインジェクションの影響を受けない理由を尋ねました。

ここで、動的ステートメントを実行せずに表示するようにルネのコードを変更します。

create or replace function GetCustomer(
  FirstN    varchar2 := null,
  LastN     varchar2 := null,
  CID       varchar2 := null
) return sys_refcursor
as
  stmt varchar2(4000);
  ret  sys_refcursor;

  type parameter_t is table of varchar2(50);
  parameters parameter_t := parameter_t();
begin
  stmt := 'select * from Customer where 1=1';

  if  FirstN is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || FirstN || '%';
      stmt := stmt || ' and c_firstname like :' || parameters.count;
  end if;

  if  LastN is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || LastN || '%';
      stmt := stmt || ' and c_lastname like :' || parameters.count;
  end if;

  if  CID is not null then
      parameters.extend;
      parameters(parameters.count) := '%' || CID || '%';
      stmt := stmt || ' and c_userid like :' || parameters.count;
  end if;


   OPEN ret for SELECT stmt FROM DUAL;


  return ret;
end;
/

今私はのような呼び出しを試すことができます

Var r refcursor
exec  GetCustomer(:r, 'Micky', '')
print r

結果は次のとおりです。

1 *でFirstNのようなFirstNのCustomerから*を選択します。

Renéのコードでは、これは次のように実行されます。

select * from Customer where 1=1  and FirstN like :1 using 'Micky'

ご覧のとおり、FirstNに指定する値は問題ではありません。クエリの意味が変わることはありません。

変数バインディングを使用する理由は他にもありますが、SQL-Serverのバックグラウンドを持っている開発者にとっては理解が困難です。これらは、Oracleがプリコンパイルされた実行プランを共有プールに格納する方法に依存します。バインド変数を使用しないと、異なるステートメントと異なる実行プランが提供されますが、バインド変数を使用すると、単一の実行プランが使用されます。

1
bernd_k

ストアドプロシージャの場合、Oracleへの最適な移行は次のようになります。

CREATE or replace PROCEDURE GetCustomer 
    p_FirstN nvarchar2 := NULL, 
    p_LastN nvarchar2 := NULL, 
    p_CUserName nvarchar2 := NULL, 
    p_CID nvarchar2 := NULL, 
    MyRefCursor IN OUT typRefCursor
as 
begin   

    IF p_FirstN IS NULL then
        if p_p_LastN is null then
            if p_CUserName is null then
                if  p_CID is null then
                    Open MyRefCursor for Select C_FirstName, C_LastName, C_UserName, C_UserID FROM CUSTOMER; 
                else
                    Open MyRefCursor for Select C_FirstName, C_LastName, C_UserName, C_UserID FROM CUSTOMER WHERE upper(C_UserID) like upper(p_CID) ; 
                end;        
            else
                if  p_CID is null then
                    Open MyRefCursor for Select C_FirstName, C_LastName, C_UserName, C_UserID FROM CUSTOMER WHERE UPPER(C_UserName) like UPPER(p_CUserName); 
                else
                    Open MyRefCursor for Select C_FirstName, C_LastName, C_UserName, C_UserID FROM CUSTOMER WHERE upper(C_UserID) like upper(p_CID) and UPPER(C_UserName) like UPPER(p_CUserName); 
                end;        
            end if;
        else
            if p_CUserName is null then
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            else
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            end if;
        end if;
    else
        if p_p_LastN is null then
            if p_CUserName is null then
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            else
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            end if;
        else
            if p_CUserName is null then
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            else
                if  p_CID is null then
                    ...
                else
                    ...
                end;        
            end if;
        end if;
    end if; 

end;
/

遅くなりましたが、私は少し怠惰ですが、残りの12件のケースへの入力は簡単です。

動的SQLを使用しないことには、いくつかの利点があります。

  1. コンパイル時に構文を確認できます
  2. コンテキストをいじる必要はありません

人間にとっては退屈に見えるので、コンピュータにとっては悪いことだとは思わないでください(特にOracleを実行している場合)。

ただし、動的SQLを使用してソリューションを強制的に表示するためだけにパラメーターを追加しないでください。代わりに、そのようなソリューションを必要とする非常識なデザインを避けてください。

0
bernd_k