一尘不染

SQL查询以查找具有最匹配关键字的行

sql

我真的很不擅长SQL,我想知道我可以使用哪种SQL解决这个问题,在这个问题以下我怀疑是NP完全问题,但是我认为查询需要很长时间才能在大型数据集上运行因为这将作为后台任务完成。首选标准sql语句,但是如果需要存储过程,则使用它。SQL必须在Postgres
9.3上运行。

问题:给定一组包含一组关键字的文章,请为每条包含最多匹配关键字的文章查找前n条文章。

文章表的精简版本如下所示:

CREATE TABLE article (
  id character varying(36) NOT NULL,  -- primary key of article
  keywords character varying,         -- comma separated set of keywords

  CONSTRAINT pk_article PRIMARY KEY (id)
);

-- Test Data
INSERT INTO article(id, keywords) VALUES(0, 'red,green,blue');
INSERT INTO article(id, keywords) VALUES(1, 'red,green,yellow');
INSERT INTO article(id, keywords) VALUES(2, 'purple,orange,blue');
INSERT INTO article(id, keywords) VALUES(3, 'lime,violet,ruby,teal');
INSERT INTO article(id, keywords) VALUES(4, 'red,green,blue,yellow');
INSERT INTO article(id, keywords) VALUES(5, 'yellow,brown,black');
INSERT INTO article(id, keywords) VALUES(6, 'black,white,blue');

这将导致以下SELECT * FROM article;查询:

Table: article
------------------------
id  keywords            
------------------------
0   red,green,blue      
1   red,green,yellow    
2   purple,orange,blue  
3   lime,violet,ruby,teal
4   red,green,blue,yellow
5   yellow,brown,black
6   black,white,blue

假设我想为每条包含最多匹配关键字的文章查找前三篇文章,则输出应为:

------------------------
id  related
------------------------
0   4,1,6
1   4,0,5
2   0,4,6
3   null
4   0,1,6
5   1,6
6   5,0,4

阅读 202

收藏
2021-03-08

共1个答案

一尘不染

就像@a_horse一样:使用规范化的设计会更简单(除了使其他任务更简单/更简洁),但
仍然不简单

另外,数据类型的PK列character varying(36)高度可疑(且效率低下),并且很可能应该是integer类型,或者至少应该是类型uuid

这是根据您的 设计的 一种可能的解决方案 ,如下所示

WITH cte AS (
   SELECT id, string_to_array(a.keywords, ',') AS keys
   FROM   article a
   )
SELECT id, string_agg(b_id, ',') AS best_matches
FROM  (
   SELECT a.id, b.id AS b_id
        , row_number() OVER (PARTITION BY a.id ORDER BY ct.ct DESC, b.id) AS rn
   FROM   cte a
   LEFT   JOIN cte b ON a.id <> b.id AND a.keys && b.keys
   LEFT   JOIN LATERAL (
      SELECT count(*) AS ct
      FROM  (
         SELECT * FROM unnest(a.keys)
         INTERSECT ALL
         SELECT * FROM unnest(b.keys)
         ) i
      ) ct ON TRUE
   ORDER  BY a.id, ct.ct DESC, b.id  -- b.id as tiebreaker
   ) sub
WHERE  rn < 4
GROUP  BY 1;

SQL Fiddleid改为使用整数)。

CTEcte将字符串转换为数组。您甚至可以拥有像这样的功能性GIN索引…

如果在前3个选择中有多行并列,则需要定义一个 _ 决胜局_ 。在我的示例中,较小的行排在id第一位。

比较是在JSON数组和SQL数组之间进行的,但它基本上是相同的问题,需要解决相同的问题。还比较了两个类似的选择。

为了加快速度,您至少应该在数组列上有一个GIN索引(而不是用逗号分隔的字符串),并且查询不需要CTE步骤。完全归一化的设计还有其他优点,但不一定比具有GIN索引的数组更快。

2021-03-08