Я использую конкретный, но гипотетический пример.
В каждом заказе обычно есть только одна позиция :
Заказы:
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
Но это дает ошибку:
Столбец или префикс "Заказы" не
совпадение с именем таблицы или псевдонимом
используется в запросе.
Предположительно потому, что внутренний выбор не видит внешнюю таблицу.
12 ответов
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
не является детерминированным: в этом запросе вы получите одну позицию на заказ, но не определено, какая именно.
Множественные вызовы запроса могут дать вам разные позиции для одного и того же заказа, даже если базовый не изменился.
Если вам нужен детерминированный порядок, вы должны добавить предложение ORDER BY
к самому внутреннему запросу.
CROSS APPLY
вместо INNER JOIN
и OUTER APPLY
вместо LEFT JOIN
(то же, что и LEFT OUTER JOIN
).
@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
Иногда вам просто нужно проверить, какой запрос дает лучшую производительность.
(SELECT NULL)
в предложении OVER?
Вы могли сделать:
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.LineItemID = null
и полностью удаляет левые порядки сущностей из результата.
Начиная с 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
, Другой подход с использованием общего табличного выражения:
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
Коррелированные подзапросы - это подзапросы, которые зависят от внешнего запроса. Это похоже на цикл 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
)
Мой любимый способ выполнить этот запрос - использовать предложение 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
)
Но я не тестировал этот метод с другими предлагаемыми здесь методами.
РЕДАКТИРОВАТЬ: неважно, у 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
Попробовал кросс, работает неплохо, но занимает чуть больше времени. Скорректированные столбцы строк, чтобы иметь максимальное значение и добавленную группу, которая сохраняла скорость и отбрасывала дополнительную запись.
Вот скорректированный запрос:
SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber
CROSS APPLY
на помощь:
SELECT Orders.OrderNumber, topline.Quantity, topline.Description
FROM Orders
cross apply
(
select top 1 Description,Quantity
from LineItems
where Orders.OrderID = LineItems.OrderID
)topline
Вы также можете добавить order by
по своему выбору.
join
и apply
не одно и то же
Я знаю, что на этот вопрос недавно был дан ответ, но при работе с большими наборами данных вложенные запросы могут быть дорогостоящими. Вот другое решение, в котором вложенный запрос будет запускаться только один раз, а не для каждой возвращаемой строки.
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 (
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
Похожие вопросы
Связанные вопросы
Новые вопросы
sql
Язык структурированных запросов (SQL) - это язык запросов к базам данных. Вопросы должны включать примеры кода, структуру таблицы, примеры данных и тег для используемой реализации СУБД (например, MySQL, PostgreSQL, Oracle, MS SQL Server, IBM DB2 и т. Д.). Если ваш вопрос относится исключительно к конкретной СУБД (использует определенные расширения / функции), используйте вместо этого тег этой СУБД. Ответы на вопросы, помеченные SQL, должны использовать стандарт ISO / IEC SQL.
group by
?group by
потребует перечисления всех других столбцов, за исключением того, где вы не хотите дублировать. Источник