JOIN连接查询中的行具有所有行的最大值/最小值

时间:2016-06-03 14:41:13

标签: sql sql-server date join max

如果我要加入的查询返回:

IDApplication  ContactDate  CInfo
1              01/06/2016   pie
1              10/01/2016   cake
1              03/02/2015   banana
2              03/06/2016   cake
2              23/12/2015   apple



IDApplication  ReplyDate    RInfo
1              30/05/2016   circle
1              03/05/2016   square
1              04/02/2015   triangle
1              14/01/2016   pentagon
2              04/06/2016   square
2              01/02/2016   pentagon
2              10/06/2016   circle

我需要将其退回:

IDApplication  ContactDate  CInfo       ReplyDate    RInfo    
1              01/06/2016   pie         NULL         NULL
1              10/01/2016   cake        30/05/2016   circle
1              03/02/2015   banana      04/02/2015   triangle
2              03/06/2016   cake        10/06/2016   square
2              23/12/2015   apple       01/02/2016   pentagon

我需要它返回第二个表/查询信息,其中日期大于第一个表中的任何相应应用程序日期,但不大于第一个表中的任何后续日期。

因此对于上面的第一条记录,它是NULL,因为在回复表中没有回复信息,并且在回复表中没有回复信息(因此没有回复),但第二条记录的回复日期为30/05/2016因为这是该申请的最大回复日期。更重要的是,对于第5条记录,回复日期是2016年2月1日,这个日期大于联系日期,但不是申请2的最大回复日期,即2016年6月10日,但另一个申请2的联系日期是在这两者之间需要显示下一个联系日期之前的最大日期。

这是让我的大脑受伤的逻辑。

我已经加入了关于回复日期大于联系日期的第二个查询,但这导致它为所有更大的日期显示行。

我需要以大于基表日期的日期加入记录,但是这些日期不大于下一个最大基表日期。

4 个答案:

答案 0 :(得分:2)

我对这类问题的解决方法通常是将它们分解为小步骤,每个步骤都可以作为CTE实现,因此我得到了一些非常容易阅读和理解的东西。如果需要,我总是可以尝试将其折叠成更少的步骤。这是一个可能的解决方案。请参阅注释,了解其工作原理。

--------------------------------------------------------------------------------
-- Set up the sample data from the question.
--------------------------------------------------------------------------------
declare @Contact table (IDApplication int, ContactDate date, CInfo varchar(32));
declare @Reply table (IDApplication int, ReplyDate date, RInfo varchar(32));

insert @Contact values
    (1, '2016-06-01',' pie'),
    (1, '2016-01-10', 'cake'),
    (1, '2015-02-03', 'banana'),
    (2, '2016-06-03', 'cake'),
    (2, '2015-12-23', 'apple');
insert @Reply values
    (1, '2016-05-30', 'circle'),
    (1, '2016-05-03', 'square'),
    (1, '2015-02-04', 'triangle'),
    (1, '2016-01-14', 'pentagon'),
    (2, '2016-06-04', 'square'),
    (2, '2016-02-01', 'pentagon'),
    (2, '2016-06-10', 'circle');

--------------------------------------------------------------------------------
-- Step 1: Sequence each group of contacts by contact date.
--------------------------------------------------------------------------------
with OrderedContactCTE as
(
    select
        *,
        [Sequence] = row_number() over (partition by IDApplication order by ContactDate)
    from
        @Contact
),

--------------------------------------------------------------------------------
-- Step 2: Match each contact with the subsequent contact (where one exists)
--         having the same IDApplication value. The date of the subsequent
--         contact will act as the upper bound on reply dates that are valid for
--         the original contact. Assign each contact a unique identifier that
--         we'll use in the following step.
--------------------------------------------------------------------------------
PairedContactCTE as
(
    select
        UniqueID = row_number() over (order by Contact.IDApplication, Contact.[Sequence]),
        Contact.IDApplication,
        Contact.ContactDate,
        Contact.CInfo,
        NextContactDate = NextContact.ContactDate
    from
        OrderedContactCTE Contact
        left join OrderedContactCTE NextContact on
            Contact.IDApplication = NextContact.IDApplication and
            Contact.[Sequence] = NextContact.[Sequence] - 1
),

--------------------------------------------------------------------------------
-- Step 3: Match every contact with all replies that are strictly after the 
--         original contact date and, where applicable, strictly before the 
--         subsequent contact date. For each unique contact, sequence the 
--         replies in reverse order by reply date.
--------------------------------------------------------------------------------
OrderedResponseCTE as
(
    select
        Contact.*,
        Reply.ReplyDate,
        Reply.RInfo,
        [Sequence] = row_number() over (partition by Contact.UniqueID order by Reply.ReplyDate desc)
    from
        PairedContactCTE Contact
        left join @Reply Reply on
            Contact.IDApplication = Reply.IDApplication and
            Contact.ContactDate < Reply.ReplyDate and
            (
                Contact.NextContactDate is null or
                Contact.NextContactDate > Reply.ReplyDate
            )
)

--------------------------------------------------------------------------------
-- Step 4: Finally, select each contact and the date/info of the latest reply
--         which is an eligible match for that contact.
--------------------------------------------------------------------------------
select
    IDApplication,
    ContactDate,
    CInfo,
    ReplyDate,
    RInfo
from 
    OrderedResponseCTE 
where 
    [Sequence] = 1;

答案 1 :(得分:1)

经过大约15分钟的心理折磨,我能够简化这个问题。唯一的空洞是我不确定连接条件是否只匹配每种情况下的单个记录。我怀疑还有另一个你没有明确提到的连接条件。

SELECT t1.IDApplication, t1.ContactDate, t1.CInfo,
    t2.ReplyDate, t2.RInfo
FROM table1 t1
LEFT JOIN
table2 t2
    ON t1.IDApplication = t2.IDApplication AND
       t2.ReplyDate > t1.ContactDate AND
       t2.ReplyDate < (SELECT MIN(t.ContactDate)
                       FROM table1 t
                       WHERE t.ContactDate > t1.ContactDate AND
                             t.IDApplication = t1.IDApplication)

答案 2 :(得分:1)

我没有要测试的SQL Server实例。让我知道这有多接近(Tim的解决方案的延伸)

SELECT c1.IDApplication, c1.ContactDate, c1.CInfo, r1.ReplyDate, r1.RInfo
FROM contact_table c1
LEFT JOIN
reply_table r1
    ON c1.IDApplication = r1.IDApplication AND
       r1.ReplyDate > c1.ContactDate AND
       r1.ReplyDate < ( SELECT isnull(MIN(c2.ContactDate),'31-DEC-9999')
                       FROM contact_table c2
                       WHERE c2.ContactDate > c1.ContactDate AND
                             c2.IDApplication = c1.IDApplication ) AND
       NOT EXISTS ( SELECT null
                    FROM reply_table r2
                    WHERE r2.IDApplication = r1.IDApplication AND
                          r2.ReplyDate > r1.ReplyDate AND
                          r2.ReplyDate < ( SELECT isnull(MIN(c2.ContactDate),'31-DEC-9999')
                                           FROM contact_table c2
                                           WHERE c2.ContactDate > c1.ContactDate AND
                                                 c2.IDApplication = c1.IDApplication ) )

答案 3 :(得分:0)

如果你记录依赖于记录上面的一些数据,那么最好实现一些存储过程逻辑。像这样:

/*
CREATE TABLE App (Id INT, ContractDate DATETIME, CInfo VARCHAR(100))

CREATE TABLE Reply (Id INT, ReplyDate DATETIME, RInfo VARCHAR(100))

INSERT App SELECT 1, '06/01/2016',' pie'
INSERT App SELECT 1, '01/10/2016', 'cake'
INSERT App SELECT 1, '02/03/2015', 'banana'
INSERT App SELECT 2, '06/03/2016', 'cake'
INSERT App SELECT 2, '12/23/2015', 'apple'


INSERT Reply SELECT 1, '05/30/2016', 'circle'
INSERT Reply SELECT 1, '05/03/2016', 'square'
INSERT Reply SELECT 1, '02/04/2015', 'triangle'
INSERT Reply SELECT 1, '01/14/2016', 'pentagon'
INSERT Reply SELECT 2, '06/04/2016', 'square'
INSERT Reply SELECT 2, '02/01/2016', 'pentagon'
INSERT Reply SELECT 2, '06/10/2016', 'circle'
*/
--SELECT * FROM App
DECLARE @AppReply TABLE (Id INT, ContractDate DATETIME, CInfo VARCHAR(100), ReplyDate DATETIME, RInfo VARCHAR(100))

DECLARE
    @Id INT,
    @PrevId INT,
    @ContractDate DATETIME,
    @PrevContractDate DATETIME,
    @CInfo VARCHAR(100)

DECLARE appcursor CURSOR FAST_FORWARD FOR 
SELECT Id, ContractDate, CInfo FROM App  

OPEN appcursor  

FETCH NEXT FROM appcursor   
INTO @Id, @ContractDate, @CInfo

WHILE @@FETCH_STATUS = 0  
BEGIN  

    IF(@Id != @PrevId)
        SET @PrevContractDate = NULL

    INSERT @AppReply (Id, ContractDate, CInfo)
    SELECT TOP 1 @Id, @ContractDate, @CInfo

    UPDATE @AppReply SET ReplyDate = R.ReplyDate, RInfo = R.RInfo
    FROM @AppReply AR
    LEFT JOIN Reply R ON R.Id = AR.Id
        AND R.ReplyDate > AR.ContractDate AND R.ReplyDate < ISNULL(@PrevContractDate, DATEADD(DD, 1, R.ReplyDate))
    WHERE AR.Id = @Id AND AR.CInfo = @CInfo

    SET @PrevContractDate = @ContractDate
    SET @PrevId = @Id

    FETCH NEXT FROM appcursor   
    INTO @Id, @ContractDate, @CInfo 
END   
CLOSE appcursor;  
DEALLOCATE appcursor;  

SELECT * FROM @AppReply

希望这会有所帮助。 P.S。:这个查询编写得很快。对不起它的逻辑很差。