SQL Server: как присоединиться к первой строке

Я буду использовать конкретный, но гипотетический пример.

Каждый Заказ обычно имеет только одну позицию:

Заказы:

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         differential girdlespring
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

Но это дает ошибку:

В столбце или префиксе "Заказы" нет совпадение с именем таблицы или псевдонимом используется в запросе.

Предположительно, потому что внутренний выбор не видит внешнюю таблицу.

Ответ 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

Ответ 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

Ответ 3

Вы можете сделать:

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

@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

Иногда вам просто нужно проверить, какой запрос дает лучшую производительность.

Ответ 5

Корреляционные подзапросы - это подзапросы, которые зависят от внешнего запроса. Это похоже на цикл for в SQL. Суб-запрос будет выполняться один раз для каждой строки во внешнем запросе:

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
)

Ответ 6

РЕДАКТИРОВАТЬ: неважно, у Квасной есть лучший ответ.

Для 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

Ответ 7

Пробовал крест, работает красиво, но занимает немного больше времени. Скорректированные столбцы строк имеют максимальную и добавленную группу, которые сохраняли скорость и опускали дополнительную запись.

Здесь скорректированный запрос:

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

Ответ 8

Я решаю аналогичную проблему, используя LEFT JOIN и GROUP BY 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 дает:

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

Мой любимый способ запустить этот запрос - это условие не существует. Я считаю, что это самый эффективный способ запуска такого рода запросов:

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

Другой подход, использующий общее выражение таблицы:

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