Select 第一个记录如果 none 匹配

Select first record if none match

在 PostgreSQL 中,我想 select 基于某些条件的行,但如果没有行符合条件,我想 return 第一行。 table 实际上包含一个序号列,因此任务应该更容易(第一行是序号为 0 的行)。例如:

SELECT street, zip, city
FROM address
WHERE street LIKE 'Test%' OR ord = 0
LIMIT 1;

但是在这种情况下,没有办法保证匹配记录的顺序,我也没有什么可以排序的。使用单个 SELECT 语句执行此操作的方法是什么?

这样的东西怎么样...(我不熟悉 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;

你走在正确的轨道上。只需添加一个 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 提出了一个很好的观点,从索引使用的角度来看,WHERE 子句中的 OR 并不是最好的方法。我会将我的答案修改为:

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;

这允许查询使用两个索引(streetord)。请注意,这实际上只是因为 LIKE 模式不以通配符开头。如果 LIKE 模式以通配符开头,那么这种形式的查询仍会执行完整的 table 扫描。

您可以执行以下操作:

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)

I would like to select a row based on some criteria, but if no row matches the criteria, I would like to return the first row

更短(并且正确)

您实际上根本不需要 WHERE 子句 :

SELECT street, zip, city
FROM   address
ORDER  BY street !~~ 'Test%', ord
LIMIT  1;

!~~ 只是 NOT LIKE 的 Postgres 运算符。你可以使用任何一个。请注意,通过反转逻辑(NOT LIKE 而不是 LIKE),我们现在可以使用默认的 ASC 排序顺序和 NULL 最后排序,这可能很重要。继续阅读。

这个更短(但不一定更快)。它也与 .

略有不同(更可靠)

boolean表达式排序时,您必须了解它是如何工作的:

  • Sorting null values after all others, except special

当前接受的答案使用 ORDER BY <boolean expression> DESC,它将首先对 NULL 进行排序。在这种情况下,您通常应该添加 NULLS LAST:

  • PostgreSQL sort by datetime asc, null first?

如果 street 被定义 NOT NULL 这显然是无关紧要的,但是 not 在问题中被定义。 (总是 提供 table 定义。)当前接受的答案通过在 WHERE 子句中排除 NULL 值来避免该问题。

其他一些 RDBMS(MySQL、Oracle 等)没有像 Postgres 这样的正确 boolean 类型,因此我们经常看到来自这些产品的人的错误建议。

您当前的查询(以及当前接受的答案)需要 WHERE 子句 - 或至少 NULLS LASTORDER BY 中的不同表达式都没有必要。

更重要的是,但是,如果多行有匹配的 street(这是预料之中的),则返回的行将是任意的,并且可以在调用之间更改- 通常是不良影响。此查询选择具有最小 ord 的行来打破平局并生成 stable 结果。

这种形式也更灵活,因为它不依赖于 ord = 0 行的存在。相反,具有最小 ord 的行会被任意选择。

索引更快

(而且仍然正确。) 对于大的 tables,以下索引将从根本上提高此查询的性能:

CREATE INDEX address_street_pattern_ops_idx ON address(street text_pattern_ops);

详细解释:

  • PostgreSQL LIKE query performance variations

根据未定义的详细信息,可能需要向索引添加更多列。
使用此索引的最快查询:

(
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

顺便说一句,这是一个语句。

这更冗长,但允许更简单的查询计划。如果第一个 SELECT 产生了足够多的行(在我们的例子中:1),那么 UNION ALL 的第二个 SELECT 永远不会被执行。如果您使用 EXPLAIN ANALYZE 进行测试,您将在查询计划中看到 (never executed)

详情:

  • Way to try multiple SELECTs till a result is available?

评价UNION ALL

回复戈登的评论。 Per documentation:

Multiple UNION operators in the same SELECT statement are evaluated left to right, unless otherwise indicated by parentheses.

大胆强调我的。
并且 LIMIT 使 Postgres 在找到足够的行后立即停止评估。这就是为什么您在 EXPLAIN ANALYZE.

的输出中看到 (never executed)

如果在最终 LIMIT 之前添加外部 ORDER BY,则无法进行此优化。然后必须收集 所有 行以查看哪个可能首先排序。