【问题标题】:Select first record if none match如果没有匹配,则选择第一条记录
【发布时间】:2015-08-25 13:32:02
【问题描述】:

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

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

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

【问题讨论】:

  • 您只需要设置ORDER BY street LIKE 'Test%' DESC, ord = 0 DESC。如果始终存在 ord = 0 行,您也可以删除 WHERE 子句。
  • @pozs:好点 - 除非street 可以为 NULL(这是地址数据中的常见情况)。我在回答中写了更多内容。

标签: sql postgresql sql-limit


【解决方案1】:

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

更短(且正确)

您实际上根本不需要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

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

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

详情:

评估UNION ALL

回复戈登的评论。 Per documentation:

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

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

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

【讨论】:

  • 欧文。 . .在 Postgres 中,我不按布尔值排序(那是潜入的 MySQL 主义),所以我什至在阅读你的答案之前就改变了它。我在帖子中添加了一个编辑,以确认您的观点。像往常一样,我从你的回答中学到了一些东西。
  • Postgres 真的能保证UNION ALL 的第一个子查询在第二个 之前执行吗?我曾尝试在其他数据库中对此进行研究,但无法找到文档线索(我同意这可能在实践中发生;我想知道是否有保证)。
  • @GordonLinoff:好问题(实际上可能是一个成熟的问题)。我添加了一段引用文档的段落。
  • 非常感谢。我一直很好奇为什么这些不是并行处理的;我想 Postgres 方法在某种程度上被标准所暗示。
  • 就像我说的,我提供的代码是一个例子。实际的标准是用户定义的,所以它可以是任何东西,真的。但一如既往,您的回答提供了宝贵的细节,帮助我优化查询。
【解决方案2】:

你在正确的轨道上。只需添加一个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 模式以通配符开头,那么这种形式的查询仍然会进行全表扫描。

【讨论】:

  • 当然...按序数降序...愚蠢的我没看到。 :-)
  • 我们中的 所有人 都看不到这一点,真是太可怜了。 @Gordon 打得很好。
  • @amcdermott:我不太喜欢这个答案,它没有指出几个隐藏的问题。我添加了另一个答案。
  • 您的答案现在解决了 NULL 问题。但任一查询仍会从多个匹配项中返回任意选择。添加的第二个查询不必要地复杂且昂贵。外部的ORDER BY 使查询规划器无法优化查询。即使没有必要,两个 SELECT 也会始终执行。如果你去掉噪音,你会得到我的第二个查询。
【解决方案3】:

您可以执行以下操作:

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)

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • UNION ALL 建议(如果您还没有决定删除重复项。)
    • 公平点 - 另外,您可以将其作为内联表,只需选择街道、邮编和城市,以避免在结果中包含“SortOrder”列。
    • 如果您去掉添加的SortOrderORDER BY,您不会损失任何东西并获得性能。我在回答中解释了更多内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多