如何在 SQL 中编写搜索查询
Posted
技术标签:
【中文标题】如何在 SQL 中编写搜索查询【英文标题】:how to write a search query in SQL 【发布时间】:2017-03-10 13:54:29 【问题描述】:我想写一个搜索查询。我有以下情况。
搜索结果有 3 个条件(ID、NAME、CITY)。 示例:
如果用户输入 ID = 123,那么 ID = 123 的所有行都应该 无论姓名和城市,都可以获取 如果用户输入 ID = 123 和 name = 'SAM',然后是 ID = 123 和 name = 'SAM' 的所有行 应该在不考虑城市的情况下获取 如果用户输入 ID = 123,name = 'SAM' 和 city = 'NY',然后是所有行 应该获取匹配项。【问题讨论】:
添加一些示例表数据和预期结果 - 以及格式化文本。同时向我们展示您当前的查询尝试。 @GordonLinoff 老实说,我认为这并不矛盾。如果只输入一个 ID,则返回具有该 ID 的所有行,然后其他 2 行只是缩小搜索结果的范围。 可能duplicate 【参考方案1】:一个典型的方法是这样的:
where (id = v_id or v_id is null) and
(name = v_name or v_name is null) and
(city = c_city or v_city is null)
【讨论】:
这样更准确。但正如第一条评论所说,发帖人应该提供更多信息,包括他们自己的尝试。【参考方案2】: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;
【讨论】:
【参考方案3】:虽然这是一个相对简单的示例和一个相对简单的解决方案,但我非常犹豫是否支持尝试构建一个通用 SQL 语句来处理所有可能性的方法。我见过一些非常复杂的例子,虽然它在功能上工作,但优化器不希望得到一个好的基数估计,然后是一个好的执行计划。 SQL 本质上不是一种过程语言。而 PL/SQL 或 Java 或 C# 或其他任何东西都是。因此,他们擅长条件:) 那么,为什么不做这样的事情,(逻辑上)
如果仅指定 ID,则运行此 SQL
select * from table where id = v_id;
;
如果指定了 ID 和名称,则运行此 SQL
select * from table
where id = v_id
and name = v_name
;
如果所有三个都指定,则运行此 SQL
select * from table
where id = v_id
and name = v_name
and city = v_city
结果是您拥有三个非常容易优化的 SQL 语句,而不是一个很难优化的语句。
现在的论点总是,“但我有一些可查询的字段,有太多的组合”。这是您赚取薪水(或咨询费!)的地方。您需要务实并将其分解为查询“集”,以便在针对每种可能组合的 SQL 和一个极其复杂的不可优化 SQL 之间做出合理的折衷。 我希望这是有道理的。
【讨论】:
【参考方案4】:我认为这会起作用:
SELECT *
FROM myTable AS t1
WHERE t1.ID = 123
OR (
t1.ID = 123
AND t1.NAME = 'SAM'
)
OR (
t1.ID = 123
AND t1.NAME = 'SAM'
AND t1.CITY = 'NY'
)
【讨论】:
以上是关于如何在 SQL 中编写搜索查询的主要内容,如果未能解决你的问题,请参考以下文章
VBA、Access 和 SQL 的新手。我如何编写一个 SQL 查询来搜索我的 AccessDB 中的一个表并在找到它时给我一个 True/False? [复制]
如何从程序向 Sql Server 和 MySql 发送查询?