Gordon Linoff 提供的答案绝对是好的、可行的和直截了当的,但它并没有解决像你这样的情况下的性能问题。
也就是说,您正在搜索的表可能非常大并且具有各种索引(例如,在您的情况下,NAME 上的索引和CITY 上的另一个索引)。在这种情况下,一个简单的静态 SQL 方法可能会使用一个或另一个索引,并且在未提供它所使用的索引的标准的情况下表现不佳。 (我知道 Oracle 11g 引入了自适应游标共享——虽然我还没有看到它真的很有帮助。我想相信它可能会使我的答案过时,它应该使我的答案过时。但我只是还没有看到它真的很好用。我欢迎 cmets 讨论它)。
无论如何,如果您没有 11g 和/或不想严重依赖自适应游标共享,我认为当前编写此类搜索查询的最佳实践是使用 REF CURSOR。像这样:
-- Create a table to query from
CREATE TABLE matt1 ( id number, name varchar2(30), city varchar2(30) );
CREATE OR REPLACE PACKAGE matt_query_pkg AS
FUNCTION get_results ( p_id NUMBER, p_name VARCHAR2, p_city VARCHAR2 ) RETURN SYS_REFCURSOR;
END matt_query_pkg;
CREATE OR REPLACE PACKAGE BODY matt_query_pkg AS
FUNCTION get_results ( p_id NUMBER, p_name VARCHAR2, p_city VARCHAR2 )RETURN SYS_REFCURSOR IS
l_rc SYS_REFCURSOR;
l_sql VARCHAR2(32000);
BEGIN
l_sql := 'SELECT id, name, city FROM matt1 WHERE 1=1';
if p_id IS NULL THEN
l_sql := l_sql || ' AND (1=1 OR :b_id IS NULL)';
else
l_sql := l_sql || ' AND (id = :b_id)';
end if;
if p_name IS NULL THEN
l_sql := l_sql || ' AND (1=1 OR :b_name IS NULL)';
else
l_sql := l_sql || ' AND (name = :b_name)';
end if;
if p_city IS NULL THEN
l_sql := l_sql || ' AND (1=1 OR :b_city IS NULL)';
else
l_sql := l_sql || ' AND (id = :b_city)';
end if;
dbms_output.put_line('Executing:');
dbms_output.put_line(l_sql);
OPEN l_rc FOR l_sql USING p_id, p_name, p_city;
RETURN l_rc;
END get_results;
END matt_query_pkg;
在仅给出ID 和NAME 条件的示例情况下,它将生成如下SQL:
SELECT id, name, city
FROM matt1
WHERE 1=1
AND (id = :b_id)
AND (name = :b_name)
AND (1=1 OR :b_city IS NULL)
Oracle 的优化器将排除 (1=1 OR :b_city IS NULL) 子句(因为它知道它始终是 true),留下一条 SQL,然后它可以针对给定的条件专门优化。
注意:添加(1=1 OR :b_city IS NULL) 子句的目的是保持绑定变量的数量不变,因此您始终可以使用以下命令运行搜索:
OPEN l_rc FOR l_sql USING p_id, p_name, p_city;
如果您没有加入那个 (1=1 OR...) 子句,那么您必须为每个可能的 null/not null 输入参数组合使用不同的 OPEN..FOR 语句。
这里有一些测试代码,供参考:
-- Test it
insert into matt1 values (1, 'Fred', 'New York');
insert into matt1 values (2, 'Fred', 'Philadelphia');
insert into matt1 values (3, 'John', 'Philadelphia');
insert into matt1 values (4, 'Mark', 'Philadelphia');
insert into matt1 values (5, 'Mark', 'Chicago');
commit;
declare
l_rc SYS_REFCURSOR;
l_id NUMBER;
l_name VARCHAR2(30);
l_city VARCHAR2(30);
begin
l_rc := matt_query_pkg.get_results (NULL, 'Fred', NULL);
loop
fetch l_rc INTO l_id, l_name, l_city;
exit when l_rc%NOTFOUND;
dbms_output.put_line ('Found: ' || l_id || ', ' || l_name || ', ' || l_city);
end loop;
end;