【问题标题】:Way to try multiple SELECTs till a result is available?尝试多个 SELECT 直到结果可用的方法?
【发布时间】:2012-12-29 16:02:34
【问题描述】:

如果我想以递减的精度搜索表中的单行,例如像这样:

SELECT * FROM image WHERE name LIKE 'text' AND group_id = 10 LIMIT 1

当这没有结果时,试试这个:

SELECT * FROM image WHERE name LIKE 'text' LIMIT 1

当这没有结果时,试试这个:

SELECT * FROM image WHERE group_id = 10 LIMIT 1

是否可以只用一个表达式来做到这一点?

当我没有两个但例如三个或更多搜索参数。有没有通用的解决方案?当然,当搜索结果按相关性排序时,它会派上用场。

【问题讨论】:

  • 您是指name LIKE '%text%' 还是name = 'text'name LIKE 'text' 毫无意义。这对性能很重要,并且对解决方案有影响。另外,您问题的第二部分不清楚。当你只返回 one 行时为什么要排序?请更清楚地定义您的目标。
  • 这只是一个细节,但它可以是任何东西:)

标签: sql postgresql union postgresql-performance


【解决方案1】:

不带通配符的LIKE 等价于=。假设您实际上是指name = 'text'

Indexes 是性能的关键。

测试设置

CREATE TABLE image (
  image_id serial PRIMARY KEY
, group_id int NOT NULL
, name     text NOT NULL
);

理想情况下,您创建两个索引(除了主键):

CREATE INDEX image_name_grp_idx ON image (name, group_id);
CREATE INDEX image_grp_idx ON image (group_id);

第二个可能不是必需的,具体取决于数据分布和其他细节。此处说明:

查询

对于您的情况,这应该是最快的查询:

SELECT * FROM image WHERE name = 'name105' AND group_id = 10
UNION ALL
SELECT * FROM image WHERE name = 'name105'
UNION ALL
SELECT * FROM image WHERE group_id = 10
LIMIT  1;

SQL Fiddle.

LIMIT 子句适用于整个查询。 Postgres 足够聪明不执行UNION ALL 的后续分支,只要它找到足够的行来满足LIMIT。因此,对于查询的第一个 SELECT 中的匹配,EXPLAIN ANALYZE 的输出看起来像这样(向右滚动!):

限制(成本=0.00..0.86 行=1 宽度=40)(实际时间=0.045..0.046 行=1 循环=1) 缓冲区:本地命中=4 -> 结果(成本=0.00..866.59 行=1002 宽度=40)(实际时间=0.042..0.042 行=1 循环=1) 缓冲区:本地命中=4 -> 追加(成本=0.00..866.59 行=1002 宽度=40)(实际时间=0.039..0.039 行=1 循环=1) 缓冲区:本地命中=4 -> 在图像上使用 image_name_grp_idx 进行索引扫描(成本=0.00..3.76 行=2 宽度=40)(实际时间=0.035..0.035 行=1 循环=1) 索引条件:((name = 'name105'::text) AND (group_id = 10)) 缓冲区:本地命中=4 -> 在图像上使用 image_name_grp_idx 进行索引扫描(成本=0.00..406.36 行=500 宽度=40)(从未执行) 索引条件: (name = 'name105'::text) -> 在图像上使用 image_grp_idx 进行索引扫描(成本=0.00..406.36 行=500 宽度=40)(从未执行) 指数条件:(group_id = 10) 总运行时间:0.087 毫秒

我的大胆强调。

不要不要添加ORDER BY 子句,这会使效果无效。然后 Postgres 在返回顶行之前必须考虑所有行。

最后的问题

有没有通用的解决方案?

通用解决方案。根据需要添加任意数量的 SELECT 语句。

当然,当搜索结果按相关性排序时,它会派上用场。

结果中只有一行LIMIT 1。空位排序的种类。

【讨论】:

  • 确实很有说服力。今天/昨天我学到了一些新东西。
  • @Erin:您的查询非常棒,而 postgres 非常棒!在第二个比较中,这个问题有LIKE,而不是=。在某些情况下,这可能会有所不同。你不觉得吗。
  • @ypercube:谢谢。 :) 你看到我的第一段和关于LIKE= 的问题下的评论了吗? UNION ALL 的主体一旦发现足够的行就停止评估,应该适用于任何 SELECT 语句。如果我们要讨论 fuzzy 字符串匹配,事情会变得更加复杂......
  • 哦,好的,刚刚注意到第一行。我想知道查询有LIKE '%text%' 并且有一行group_id = 10 但没有一个与LIKE 匹配的情况。在这种情况下,在 UNION 的第一部分运行(并且不返回任何行)之后,将运行第二部分(这也将不返回任何行)。然后是第 3 部分,这将给出 1 个结果。但是如果我们按照 1-3 的顺序运行这三个部分,它会更快(在那种特定情况下,真的不需要运行第二个部分)。
  • @ypercube:是的,如果一个特定条件比其他条件贵很多,那么组合 SELECT 可能有助于进一步优化。对于非左锚LIKE,我会以GIN index using gin_trgm_ops 开头来支持它。
【解决方案2】:

已经很晚了,我不想写出一个完整的解决方案,但如果我需要这个,我可能会创建一个客户function,它返回客户类型、记录或表格(取决于您的需求) .这样做的好处是,一旦你找到你的记录,你就可以停下来。

使参数的数量动态化将使其更具挑战性。根据您的 PostgreSQL 版本(以及您可以使用的扩展),您可能可以传入 hstore 或 json 和 dynamically 构建查询。

也许不是最好的 SO 答案,但它不仅仅是一个评论,希望是一些思考的食物。

【讨论】:

  • 如果我理解正确,您认为 UDF 将逐一返回行的假设是错误的。还没有找到增加这个的链接。
  • OP 并没有完全说明他在这里想要做什么。也许他只需要返回记录的主键值? (显然这里是猜测)。我只是想指出,您可以将他想要的逻辑包装在 UDF 中,并在找到记录时停止并返回一些东西(有些东西不清楚)。我只是想强调一种解决尚未提及的问题的方法。此外,如果他想让它更灵活,UDF 可能是可行的方法,但会很混乱。
  • 我不想与你的整体观点争论,只是挑剔了一点:)
  • 带有LIMIT 子句的UNION ALL 查询碰巧在找到足够的行后立即自动停止评估。我在另一个答案中演示。除非我也会去参加一个函数。
  • @dezso - 总是欢迎挑剔!感谢您的 cmets!
【解决方案3】:

在找到所需结果之前,我认为单独运行这些查询没有任何问题。虽然有多种方法可以将这些组合到一个查询中,但最终会变得更复杂、更慢,这不是您想要的。

您应该考虑在一个事务中运行所有查询,最好在可重复读取隔离级别下运行,这样您可以获得一致的结果并避免设置重复事务的开销。此外,如果您明智地使用预准备语句,您的开销将与在一个组合语句中运行所有三个查询几乎相同。

【讨论】:

    【解决方案4】:
    SELECT *, 
    CASE WHEN name like 'text' AND group_id = 10 THEN 1
    WHEN name like 'text' THEN 2
    WHEN group_id = 10 THEN 3
    ELSE 4
    END ImageRank
    FROM image
    WHERE ImageRank <> 4
    ORDER BY ImageRank ASC
    LIMIT 1
    

    这将是一种伪解决方案,但我不完全确定您的场景中的语法是否允许它

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-27
      • 2012-05-28
      • 1970-01-01
      • 1970-01-01
      • 2022-11-29
      • 2018-06-08
      • 2022-01-20
      • 1970-01-01
      相关资源
      最近更新 更多