SQL 查询:跨联接表中的多个字段搜索

SQL Query: Search Across Multiple Fields in JOINed Tables

SQL 版本:MySQL 8.0 或 SQL 服务器

SQL Fiddle: https://www.db-fiddle.com/f/wcHeXkcynUiYP3qzryoYJ7/6

我有 table 个图像和 table 个标签,这些标签 link 到这些图像。

==================================  ===================================================
| 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) 个标签传递给查询并使其 select 仅匹配两个标签的图像。例如,我想传递 November2021 并只传递 return 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'

但是 returns:

f_imageID   f_imagefilename
================================
1           1.jpg
1           1.jpg
2           2.jpg

如何重写此查询以仅获取与两个标签都匹配的图像?

一种方法使用聚合:

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;

想法是按图像聚合,然后断言 November2021 都显示为标记值,​​跨越每个图像组中的一些记录。

这是你的updated DB Fiddle

您可以为此使用 EXISTS

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)

问题是您的数据跨不同的行。如果所有的数据都在同一行,那就容易了

SELECT * FROM blah WHERE month = nov and year = 2021

当它在不同的行中时,您希望像现在一样获得两行..

..但是你只想要那些有两行的图像。如果只有一行(例如只有 11 月或只有 2021 年),则您不希望这样

有多种方法可以做到这一点。一种是将标签 table 连接到自身,将其中一侧过滤到几个月,另一侧过滤到几年

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 月加倍,那么它也会把它们捡起来。 min max 只适用于两个标签..你也可以使用类似

的东西
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 returns 如果有符合条件的行则为真:他的 sql 表示“图像中有一些标记行是 November 并且有一些(其他)标记行是 2021 年


无论你做什么,你都需要想办法将数据分组到它存在的 N 行中,然后做一些事情,这意味着这些行作为 相遇标准。这是一个技巧,因为我们通常不会按照人类的那些固定术语来思考,我们倾向于更多地“逐行”思考