【问题标题】:How to Join to first row如何加入第一行
【发布时间】:2011-01-03 20:03:49
【问题描述】:

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

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

订单:

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

订单项:

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, ...

所以问题是如何选择

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

第一次尝试

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

但这给出了错误:

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

大概是因为inner select没有看到outer table。

【问题讨论】:

  • 你不能用group by吗?
  • 我认为(如果我错了,请纠正我)group by 需要列出所有其他列,不包括您不想重复的列。 Source

标签: sql sql-server tsql sql-server-2000


【解决方案1】:
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

请注意,没有ORDER BYTOP 1 不是确定性的:此查询将为每个订单获取一个订单项,但未定义它将是哪一个。

查询的多次调用可以为同一订单提供不同的订单项,即使底层证券没有改变。

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

Example sqlfiddle

【讨论】:

  • 很好,很有效;将 TOP 1 从派生表子句移动到联接子句。
  • 和“OUTER JOIN”等价物将是“OUTER APPLY”
  • LEFT OUTER JOIN 怎么样?
  • 如果连接是通过复合键/有多个列,你怎么做?
  • CROSS APPLY 代替INNER JOINOUTER APPLY 代替LEFT JOIN(与LEFT OUTER JOIN 相同)。
【解决方案2】:

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

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

【讨论】:

  • 如果 your 'LineItemId' 列未正确编入索引,这也会快得多。与接受的答案相比。
  • 但是,如果 Max 不可用,因为您需要按与要返回的列不同的列进行排序,您将如何执行此操作?
  • 你可以按照你想要的方式对派生表进行排序,并在 SQL Server 中使用 TOP 1 或在 MySQL 中使用 LIMIT 1
  • 发现这在更大的数据集上要快得多
  • 您能详细说明一下吗?仅就语法而言,您的答案与 Quassnoi 的一样嵌套:恰好一个子查询。您不能仅仅暗示一个将“为返回的每一行”运行,而另一个不会仅仅因为语法 似乎 如此。你必须包括一个计划。
【解决方案3】:

@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 哪个查询提供更好的性能。

【讨论】:

  • 这是我发现的唯一一个真正“左”连接的答案,这意味着它不再添加任何行,然后是在“左”表中。您只需要放入子查询并添加“where RowNum is not null”
  • 同意这是最好的解决方案。此解决方案还不需要您在要加入的表中具有唯一 ID,并且比投票最多的答案要快得多。您还可以通过在子查询中使用 ORDER BY 子句来为您希望返回的行添加条件,而不仅仅是随机取行。
  • 这是一个很好的解决方案。请注DateCreated desc 将是某些表的一种选择,但这取决于很多事情)
  • OVER 子句中的(SELECT NULL) 是什么意思?
【解决方案4】:

你可以这样做:

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 上的索引,否则会很慢。

【讨论】:

  • 如果订单没有 LineItems,这将不起作用。然后子表达式计算 LineItems.LineItemID = null 并从结果中完全删除左侧实体订单。
  • 这也是inner join的效果,所以……是啊。
  • 可以适配 LEFT OUTER JOIN 的解决方案:stackoverflow.com/a/20576200/510583
  • @leo 是的,但是 OP 自己使用了内连接,所以我不明白你的反对意见。
【解决方案5】:

从 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

【讨论】:

  • 如果你问我最好的答案。
  • 我认为这是最好的答案
【解决方案6】:

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

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

【讨论】:

    【解决方案7】:

    相关子查询是依赖于外部查询的子查询。这就像 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
    )
    

    【讨论】:

      【解决方案8】:

      编辑:没关系,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
      

      【讨论】:

        【解决方案9】:

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

        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】:

          尝试过十字架,效果很好,但需要的时间稍长。调整行列以具有最大值并添加组,以保持速度并丢弃额外记录。

          这是调整后的查询:

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

          【讨论】:

          • 但是在两列上分别设置最大值意味着数量可能与描述无关。如果订单是 2 个小部件和 10 个小工具,则查询将返回 10 个小部件。
          【解决方案11】:

          试试这个

          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
          

          【讨论】:

          • 请考虑解释您的查询如何解决 OP 的问题
          • 这看起来和 Justin Fisher 的回答一样。
          猜你喜欢
          • 1970-01-01
          • 2011-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-22
          • 2013-08-10
          相关资源
          最近更新 更多