it-swarm-fr.com

Bâtiment dynamique oracle où la clause

Je travaille sur demande qui utilise une requête dynamique pour effectuer une instruction SELECT en fonction de la saisie de l'utilisateur, après avoir discuté de la sécurité avec DBAS, ils veulent que je convertit mon énoncé de sélection dynamique en procédure stockée.

J'ai construit SQL dynamique à l'aide de MSSQL mais je ne peux pas comprendre comment le convertir en 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

* Veuillez noter que je veux empêcher l'injection SQL, je ne veux pas simplement ajouter une chaîne ensemble

** J'ai construit une classe distincte pour créer cette requête dynamique pour mon application dans .net, j'ai presque 1 000 lignes de code pour tout gérer et empêcher l'injection SQL, mais DBAS m'a dit qu'ils souhaitaient des procédures stockées afin de pouvoir contrôler les intrants et production.

7
Vladimir Oselsky

Cela pourrait vous donner une idée:

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;
/

Plus tard, dans 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;
/

Edit: Le commentaire a raison et la procédure est soumise à Injection SQL . Ainsi, afin d'éviter cela, vous pouvez utiliser des variables liées telles que dans cette procédure modifiée:

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;
/

Notez que maintenant, quelle que soit l'entrée, la déclaration SELECT devient quelque chose comme select ... from ... where 1=1 and col1 like :1 and col2 :2 ... qui est évidemment beaucoup plus sûr.

7
René Nyffenegger

Vous n'avez pas nécessairement besoin de SQL dynamique simplement parce que certaines conditions ne s'appliquent pas quand elles ne sont pas présentes.

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)

Placer ce code dans une procédure stockée à l'intérieur d'un package est une excellente idée.

Oracle fournit une excellente documentation qui peut vous mettre au courant des procédures et des packages stockés. Vous voudrez peut-être commencer avec le Guide des concepts pour comprendre comment Oracle fonctionne, puis passez à la section référence de langue SQL et langue PL/SQL). Référence Pour des informations pertinentes pour votre tâche actuelle.

6
Leigh Riffel

Ce n'est pas une réponse indépendante, mais une explication ajoutée au code de René Nyffenegger en utilisant des variables liées.

Sauce a demandé pourquoi ce code est immunisé contre l'injection SQL.

Ici, je modifie le code de René pour ne pas exécuter la déclaration dynamique, mais pour l'afficher:

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;
/

Maintenant je peux essayer des appels comme

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

Le résultat est:

sélectionnez * du client où 1 = 1 et firstn comme: 1

Dans le code de René, cela sera exécuté comme suit:

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

Vous voyez, peu importe la valeur fournie pour le premier. Cela ne change jamais la signification de la requête.

Il existe d'autres raisons d'utiliser une liaison variable, qui sont difficiles à saisir pour les développeurs avec un arrière-plan SQL-Server. Ils dépendent de la manière dont Oracle stocke des plans d'exécution précompilés dans le pool partagé. L'utilisation de variables de liaison donne différentes états et différents plans d'exécution, tout en utilisant des variables de liaison utilise un plan d'exécution unique.

1
bernd_k

Pour votre procédure stockée, la meilleure migration vers Oracle irait comme

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;
/

Ok, c'est tard et je suis un peu paresseux, mais les 12 cas restants sont tout droit.

Ne pas utiliser SQL dynamique a quelques avantages:

  1. Vous pouvez vérifier votre syntaxe à la compilation
  2. Vous n'avez pas à jouer avec des contextes

Ne pensez pas parce que cela semble ennuyeux pour un humain, qu'il est mauvais pour un ordinateur (surtout lors de la course à l'oracle).

Mais n'ajoutez pas d'autres paramètres uniquement pour me forcer à montrer une solution à l'aide de SQL dynamique, évitez plutôt d'éviter des conceptions insensées nécessitant de telles solutions.

0
bernd_k