1

鉴于此表结构:

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

4 回答 4

2

正如@Joel Coehoorn 之前所说,必须有一种机制来选择从输出中过滤掉哪些具有相同 (b) 的 (a,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;
于 2013-10-23T18:30:30.290 回答
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 检查是否正确以消除重复项。

如果确实出现这些问题,请告诉我,我可以帮助解决问题。

于 2013-10-23T18:22:52.173 回答
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 中在 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 是否允许这样的子查询,但如果不允许,您可以将其定义为“查询”,然后在另一个中使用它。

于 2013-10-23T19:23:36.013 回答
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
于 2013-10-23T17:45:53.853 回答