it-swarm.com.de

SQL Server: Beitritt zur ersten Zeile

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

  • eliminieren Sie "doppelte" Zeilen
  • schließen Sie sich nur einer der Zeilen an, um Doppelungen zu vermeiden

Erster Versuch

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.

699
Ian Boyd
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.

1102
Quassnoi

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
103
Justin Fisher

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.

26
Tomalak

@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.

17
BornToCode

, 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
11
avb

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
)
8
Abdullah Yousuf

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
6
P. Olesen

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
5
Peter Radocchia

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.

3
Anand

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
2
ernst

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.

2
smerlung