在 PostgreSQL 中,我想根据某些条件选择一行,但如果没有行符合条件,我想返回第一行。该表实际上包含一个序数列,因此任务应该更容易(第一行是序数为 0 的行)。例如:
SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%' OR ord = 0
LIMIT 1;
但是在这种情况下,没有办法保证匹配的记录的顺序,我也没有什么可以排序的。使用单个SELECT
语句执行此操作的方法是什么?
在 PostgreSQL 中,我想根据某些条件选择一行,但如果没有行符合条件,我想返回第一行。该表实际上包含一个序数列,因此任务应该更容易(第一行是序数为 0 的行)。例如:
SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%' OR ord = 0
LIMIT 1;
但是在这种情况下,没有办法保证匹配的记录的顺序,我也没有什么可以排序的。使用单个SELECT
语句执行此操作的方法是什么?
你在正确的轨道上。只需添加一个order by
:
SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%' OR ord = 0
ORDER BY (CASE WHEN street LIKE 'Test%' THEN 1 ELSE 0 END) DESC
LIMIT 1;
或者,交替:
ORDER BY ord DESC
这些中的任何一个都会将ord = 0
行放在最后。
编辑:
Erwin 提出了一个很好的观点,即从索引使用的角度来看,OR
inWHERE
子句并不是最好的方法。我会将我的答案修改为:
SELECT *
FROM ((SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%'
LIMIT 1
)
UNION ALL
(SELECT street, zip, city
FROM address
WHERE ord = 0
LIMIT 1
)
) t
ORDER BY (CASE WHEN street LIKE 'Test%' THEN 1 ELSE 0 END) DESC
LIMIT 1;
这允许查询使用两个索引(street
和ord
)。请注意,这实际上只是因为该LIKE
模式不以通配符开头。如果LIKE
模式以通配符开头,那么这种形式的查询仍然会进行全表扫描。
我想根据某些条件选择一行,但如果没有行符合条件,我想返回第一行
您实际上根本不需要WHERE
子句:
SELECT street, zip, city
FROM address
ORDER BY street !~~ 'Test%', ord
LIMIT 1;
!~~
只是 Postgres 运算符NOT LIKE
。你可以使用任何一个。请注意,通过反转逻辑(NOT LIKE
而不是LIKE
),我们现在可以使用默认ASC
排序顺序和 NULL 排序最后,这可能很重要。继续阅读。
这更短(但不一定更快)。它也与@Gordon 当前接受的答案略有不同(更可靠) 。
当按表达式排序boolean
时,您必须了解它是如何工作的:
当前接受的答案使用ORDER BY <boolean expression> DESC
,它将首先对 NULL 进行排序。在这种情况下,您通常应该添加NULLS LAST
:
如果street
定义NOT NULL
了这显然是无关紧要的,但问题中没有定义。(始终提供表定义。)当前接受的答案通过在WHERE
子句中排除 NULL 值来避免问题。
其他一些 RDBMS(MySQL、Oracle 等)没有boolean
像 Postgres 这样的正确类型,因此我们经常看到来自这些产品的人的错误建议。
您当前的查询(以及当前接受的答案)需要该WHERE
子句 - 或至少NULLS LAST
. 用不同的表达方式ORDER BY
都没有必要。
然而,更重要的是,如果多行匹配street
(这是意料之中的),则返回的行将是任意的,并且可能在调用之间发生变化——通常是一种不良影响。此查询选择最小的行ord
来打破平局并产生稳定的结果。
这种形式也更加灵活,因为它不依赖于行的存在ord = 0
。ord
相反,无论哪种方式都选择最小的行。
(而且仍然正确。)对于大表,以下索引将从根本上提高此查询的性能:
CREATE INDEX address_street_pattern_ops_idx ON address(street text_pattern_ops);
详细解释:
根据未定义的详细信息,可能需要向索引添加更多列。
使用此索引的最快查询:
(
SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%'
ORDER BY ord -- or something else?
-- LIMIT 1 -- you *could* add LIMIT 1 in each leg
)
UNION ALL
(
SELECT street, zip, city
FROM address
ORDER BY ord
-- LIMIT 1 -- .. but that's not improving anything in *this* case
)
LIMIT 1
顺便说一句,这是一个单一的声明。
这更冗长,但允许更简单的查询计划。如果第一个产生足够的行(在我们的例子中:1),则第二个SELECT
永远不会执行。如果您使用 进行测试,您将在查询计划中看到。UNION ALL
SELECT
EXPLAIN ANALYZE
(never executed)
细节:
UNION ALL
回复戈登的评论。根据文档:
除非括号中另有说明,否则
UNION
同一SELECT
语句中 的多个运算符从左到右求值。
大胆强调我的。
并LIMIT
让 Postgres 在找到足够的行后立即停止评估。这就是为什么您(never executed)
在EXPLAIN ANALYZE
.
ORDER BY
如果在 final 之前添加外部,LIMIT
则无法进行此优化。然后必须收集所有行以查看哪些可能首先排序。
像这样的东西怎么样......(我不熟悉 PostgreSQL,所以语法可能略有偏差)
SELECT street, zip, city, 1 as SortOrder
FROM address
WHERE street LIKE 'Test%'
--
union all
--
SELECT street, zip, city, 2 as SortOrder
FROM address
WHERE ord = 0
ORDER BY SortOrder
LIMIT 1;
您可以执行以下操作:
SELECT street, zip, city
FROM address
WHERE (EXISTS(SELECT * FROM address WHERE street LIKE 'Test%') AND street LIKE 'Test%') OR
(NOT EXISTS(SELECT * FROM address WHERE street LIKE 'Test%') AND ord = 0)