我有一个数据库,我们经常需要对字符串进行模糊/距离匹配。在此示例中,目标citext
字段被命名为analytic_scan.inv_name
。但是,相同类型的代码对于任何其他text
和citext
字段也可能有用。表结构的其余部分对此查询不起作用。
从Tom Lane的tip about K-NN text searches开始,我得到了trigram GIST索引,该索引实现了<->
距离运算符。使用提示,查询与字符串最接近的10个邻居是这样的:
select distinct on (inv_name <-> 'Pack CT - 002') inv_name,
inv_name <-> 'Pack CT - 002' AS distance
from analytic_scan
order by 2 -- order by the distance column...using the column position saves retyping the formula here.
limit 10;
这很好,尽管有7M +行,但要花一些时间。我的目标是计算和存储一组值以进行快速查找,例如:
在目标字段analytic_scan.inv_name
中找到不同的术语。
对于每个术语,计算术语的频率和频率百分位数。
对于每个术语,找到10个(或100个,等等)最近的邻居及其距离。
[从那里,我想为每个术语添加distance_min
,distance_max
和distance_width
,我正在确定自己可以使用正确的窗口函数魔术来完成。 (我不在这里尝试该部分。)
K-NN搜索在上面,频率计数搜索非常简单:
select distinct inv_name,
count(*) as frequency,
ntile(100) OVER(ORDER BY count(*)) as frequency_percentile
from analytic_scan
group by inv_name
order by 1,2;
将两个查询结合起来是我的绊脚石。感觉像LATERAL JOIN
,但我对此可能完全错了。我已经尝试过一些,但这并没有给我KNN子查询的列中的任何值,它们都是NULL
。而且,我每学期只得到一行,而不是10。所以,很明显,我错了。
为清楚起见,我do获得预期的列:
inv_name
frequency
frequency_percentile
neighbor_name
distance
...但是没有填充基于KNN的字段,我只得到一行输出,而不是KNN搜索的LIMIT 10
子句中设置的10行。我知道我想要对样本中的每个项目应用“查找10个邻居”代码,并且不知道如何正确执行此操作。我正在输入LATERAL
,但如果有更好的方法,我全力以赴。
-- Final results I'm after, with one row per *neighbor*.
-- So, 10x the distinct terms, in this case.
select frequency_table.inv_name,
frequency_table.frequency,
frequency_table.frequency_percentile,
knn.neighbor_name,
knn.distance
-- Calculate the distinct terms and their frequencies. There are 6,958 distinct terms in my sample table.
from (
select inv_name,
count(*) as frequency,
ntile(100) OVER(ORDER BY count(*)) as frequency_percentile
from analytic_scan
group by inv_name
) frequency_table
-- I'm wanting to "multiply" the terms above with the 10 neighbors below. LEFT JOIN is obviously wrong.
left join lateral -- CROSS JOIN LATERAL gives me 70 rows on 6,958 distinct terms. ¯\_(ツ)_/¯
-- Find the 10 nearest neighbors.
(select distinct on (analytic_scan.inv_name <-> frequency_table.inv_name) analytic_scan.inv_name AS neighbor_name,
analytic_scan.inv_name <-> frequency_table.inv_name AS distance
from analytic_scan
where frequency_table.inv_name = analytic_scan.inv_name and
frequency_table.frequency_percentile = 1
limit 10
) knn ON TRUE
order by frequency_table.inv_name,
knn.distance
[如果有人可以指出正确的方向,那太好了。我显然在这里滑雪。
注意:我很可能最终每学期用数组或带有相邻数据的jsonb
存储一行。目前,数据将由客户端应用程序使用,他们只需要JSON数组。通常,我对压缩字段过敏,但是在这种情况下,这可能是有道理的。我没有在这里尝试合并,因为我认为正确进行基本查询是有意义的。但是,如果某人的解决方案最终创建了JSON聚合而不是我的邻居,那么也可以。这是我想象中的表格:
CREATE TABLE IF NOT EXISTS analytics.inv_name_frequency (
id uuid NOT NULL DEFAULT extensions.gen_random_uuid(), -- What the boss likes.
inv_name citext NOT NULL DEFAULT 0,
frequency integer NOT NULL DEFAULT 0,
frequency_range int4range NOT NULL DEFAULT '(0,0)'::int4range -- For min, max distances.
frequency_width integer NOT NULL DEFAULT 0, -- Stores min-max value, can use a calculated column in PG12.
neighbors jsonb NOT NULL DEFAULT '{}'::jsonb) -- JSON array with {"term","foo","distance":0.3} for each neighbor.
jjanes,我在SO档案中经常使用的注释和代码,花了一些时间回复。我的回答不适合评论,所以我在这里添加。可能有助于弄清是,我正在查看的数据是混乱的。我们要做的第一件事是帮助人们将非标准名称和不一致的名称转变为一组高度标准化的名称。这需要一堆软件,以及同样多的人类技能和精力。老实说,我们应该聘请人类学家,因为大部分工作是在提取当地知识。标准化过程的起点是多年积累的具有各种不一致情况的真实世界数据。这就是我在这里查看的数据。
我们已经攻击了带有大量模糊字符串比较的自动匹配,我真的很喜欢Trigram的Postgres实现。当我阅读“ K-NN”搜索提示时,它看起来像是一种非常有趣的方法,可以在历史数据的“汤”表中查找模式。就它的工作而言,这相当快。。。即使我已经编写了代码。通过迅速捕获和/或存储大量的近期术语以供检索,那么您就有了一个很好的起点,可以使用Levenshtein等进行更昂贵的相似性评分。
因此,作为一项实验,我想在历史数据上建立一个术语表和附近的邻居。我可以使用客户端语言轻松完成此操作,即使是PL / PgSQL
select distinct
with Postgres但是这似乎在Postgres的直接SQL中应该可以实现,我想弄清楚怎么做。我有很多次要对文本进行扩展的频率分析。正如我的代码中的注释所示,很明显,我在查询过程中所发生的事情的思维导图是.....相当空白。我想更好地了解如何执行lateral join
或子查询等以使用SQL解决此问题。
横向连接似乎确实是您想要的,但是您对它的实现很奇怪:
analytic_scan.inv_name <-> frequency_table.inv_name AS distance ... where frequency_table.inv_name = analytic_scan.inv_name
当然,把所有相似的东西都拉进去,然后删除所有不相同的东西,当然没有意义。
frequency_table.frequency_percentile = 1
这似乎也没有任何意义。如果您真的想对此进行过滤(并且这样做似乎与您的散文描述相矛盾),那么为什么要在这里进行呢?该子句似乎与您在描述您要做什么时所发现的内容无关。除了frequency_table的第一行之外,您不会填充其他任何列,因为您不愿意删除这些结果。
您“ knn”横向查询似乎具有它不需要的WHERE子句,并且缺少它确实需要的ORDER BY。
而且,您的表analytic_scan可能设计错误。如果要查找相似的名称,则可能需要一个重复数据删除名称的表。然后,您将不需要DISTINCT ON,这可能会导致索引使用出现问题。