在两个表之间匹配多组值,寻求一种高效且易于维护的方法
我有以下两张表:
表article_description:
| 文章代码 | 区域 | 描述 |
|---|---|---|
| 0001 | EN | 示例描述,可能包含关键字ASC |
| 0001 | NL | 虚拟描述,带有关键字ASC |
| 1234 | EN | 示例描述ASC,可能包含关键字 |
| 1234 | NL | 虚拟描述ASC,带有关键字 |
| 4567 | EN | 示例描述,没有关键字 |
| 5678 | EN | 描述,包含其他关键字FT |
表article_labels:
| 文章代码 | 区域 | 标签代码 |
|---|---|---|
| 0001 | EN | QM0029 |
| 0001 | NL | QM0029 |
我想通过将article_description中出现的关键词与另一张表中存在的质量标签进行对比,来识别可能缺失标签代码的article_code。(遗憾的是,我们缺少其他更强健的信息来进行更好的检查。)
一个示例关键词是 "ASC";描述中包含该关键词的每条目都应对应标签 "QM0029"。我大约有30对标签与关键词。
我在努力写出一个可维护、能实现此逻辑的查询,但还没有想出一个既高效又优雅的实现方式。
我最初先写了一个查询来处理单对的情况,通过正则表达式匹配独立的模式(见下面的代码)。
我希望能把查询扩展成能覆盖所有关键词对的情形,但不确定如何以高效的方式实现。也许把下面的查询复制多次并用UNION ALL是最简单的办法,但可维护性很差。有没有更好的办法?
可能有帮助的一些说明:
- 我希望返回每个至少有一个区域缺失标签的article_code
- 描述总是大写;因此搜索时不需要区分大小写。描述中应仅包含(大写)字母和/或数字
- 原则上,每个article_code要么有0 个要么只有1 个质量标签,但如果能对多标签的情况具有鲁棒性会是加分项
- 约有100,000条不同的article_code,和5 个不同的区域locales。希望有一定的效率
- 我把一个关键词视为若与其他单词至少用一个空格分隔即可出现(可以出现在中间、开头或结尾)
- 根据示例表,我预计会返回article_code 1234和 5678。它们在至少一个描述中包含关键字 "ASC" 和 "FT",但没有对应的标签。1234应该有QM0029标签;5678应该有与FT对应的质量标签(20种可能之一)。
- 预期输出:
| 文章代码 | 检查结果 |
|---|---|
| 1234 | ASC标签缺失 |
| 5678 | FT标签缺失 |
如果你有任何问题,或对本文的改进有建议,请告诉我。另外,我的SQL还不是很熟练,任何其他改进/建议也将不胜感激。
Cheers!
WITH article_labels AS (
SELECT article_code, locale, label_code
FROM article_label
WHERE label_code = 'QM0029'
),
article_descriptions AS (
SELECT article_code, locale
FROM article_description
WHERE article_description ~ '(^| )ASC( |$)'
AND article_description IS NOT NULL
)
SELECT DISTINCT t1.article_code, 'ASC label missing' AS check_value
FROM article_descriptions AS t1
LEFT JOIN article_labels AS t2
ON
t1.article_code = t2.article_code
AND
t1.locale = t2.locale
WHERE t2.label_code IS NULL
表中包含我所有标签/关键词对的表:
| 标签 | 关键词 |
|---|---|
| QM_0029 | ASC |
| QM_0031 | BIO |
| QM_0037 | BIO |
| QM_0197 | BIO |
| QM_0228 | BIO |
| QM_0244 | BIO |
| QM_0622 | BIO |
| QM_0053 | BL1* |
| QM_0054 | BL2* |
| QM_0055 | BL3* |
| QM_0253 | FT |
| QM_0258 | FT |
| QM_0259 | FT |
| QM_0697 | FT |
| QM_0698 | FT |
| QM_0699 | FT |
| QM_0700 | FT |
| QM_0701 | FT |
| QM_0702 | FT |
| QM_0703 | FT |
| QM_0704 | FT |
| QM_0705 | FT |
| QM_0706 | FT |
| QM_0707 | FT |
| QM_0708 | FT |
| QM_0709 | FT |
| QM_0710 | FT |
| QM_0711 | FT |
| QM_0712 | FT |
| QM_0713 | FT |
| QM_0695 | GGN |
| QM_0423 | MSC |
解决方案
First, about keyword search.
You can check each keyword as shown in your example using regexp.
Or you can split the "description" into separate words and search for a match (equality).
In my example used "string_to table" function to split description to words and then comparing with keywors for JOIN.
First part of query (splitting)
select *
from article_description ad
cross join lateral string_to_table(article_description,' ')w(word)
For first row we get
| 文章代码 | 区域 | 描述 | 单词 |
|---|---|---|---|
| 0001 | EN | 示例描述,可能包含关键字ASC | SAMPLE |
| 0001 | EN | 示例描述,可能包含关键字ASC | DESCRIPTION |
| 0001 | EN | 示例描述,可能包含关键字ASC | THAT |
| 0001 | EN | 示例描述,可能包含关键字ASC | MAY |
| 0001 | EN | 示例描述,可能包含关键字ASC | CONTAIN |
| 0001 | EN | 示例描述,可能包含关键字ASC | A |
| 0001 | EN | 示例描述,可能包含关键字ASC | KEYWORD |
| 0001 | EN | 示例描述,可能包含关键字ASC | ASC |
Next join (INNER) table "article_labels" to splitted words on "al.label_code=lk.label_code"
select *
from article_description ad
cross join lateral string_to_table(article_description,' ')w(word)
inner join label_keywords lk on lk.keyword=w.word
| 文章代码 | 区域 | 描述 | 单词 | 标签代码 | 关键词 |
|---|---|---|---|---|---|
| 0001 | EN | 示例描述,可能包含关键字ASC | ASC | QM_0029 | ASC |
| 0001 | NL | 虚拟描述,带有关键字ASC | ASC | QM_0029 | ASC |
| 1234 | EN | 示例描述ASC,可能包含关键字 | ASC | QM_0029 | ASC |
| 1234 | NL | 虚拟描述ASC,带有关键字 | ASC | QM_0029 | ASC |
Finally, we LEFT join existing pairs (label_code,keyword). If pair not exists,article_labels.article_code is null.
After JOIN we filter not matching rows (antijoin) by "where al.article_code is null"
With anti JOIN
select *
from article_description ad
cross join lateral string_to_table(article_description,' ')w(word)
inner join label_keywords lk on lk.keyword=w.word
left join article_labels al on al.label_code=lk.label_code and al.article_code=ad.article_code
and al.locale=ad.locale
where al.article_code is null
;
Or with NOT EXISTS.
There we consider only articles, not having reference in "article_labels" table by columns(article_code,locale)
select *
from article_description ad
cross join lateral string_to_table(article_description,' ')w(word)
inner join label_keywords lk on lk.keyword=w.word
left join article_labels al on al.label_code=lk.label_code and al.article_code=ad.article_code
and al.locale=ad.locale
where not exists (select 1 from article_labels al2 where al2.article_code=ad.article_code and al2.locale=ad.locale)
| 文章代码 | 区域 | 描述 | 单词 | 标签代码 | 关键词 | 文章代码 | 区域 | 标签代码 |
|---|---|---|---|---|---|---|---|---|
| 1234 | EN | 示例描述ASC,可能包含关键字 | ASC | QM_0029 | ASC | |||
| 1234 | NL | 虚拟描述ASC,带有关键字 | ASC | QM_0029 | ASC | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0713 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0712 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0711 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0710 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0699 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0698 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0697 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0259 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0258 | FT | |||
| 5678 | EN | 描述,包含其他关键字FT | FT | QM_0253 | FT |