Mysql JOIN with IN 和 RIGHT 表的多个结果

Posted

技术标签:

【中文标题】Mysql JOIN with IN 和 RIGHT 表的多个结果【英文标题】:Mysql JOIN with IN and multiple results of RIGHT table 【发布时间】:2012-09-13 04:17:09 【问题描述】:

LEFT 表(简化)是:

id
name (ex:Bob)
tags (ex:1,4,6)

正确的表是:

id
tag (ex:Sailing)

我想像这样得到 LEFT 表的结果:

row 1:
   name: Bob
   tags: Sailing,Snowboard,...

我得到的最近的是那里:

SELECT `name`, GROUP_CONCAT(`right`.`tag`) AS tags
FROM (`left`)
LEFT OUTER JOIN `right` ON `right`.`id` IN(left.tags)
ORDER BY `name` asc

但由于 GROUP_CONCAT(),这只给了我一行。没有它,它给了我所有的结果,但只有逗号分隔列表中的第一个标签。我知道我已经很接近了,但我在这方面浪费了太多时间。任何帮助表示赞赏。

我知道我可以创建第三个表 left_right,其中包含行(id、left_id、right_id)以使其工作,但我想避免这种情况。

【问题讨论】:

tags 是整数字段? (你可以有多个带有不同标签的 Bob 行?) 你无法真正避免这种情况,将来它会变得更加混乱。如果您使用的是关系数据库,请尝试正确使用它们。您需要创建第三个表。 tags 是一个带有逗号分隔数字的 VARCHAR。只有一个“Bob”行包含他的所有标签。我完全同意你的观点,特别是如果我们想在将来从用户中删除标签时让它变得更简单,但是我正在与之一起编写数据库插入编码的另一个人对 mysql 不太好,所以我我试图让他变得简单。 哎哟。然后你想翻译标签从数字到文本?我会编辑我的答案。不过,它变得更加复杂。 是的,我知道,如果太多了,我会帮助我的队友并创建第三张桌子 【参考方案1】:

首先,bažmegakapa 说的是正确的,还有更多。如果我正确理解了您描述的设置,那么您已经在浪费大量空间(和性能)了。

你可以这样做:

CREATE TABLE pleft ( id integer, name varchar(20), tags integer );
CREATE TABLE pright ( id integer, tag varchar(20));

INSERT INTO pleft VALUES ( 1, 'Bob', 1 ), ( 9, 'Bob', 4 ), ( 15, 'Bob', 6 );
INSERT INTO pleft VALUES ( 2, 'Ann', 1 ), ( 3, 'Joe', 4 ), ( 4, 'Joe', 6 );

INSERT INTO pright VALUES ( 1, 'Sailing' ), ( 4, 'Snowboarding' ), ( 6, 'Skiing' );


SELECT pleft.name, GROUP_CONCAT(pright.tag)
    FROM pleft JOIN pright ON ( pleft.tags = pright.id )
    GROUP BY pleft.name ORDER BY pleft.name;

+------+-----------------------------+
| name | GROUP_CONCAT(pright.tag)    |
+------+-----------------------------+
| Ann  | Sailing                     |
| Bob  | Sailing,Skiing,Snowboarding |
| Joe  | Snowboarding,Skiing         |
+------+-----------------------------+

...但请注意名称在pleft 表的每一行中是如何不必要地重复的。理想情况下,您将有一张表对人进行建模:(id=1, name="Bob"),一张表对标签进行建模 (id=6, value="Skiing") 和一张包含它们的关系的表。这将确保,例如,Bob 决定使用“Robert”,您不必对整个标签表进行 de-bob,而只需对涉及 Bob 的一行进行 de-bob。

更新

所以tags 是一个包含“1,4,6”的varchar 字段。同样的逻辑也适用,但现在我们必须在重新组合之前拆分字段。你不能使用像“1 in tags”这样的东西,因为“11”会返回true(“1”毕竟包含在“11”中)。 (这是被称为“乱穿马路”的 SQL 反模式:参见例如 https://groups.google.com/forum/?fromgroups=#!topic/django-users/5j4AmQE6nTk)

SELECT pleft.name, GROUP_CONCAT(pright.tag)
FROM pleft JOIN pright
    ON ( CONCAT(',',pleft.tags,',') LIKE CONCAT('%,',pright.id,',%' ))
GROUP BY pleft.name ORDER BY pleft.name;

另一种方法是使用存储过程:参见http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/。

CREATE TABLE pleft ( id integer, name varchar(20), tags varchar(20) );
INSERT INTO pleft VALUES ( 1, 'Bob', '1,4,6' ), ( 2, 'Jill', '4,1' );
SELECT pleft.name, GROUP_CONCAT(pright.tag)
    FROM pleft JOIN pright
        ON ( CONCAT(',',pleft.tags,',') LIKE CONCAT('%,',pright.id,',%' ))
    GROUP BY pleft.name ORDER BY pleft.name;
+------+-----------------------------+
| name | GROUP_CONCAT(pright.tag)    |
+------+-----------------------------+
| Bob  | Sailing,Snowboarding,Skiing |
| Jill | Sailing,Snowboarding        |
+------+-----------------------------+

【讨论】:

哇,感谢 Isemi 的提醒,我不知道 MySQL 对 Jaywalkimg 反模式采取了这种方式,这在开发后期会遇到一些麻烦,谢谢!然而,对于包含数十万行的表,使用 LIKE 的解决方法会很慢。如果没有更快的方法,我将创建我的第三个关系表【参考方案2】:

尝试添加一个 GROUP BY 名称字段,您应该能够选择所有名称及其标签的列表。

SELECT `name`, GROUP_CONCAT(`right`.`tag`) AS tags
FROM (`left`)
LEFT OUTER JOIN `right` ON `right`.`id` IN(left.tags)
GROUP BY `name`
ORDER BY `name` ASC;

【讨论】:

不,不工作,无论如何使用 GROUP_BY 是没有意义的,GROUP_CONCAT 查询无论如何只返回一行。

以上是关于Mysql JOIN with IN 和 RIGHT 表的多个结果的主要内容,如果未能解决你的问题,请参考以下文章

Oracle left outer join with is null in JOIN vs WHERE 条件(示例)

Mysql join with subquery join 使用相关名称

ClickHouse高级数据查询SQL: WITH/JOIN/IN/INTO OUTFILE/嵌套子查询/交并差计算等

Join EC2 into AD with SSM and remote powershell in AWS

Mysql LEFT JOIN with count 返回未知列

Mysql Join 两个表上 Like with Concat