Ich werde ein konkretes, aber hypothetisches Beispiel verwenden.
Jeder Auftrag hat normalerweise nur einen Werbebuchung:
Bestellungen:
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
Gelegentlich wird es jedoch einen Auftrag mit zwei Positionen geben:
LineItemID Order ID Quantity Description
========== ======== ======== =================================
{A58A1...} 6,784,329 5 pentametric fan
{0E9BC...} 6,784,329 5 differential girdlespring
Normalerweise, wenn dem Benutzer die Bestellungen angezeigt werden:
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
Ich möchte den einzelnen Artikel auf der Bestellung anzeigen. Aber mit dieser gelegentlichen Bestellung, die zwei (oder mehr) Artikel enthält, würden die Bestellungen erscheinen dupliziert:
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
Was ich wirklich möchte, ist, dass SQL Server nur eines auswählt, da es gut genug sein wird :
OrderNumber Quantity Description
=========== ======== ====================
STL-7442-1 7 prefabulated amulite
MPT-9931-8A 32 differential girdlespring
KSG-0619-81 5 panametric fan
Wenn ich abenteuerlustig werde, zeige ich dem Benutzer möglicherweise eine Ellipse, um anzuzeigen, dass es mehr als eine gibt:
OrderNumber Quantity Description
=========== ======== ====================
STL-7442-1 7 prefabulated amulite
MPT-9931-8A 32 differential girdlespring
KSG-0619-81 5 panametric fan, ...
Die Frage ist also, wie es geht
Mein erster naiver Versuch war, nur den Werbebuchungen "TOP 1" beizutreten:
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
Aber das gibt den Fehler:
Die Spalte oder das Präfix "Bestellungen" enthält keine
stimmt mit einem Tabellennamen oder Aliasnamen überein
in der Abfrage verwendet.
Vermutlich, weil die innere Auswahl die äußere Tabelle nicht sieht.
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
JOIN LineItems
ON LineItems.LineItemGUID =
(
SELECT TOP 1 LineItemGUID
FROM LineItems
WHERE OrderID = Orders.OrderID
)
In SQL Server 2005
und höher können Sie INNER JOIN
einfach durch CROSS APPLY
ersetzen:
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
Bitte beachten Sie, dass TOP 1
ohne ORDER BY
nicht deterministisch ist: Bei dieser Abfrage erhalten Sie eine Werbebuchung pro Auftrag, es ist jedoch nicht definiert, um welche es sich handelt.
Durch mehrere Aufrufe der Abfrage können Sie unterschiedliche Werbebuchungen für den gleichen Auftrag erhalten, auch wenn sich der zugrunde liegende Wert nicht geändert hat.
Wenn Sie eine deterministische Reihenfolge wünschen, sollten Sie der innersten Abfrage eine ORDER BY
-Klausel hinzufügen.
Ich weiß, dass diese Frage vor einiger Zeit beantwortet wurde, aber bei großen Datenmengen können verschachtelte Abfragen kostspielig sein. Hier ist eine andere Lösung, bei der die verschachtelte Abfrage nur einmal und nicht für jede zurückgegebene Zeile ausgeführt wird.
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
Du könntest es tun:
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
)
Dies erfordert einen Index (oder Primärschlüssel) für LineItems.LineItemID
und einen Index für LineItems.OrderID
, oder er ist langsam.
@Quassnoi Antwort ist gut, in einigen Fällen (besonders wenn die äußere Tabelle groß ist), könnte eine effizientere Abfrage mit der Verwendung von Fensterfunktionen wie folgt sein:
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
Manchmal müssen Sie nur testen welche Abfrage eine bessere Leistung liefert.
, Ein weiterer Ansatz, bei dem ein allgemeiner Tabellenausdruck verwendet wird:
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
oder möchten Sie am Ende vielleicht alle verbundenen Zeilen anzeigen?
kommagetrennte Version hier:
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
Korrelierte Unterabfragen sind Unterabfragen, die von der äußeren Abfrage abhängen. Es ist wie eine for-Schleife in SQL. Die Unterabfrage wird einmal für jede Zeile in der äußeren Abfrage ausgeführt:
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
)
Ab SQL Server 2012 wird dies der Trick sein:
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
EDIT: egal, Quassnoi hat eine bessere Antwort.
Für SQL2K ungefähr so:
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
Meine Lieblingsmethode zum Ausführen dieser Abfrage ist eine nicht vorhandene Klausel. Ich glaube, dies ist der effizienteste Weg, um diese Art von Abfrage auszuführen:
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
)
Ich habe diese Methode jedoch nicht mit anderen hier vorgeschlagenen Methoden verglichen.
Versuchte das Kreuz, funktioniert gut, dauert aber etwas länger. Angepasste Zeilenspalten, um maximale und hinzugefügte Gruppen zu haben, die die Geschwindigkeit beibehielten und den zusätzlichen Datensatz fallen ließen.
Hier ist die angepasste Abfrage:
SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
INNER JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber
Ich löse ein ähnliches Problem mit LEFT JOIN und GROUP BY Orders.OrderNumber. Gibt es einen Grund, es nicht so zu machen?
SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
LEFT JOIN LineItems
ON Orders.OrderID = LineItems.OrderID
GROUP BY Orders.OrderNumber
Ich beantworte Ihre Antwortfrage mit einer Antwort in Ihrer eigenen Frage:
Orders LineItems
+-------------+ +---------+----------+---------------+
| OrderNumber | | OrderID | Quantity | Description |
+-------------+ +---------+----------+---------------+
| 22586 | | 22586 | 17 | Trunion |
+-------------+ | 22586 | 3 | Girdle Spring |
+---------+----------+---------------+
Das Zusammenfügen der beiden auf OrderNumber ergibt:
OrderNumber Quantity Description
----------- -------- -------------
22586 17 Trunion
22586 3 Girdle Spring
2 row(s) affected
Wo wir wollten, dass es nur eine Zeile zurückgibt:
OrderNumber Quantity Description
----------- -------- -------------
22586 17 Trunion
1 row(s) affected
Aus diesem Grund verwende ich GROUP BY Orders.OrderNumber, die nur eine Zeile pro OrderNumber zurückgibt.