应用一长串 LIKE 模式的最佳方法?

Vér*_*ace 8 rdbms postgresql performance pattern-matching query-performance

作为这个问题的后续行动,我有一个我自己的。

最初的问题涉及使用CASE超过 100 个选项的语句,并且该语句必须在 4 个地方使用 - 所以显然 SQL 非常多毛。OP 的问题与 SQL Server 2012 有关,但是我的问题是关于 PostgreSQL。

在我的回答中,我建议使用 aVIEW作为“一站式”解决方案 - 即声明VIEW一次,在任何地方使用它 - 这也适用于未来的任何查询及其任何变体。

另一位发帖人(@AndriyM)建议使用 aCROSS APPLY来解决该问题,这是另一种解决方案。PostgreSQL 语法是JOIN LATERAL

然后,我在原始答案中添加了 CTE(通用表表达式)作为另一种可能的解决方案。

因此,OP 现在有 5 个选项:

  1. CASE
  2. VIEW
  3. JOIN LATERALCROSS APPLY对于 SQL Server)
  4. CTE
  5. Separate table

我排除了更改基础数据的选项,因为在本论坛中,顾问/DBA/程序员经常不允许更改基础数据 - 也使答案更有趣!

显然,CASE具有 > 100 个选项 (x4)的表达式非常麻烦和复杂 - 但什么时候使用是个好主意CASE,在什么时候它会变成减号而不是加号?

在我看来(不仅仅是因为这是我的答案!),aVIEW是最佳解决方案 - 它很简单,适用于所有 RDBMS 并且是永久性的,并且适用于现在和将来的所有查询,如果 OP 希望修改查询.

JOIN LATERAL构造也将作为一种派生表工作,这几乎CTE也是 a 。它们都可以在同一查询中直接使用。

这 5 种方法中的哪一种更好/最好,技术(易用性、速度、查询计划优化)在哪些方面偏向于特定解决方案?

Erw*_*ter 6

我会在子查询中使用翻译表。演示(Postgres 10+):LATERAL

CREATE TABLE ac_translate (
   ord_nr int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
 , like_pattern text NOT NULL
 , target text NOT NULL
);

INSERT INTO ac_translate(like_pattern, target) VALUES 
   ('AIR NEW Z%'       , 'AIR NEW ZEALAND')  -- rows in order of precedence!
 , ('AIR BP%'          , 'AIR BP')
 , ('ADDICTION ADVICE%', 'ADDICTION ADVICE')
 , ('AIA%'             , 'AIA')
;
Run Code Online (Sandbox Code Playgroud)

看:

操纵ord_nr以调整优先级。

询问:

SELECT COALESCE(act.target, ac.accountName) AS accountname
     , SUM(ac.charge_amount) AS gstexcl
FROM   account_code ac
LEFT   JOIN LATERAL (
   SELECT a1.target
   FROM   ac_translate a1
   WHERE  ac.accountname LIKE a1.like_pattern
   ORDER  BY a1.ord_nr
   LIMIT  1
   ) act ON true
GROUP BY 1;
Run Code Online (Sandbox Code Playgroud)

或者使用相关子查询

SELECT COALESCE(
        (SELECT a1.target
         FROM   ac_translate a1
         WHERE  ac.accountname LIKE a1.like_pattern
         ORDER  BY a1.ord_nr
         LIMIT  1), ac.accountName) AS accountname
     , SUM(ac.charge_amount) AS sum_amount
FROM   account_code ac
GROUP BY 1;
Run Code Online (Sandbox Code Playgroud)

这很容易处理,将一长串选项保留在代码之外,并将其放入可以正确维护的表中。并且速度适中。

我们不能轻易使用普通LEFT JOIN ac_translateCASE横梁模式,以便返回单一第一个匹配。我们不能只加入一个集合,如果一个模式是另一个模式的前缀,则可能会返回多个匹配项,例如“AIR%”和“AIR N%”。所以我们在翻译表中使用一个排序号来确定子查询中匹配的优先级。

引用问题中ELSE子句解析为原始值。这是在这里实现的。基本上,这结合了那里前两个答案的优点。COALESCE

最重要的是,我GROUP BY 1以另一种方式避免重复冗长的表达式(这里实际上不再需要)。看:

速度

性能随着转换表中的行数而下降,因为 Postgres 被迫按顺序遍历所有行并评估LIKE表达式。如果这还不够快,我们需要索引支持,但表达式不是“sargable”——我们需要索引的表达式在运算符的右侧,没有COMMUTATORfor LIKE。细节:

不过,有一个解决方法。我的示例要求模式至少有 3 个前导字符3是我的任意选择)。CHECK在转换表中添加约束以强制执行此规则,并在前导三元组上添加表达式索引:

CREATE INDEX ac_translate_left_idx ON ac_translate (left(like_pattern, 3));
Run Code Online (Sandbox Code Playgroud)

调整查询:

SELECT COALESCE(act.target, ac.accountName) AS accountname
     , SUM(ac.charge_amount) AS gstexcl
FROM   account_code ac
LEFT   JOIN LATERAL (
   SELECT a1.target
   FROM   ac_translate a1
   WHERE  left(ac.accountname, 3) = left(a1.like_pattern, 3)
   AND    ac.accountname LIKE a1.like_pattern
   ORDER  BY a1.ord_nr
   LIMIT  1
   ) act ON true
GROUP BY 1;
Run Code Online (Sandbox Code Playgroud)

如果翻译表中有足够多的行(以及有利的估计和成本设置),Postgres 将使用非常快速的索引扫描将其缩小到少数候选者(如果有),并仅使用表达式过滤其余部分LIKE。应该缩放就好了。我将EXPLAIN输出添加到小提琴作为概念证明:

db<>在这里摆弄