LATERAL JOIN 不使用三元组索引

2023-11-25

我想使用 Postgres 对地址进行一些基本的地理编码。我有一个地址表,其中包含大约 100 万个原始地址字符串:

=> \d addresses
  Table "public.addresses"
 Column  | Type | Modifiers
---------+------+-----------
 address | text |

我还有一个位置数据表:

=> \d locations
   Table "public.locations"
   Column   | Type | Modifiers
------------+------+-----------
 id         | text |
 country    | text |
 postalcode | text |
 latitude   | text |
 longitude  | text |

大多数地址字符串都包含邮政编码,因此我的第一次尝试是进行类似和横向连接:

EXPLAIN SELECT * FROM addresses a
JOIN LATERAL (
    SELECT * FROM locations
    WHERE address ilike '%' || postalcode || '%'
    ORDER BY LENGTH(postalcode) DESC
    LIMIT 1
) AS l ON true;

这给出了预期的结果,但速度很慢。这是查询计划:

                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Nested Loop  (cost=18383.07..18540688323.77 rows=1008572 width=91)
   ->  Seq Scan on addresses a  (cost=0.00..20997.72 rows=1008572 width=56)
   ->  Limit  (cost=18383.07..18383.07 rows=1 width=35)
         ->  Sort  (cost=18383.07..18391.93 rows=3547 width=35)
               Sort Key: (length(locations.postalcode))
               ->  Seq Scan on locations  (cost=0.00..18365.33 rows=3547 width=35)
                     Filter: (a.address ~~* (('%'::text || postalcode) || '%'::text))

我尝试在地址列中添加一个要点三元组索引,就像上面提到的那样https://stackoverflow.com/a/13452528/36191,但是上述查询的查询计划没有使用它,并且查询计划保持不变。

CREATE INDEX idx_address ON addresses USING gin (address gin_trgm_ops);

我必须删除横向连接查询中的 order by 和 limit 才能使用索引,这不会给我想要的结果。这是没有查询的查询计划ORDER or LIMIT:

                                          QUERY PLAN
-----------------------------------------------------------------------------------------------
 Nested Loop  (cost=39.35..129156073.06 rows=3577682241 width=86)
   ->  Seq Scan on locations  (cost=0.00..12498.55 rows=709455 width=28)
   ->  Bitmap Heap Scan on addresses a  (cost=39.35..131.60 rows=5043 width=58)
         Recheck Cond: (address ~~* (('%'::text || locations.postalcode) || '%'::text))
         ->  Bitmap Index Scan on idx_address  (cost=0.00..38.09 rows=5043 width=0)
               Index Cond: (address ~~* (('%'::text || locations.postalcode) || '%'::text))

我可以做些什么来让查询使用索引,或者有更好的方法来重写这个查询吗?


Why?

该查询不能使用主体上的索引。您需要在表上建立索引locations,但是你的那个在桌子上addresses.

您可以通过设置来验证我的声明:

SET enable_seqscan = off;

(仅在您的会话中,并且仅用于调试。切勿在生产中使用它。)索引并不比顺序扫描更昂贵,Postgres 无法将它用于您的查询at all.

Aside: [INNER] JOIN ... ON true只是一种尴尬的说法CROSS JOIN ...

为什么删除后还使用索引ORDER and LIMIT?

因为 Postgres 可以将这个简单的形式重写为:

SELECT *
FROM   addresses a
JOIN   locations l ON a.address ILIKE '%' || l.postalcode || '%';

您将看到完全相同的查询计划。 (至少我在 Postgres 9.5 上的测试中是这样做的。)

Solution

你需要一个索引locations.postalcode。并且在使用时LIKE or ILIKE您还需要带上索引表达式(postalcode)到left操作员一侧。ILIKE与运营商一起实施~~*并且这个操作符没有COMMUTATOR(逻辑上的必然性),所以不可能翻转操作数。这些相关答案中的详细解释:

  • PostgreSQL 可以索引数组列吗?
  • PostgreSQL - 文本数组包含类似于的值
  • 有没有办法有效地索引包含正则表达式模式的文本列?

一个解决方案是使用三元组相似算子%或其倒数,即距离算子<-> in a 最近的邻居改为查询(每个都是其自身的换向器,因此操作数可以自由切换位置):

SELECT *
FROM   addresses a
JOIN   LATERAL (
   SELECT *
   FROM   locations
   ORDER  BY postalcode <-> a.address
   LIMIT  1
   ) l ON address ILIKE '%' || postalcode || '%';

找到最相似的postalcode对于每个address,然后检查是否postalcode实际上完全匹配。

这样,更长的时间postalcode将自动成为首选,因为它比较短的更相似(距离更小)postalcode这也匹配。

仍然存在一些不确定性。根据可能的邮政编码,由于字符串其他部分中的三元组匹配,可能会出现误报。问题中没有足够的信息可以说更多。

Here, [INNER] JOIN代替CROSS JOIN这是有道理的,因为我们添加了实际的连接条件。

手册:

这可以通过 GiST 索引非常有效地实现,但不能通过 GIN 索引。

So:

CREATE INDEX locations_postalcode_trgm_gist_idx ON locations
USING gist (postalcode gist_trgm_ops);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LATERAL JOIN 不使用三元组索引 的相关文章

随机推荐

  • Elixir 中等号的确切含义是什么?

    我不明白 Elixir 中的等号到底意味着什么 不清楚的是它看起来像是赋值和模式匹配操作的混合 iex 1 gt x 4 4 iex 2 gt y 5 5 iex 3 gt 3 y MatchError no match of right
  • 使用 mmap 将文件读取为字符串

    我正在尝试使用 mmap 将文件读取为字符串 我正在遵循这个例子 http www lemoda net c mmap example index html 我的代码看起来像这样 unsigned char f int size int m
  • 如果文档中使用复数,而搜索文本中使用单数,则配置 SOLR 来查找文档?

    我正在使用 solr 设置为 localhost 8983 我基本上使用的是开箱即 用的示例 我输入了一份名为 Car 的文档 另一份名为 Cars 的文档 如果我访问 http localhost 8983 solr select q C
  • 如何在 C++ 中克隆为派生对象

    我在 C 中定义了两个类 一个是基类 一个是派生类 class CBaseClass class CDerivedClass public CBaseClass 并想实现一个克隆功能如下 CBaseClass Clone const CBa
  • 我可以在 JDBC 准备好的查询中使用多个语句吗?

    我想在我的 MySQL 服务器上执行类似的操作 SET id SELECT id FROM lookupTable WHERE field SELECT FROM table2 WHERE id id UNION SELECT FROM t
  • 正确使用 LabelValueBean

    我有一个变量 private ArrayList
  • PHPStorm 和魔法方法

    我正在使用 PHPStorm 并编写了一个利用 SimpleXML 类的类 一切都很好 除了当我遍历 XML 字符串时 我会收到 未定义的属性 警告 xml simplexml load string string Returns Simp
  • 什么是参考插座?

    我最近刚刚将 Xcode 3 的副本升级到 Xcode 3 1 并且我注意到界面生成器中的一些新内容 引用插座 谁能解释一下引用出口是什么 以及它们与 mac 开发有何关系 这些是设置到相关对象的插座 例如 如果您选择一个作为窗口委托的对象
  • 如果与上次不同则发出的 RxJava 运算符

    如果要发射的项目不等于最后发射的项目 是否有一个运算符可以发射项目 有distinctUntilChanged RxJava还实现了distinctUntilChanged运算符 它仅将源 Observable 发出的项目与它们的直接前辈进
  • 有人可以向我解释 ASP.NET 信任级别吗?

    我听说过很多有关信任级别的内容 有人试图向我解释 但仍然无法给出一个场景 在该场景中我将应用一个信任级别而不是另一个信任级别 我已读完MSDN 的文章但这对我没有多大作用 有人可以提供一个真实世界的示例 说明您何时想要使用各种级别 完全 高
  • Java中Integer.MAX_VALUE的乘法[重复]

    这个问题在这里已经有答案了 我只是在玩Java 写了这个小程序 public class MaxValue public static void main String args int i Integer MAX VALUE for in
  • AngularJS 指令在模板中使用原始元素类型

    我正在为 Angular 开发基于 UI 和排版的指令 在这种情况下 指令所应用的元素是未知的 从 div span h1 到 h5 的任何元素 使用模板的原因是这样我可以添加ng 指令 因此开发人员除了指令名称之外不需要记住任何内容 我在
  • ASP.NET API 版本控制

    我是 ASP NET 新手 但我希望为即将开始的新 API 实现一些版本控制 我什至不确定我正在寻找的是否可能 但我正在寻找使用标头变量的非常干净的版本方法 理想情况下 我希望能够在代码结构中拥有一个版本文件夹 并在其中包含包含不同 API
  • Laravel 5.2 $errors 没有出现在 Blade 中

    因此 我正在学习 Laravel 5 基础教程 但我一直停留在表单验证上 我完全按照教程进行操作 但我收到一个未定义的变量 创建文章视图中的错误 在我遵循的教程和我在网上找到的内容中 他们说错误变量始终存在于刀片文件中供您使用 所以我不知道
  • ember.js 集合视图中特定于项目的操作

    我刚刚开始使用 ember js 库来了解它的全部内容 我想显示一个数据表 并在每行的右侧有一个删除按钮 用于从表中删除该项目 但我不知道该怎么做 请注意 我还尝试创建一个子视图 ItemView 并在 each each 部分中内联使用它
  • 在google colab中将python版本从3.7降级到3.6

    一些 python 包在 python 3 7 中无法工作 所以想降级google colab中的默认python版本 可以吗 如果是这样 该如何进行 请指导我 你可以安装 python 3 6miniconda bash MINICOND
  • Android:“启动画面”仅一次

    我有一个 Android 应用程序需要启动画面 我有一个简单的活动 它显示启动屏幕 几秒钟后 它启动 主要 活动 现在 当用户在该活动中按下后退按钮时 启动画面将再次显示 我怎样才能防止这种情况发生 我尝试保留一个布尔值 shownSpla
  • 如何在 MSSQL 中将 VARCHAR 转换为 TIMESTAMP?

    您希望在 T SQL 中调用 MS SQL 上具有 TIMESTAMP 参数类型的存储过程 而不是使用 VARCHAR 值 例如 0x0000000002C490C8 的 ADO NET 你做什么工作 更新 这是您收到 时间戳 值的地方 但
  • maven中的多次部署

    我们有一个内部工件存储库 目前所有快照都将部署在那里 我们还希望拥有一个具有 Web 界面的不同服务器 并希望将创建的工件复制到其中 对于我们的构建 我们使用 Hudson 但是构建后操作 将工件部署到 Maven 存储库 与 scp 一起
  • LATERAL JOIN 不使用三元组索引

    我想使用 Postgres 对地址进行一些基本的地理编码 我有一个地址表 其中包含大约 100 万个原始地址字符串 gt d addresses Table public addresses Column Type Modifiers ad