使用左连接选择一对多关系中的第一条记录
Posted
技术标签:
【中文标题】使用左连接选择一对多关系中的第一条记录【英文标题】:Select first record in a One-to-Many relation using left join 【发布时间】:2011-12-30 23:29:02 【问题描述】:我正在尝试使用左连接来连接两个表。并且结果集必须只包含“右”连接表中的第一条记录。
假设我有两个表 A 和 B,如下所示;
表“A”
code | emp_no
101 | 12222
102 | 23333
103 | 34444
104 | 45555
105 | 56666
表“B”
code | city | county
101 | Glen Oaks | Queens
101 | Astoria | Queens
101 | Flushing | Queens
102 | Ridgewood | ***lyn
103 | Bayside | New York
预期输出:
code | emp_no | city | county
101 | 12222 | Glen Oaks | Queens
102 | 23333 | Ridgewood | ***lyn
103 | 34444 | Bayside | New York
104 | 45555 | NULL | NULL
105 | 56666 | NULL | NULL
如果你注意到我的结果在左连接后只有一个匹配的记录“B”(不管匹配什么记录)(它是一对多映射)
我需要从表 B 中选择第一个匹配的记录并忽略所有其他行。
请帮忙!
谢谢
【问题讨论】:
行在 SQL 中没有隐含的顺序。你如何决定哪一个是“第一个匹配的记录”? 如果我们在 B 表上按城市订购 astoria 成为第一条记录,我们如何知道哪个是“第一” 对于代码 101,匹配的行可以是 3 行中的任何一行。选择哪一行并不重要。因此,结果集中可能是 Glan Oaks 或 Astoria 或 Flushing。 【参考方案1】:玩了一会儿之后,结果证明这比我预期的要棘手!假设 table_b
有一些唯一的单列(例如,单字段主键),看起来你可以这样做:
SELECT table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM table_a
LEFT
JOIN table_b
ON table_b.code = table_a.code
AND table_b.field_that_is_unique =
( SELECT TOP 1
field_that_is_unique
FROM table_b
WHERE table_b.code = table_a.code
)
;
【讨论】:
啊,我明白了。我想我也可以从我的查询中摆脱is null
测试。
这个答案对我来说效果很好,并且比互联网上关于这个问题的其他一些子查询解决方案要优雅得多。非常感谢!【参考方案2】:
最高投票的答案对我来说似乎不正确,而且似乎过于复杂。 只需在子查询中按表 B 上的代码字段分组,然后选择每个分组的最大 Id。
SELECT
table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM
table_a
LEFT JOIN
table_b
ON table_b.code = table_a.code
AND table_b.field_that_is_unique IN
(SELECT MAX(field_that_is_unique)
FROM table_b
GROUP BY table_b.code)
【讨论】:
虽然这似乎是一个可行的解决方案,但这意味着您要选择具有最大值的行,而 OP 只想拥有任何一行。因此,@ruakh 的答案似乎更精确(并且可能更快,因为没有使用聚合函数) 感谢您的反馈,马塞尔。 The maximum value is as good as any other when it does not matter to the OP which row is selected (not to be confused with "random") - I fail to see how this makes the accepted answer more precise.就速度而言,接受的答案可能会更快,但它是以复杂性为代价的。除非您要详尽地运行此查询,否则性能差异可以忽略不计,并且我的回答更简单。 子查询也可以使用 and = 代替 IN @Isan Rodriguez Trimiño True.【参考方案3】:另一个选项:OUTER APPLY
如果数据库支持,OUTER APPLY
是一个高效且简洁的选项。
SELECT *
FROM
Table_A a
OUTER APPLY
(SELECT TOP 1 *
FROM Table_B b_1
WHERE b_1.code = a.code
) b
;
这将导致与 indeterminate 第一个匹配记录的左连接。我的测试表明它比任何其他已发布的解决方案都更快(在 MS SQL Server 2012 上)。
【讨论】:
重要的是要注意,与接受的答案不同,这将适用于 SQLCE。接受的答案使用不受支持的标量查询。【参考方案4】:如果您使用的是 SQL Server 2005 或更高版本,您可以使用ranking 来实现您想要的。特别是,ROW_NUMBER()
似乎非常适合您的需求:
WITH B_ranked AS (
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY code ORDER BY city)
FROM B
)
SELECT
A.code,
A.emp_no,
B.city,
B.county
FROM A
LEFT JOIN B_ranked AS B ON A.code = B.code AND b.rnk = 1
或
WITH B_unique_code AS (
select * from(
SELECT
*,
rnk = ROW_NUMBER() OVER (PARTITION BY code ORDER BY city)
FROM B
) AS s
where rnk = 1
)
SELECT
A.code,
A.emp_no,
B.city,
B.county
FROM A
LEFT JOIN B_unique_code AS B ON A.code = B.code
【讨论】:
@RayL:感谢您提供替代版本。我没有意识到AND b.rnk = 1
位可能不清楚。 (我以为我会以不会引起任何混乱的方式对其进行格式化,但是哦。)
对于 some 替代方案可能更易于阅读。我喜欢我的替代方案,但对大多数人来说可能并不容易阅读。【参考方案5】:
我修改了 ruakh 的答案,这似乎与 mysql 完美配合。
SELECT
table_a.code,
table_a.emp_no,
table_b.city,
table_b.county
FROM table_a a
LEFT JOIN table_b b
ON b.code = a.code
AND b.id = ( SELECT id FROM table_b
WHERE table_b.code = table_a.code
LIMIT 1
)
;
【讨论】:
【参考方案6】:是这样的:
Select * From TableA a
Left Join TableB b
On b.Code = a.Code
And [Here put criteria predicate that 'defines' what the first record is]
嘿,如果城市和县是唯一的,那就使用它们
Select * From TableA a
Left Join TableB b
On b.Code = a.Code
And b.City + b.county =
(Select Min(city + county)
From TableB
Where Code = b.Code)
但关键是你必须在其中添加一些表达式来告诉查询处理器意味着是什么first。
【讨论】:
对于代码 101,匹配的行可以是 3 行中的任何一行。选择哪一行并不重要。因此,结果集中可能是 Glan Oaks 或 Astoria 或 Flushing @Sandra:SQL 的哪个方言?不同的有不同的选择一行的方法(WHERE ROWNUM = 1
用于 Oracle,LIMIT 1
用于 MySQL 和 PostgreSQL,TOP 1
用于 SQLServer,等等)。【参考方案7】:
在 Oracle 中你可以这样做:
WITH first_b AS (SELECT code, min(rowid) AS rid FROM b GROUP BY code))
SELECT a.code, a.emp_no, b.city, b.county
FROM a
INNER JOIN first_b
ON first_b.code = a.code
INNER JOIN b
ON b.rowid = first_b.rid
【讨论】:
以上是关于使用左连接选择一对多关系中的第一条记录的主要内容,如果未能解决你的问题,请参考以下文章