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) 个标签传递给查询,并让它只选择与两个标签匹配的图像。例如,我想传递 November2021 并让它只返回 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;

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

这是你的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 表中搜索多个字段的主要内容,如果未能解决你的问题,请参考以下文章

如何从sql中的两个表之一中搜索

mysql 查询结果,先查含有某字段的数据,再查其他字段的数据

MYSQL查一个字段中 多个值

SQL中查询多个字段时,GROUP BY 要怎么使用?

怎样一个表中的2个查询结果合并到一个表中的两列

SQL语句 怎么把从一个表中查出来数据插入到另一个表中