从 MySQL 中具有不同列的表的多个连接结果中删除重复项
Posted
技术标签:
【中文标题】从 MySQL 中具有不同列的表的多个连接结果中删除重复项【英文标题】:Removing duplicates from result of multiple join on tables with different columns in MySQL 【发布时间】:2011-08-11 16:53:46 【问题描述】:我正在尝试创建一个语句来从 3 个相关表中提取数据(因为它们都共享一个公共字符串索引)。我无法阻止 mysql 返回两个表的乘积,从而使结果集比我想要的大得多。每个表都有不同数量的列,我宁愿不使用 UNION,因为每个表中的数据都是独立的。
这是一个例子:
表 X 是主表,具有字段 A B。
表 Y 有字段 A C D。
表 Z 有字段 A E F G。
-
我的理想结果应该是:
A1 B1 C1 D1 E1 F1 G1
A1 B2 C2 D2 00 00 00
A2 B3 C3 D3 E2 F2 G2
A2 B4 00 00 E3 F3 G3
等等……
-
这是我尝试过的最简单的 SQL,它显示了我的问题(也就是说,它返回由来自 A 的数据索引的 Y * Z 的乘积:
SELECT DISTINCT *
FROM X
LEFT JOIN Y USING (A)
LEFT JOIN Z USING (A)
-
我尝试在 Y 和 Z 上的字段中添加 group by 子句。但是,如果我只按一列分组,它只会返回与该列中每个唯一值匹配的第一个结果(即:A1 C1 E1、A1 C2 E1,A1 C3 E1)。如果我按两列分组,它会再次返回两个表的乘积。
我也尝试在查询中执行多个选择语句,然后加入结果表,但我再次收到表的产品作为输出。
基本上我想将三个 select 语句的结果合并为一个结果,而不是给我所有的数据组合。如果需要,我可以进行多个查询。但是,由于它们都包含一个公共索引,我觉得应该有一种方法可以在我缺少的一个查询中做到这一点。
感谢您的帮助。
【问题讨论】:
好的,也许这会让我更容易理解我的问题。只需忽略表 X 并尝试在字段 A 上连接表 Y 和 Z。即:SELECT * FROM Y INNER JOIN Z USING (A)。你会看到这输出了两个表的乘积。 【参考方案1】:我不知道我是否理解您的问题,但您为什么要使用 LEFT JOIN?这个故事听起来更像是一个 INNER JOIN。这里不需要 UNION。
[编辑] 好的,我想我现在明白你想要什么了。我从未尝试过我将要建议的内容,而且,一些数据库不支持它(还),但我认为你想要一个窗口功能。
WITH Y2 AS (SELECT Y.*, ROW_NUMBER() OVER (PARTITION BY A) AS YROW FROM Y),
Z2 AS (SELECT Z.*, ROW_NUMBER() OVER (PARTITION BY A) AS ZROW FROM Z)
SELECT COALESCE(Y2.A,Z2.A) AS A, Y2.C, Y2.D, Z2.E, Z2.F, Z2.G
FROM Y2 FULL OUTER JOIN Z2 ON Y2.A=Z2.A AND YROW=ZROW;
我们的想法是在尽可能少的行中打印列表,对吗?因此,如果 A1 在 Y 中有 10 个条目,在 Z 中有 7 个条目,那么我们会得到 10 行,其中 3 行的 Z 字段为 NULL。这适用于 Postgres。我不相信这种语法在 MySQL 中可用。
是的:
a | d | c
---+---+----
1 | 1 | -1
1 | 2 | -1
2 | 0 | -1
Z:
a | f | g | e
---+---+---+---
1 | 9 | 9 | 0
2 | 1 | 1 | 0
3 | 0 | 1 | 0
上述语句的输出:
a | c | d | e | f | g
---+----+---+---+---+---
1 | -1 | 1 | 0 | 9 | 9
1 | -1 | 2 | | |
2 | -1 | 0 | 0 | 1 | 1
3 | | | 0 | 0 | 1
【讨论】:
我已经尝试过内连接和左连接;两种方式都会产生 y 和 z 的乘积。 感谢您的帮助,安德鲁。我刚刚切换到 PostgreSQL。但是我只需在 on 子句中添加另一个字段,就可以在 MySQL 中获得“足够好”的结果。 SELECT Y.*, Z.* FROM Y LEFT OUTER JOIN Z USING (A, id) UNION DISTINCT SELECT Y.*, Z.* FROM Y RIGHT OUTER JOIN Z USING (A, id)。 另外,我发现了如何在 MySQL 中的查询中添加行号。这就是让我想到只使用 id 字段并接受稍微脱节的行的原因。至少它只返回每一行 1 次。 SELECT @rownum:=@rownum+1 AS rownum, Y.* FROM (SELECT @rownum:=0) AS rownum, Y;【参考方案2】:是的,UNION
不是答案。
我想你想要:
SELECT *
FROM x
JOIN y ON x.a = y.a
JOIN z ON x.a = z.a
GROUB BY x.a;
【讨论】:
这是错误的,因为 y 和 z 中可能有多个记录需要为 x 中的每一个检索。如果我按 x.a 分组,我只会从每个表中获取第一行。如果我按部分取出组,我会得到 y 和 z 的乘积。【参考方案3】:我发现了一种编辑这篇文章的新方法,这可以用来合并两个表 根据唯一 ID。 试试这个:
create table y
(
a int,
d int,
c int
)
create table z
(
a int,
f int,
g int,
e int
)
go
insert into y values(1,1,-1)
insert into y values(1,2,-1)
insert into y values(2,0,-1)
insert into z values(1,9,9,0)
insert into z values(2,1,1,0)
insert into z values(3,0,1,0)
go
select * from y
select * from z
WITH Y2 AS (SELECT Y.*, ROW_NUMBER() OVER (ORDER BY A) AS YROW FROM Y where A = 3),
Z2 AS (SELECT Z.*, ROW_NUMBER() OVER (ORDER BY A) AS ZROW FROM Z where A = 3)
SELECT COALESCE(Y2.A,Z2.A) AS A, Y2.C, Y2.D, Z2.E, Z2.F, Z2.G
FROM Y2 FULL OUTER JOIN Z2 ON Y2.A=Z2.A AND YROW=ZROW;
【讨论】:
【参考方案4】:PostgreSQL 始终是大多数 MySQL 问题的正确答案,但您的问题本可以通过这种方式解决:
您遇到的问题是您有两个左连接,即
左连接 X 左连接 Y 不可避免地会给你 A x X x Y 你想要的 (AxX)x(AxY)
一个简单的解决方案可能是:
select x.A,x.B,x.C,x.D,y.E,y.F,y.G from (SELECT A.A,A.B,X.C,X.D FROM A LEFT JOIN X ON A.A=X.A) x INNER JOIN (SELECT A.A,Y.E,Y.F,Y.G FROM A LEFT JOIN Y ON A.A=Y.A) y ON x.A=y.A
测试详情:
CREATE TABLE A (A varchar(3),B varchar(3));
CREATE TABLE X (A varchar(3),C varchar(3), D varchar(3));
CREATE TABLE Y (A varchar(3),E varchar(3), F varchar(3), G varchar(3));
INSERT INTO A(A,B) VALUES ('A1','B1'), ('A2','B2'), ('A3','B3'), ('A4','B4');
INSERT INTO X(A,C,D) VALUES ('A1','C1','D1'), ('A3','C3','D3'), ('A4','C4','D4');
INSERT INTO Y(A,E,F,G) VALUES ('A1','E1','F1','G1'), ('A2','E2','F2','G2'), ('A4','E4','F4','G4');
select x.A,x.B,x.C,x.D,y.E,y.F,y.G from (SELECT A.A,A.B,X.C,X.D FROM A LEFT JOIN X ON A.A=X.A) x INNER JOIN (SELECT A.A,Y.E,Y.F,Y.G FROM A LEFT JOIN Y ON A.A=Y.A) y ON x.A=y.A
总而言之,是的,MySQL 有很多很多问题,但这不是其中之一 - 大多数问题涉及更高级的东西。
【讨论】:
【参考方案5】:如果我理解正确,表 X
与 both 表 Y
和 Z
具有 1:n
关系。因此,您看到的行为是预期的。你得到的结果是一种叉积。
如果X
有人员数据,Y
有这些人的地址数据,Z
有这些人的电话数据,那么您的查询自然会显示每个人的所有地址和电话组合。如果某人在您的表中有 3 个地址和 4 个电话,则查询在结果中显示 12 行。
您可以通过使用UNION
查询或发出两个查询来避免它:
SELECT X.*
, Y.*
FROM X
LEFT JOIN Y
ON Y.A = X.A
和:
SELECT X.*
, Z.*
FROM X
LEFT JOIN Z
ON Z.A = X.A
【讨论】:
以上是关于从 MySQL 中具有不同列的表的多个连接结果中删除重复项的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Nhibernate 从连接两个具有所有 id 的表中选择只有一个不同列的多个列是 UNIQUEIDENTIFIER