Xây dựng Oracle động ở đâu


7

Tôi đang làm việc trên ứng dụng sử dụng truy vấn động để thực hiện một câu lệnh chọn dựa trên đầu vào của người dùng, sau khi thảo luận về bảo mật với các DBA, họ muốn tôi chuyển đổi câu lệnh chọn động của mình thành Thủ tục lưu trữ.

Tôi đã xây dựng sql động bằng MSSQL nhưng tôi không thể tìm ra cách chuyển đổi nó sang 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

* xin lưu ý rằng tôi muốn ngăn SQL tiêm, tôi không muốn thêm chuỗi với nhau

** Tôi đã xây dựng một lớp riêng để tạo truy vấn động này cho ứng dụng của mình trong .net Tôi có gần 1000 dòng mã để xử lý mọi thứ và ngăn chặn lệnh sql, nhưng các DBA đã nói với tôi rằng họ muốn các thủ tục được lưu trữ để họ có thể kiểm soát đầu vào và đầu ra.


Tại sao bạn lãng phí 1000 dòng mã, khi quy trình lưu trữ SQL-Server của bạn miễn dịch với sql tiêm cf dba.stackexchange.com/questions/790/... ?
bernd_k

Câu trả lời:


7

Điều này có thể cho bạn một ý tưởng:

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

Sau đó, trong 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;
/

Chỉnh sửa : Nhận xét là đúng và quy trình chịu sự tiêm SQL . Vì vậy, để ngăn chặn điều đó, bạn có thể sử dụng các biến liên kết, chẳng hạn như trong quy trình sửa đổi này:

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

Lưu ý rằng bây giờ, bất kể đầu vào là gì, câu lệnh select sẽ trở thành thứ gì select ... from ... where 1=1 and col1 like :1 and col2 :2 ...đó rõ ràng an toàn hơn nhiều.


1
nếu FirstN không null thì stmt: = stmt | | 'và c_firstname như' || Đầu tiên; kết thúc nếu; tôi có thể thấy rằng bạn trong ví dụ của bạn đã kết hợp chuỗi? Làm thế nào để bạn biết rằng người dùng không đặt sql tiêm trong tham số?
Vladimir Oselsky

é Giải pháp của bạn sử dụng các biến liên kết có vẻ rất hứa hẹn.
bernd_k

1
@Sauce Tôi đã thêm một câu trả lời thứ hai để chứng minh tại sao mã của René miễn dịch với việc tiêm sql
bernd_k

6

Bạn không nhất thiết cần SQL động chỉ vì một số điều kiện không áp dụng khi chúng không xuất hiện.

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)

Đặt mã này trong một thủ tục được lưu trữ bên trong một gói là một ý tưởng tuyệt vời.

Oracle cung cấp một số tài liệu tuyệt vời có thể giúp bạn tăng tốc về các thủ tục và gói được lưu trữ. Bạn có thể muốn bắt đầu với Hướng dẫn về khái niệm để hiểu về cách thức hoạt động của Oracle, sau đó chuyển sang Tham chiếu ngôn ngữ SQLTham chiếu ngôn ngữ PL / SQL để biết thông tin phù hợp với nhiệm vụ hiện tại của bạn.


Vì vậy, nếu tôi hiểu mã của bạn. bạn chỉ quan tâm đến biến mà không phải là rỗng ví dụ nếu tôi vượt qua FirstN và CID gì tôi thực hiện sẽ trông như thế này CHỌN C_FirstName, C_LastName, C_UserName, C_UserID TỪ KHÁCH HÀNG Ở ĐÂU C_FirstName NHƯ FirstN VÀ C_UserID NHƯ CID
Vladimir Oselsky

Những gì bạn thực hiện sẽ trông giống hệt nhau; việc sử dụng logic truy vấn sẽ quan tâm đến việc liệu biến được sử dụng để giới hạn các hàng từ kết quả hay không.
Leigh Riffel

Điều gì về việc xác nhận các biến để đảm bảo rằng ai đó không nhập sql tiêm?
Vladimir Oselsky

Giả sử bạn đặt cái này trong một gói, nó là SQL nhúng chứ không phải SQL động, do đó nó không dễ bị SQL tiêm. Để biết thêm thông tin, hãy xem whitepaper của Oracle "Cách viết PL / SQL bằng chứng tiêm SQL" tại oracle.com/technetwork/database/features/plsql/overview/ory
Leigh Riffel

Bỏ qua chuỗi caseinsensitive so sánh đây là một giải pháp chính xác. Nhìn vào hiệu suất của các truy vấn cá nhân, nó không phải là tối ưu. Khi được sử dụng trong một thủ tục được lưu trữ, nó không gây ra vấn đề khi nhiều truy vấn với các tham số khác nhau được thực thi. Nó là một cơ sở tốt để so sánh hiệu suất.
bernd_k

1

Đây không phải là câu trả lời độc lập, mà là một lời giải thích bổ sung cho mã của René Nyffalanger bằng các biến liên kết.

SaUce hỏi tại sao mã này miễn dịch với tiêm sql.

Ở đây tôi thay đổi mã của René để không thực thi câu lệnh động, nhưng để hiển thị nó:

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

Bây giờ tôi có thể thử các cuộc gọi như

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

Kết quả là:

chọn * từ Khách hàng trong đó 1 = 1 và FirstN như: 1

Trong mã của René, điều này sẽ được thực thi như sau:

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

Bạn thấy đấy, không quan trọng giá trị nào được cung cấp cho FirstN. Nó không bao giờ thay đổi ý nghĩa của truy vấn.

Có nhiều lý do hơn để sử dụng liên kết biến, rất khó nắm bắt đối với các nhà phát triển có nền tảng SQL-Server. Chúng phụ thuộc vào cách Oracle lưu trữ các kế hoạch thực hiện được biên dịch trước trong nhóm chia sẻ. Không sử dụng các biến liên kết sẽ cho các câu lệnh khác nhau và các kế hoạch thực hiện khác nhau, trong khi sử dụng các biến liên kết sử dụng một kế hoạch thực hiện duy nhất.


0

Đối với thủ tục được lưu trữ của bạn, việc di chuyển tốt nhất đến nhà tiên tri sẽ diễn ra như sau

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 muộn và tôi hơi lười biếng, nhưng điền vào 12 trường hợp còn lại là thẳng về phía trước.

Không sử dụng SQL động có một số lợi thế:

  1. Bạn có thể xác minh cú pháp của bạn tại thời gian biên dịch
  2. Bạn không cần phải loay hoay với bối cảnh

Đừng nghĩ bởi vì nó có vẻ nhàm chán đối với con người, rằng nó không tốt cho máy tính (đặc biệt là khi chạy Oracle).

Nhưng đừng thêm các tham số nữa chỉ để buộc tôi hiển thị giải pháp bằng cách sử dụng sql động, thay vào đó, tránh các thiết kế điên rồ yêu cầu các giải pháp đó.


Vâng, điều này là tốt nếu bạn chỉ có 3 tham số để đi qua. Điều gì về việc trải qua 10 thông số khác nhau? Tôi biết tôi có thể xây dựng điều này dễ dàng cho một cái gì đó mà ether có 1 hoặc hai tham số nhưng sẽ không dễ để mở rộng quy mô để chấp nhận các tham số khác nhau và thay đổi nội dung. Khi tôi hoàn thành 1 thủ tục, tôi cần viết thêm khoảng 6 cho nhiều tham số đầu vào.
Vladimir Oselsky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.