2016-03-15 70 views
1

试想以下两个表,一个名为“用户”及“订单”:LEFT OUTER JOIN和只返回第一个匹配

ID NAME 
1 Foo 
2 Bar 
3 Qux 


ID USER ITEM SPEC TIMESTAMP 
1 1  12 4  20150204102314 
2 1  13 6  20151102160455 
3 3  25 9  20160204213702 

我想要得到的输出是:

USER ITEM SPEC TIMESTAMP 
1  12 4  20150204102314 
2  NULL NULL NULL 
3  25 9  20160204213702 

换言之:在用户和订单之间执行LEFT OUTER JOIN,如果您没有为该用户找到任何订单,则返回null,但是如果您确实找到了一些,则只返回第一个(最早的一个基于时间戳)。

如果我只使用一个LEFT OUTER JOIN,它将为用户1返回两行,我不希望这样。我想在另一个select中嵌套LEFT OUTER JOIN,GROUP BY其他字段并获取MIN(TIMESTAMP),但这不起作用,因为我需要在我的组中有“SPEC”,并且由于这两个命令有不同的SPEC,他们仍然出现。

有关如何达到预期结果的任何想法,

回答

3

我能想到的最好的办法是使用OUTER APPLY

SELECT * 
FROM Users u 
     OUTER apply (SELECT TOP 1 * 
        FROM Orders o 
        WHERE u.ID = o.[USER] 
        ORDER BY TIMESTAMP DESC) ou 

另外创建于ORDERS表下方NON-Clustered指数将帮助您提高查询

CREATE NONCLUSTERED INDEX IX_ORDERS_USER 
    ON ORDERS ([USER], TIMESTAMP) 
    INCLUDE ([ITEM], [SPEC]); 
1

另一种方式是表现使用开窗功能作为Cte:

with Sorted as 
(
    select u.id as User, o.Item, o.Spec, o.Timestamp 
     row_number() over (partition by u.Id order by Timestamp) as Row 
    from Users u 
    left join orders o 
     on o.User = u.Id 
) 
select User, Item, Spec, Timestamp 
    from Sorted where Row = 1 
1

你会在this question找到很多建议。你有一个左连接的事实是你想要做的事情的附带,所以这些答案应该很容易适应你的问题。我同意@MotoGP对于SQLServer OUTER APPLY可能是最好的方法。这与Postgres的LATERAL JOIN(在其他链接中提到)非常相似。

2

这应该做的伎俩:

SELECT Users.ID, Orders2.USER , Orders2.ITEM , Orders2.SPEC , Orders2.TIMESTAMP 
FROM Users 
LEFT JOIN 
     (
     SELECT Orders.ID, Orders.USER , Orders.ITEM , Orders.SPEC , Orders.TIMESTAMP, ROW_NUMBER() 
       OVER (PARTITION BY ID ORDER BY TIMESTAMP DESC) AS RowNum 
     FROM Orders 

     ) Orders2 ON Orders2.ID = Users.ID And RowNum = 1