从 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】:

如果我理解正确,表 XbothYZ 具有 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

mysql 来自同一列的多个连接具有不同的键

单个查询从具有不同列的多个表中获取记录

连接来自两个不同表的两列

如何格式化结果是具有 3 列的表的 CString 变量?

如何合并具有不同列号的两个表,同时删除具有大量列的表的重复项