2010-09-06 29 views
7

Nói rằng tôi có một bảng mẫu:Cách xử lý các tham số tùy chọn trong truy vấn SQL?

id_pk value 
------------ 
1  a 
2  b 
3  c 

Và tôi có một/block SQL mẫu PL, trong đó có một truy vấn mà hiện chọn một hàng duy nhất vào một mảng:

declare 

    type t_table is table of myTable%rowtype; 

    n_RequiredId myTable.id_pk%type := 1; 
    t_Output t_table := t_table(); 

begin 

    select m.id_pk, m.value 
    bulk collect into t_Output 
    from myTable m 
    where m.id_pk = n_RequiredId; 

end; 

Những gì tôi cần cần làm là triển khai khả năng chọn một hàng đơn lẻ thành mảng, như được hiển thị trong khối ở trên, HOẶC để chọn tất cả các hàng thành một mảng, nếu n_RequiredID, thực tế là tham số do người dùng nhập, được đặt thành null .

Và, câu hỏi là, cách tốt nhất để xử lý tình huống như thế nào?

tôi có thể nghĩ về sửa đổi where khoản truy vấn của tôi với một cái gì đó như thế này:

where m.id_pk = nvl(n_RequiredId, m.id_pk); 

nhưng tôi cho rằng điều đó sẽ làm chậm truy vấn nếu tham số sẽ không được null, và tôi nhớ Kyte cho biết một cái gì đó thực sự xấu về cách tiếp cận này.

Tôi cũng có thể nghĩ đến việc thực hiện logic PL/SQL sau:

if n_RequiredId is null then 

    select m.id_pk, m.value bulk collect into t_Output from myTable m; 

else 

    select m.id_pk, m.value bulk collect 
    into t_Output 
    from myTable m 
    where m.id_pk = n_RequiredId; 

end if; 

Nhưng sẽ trở nên quá phức tạp nếu tôi gặp phải nhiều hơn một tham số của loại hình này.

Bạn sẽ tư vấn cho tôi điều gì?

Trả lời

14

Vâng, sử dụng bất kỳ những điều sau đây:

WHERE m.id_pk = NVL(n_RequiredId, m.id_pk); 
WHERE m.id_pk = COALESCE(n_RequiredId, m.id_pk); 
WHERE (n_RequiredId IS NULL OR m.id_pk = n_RequiredId); 

... là not sargable. Họ sẽ làm việc, nhưng thực hiện tồi tệ nhất của các tùy chọn có sẵn.

Nếu bạn chỉ có một tham số, câu lệnh IF/ELSE và riêng biệt, được thiết kế riêng là một lựa chọn tốt hơn.

Tùy chọn tiếp theo sau đó là dynamic SQL. Nhưng việc mã hóa SQL động là vô dụng nếu bạn mang theo các biến vị ngữ không sargable trong ví dụ đầu tiên. Dynamic SQL cho phép bạn điều chỉnh truy vấn trong khi chứa nhiều đường dẫn. Nhưng nó cũng có nguy cơ SQL injection, vì vậy nó nên được thực hiện sau các truy vấn tham số (tốt nhất là trong vòng thủ tục lưu trữ/chức năng trong các gói.

+0

Rất tuyệt! Điều này tiết kiệm trong ngày của tôi. Tuy nhiên, cái thứ nhất không làm việc coz nếu cột là nullable, mệnh đề này sẽ lọc giá trị null trong khi trong câu hỏi bạn sẽ không lọc các giá trị null trong trường hợp thứ hai. –

4

OMG_Ponies' và câu trả lời Rob van Wijk của là hoàn toàn đúng, đây chỉ là bổ sung.

Có một mẹo hay để làm cho nó dễ sử dụng các biến liên kết và vẫn sử dụng SQL động Nếu bạn đặt tất cả các liên kết trong mệnh đề with ở đầu, bạn luôn có thể liên kết cùng một tập các biến, cho dù bạn có định Sử dụng chúng

Ví dụ: giả sử bạn có ba tham số, đại diện cho phạm vi ngày và ID.Nếu bạn muốn chỉ cần tìm kiếm trên ID, bạn có thể đặt câu hỏi với nhau như thế này:

with parameters as (
    select :start_date as start_date, 
      :end_date as end_date, 
      :search_id as search_id 
    from dual) 
select * 
from your_table 
    inner join parameters 
     on parameters.search_id = your_table.id; 

Mặt khác, nếu bạn cần phải tìm kiếm trên ID và phạm vi ngày, nó có thể trông như thế này:

with parameters as (
    select :start_date as start_date, 
      :end_date as end_date, 
      :search_id as search_id 
    from dual) 
select * 
from your_table 
    inner join parameters 
     on parameters.search_id = your_table.id 
      and your_table.create_date between (parameters.start_date 
               and parameters.end_date); 

Điều này có vẻ giống như cách xử lý vấn đề này, nhưng kết quả cuối cùng là cho dù bạn phức tạp với SQL động nào, miễn là nó chỉ cần ba tham số đó, cuộc gọi PL/SQL luôn là một cái gì đó như:

execute immediate v_SQL using v_start_date, v_end_date, v_search_id; 

Theo kinh nghiệm của tôi, tốt hơn là làm cho việc xây dựng SQL phức tạp hơn một chút để đảm bảo rằng chỉ có một dòng nơi nó thực sự được thực hiện.

+0

Giải pháp này trông rõ ràng hơn so với Rob (bài viết của Tom Kyte) nhưng sau khi thử nghiệm trên Oracle 10.2.0.5, tôi thấy rằng trình tối ưu hóa hoạt động tốt hơn (ví dụ: chọn thêm chỉ mục chọn lọc) với aproach của Tom. – Ochoto

2

Phương pháp NVL thường hoạt động tốt. Trình tối ưu hóa nhận dạng mẫu này và sẽ xây dựng một kế hoạch động. Kế hoạch sử dụng một chỉ mục cho một giá trị duy nhất và quét toàn bộ bảng cho một NULL.

bảng mẫu và các dữ liệu

drop table myTable; 
create table myTable(
    id_pk number, 
    value varchar2(100), 
    constraint myTable_pk primary key (id_pk) 
); 

insert into myTable select level, level from dual connect by level <= 100000; 
commit; 

Execute với các vị từ khác nhau

--Execute predicates that return one row if the ID is set, or all rows if ID is null. 
declare 
    type t_table is table of myTable%rowtype; 
    n_RequiredId myTable.id_pk%type := 1; 
    t_Output t_table := t_table(); 
begin 
    select /*+ SO_QUERY_1 */ m.id_pk, m.value 
    bulk collect into t_Output 
    from myTable m 
    where m.id_pk = nvl(n_RequiredId, m.id_pk); 

    select /*+ SO_QUERY_2 */ m.id_pk, m.value 
    bulk collect into t_Output 
    from myTable m 
    where m.id_pk = COALESCE(n_RequiredId, m.id_pk); 

    select /*+ SO_QUERY_3 */ m.id_pk, m.value 
    bulk collect into t_Output 
    from myTable m 
    where (n_RequiredId IS NULL OR m.id_pk = n_RequiredId); 
end; 
/

thực hiện kế hoạch Nhận

select sql_id, child_number 
from gv$sql 
where lower(sql_text) like '%so_query_%' 
    and sql_text not like '%QUINE%' 
    and sql_text not like 'declare%'; 

select * from table(dbms_xplan.display_cursor(sql_id => '76ucq3bkgt0qa', cursor_child_no => 1, format => 'basic')); 
select * from table(dbms_xplan.display_cursor(sql_id => '4vxf8yy5xd6qv', cursor_child_no => 1, format => 'basic')); 
select * from table(dbms_xplan.display_cursor(sql_id => '457ypz0jpk3np', cursor_child_no => 1, format => 'basic')); 

kế hoạch xấu đối với các liên hiệp và IS NULL OR

EXPLAINED SQL STATEMENT: 
------------------------ 
SELECT /*+ SO_QUERY_2 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK 
= COALESCE(:B1 , M.ID_PK) 

Plan hash value: 1229213413 

------------------------------------- 
| Id | Operation   | Name | 
------------------------------------- 
| 0 | SELECT STATEMENT |   | 
| 1 | TABLE ACCESS FULL| MYTABLE | 
------------------------------------- 


EXPLAINED SQL STATEMENT: 
------------------------ 
SELECT /*+ SO_QUERY_3 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE (:B1 IS 
NULL OR M.ID_PK = :B1) 

Plan hash value: 1229213413 

------------------------------------- 
| Id | Operation   | Name | 
------------------------------------- 
| 0 | SELECT STATEMENT |   | 
| 1 | TABLE ACCESS FULL| MYTABLE | 
------------------------------------- 

Tốt kế hoạch NVL

Các FILTER hoạt động cho phép tối ưu hóa để lựa chọn một kế hoạch khác nhau tại thời gian chạy, tùy thuộc vào giá trị đầu vào.

EXPLAINED SQL STATEMENT: 
------------------------ 
SELECT /*+ SO_QUERY_1 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK 
= NVL(:B1 , M.ID_PK) 

Plan hash value: 730481884 

---------------------------------------------------- 
| Id | Operation      | Name  | 
---------------------------------------------------- 
| 0 | SELECT STATEMENT    |   | 
| 1 | CONCATENATION    |   | 
| 2 | FILTER      |   | 
| 3 | TABLE ACCESS FULL   | MYTABLE | 
| 4 | FILTER      |   | 
| 5 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 
| 6 |  INDEX UNIQUE SCAN   | MYTABLE_PK | 
---------------------------------------------------- 

Warnings

FILTER hoạt động và NVL thủ thuật này vẫn chưa được ghi nhận. Tôi không chắc phiên bản nào đã giới thiệu các tính năng này nhưng nó hoạt động với 11g. Tôi đã gặp sự cố khi nhận được FILTER để hoạt động chính xác với một số truy vấn phức tạp, nhưng đối với các truy vấn đơn giản như vậy, nó đáng tin cậy.

Các vấn đề liên quan