是否可以将两个表连接起来并且每个行的右表只能提供一次?

时间:2013-10-23 17:39:14

标签: sql left-join

鉴于此表结构:

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查询来解决这个问题,因为它不适合我,我不会有奢侈品看到数据或操纵环境。

有什么想法吗?感谢

4 个答案:

答案 0 :(得分:2)

正如@Joel Coehoorn先前所说,必须有一种机制可以选择从输出中滤出哪些(a,b)与(b)相同的对。 SQL允许您在多个匹配时选择一行,因此不太好,因此需要创建一个数据透视查询,过滤掉您不想要的记录。在这种情况下,可以通过将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;

答案 1 :(得分:1)

据我所知,使用简单的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个潜在的问题

  1. 如果没有结果,我不确定更新会如何反应
  2. 我不确定IS NULL检查是否正确以消除重复项。
  3. 如果出现这些问题,请告诉我,我可以帮您解决问题。

答案 2 :(得分:1)

在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中没有匹配行的行,甚至是某些尚未选中的行。


也可以使用窗口函数完成,但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是否允许这样的子查询,但如果没有,则可以将其定义为“查询”,然后在另一个中使用它。

答案 3 :(得分:0)

使用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