具有非平凡连接的SQL查询

时间:2011-11-18 17:43:27

标签: sql sql-server sql-server-2005 tsql

我有两个表正在尝试加入查询,但是我在获取所需输出时遇到了一些困难。

表:扫描

ScanId  ScanTime
1        8:00
2        8:15
3        9:00
4        9:30
6       10:00
7       10:45
8       11:00
9       11:10

表:回应

ScanId  RespA  RespB
3       ABC    X
7       DEF    Y
9       GHI    Z

Responses.ScanId上有一个引用Scans.ScanId的外键。

“回复”表可能会也可能没有“扫描”表中每行的相应行。

我需要做的是生成如下所示的输出,Scans表中的每一行都返回最近的先前响应值。

对于Scans.ScanId 1和2,没有先前的响应,因此Response列为空。

对于Scans.ScanId 3,4和6,最近的前一个RespA是ABC,RespB是X(ScanId = 3)

对于Scans.ScanId 7和8,最近的RespA是DEF,RespB是Y(ScanId = 7)

对于Scan.ScanId 9,最近的RespA是GHI,RespB是Z(ScanId = 9)

期望的输出:

ScanId  ScanTime  RespScanId  RespA  RespB
1        8:00     NULL        NULL   NULL
2        8:15     NULL        NULL   NULL
3        9:00     3           ABC    X
4        9:30     3           ABC    X
6       10:00     3           ABC    X
7       10:45     7           DEF    Y
8       11:00     7           DEF    Y
9       11:10     9           GHI    Z

我很难弄清楚如何为这个编写join子句。它需要在Sql Server 2005及更高版本上运行。

5 个答案:

答案 0 :(得分:1)

Here's a solution using a CTE...

DECLARE @Scans TABLE (
    ScanID INT,
    ScanTime DATETIME
)

DECLARE @Responses TABLE (
    ScanID INT, 
    RespA VARCHAR(50),
    RespB VARCHAR(50)
)

INSERT INTO @Scans VALUES (1, '8:00')
INSERT INTO @Scans VALUES (2, '8:15')
INSERT INTO @Scans VALUES (3, '9:00')
INSERT INTO @Scans VALUES (4, '9:30')
INSERT INTO @Scans VALUES (6, '10:00')
INSERT INTO @Scans VALUES (7, '10:45')
INSERT INTO @Scans VALUES (8, '11:00')
INSERT INTO @Scans VALUES (9, '11:10')

INSERT INTO @Responses VALUES (3, 'ABC', 'X')
INSERT INTO @Responses VALUES (7, 'DEF', 'Y')
INSERT INTO @Responses VALUES (9, 'GHI', 'Z')

;WITH ResponsesScan AS
(
    SELECT
        r.*, s.ScanTime
    FROM
        @Responses r
        JOIN @Scans s ON s.ScanID = r.ScanID
)
SELECT
    s.ScanID,
    s.ScanTime,
    rs.ScanID AS RespScanId,
    rs.RespA,
    rs.RespB
FROM
    @Scans s 
    LEFT JOIN ResponsesScan rs
        ON rs.ScanID = (
            SELECT TOP 1 ScanID
            FROM ResponsesScan
            WHERE ScanTime <= s.ScanTime
            ORDER BY ScanTime DESC
        )

答案 1 :(得分:0)

使用窗口函数:

SELECT S.ScanId, ScanTime, R.ScanId AS RespScanId, RespA, RespB
FROM Scans S
LEFT OUTER JOIN (SELECT ScanId, 
                        RespA, 
                        RespB, 
                        LEAD(ScanId) OVER(ORDER BY ScanId) AS NextScanId
                 FROM Responses) R
    ON S.ScanId >= R.ScanId 
   AND (S.ScanId < R.NextScanId OR R.NextScanID IS NULL);

根据结果集在PostgreSQL上返回:

 scanid | scantime | respscanid | respa | respb 
--------+----------+------------+-------+-------
      1 | 08:00:00 |            |       | 
      2 | 08:15:00 |            |       | 
      3 | 09:00:00 |          3 | ABC   | X
      4 | 09:30:00 |          3 | ABC   | X
      6 | 10:00:00 |          3 | ABC   | X
      7 | 10:45:00 |          7 | DEF   | y
      8 | 11:00:00 |          7 | DEF   | y
      9 | 11:10:00 |          9 | GHI   | Z

答案 2 :(得分:0)

我的内部查询获取扫描ID和它上面的最小扫描ID ..如果没有其他,则它具有NULL作为“NextScan”值。然后,我加入扫描。我对每个扫描ID都进行了限定,以便扫描ID至少是相关的扫描ID,但比后面的扫描更少。

SELECT 
      scans.scanID,
      scans.scanTime,
      rsp3.ScanID,
      rsp3.RespA,
      rsp3.RespB
   from 
      scans
         LEFT JOIN 
            ( SELECT rsp.scanID,
                  ( select MIN( rsp2.scanID ) 
                            from responses rsp2
                            where rsp2.scanID > rsp.ScanID ) as NextScan
                 from 
                    responses rsp ) PQ 
            on Scans.ScanID >= PQ.ScanID
            AND Scans.ScanID <= case when PQ.NextScan IS NULL
                                     then PQ.ScanID
                                     else PQ.NextScan -1
                                end
            LEFT JOIN Responses rsp3
               on PQ.ScanID = rsp3.ScanID

答案 3 :(得分:0)

select s.scanid, s.scantime, xjoin.RespScanId, r.respa, r.respb 
from scans s
left join 
-- begin trick
    (select si.scanid, max(ri.scanid) as RespScanId
    from scans si
    left join responses ri on si.scanid >= ri.scanid
    group by si.scanid)  as  xjoin
-- end trick
       on s.scanid = xjoin.scanid
left join responses r on xjoin.RespScanId = r.scanid

答案 4 :(得分:0)

建立Michael Fredrickson的Common Table Expression,这是select查询的另一个版本。

虽然这个查询无疑更复杂,但它具有不同的性能特征(即它可能表现更好)。但是,如果索引ScanTime列,性能差异是微不足道的。

select
    s.ScanId,
    s.ScanTime,
    rs.ScanId as RespScanId,
    rs.RespA,
    rs.RespB
from Scans s
left outer join ResponsesScan rs on
    -- either there is a response for this specific scan
    rs.ScanId = s.ScanId
    or (
        -- or there is a response from a previous scan
        rs.ScanTime < s.ScanTime
        -- and there does not exist any other response
        and not exists (
            select 1
            from ResponsesScan rs2
            where
                -- for this specific scan
                rs2.ScanId = s.ScanId
                or (
                    -- or from a previous scan
                    rs2.ScanTime < s.ScanTime
                    -- that is later than that response
                    and rs2.ScanTime > rs.ScanTime
                )
        )
    )