![](/img/trans.png)
[英]SQL Server: join two tables where left table contains all rows of right table in each of its identifier
[英]Is it possible to left join two tables and have the right table supply each row no more than once?
给定此表结构:
Table A
ID AGE EDUCATION
1 23 3
2 25 6
3 22 5
Table B
ID AGE EDUCATION
1 26 4
2 24 6
3 21 3
我想找到两个表之间的所有匹配,其中年龄在2岁以下,教育程度在2岁以下。但是,我不想从TableB中选择任何行超过一次。 B中的每一行应选择0或1次,A中的每一行应选择一次或多次(标准左联接)。
SELECT *
FROM TableA as A LEFT JOIN TableB as B ON
abs(A.age - B.age) <= 2 AND
abs(A.education - B.education) <= 2
A.ID A.AGE A.EDUCATION B.ID B.AGE B.EDUCATION
1 23 3 3 21 3
2 25 6 1 26 4
2 25 6 2 24 6
3 22 5 2 24 6
3 22 5 3 21 3
如您所见,与整个结果集相比,输出中的最后两行具有重复的B.ID 2和3。 我希望这些行作为A.ID = 3的单个空匹配返回,因为它们都与先前的A值匹配。
所需的输出:
(请注意,对于A.ID = 3,B中没有匹配项,因为B中的所有行均已连接到A中的行。)
A.ID A.AGE A.EDUCATION B.ID B.AGE B.EDUCATION
1 23 3 3 21 3
2 25 6 1 26 4
2 25 6 2 24 6
3 22 5 null null null
我可以用一个简短的程序来做到这一点,但是我想使用SQL查询来解决该问题,因为它不适合我,而且我再也不会看到数据或操作环境。
有任何想法吗? 谢谢
正如@Joel Coehoorn先前所说,必须有一种机制可以选择从输出中滤除哪些对(a,b)与相同(b)。 SQL不能很好地允许您在多个匹配项时选择ONE行,因此需要创建数据透视查询,在其中过滤掉不需要的记录。 在这种情况下,可以通过将B的所有匹配ID减小为最小(或最大,这并不重要)来进行过滤,使用可以从集合中返回一个值的任何函数,即min()和max ()最方便使用。 一旦将结果简化为知道所关心的(a,b)对,然后针对该结果进行联接,以提取其余表数据。
select a.id a_id, a.age a_age, a.education a_e,
b.id b_id, b.age b_age, b.education b_e
from a left join
(
SELECT
a.id a_id, min(b.id) b_id from a,b where
abs(A.age - B.age) <= 2 AND
abs(A.education - B.education) <= 2
group by a.id
) g on a.id = g.a_id
left join b on b.id = g.b_id;
据我所知,使用简单的select语句和联接是不可能实现这样的事情的,因为您需要知道已经选择了什么才能消除重复项。
但是,您可以尝试一些类似的方法:
DECLARE @JoinResults TABLE
(A_ID INT, A_Age INT, A_Education INT, B_ID INT, B_Age INT, B_Education INT)
INSERT INTO @JoinResults (A_ID, A_Age, A_Education)
SELECT ID, AGE, EDUCATION
FROM TableA
DECLARE @i INT
SET @i = 1
--Assume that A_ID is incremental and no values missed
WHILE (@i < (SELECT Max(A_ID) FROM @JoinResults
BEGIN
UPDATE @JoinResult
SET B_ID = SQ.ID,
B_Age = SQ.AGE,
B_Education = SQ.Education
FROM (
SELECT ID, AGE, EDUCATION
FROM TableB b
WHERE (
abs((SELECT A_Age FROM @JoinResult WHERE A_Id = @i) - AGE) <=2
AND abs((SELECT A_Education FROM @JoinResult WHERE A_Id = @i) - EDUCATION) <=2
) AND (SELECT B_ID FROM @JoinResults WHERE B_ID = b.id) IS NULL
) AS SQ
SET @i = @i + 1
END
SELECT @JoinResults
注意:我目前无权访问数据库,因此未经测试,因此我厌倦了2个潜在问题
如果确实出现这些问题,请告诉我,我可以帮助您进行故障排除。
在SQL Server中,可以使用CROSS APPLY
语法:
SELECT
a.id, a.age, a.education,
b.id AS b_id, b.age AS b_age, b.education AS b_education
FROM tableB AS b
CROSS APPLY
( SELECT TOP (1) a.*
FROM tableA AS a
WHERE ABS(a.age - b.age) <= 2
AND ABS(a.education - b.education) <= 2
ORDER BY a.id -- your choice here
) AS a ;
根据您在子查询中选择的顺序,将选择来自tableA
不同行。
编辑 (在更新之后):但是,上面的查询将不会显示A中没有匹配的行,甚至不会选择B中没有匹配的行。
也可以使用窗口函数来完成,但是Access没有它们。 这是我认为可以在Access中使用的查询:
SELECT
a.id, a.age, a.education,
s.id AS s_id, s.age AS b_age, s.education AS b_education
FROM tableB AS a
LEFT JOIN
( SELECT
b.id, b.age, b.education, MIN(a.id) AS a_id
FROM tableB AS b
JOIN tableA AS a
ON ABS(a.age - b.age) <= 2
AND ABS(a.education - b.education) <= 2
GROUP BY b.id, b.age, b.education
) AS s
ON a.id = s.a_id ;
我不确定Access是否允许这种子查询,但是如果不允许,则可以将其定义为“查询”,然后在另一个查询中使用它。
使用SELECT DISTINCT
SELECT DISTINCT A.id, A.age, A.education, B.age, B.education
FROM TableA as A LEFT JOIN TableB as B ON
abs(A.age - B.age) <= 2 AND
abs(A.education - B.education) <= 2
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.