SQL Server:如何加入第一行

Ian*_*oyd 718 sql t-sql sql-server sql-server-2000

我将使用一个具体的,但假设的例子.

每个订单通常只有一个订单项:

命令:

OrderGUID   OrderNumber
=========   ============
{FFB2...}   STL-7442-1      
{3EC6...}   MPT-9931-8A
Run Code Online (Sandbox Code Playgroud)

了LineItem:

LineItemGUID   Order ID Quantity   Description
============   ======== ========   =================================
{098FBE3...}   1        7          prefabulated amulite
{1609B09...}   2        32         spurving bearing
Run Code Online (Sandbox Code Playgroud)

但偶尔会有一个包含两个订单项的订单:

LineItemID   Order ID    Quantity   Description
==========   ========    ========   =================================
{A58A1...}   6,784,329   5          pentametric fan
{0E9BC...}   6,784,329   5          differential girdlespring 
Run Code Online (Sandbox Code Playgroud)

通常在向用户显示订单时:

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID
Run Code Online (Sandbox Code Playgroud)

我想在订单上显示单个项目.但是,由于此偶尔包含两个(或更多)商品的订单,订单将显示重复:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         spurving bearing
KSG-0619-81   5          panametric fan
KSG-0619-81   5          differential girdlespring
Run Code Online (Sandbox Code Playgroud)

我真正想要的是让SQL Server 选择一个,因为它足够好:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan
Run Code Online (Sandbox Code Playgroud)

如果我喜欢冒险,我可能会向用户显示一个省略号,表示不止一个:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan, ...
Run Code Online (Sandbox Code Playgroud)

所以问题是如何

  • 消除"重复"行
  • 只加入其中一行,以避免重复

第一次尝试

我的第一个天真的尝试是只加入" TOP 1 "订单项:

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN (
       SELECT TOP 1 LineItems.Quantity, LineItems.Description
       FROM LineItems
       WHERE LineItems.OrderID = Orders.OrderID) LineItems2
    ON 1=1
Run Code Online (Sandbox Code Playgroud)

但是这给出了错误:

列或前缀"Orders"

查询中使用的表名或别名不匹配.

大概是因为内部选择没有看到外表.

Qua*_*noi 1135

SELECT   Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM     Orders
JOIN     LineItems
ON       LineItems.LineItemGUID =
         (
         SELECT  TOP 1 LineItemGUID 
         FROM    LineItems
         WHERE   OrderID = Orders.OrderID
         )
Run Code Online (Sandbox Code Playgroud)

SQL Server 2005以上,你可以只更换INNER JOINCROSS APPLY:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
CROSS APPLY
        (
        SELECT  TOP 1 LineItems.Quantity, LineItems.Description
        FROM    LineItems
        WHERE   LineItems.OrderID = Orders.OrderID
        ) LineItems2
Run Code Online (Sandbox Code Playgroud)

请注意,TOP 1没有ORDER BY确定性:此查询您将获得每个订单一个订单项,但未定义它将是哪一个.

对查询进行多次调用可以为同一订单提供不同的行项目,即使底层证券没有更改也是如此.

如果您想要确定性顺序,则应该ORDER BY在最里面的查询中添加一个子句.

  • 并且"OUTER JOIN"等价物将是"OUTER APPLY" (103认同)
  • LEFT OUTER JOIN怎么样? (9认同)
  • 如果连接是通过复合键/有多列,你如何做到这一点? (8认同)
  • “交叉应用”而不是“内部联接”,而“外部应用”而不是“左联接”(与“左外部联接”相同)。 (5认同)
  • 优秀,有效; 将TOP 1从派生表子句移动到join子句. (3认同)
  • @JeffDavis:如果`OrderId`是主键中的前导列,它将为每行节省一次搜索. (2认同)
  • 请记住,没有 ORDER BY 的 TOP 是不确定的。您不能保证获得您认为会获得的行。 (2认同)
  • 或者,使用 MIN() 而不是 TOP 1。我发现我通常对第一个 Id 感兴趣。 (2认同)

Jus*_*her 109

我知道这个问题已经回答了一段时间,但是在处理大型数据集时,嵌套查询的代价很高.这是一个不同的解决方案,嵌套查询只运行一次,而不是返回每一行.

SELECT 
  Orders.OrderNumber,
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders
  INNER JOIN (
    SELECT
      Orders.OrderNumber,
      Max(LineItem.LineItemID) AS LineItemID
    FROM
      Orders INNER JOIN LineItems
      ON Orders.OrderNumber = LineItems.OrderNumber
    GROUP BY Orders.OrderNumber
  ) AS Items ON Orders.OrderNumber = Items.OrderNumber
  INNER JOIN LineItems 
  ON Items.LineItemID = LineItems.LineItemID
Run Code Online (Sandbox Code Playgroud)

  • 但是,如果Max无法使用,您将如何执行此操作,因为您需要通过与要返回的列不同的列进行排序? (3认同)
  • 如果没有正确索引_your_'LineItemId'列,这也会快得多.与接受的答案相比. (2认同)
  • 你可以按照你想要的方式订购派生表,并在SQL Server中使用TOP 1或在MySQL中使用LIMIT 1 (2认同)

Tom*_*lak 26

你可以这样做:

SELECT 
  Orders.OrderNumber, 
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders INNER JOIN LineItems 
  ON Orders.OrderID = LineItems.OrderID
WHERE
  LineItems.LineItemID = (
    SELECT MIN(LineItemID) 
    FROM   LineItems
    WHERE  OrderID = Orders.OrderID
  )
Run Code Online (Sandbox Code Playgroud)

这需要索引(或主键)LineItems.LineItemID和索引,LineItems.OrderID或者它会很慢.

  • 这也是内部联接的影响,所以...是的. (6认同)
  • @leo是的,但OP自己使用了内部联接,所以我不理解你的反对意见. (3认同)
  • 如果 Orders 没有 LineItems,这将不起作用。然后子表达式评估`LineItems.LineItemID = null`,并从结果中完全删除左侧实体订单。 (2认同)

Bor*_*ode 23

@Quassnoi答案很好,在某些情况下(特别是如果外表很大),使用窗口函数可能会有更高效的查询,如下所示:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
LEFT JOIN 
        (
        SELECT  LineItems.Quantity, LineItems.Description, OrderId, ROW_NUMBER()
                OVER (PARTITION BY OrderId ORDER BY (SELECT NULL)) AS RowNum
        FROM    LineItems

        ) LineItems2 ON LineItems2.OrderId = Orders.OrderID And RowNum = 1
Run Code Online (Sandbox Code Playgroud)

有时您只需要测试哪个查询可以提供更好的性能.

  • 这是我发现的唯一一个真正“左”连接的答案,这意味着它不会在“左”表中添加更多行。您只需要放入子查询并添加“其中 RowNum 不为空” (3认同)
  • 同意这是最好的解决方案。此解决方案也不要求您在要加入的表中拥有唯一的 ID,并且比得票最高的答案要快得多。您还可以通过在子查询中使用 ORDER BY 子句来添加您希望返回的行的条件,而不仅仅是获取随机行。 (2认同)

avb*_*avb 11

,另一个使用公用表表达式的方法:

with firstOnly as (
    select Orders.OrderNumber, LineItems.Quantity, LineItems.Description, ROW_NUMBER() over (partiton by Orders.OrderID order by Orders.OrderID) lp
    FROM Orders
        join LineItems on Orders.OrderID = LineItems.OrderID
) select *
  from firstOnly
  where lp = 1
Run Code Online (Sandbox Code Playgroud)

或者,最后可能你想显示所有加入的行?

逗号分隔版本:

  select *
  from Orders o
    cross apply (
        select CAST((select l.Description + ','
        from LineItems l
        where l.OrderID = s.OrderID
        for xml path('')) as nvarchar(max)) l
    ) lines
Run Code Online (Sandbox Code Playgroud)


小智 9

相关子查询是依赖于外部查询的子查询.它就像SQL中的for循环.对于外部查询中的每一行,子查询将运行一次:

select * from users join widgets on widgets.id = (
    select id from widgets
    where widgets.user_id = users.id
    order by created_at desc
    limit 1
)
Run Code Online (Sandbox Code Playgroud)


小智 8

从SQL Server 2012起,我认为这可以解决问题:

SELECT DISTINCT
    o.OrderNumber ,
    FIRST_VALUE(li.Quantity) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Quantity ,
    FIRST_VALUE(li.Description) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Description
FROM    Orders AS o
    INNER JOIN LineItems AS li ON o.OrderID = li.OrderID
Run Code Online (Sandbox Code Playgroud)

  • 如果你问我,最好的答案。 (4认同)

Pet*_*hia 5

编辑:没关系,Quassnoi有一个更好的答案.

对于SQL2K,这样的事情:

SELECT 
  Orders.OrderNumber
, LineItems.Quantity
, LineItems.Description
FROM (  
  SELECT 
    Orders.OrderID
  , Orders.OrderNumber
  , FirstLineItemID = (
      SELECT TOP 1 LineItemID
      FROM LineItems
      WHERE LineItems.OrderID = Orders.OrderID
      ORDER BY LineItemID -- or whatever else
      )
  FROM Orders
  ) Orders
JOIN LineItems 
  ON LineItems.OrderID = Orders.OrderID 
 AND LineItems.LineItemID = Orders.FirstLineItemID
Run Code Online (Sandbox Code Playgroud)


Ana*_*and 5

我最喜欢的运行此查询的方法是使用 not contains 子句。我相信这是运行此类查询的最有效方法:

select o.OrderNumber,
       li.Quantity,
       li.Description
from Orders as o
inner join LineItems as li
on li.OrderID = o.OrderID
where not exists (
    select 1
    from LineItems as li_later
    where li_later.OrderID = o.OrderID
    and li_later.LineItemGUID > li.LineItemGUID
    )
Run Code Online (Sandbox Code Playgroud)

但我尚未将此方法与此处建议的其他方法进行测试。