代码之家  ›  专栏  ›  技术社区  ›  Ian Boyd

SQL Server:如何联接到第一行

  •  642
  • Ian Boyd  · 技术社区  · 15 年前

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

    秩序 通常只有一个 行项目 以下内容:

    命令:

    OrderGUID   OrderNumber
    =========   ============
    {FFB2...}   STL-7442-1      
    {3EC6...}   MPT-9931-8A
    

    LineItems:

    LineItemGUID   Order ID Quantity   Description
    ============   ======== ========   =================================
    {098FBE3...}   1        7          prefabulated amulite
    {1609B09...}   2        32         spurving bearing
    

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

    LineItemID   Order ID    Quantity   Description
    ==========   ========    ========   =================================
    {A58A1...}   6,784,329   5          pentametric fan
    {0E9BC...}   6,784,329   5          differential girdlespring 
    

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

    SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
    FROM Orders
        INNER JOIN LineItems 
        ON Orders.OrderID = LineItems.OrderID
    

    我想在订单上显示单个项目。但是,如果这个偶尔的订单包含两个(或更多)项目,订单将 出现 复制的 :

    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
    

    我真正想要的是拥有SQL Server 选一个 ,就这样 足够好 :

    OrderNumber   Quantity   Description
    ===========   ========   ====================
    STL-7442-1    7          prefabulated amulite
    MPT-9931-8A   32         differential girdlespring
    KSG-0619-81   5          panametric fan
    

    如果我有冒险精神,我可能会给用户看一个省略号,表示有不止一个:

    OrderNumber   Quantity   Description
    ===========   ========   ====================
    STL-7442-1    7          prefabulated amulite
    MPT-9931-8A   32         differential girdlespring
    KSG-0619-81   5          panametric fan, ...
    

    所以问题是如何

    • 消除“重复”行
    • 只连接到其中一行,以避免重复

    第一次尝试

    我第一次天真的尝试是加入 前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
    

    但这就产生了错误:

    列或前缀“orders”没有
    与表名或别名匹配
    在查询中使用。

    可能是因为内部选择没有看到外部表。

    11 回复  |  直到 6 年前
        1
  •  1024
  •   Quassnoi    6 年前
    SELECT   Orders.OrderNumber, LineItems.Quantity, LineItems.Description
    FROM     Orders
    JOIN     LineItems
    ON       LineItems.LineItemGUID =
             (
             SELECT  TOP 1 LineItemGUID 
             FROM    LineItems
             WHERE   OrderID = Orders.OrderID
             )
    

    SQL Server 2005 上面,你可以换一个 INNER JOIN 具有 CROSS 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
    

    请注意 TOP 1 没有 ORDER BY 不是确定性的:这个查询您将获得每个订单一个行项目,但是它没有定义它将是哪个。

    查询的多次调用可以为同一顺序提供不同的行项目,即使底层没有更改。

    如果要确定顺序,应添加 按顺序 子句到最里面的查询。

        2
  •  97
  •   Justin Fisher    12 年前

    我知道这个问题不久前就得到了解答,但是在处理大型数据集时,嵌套查询可能会很昂贵。这里有一个不同的解决方案,其中嵌套查询只运行一次,而不是针对返回的每一行。

    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
    
        3
  •  26
  •   Tomalak    15 年前

    你可以这样做:

    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
      )
    

    这要求在 LineItems.LineItemID 和上的索引 LineItems.OrderID 否则会很慢。

        4
  •  16
  •   Community CDub    7 年前

    @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
    

    有时候你只是 need to test 哪个查询提供更好的性能。

        5
  •  9
  •   avb    7 年前

    ,另一个使用公共表表达式的函数:

    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
    

    或者,最后,您可能希望显示所有连接的行?

    此处使用逗号分隔的版本:

      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
    
        6
  •  7
  •   Racil Hilan    8 年前

    相关子查询是依赖外部查询的子查询。它就像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
    )
    
        7
  •  5
  •   Peter Radocchia    15 年前

    编辑:无论如何,奎斯诺有更好的答案。

    对于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
    
        8
  •  3
  •   smerlung    8 年前

    我使用左联接和按orders.ordernumber分组来解决类似的问题。有没有理由不这样做?

    SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
    FROM Orders
        LEFT JOIN LineItems 
        ON Orders.OrderID = LineItems.OrderID
    GROUP BY Orders.OrderNumber
    

    我会用你自己的问题回答你的答案:

    Orders             LineItems
    +-------------+    +---------+----------+---------------+
    | OrderNumber |    | OrderID | Quantity | Description   |
    +-------------+    +---------+----------+---------------+
    | 22586       |    | 22586   | 17       | Trunion       |
    +-------------+    | 22586   | 3        | Girdle Spring |
                       +---------+----------+---------------+
    

    将这两个订单号连接在一起可以得到:

    OrderNumber  Quantity  Description
    -----------  --------  -------------
    22586        17        Trunion
    22586        3         Girdle Spring
    
    2 row(s) affected
    

    我们希望它只返回一行:

    OrderNumber  Quantity  Description
    -----------  --------  -------------
    22586        17        Trunion
    
    1 row(s) affected
    

    这就是为什么我使用group by orders.ordernumber,它只返回每个ordernumber一行。

        9
  •  3
  •   Anand    7 年前

    我最喜欢使用不存在子句来运行此查询。我认为这是运行此类查询的最有效方法:

    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
        )
    

    但我并没有用这里建议的其他方法来测试这个方法。

        10
  •  3
  •   P. Olesen    6 年前

    从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
    
        11
  •  2
  •   Krease    12 年前

    试过交叉,效果很好,但需要稍长时间。将行列调整为“最大”,并添加组以保持速度并删除额外记录。

    下面是调整后的查询:

    SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
    FROM Orders
        INNER JOIN LineItems 
        ON Orders.OrderID = LineItems.OrderID
    Group by Orders.OrderNumber