SQL 查询:在 JOINed 表中搜索多个字段
Posted
技术标签:
【中文标题】SQL 查询:在 JOINed 表中搜索多个字段【英文标题】:SQL Query: Search Across Multiple Fields in JOINed Tables 【发布时间】:2021-12-30 23:56:34 【问题描述】:SQL 版本:mysql 8.0 或 SQL Server
SQL 小提琴:https://www.db-fiddle.com/f/wcHeXkcynUiYP3qzryoYJ7/6
我有一张图片表和一张链接到这些图片的标签表。
================================== ===================================================
| tb_images | | tb_imagetags |
================================== ===================================================
| f_imageID | f_imagefilename | | f_imagetagID | f_imagetagimage | f_imagetagname |
---------------------------------- ---------------------------------------------------
| 1 | 1.jpg | | 10 | 1 | November |
| 2 | 2.jpg | | 11 | 1 | 2021 |
| 3 | 3.jpg | | 12 | 2 | November |
================================== | 13 | 2 | 2020 |
| 14 | 3 | December |
| 15 | 3 | 2020 |
===================================================
我希望能够将 (2) 个标签传递给查询,并让它只选择与两个标签匹配的图像。例如,我想传递 November
和 2021
并让它只返回 1.jpg
。
如果我这样做:
SELECT f_imageID, f_imagefilename
FROM tb_images
LEFT JOIN tb_imagetags
ON f_imagetagimage = f_imageID
WHERE f_imagetagname = 'November'
OR f_imagetagname = '2021'
但是返回:
f_imageID f_imagefilename
================================
1 1.jpg
1 1.jpg
2 2.jpg
如何重写此查询以仅获取与两个标签都匹配的图像?
【问题讨论】:
作为一般建议,如果您要在 WHERE 子句中提及 y,那么执行 x left join y 是没有意义的,因为它会转换为内连接,除非您添加的谓词允许左连接过程创建的 NULL 以“生存” 【参考方案1】:问题是您的数据是相关的不同的行。如果所有数据都在同一行中,那就很容易了
SELECT * FROM blah WHERE month = nov and year = 2021
当它在不同的行中时,您希望像现在一样获得两行..
..但是你只想要那些有两行的图像。如果只有一行(例如仅 11 月或仅 2021 年),您不希望这样
有多种方法可以做到这一点。一种是将标签表连接到自身,将一侧过滤到几个月,另一侧过滤到几年
tb_imagetags tmonth
JOIN tb_imagetags tyear
ON
tmonth.f_imagetagname = 'November' AND
tyear.f_imagetagname = '2021' AND
tmonth.f_imagetagimage = tyear.f_imagetagimage
这会隐式地将 11 月和 2021 年“放在同一行”,因此只有带有这两个标签的图像才会出现在连接结果中。..
..但我们进行此类“跨行”查询的常用方法可能是在对它们进行分组后检查计数,或者检查最小值是否为 x,最大值为 y,例如:
SELECT f_imageID, f_imagefilename
FROM tb_images
INNER JOIN tb_imagetags
ON f_imagetagimage = f_imageID
WHERE f_imagetagname = 'November'
OR f_imagetagname = '2021'
GROUP BY f_imageID
HAVING COUNT(*) = 2
或者
HAVING MIN(f_imagetagname) = '2021' AND MAX( f_imagetagname) = 'November'
如果标签名称不同,则计数有效。如果你能意外地把 11 月加倍,那么它也会把它们加倍。最小值最大值仅适用于两个标签..您也可以使用类似
HAVING SUM(CASE f_imagetagname WHEN 'November' THEN 1 WHEN '2021' THEN 2 END) = 3
这对任何数量的标准都有好处,您只需以 2 的幂次方上升,因此对于 3 个标签,当 1、2、4 并要求总和为 7 时,您也可以采用任何值的幂次,例如以 10 为底.. 增加 1,10,100 并要求总和为 111..
您也可以多次询问是否存在相关行:
SELECT f_imageID, f_imagefilename
FROM tb_images
WHERE
EXISTS(SELECT null FROM tb_imagetags WHERE f_imagetagimage = f_imageID AND f_imagetagname = 'November')
AND
EXISTS(SELECT null FROM tb_imagetags WHERE f_imagetagimage = f_imageID AND f_imagetagname = '2021')
如果存在符合条件的行,则 EXISTS 返回 true:他的 sql 表示“有一些标记行是 11 月并且有一些(其他)标记行是 2021 年的图像”
无论你做什么,你都需要想出一种方法来将数据分组到它存在的 N 行中,然后做一些事情,这意味着作为一个 组 的行满足条件。这是一个技巧,因为我们通常不会像人类一样按照这些固定的术语来思考,我们倾向于更多地“逐行”地思考
【讨论】:
【参考方案2】:一种方法使用聚合:
SELECT i.f_imageID, i.f_imagefilename
FROM tb_images i
INNER JOIN tb_imagetags it
ON it.f_imagetagimage = i.f_imageID
GROUP BY i.f_imageID, i.f_imagefilename
HAVING SUM(f_imagetagname = 'November') > 0 AND
SUM(f_imagetagname = '2021') > 0;
这个想法是按图像聚合,然后断言November
和2021
在每个图像组内的一些记录中都显示为标签值。
这是你的updated DB Fiddle。
【讨论】:
【参考方案3】:您可以为此而存在
SELECT DISTINCT f_imageID, f_imagefilename
FROM tb_images
LEFT JOIN tb_imagetags fi2
ON f_imagetagimage = f_imageID
WHERE f_imagetagname = 'November'
AND EXISTS(SELECT 1 FROM tb_imagetags Fi WHERE f_imagetagname = '2021' AND fi.f_imageID = fi2.f_imageID)
【讨论】:
什么?只要任何图像具有 2021 标记,就选择具有 11 月标记的图像?协调子 我正在编辑答案再试一次以上是关于SQL 查询:在 JOINed 表中搜索多个字段的主要内容,如果未能解决你的问题,请参考以下文章