So verbinden Sie sich mit der ersten Reihe

773

Ich werde ein konkretes, aber hypothetisches Beispiel verwenden.

Jede Bestellung hat normalerweise nur eine Werbebuchung :

Aufträge:

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 gibt es jedoch eine Bestellung mit zwei Werbebuchungen:

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 in der Bestellung anzeigen. Aber mit dieser gelegentlichen um zwei , die (oder mehr) Einzelteile, würden die Aufträge erscheinen werden 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 einen auswählt , da dies gut genug ist :

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 ein Auslassungszeichen, um anzuzeigen, dass es mehr als eines 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

  • Beseitigen Sie "doppelte" Zeilen
  • Verbinden Sie sich nur mit einer der Zeilen, um Doppelarbeit zu vermeiden

Erster Versuch

Mein erster naiver Versuch war, mich nur den Werbebuchungen " TOP 1 " anzuschließen :

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' stimmt nicht
mit einem
in der Abfrage verwendeten Tabellennamen oder Aliasnamen überein .

Vermutlich, weil die innere Auswahl die äußere Tabelle nicht sieht.

Ian Boyd
quelle
3
Kannst du nicht benutzen group by?
Dariush Jafari
2
Ich denke (und korrigiere mich, wenn ich falsch liege) group by, dass alle anderen Spalten aufgelistet werden müssen, mit Ausnahme derjenigen, in der Sie keine Duplikate möchten. Quelle
Joshua Nelson

Antworten:

1213
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 ersetzen Sie einfach INNER JOINmit 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

Bitte beachten Sie, dass TOP 1ohne ORDER BYnicht deterministisch ist: Bei dieser Abfrage erhalten Sie eine Werbebuchung pro Bestellung, es ist jedoch nicht definiert, um welche es sich handelt.

Durch mehrere Aufrufe der Abfrage können Sie unterschiedliche Werbebuchungen für dieselbe Bestellung erhalten, auch wenn sich der Basiswert nicht geändert hat.

Wenn Sie eine deterministische Reihenfolge wünschen, sollten Sie ORDER BYder innersten Abfrage eine Klausel hinzufügen .

Quassnoi
quelle
3
Hervorragend, das funktioniert; Verschieben von TOP 1 von der abgeleiteten Tabellenklausel in die Join-Klausel.
Ian Boyd
107
und das "OUTER JOIN" -Äquivalent wäre "OUTER APPLY"
Alex
9
Wie wäre es mit LEFT OUTER JOIN?
Alex Nolasco
8
Wie machen Sie das, wenn der Join über einen zusammengesetzten Schlüssel erfolgt / mehrere Spalten hat?
Brett Ryan
7
CROSS APPLYstattdessen INNER JOINund OUTER APPLYstattdessen LEFT JOIN(das gleiche wie LEFT OUTER JOIN).
Hastrb
117

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 ausgeführt wird, anstatt für jede zurückgegebene Zeile.

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
Justin Fisher
quelle
2
Dies ist auch viel schneller, wenn Ihre 'LineItemId'-Spalte nicht richtig indiziert ist. Im Vergleich zur akzeptierten Antwort.
GER
3
Aber wie würden Sie dies tun, wenn Max nicht verwendbar ist, da Sie nach einer anderen Spalte als der, die Sie zurückgeben möchten, bestellen müssen?
NickG
2
Sie können die abgeleitete Tabelle beliebig bestellen und TOP 1 in SQL Server oder LIMIT 1 in MySQL verwenden
am
28

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) LineItems.LineItemIDund einen Index LineItems.OrderID, sonst ist er langsam.

Tomalak
quelle
2
Dies funktioniert nicht, wenn ein Auftrag keine Werbebuchungen enthält. Der Unterausdruck wertet dann LineItems.LineItemID = nulldie linken Entitätsreihenfolgen aus und entfernt sie vollständig aus dem Ergebnis.
Leo
6
Das ist auch der Effekt der inneren Verbindung, also ... ja.
Tomalak
1
Lösung, die für LEFT OUTER JOIN angepasst werden kann: stackoverflow.com/a/20576200/510583
Leo
3
@leo Ja, aber das OP hat selbst einen inneren Join verwendet, daher verstehe ich Ihren Einwand nicht.
Tomalak
27

Die Antwort von @Quassnoi ist gut. In einigen Fällen (insbesondere wenn die äußere Tabelle groß ist) kann eine effizientere Abfrage die Verwendung von Fensterfunktionen wie der folgenden 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 bietet.

BornToCode
quelle
3
Dies ist die einzige Antwort, die ich gefunden habe, die einen echten "Links" -Verbindungspunkt ausführt, was bedeutet, dass keine weiteren Zeilen hinzugefügt werden, als in der "Links" -Tabelle. Sie müssen nur eine Unterabfrage eingeben und "wo RowNum nicht null ist"
hinzufügen
1
Einverstanden ist dies die beste Lösung. Diese Lösung erfordert auch keine eindeutige ID in der Tabelle, der Sie beitreten, und ist viel schneller als die Antwort mit der höchsten Bewertung. Sie können auch Kriterien hinzufügen, für die Sie eine Zeile zurückgeben möchten, anstatt nur eine zufällige Zeile zu verwenden, indem Sie eine ORDER BY-Klausel in der Unterabfrage verwenden.
Geoff Griswald
Dies ist eine gute Lösung. Bitte beachten Sie: Wenn Sie für Ihre eigene Situation verwenden, achten Sie sehr darauf, wie Sie PARTION BY (normalerweise möchten Sie dort wahrscheinlich eine ID-Spalte) und ORDER BY (was von fast allem möglich ist, je nachdem, welche Zeile Sie behalten möchten, z DateCreated desc wäre eine Wahl für einige Tische, aber es würde von vielen Dingen abhängen)
JosephDoggie
14

, Ein weiterer Ansatz mit allgemeinem Tabellenausdruck:

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?

durch Kommas getrennte 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
avb
quelle
13

Ab SQL Server 2012 wird dies meiner Meinung nach den Trick machen:

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
P. Olesen
quelle
2
Beste Antwort, wenn Sie mich fragen.
Thomas
11

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
)
Abdullah Yousuf
quelle
5

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
Peter Radocchia
quelle
4

Meine bevorzugte Methode zum Ausführen dieser Abfrage ist eine Klausel, die nicht vorhanden ist. 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 gegen andere hier vorgeschlagene Methoden getestet.

Anand
quelle
2

Versuchte das Kreuz, funktioniert gut, dauert aber etwas länger. Die Zeilenspalten wurden so angepasst, dass sie eine maximale und eine hinzugefügte Gruppe haben, die die Geschwindigkeit beibehält und den zusätzlichen Datensatz löscht.

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
ernst
quelle
10
Wenn jedoch maximal zwei Spalten getrennt sind, hängt die Menge möglicherweise nicht mit der Beschreibung zusammen. Wenn die Reihenfolge 2 Widgets und 10 Gadgets wäre, würde die Abfrage 10 Widgets zurückgeben.
Brianorca
1

Versuche dies

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
Bane Neba
quelle
2
Bitte erläutern Sie, was Ihre Anfrage zur Lösung des OP-Problems
bewirkt