如何加入第一行


773

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

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

命令:

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

所以问题是如何

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

第一次尝试

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

但这给出了错误:

列或前缀“订单”

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

大概是因为内部选择看不到外部表。


3
你不能用group by吗?
Dariush Jafari

2
我认为(如果我错了,请纠正我)group by将要求列出所有其他列,但您不希望重复的列除外。消息来源
约书亚·纳尔逊

Answers:


1212
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 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

请注意,TOP 1没有ORDER BY不确定性:这个查询,你会得到你每个订单一个项目,但它没有定义哪一个会。

即使基础没有变化,多次调用查询也可以为同一订单提供不同的订单项。

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


3
优秀,有效;将TOP 1从派生表子句移到join子句。
伊恩·博伊德

107
而等效的“外部加入”将是“外部申请”
Alex

9
LEFT OUTER JOIN怎么样?
亚历克斯·诺拉斯科

8
如果联接是通过复合键/具有多个列,该怎么办?
Brett Ryan

7
CROSS APPLY代替INNER JOINOUTER APPLY代替LEFT JOIN(与相同LEFT OUTER JOIN)。
hastrb

117

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

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

2
如果您的 “ LineItemId”列未正确建立索引,这也会更快。与接受的答案相比。
德国

3
但是,如果无法使用Max(因为您需要按与要返回的列不同的列进行排序),您将如何做呢?
NickG

2
您可以按照需要的方式对派生表进行排序,并在SQL Server中使用TOP 1或在MySQL中使用LIMIT 1
stifin 2015年

28

您可以这样做:

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否则它将很慢。


2
如果订单没有LineItems,则此方法不起作用。然后,子表达式评估LineItems.LineItemID = null并从结果中完全删除剩余的实体订单。
leo

6
这也是内部联接的效果,所以...是的。
Tomalak

1
可以适用于左外部联接的解决方案:stackoverflow.com/a/20576200/510583
leo

3
@leo是的,但是OP自己使用了内部联接,所以我不理解您的反对意见。
Tomalak

27

@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

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


3
这是我发现的唯一可以进行真正的“左”联接的答案,这意味着它不会再添加“左”表中的行。您只需要放入子查询并添加“其中RowNum不为null”
user890332

1
同意这是最好的解决方案。此解决方案也不需要您在要加入的表中具有唯一的ID,并且比最受好评的答案要快得多。您还可以通过在子查询中使用ORDER BY子句,为希望返回的行添加条件,而不仅仅是获取随机行。
杰夫·

这是一个很好的解决方案。请注意:在用于您自己的情况时,请务必小心如何使用PARTION BY(通常可能确实希望在其中找到一些ID列)和ORDER BY(可以执行大多数操作,具体取决于您要保留的那一行),例如DateCreated desc是某些表的一种选择,但这要取决于很多事情)
JosephDoggie

14

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

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

13

从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

2
最好的答案,如果你问我。
托马斯,

11

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

5

编辑:没关系,夸斯诺伊有一个更好的答案。

对于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

4

我最喜欢的运行此查询的方式是使用不存在子句。我相信这是运行这种查询的最有效方法:

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
    )

但是我没有针对这里建议的其他方法测试过这种方法。


2

尝试了十字架,效果很好,但需要更长的时间。调整行列以具有最大数量并添加组,以保持速度并删除多余的记录。

这是调整后的查询:

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

10
但是在两列上分别具有max意味着数量可能与描述不相关。如果顺序是2个小部件和10个小工具,则查询将返回10个小部件。
Brianorca 2015年

1

尝试这个

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

2
请考虑说明您的查询可以解决OP的问题
Simas Joneliunas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.