Как выполнить JOIN с первой строкой?
Заголовок: Как избежать дублирования строк в SQL-запросе при наличии нескольких строк в заказе?
Я приведу конкретный, но гипотетический пример.
Каждый Заказ обычно имеет только один позиционный элемент:
Заказы:
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
Но это вызывает ошибку:
The column or prefix 'Orders' does not
match with a table name or alias name
used in the query.
Предположительно, это происходит из-за того, что внутренний запрос не видит внешнюю таблицу.
5 ответ(ов)
Этот вопрос был задан некоторое время назад, но при обработке больших объемов данных вложенные запросы могут оказаться дорогими. Вот альтернативное решение, где вложенный запрос будет выполнен всего один раз, а не для каждой возвращаемой строки.
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
Это решение оптимизирует запрос, снижая его стоимость за счет уменьшения количества выполнение вложенного запроса. Вместо того чтобы выполнять вложенный запрос для каждой строки, мы сначала получаем максимальный идентификатор элемента в корзине для каждого номера заказа и затем связываем результаты с остальными таблицами. Это значительно улучшает производительность при работе с большими наборами данных.
Вы можете сделать так:
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
, иначе запрос может выполняться медленно.
Для 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
Этот запрос использует функцию FIRST_VALUE()
совместно с оператором OVER
и PARTITION BY
, чтобы получить первое значение Quantity
и Description
для каждой группы заказов, отсортированных по описанию позиций в заказе. Убедитесь, что таблицы Orders
и LineItems
связаны через OrderID
.
Вот другой подход с использованием общего табличного выражения (CTE):
WITH firstOnly AS (
SELECT
Orders.OrderNumber,
LineItems.Quantity,
LineItems.Description,
ROW_NUMBER() OVER (PARTITION BY Orders.OrderID ORDER BY Orders.OrderID) AS 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 = o.OrderID
FOR XML PATH('')) AS NVARCHAR(MAX)) AS lines
) AS l
Этот запрос объединяет описания всех позиций заказа в одну строку, разделенную запятой. Выберите подходящий для вашей задачи метод!
Коррелированные подзапросы — это подзапросы, которые зависят от внешнего запроса. Это похоже на цикл 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
)
В этом случае для каждой строки из таблицы users
подзапрос будет возвращать id
последнего виджета (widgets
), связанного с соответствующим пользователем, что позволяет объединить результаты.
"Вставка результатов хранимой процедуры в временную таблицу"
Как экранировать одинарную кавычку в SQL Server?
Как выполнить оператор UPDATE с JOIN в SQL Server?
Функция против Хранимой процедуры в SQL Server
Следует ли мне использовать != или <> для обозначения "не равно" в T-SQL?