如果没有匹配则选择第一条记录

时间:2015-06-10 10:38:57

标签: sql postgresql sql-limit

在PostgreSQL中,我想根据某些条件选择一行,但如果没有符合条件的行,我想返回第一行。该表实际上包含一个序数列,因此任务应该更容易(第一行是序号为0的那一行)。例如:

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

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

4 个答案:

答案 0 :(得分:5)

你走在正确的轨道上。只需添加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子句中的WHERE不是最好的方法。我会修改我的答案:

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模式以通配符开头,则此查询形式仍将执行全表扫描。

答案 1 :(得分:3)

  

我想根据某些条件选择一行,但如果没有行   符合标准,我想返回第一行

更短(和更正确)

您根本不需要WHERE

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

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

这更短(但不一定更快)。它与currently accepted answer by @Gordon具有微妙的差异(更可靠)。

boolean表达式排序时,您必须了解其工作原理:

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

如果定义street NOT NULL,这显然无关紧要,但问题中已定义 。 (始终提供表定义。)当前接受的答案通过排除WHERE子句中的NULL值来避免此问题。

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

您当前的查询(以及当前接受的答案)需要 WHERE子句 - 或至少NULLS LASTORDER 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

BTW,这是一个语句。

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

详细说明:

UNION ALL

的评估

回复戈登的评论。 Per documentation:

  

评估同一UNION语句中的多个SELECT运算符   从左到右,除非括号另有说明。

大胆强调我的 并LIMIT使Postgres在找到足够的行后立即停止评估。这就是为什么您在(never executed)的输出中看到EXPLAIN ANALYZE的原因。

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

答案 2 :(得分:2)

这样的事情怎么样......(我对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;

答案 3 :(得分:0)

您可以执行以下操作:

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)