Come partecipare alla prima riga


773

Userò un esempio concreto, ma ipotetico.

Ogni ordine ha normalmente un solo elemento pubblicitario :

Ordini:

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

Ma a volte ci sarà un ordine con due elementi pubblicitari:

LineItemID   Order ID    Quantity   Description
==========   ========    ========   =================================
{A58A1...}   6,784,329   5          pentametric fan
{0E9BC...}   6,784,329   5          differential girdlespring 

Normalmente quando si mostrano gli ordini all'utente:

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID

Voglio mostrare il singolo articolo sull'ordine. Ma con questo ordine occasionale contenente due (o più) elementi, gli ordini sarebbero appaiono essere duplicato :

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

Quello che voglio davvero è avere SQL Server solo sceglierne uno , poiché sarà abbastanza buono :

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan

Se divento avventuroso, potrei mostrare all'utente un'ellissi per indicare che ce n'è più di uno:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan, ...

Quindi la domanda è come

  • eliminare le righe "duplicate"
  • solo unirsi a una delle righe, per evitare la duplicazione

Primo tentativo

Il mio primo ingenuo tentativo è stato quello di unirmi solo agli elementi pubblicitari " 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

Ma questo dà l'errore:

La colonna o il prefisso "Ordini" non
corrispondono a un nome di tabella o alias
utilizzato nella query.

Presumibilmente perché la selezione interna non vede la tabella esterna.


3
Non puoi usare group by?
Dariush Jafari,

2
Penso (e correggimi se sbaglio) group byrichiederei di elencare tutte le altre colonne, esclusa quella in cui non vuoi duplicati. Fonte
Joshua Nelson,

Risposte:


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 e versioni successive, è possibile sostituire INNER JOINcon 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

Tieni presente che TOP 1senza ORDER BYnon è deterministico: questa query ti fornirà un elemento pubblicitario per ordine, ma non è definito quale sarà.

Più invocazioni della query possono fornire elementi pubblicitari diversi per lo stesso ordine, anche se il sottostante non è cambiato.

Se si desidera un ordine deterministico, è necessario aggiungere una ORDER BYclausola alla query più interna.


3
Eccellente, funziona; spostare TOP 1 dalla clausola della tabella derivata alla clausola join.
Ian Boyd,

107
e l'equivalente "OUTER JOIN" sarebbe "OUTER APPLY"
Alex

9
Che ne dici di LEFT OUTER JOIN?
Alex Nolasco,

8
Come si fa se l'unione avviene tramite una chiave composta / ha più colonne?
Brett Ryan,

7
CROSS APPLYinvece INNER JOINe OUTER APPLYinvece LEFT JOIN(lo stesso di LEFT OUTER JOIN).
hastrb,

117

So che a questa domanda è stata data risposta qualche tempo fa, ma quando si ha a che fare con set di dati di grandi dimensioni, le query nidificate possono essere costose. Ecco una soluzione diversa in cui la query nidificata verrà eseguita una sola volta, anziché per ogni riga restituita.

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

2
Questo è anche molto più veloce se la colonna 'LineItemId' non è indicizzata correttamente. Rispetto alla risposta accettata.
GER

3
Ma come faresti se Max non fosse utilizzabile in quanto è necessario ordinare da una colonna diversa da quella che si desidera restituire?
NickG

2
puoi ordinare la tabella derivata come preferisci e utilizzare TOP 1 in SQL Server o LIMIT 1 in MySQL
stifin

28

Potresti fare:

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
  )

Ciò richiede un indice (o chiave primaria) attivo LineItems.LineItemIDe un indice attivo LineItems.OrderIDo sarà lento.


2
Questo non funziona se un ordine non ha LineItems. La sottoespressione quindi valuta LineItems.LineItemID = nulle rimuove completamente gli ordini dell'entità sinistra dal risultato.
Leo,

6
Questo è anche l'effetto del join interno, quindi ... sì.
Tomalak,

1
Soluzione che può essere adattata per LEFT OUTER JOIN: stackoverflow.com/a/20576200/510583
leo

3
@leo Sì, ma l'OP ha usato un join interno, quindi non capisco la tua obiezione.
Tomalak,

27

La risposta di @Quassnoi è buona, in alcuni casi (specialmente se la tabella esterna è grande), una query più efficiente potrebbe essere con l'utilizzo di funzioni con finestre, come questa:

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

A volte è sufficiente verificare quale query offre prestazioni migliori.


3
Questa è l'unica risposta che ho trovato che fa un vero join "Sinistra", il che significa che non aggiunge più righe rispetto alla tabella "Sinistra". Devi solo inserire la subquery e aggiungere "dove RowNum non è null"
user890332

1
D'accordo, questa è la soluzione migliore. Inoltre, questa soluzione non richiede di avere un ID univoco nella tabella a cui ti stai unendo ed è molto più veloce della risposta più votata. Puoi anche aggiungere criteri per la riga che preferisci restituire, piuttosto che semplicemente prendere una riga casuale, usando una clausola ORDER BY nella sottoquery.
Geoff Griswald,

Questa è una buona soluzione Nota: quando usi per la tua situazione, fai molta attenzione a come PARTION BY (di solito probabilmente vuoi qualche colonna ID lì) e ORDER BY (che potrebbe essere fatto da quasi tutto, a seconda di quale riga vuoi conservare, ad es. DateCreated desc sarebbe una scelta per alcuni tavoli, ma dipenderà da molte cose)
JosephDoggie

14

, Un altro approccio che utilizza l'espressione di tabella comune:

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

o, alla fine, vorresti mostrare tutte le righe unite?

versione separata da virgola qui:

  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

13

Da SQL Server 2012 in poi penso che questo farà il trucco:

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

2
La migliore risposta se me lo chiedi.
thomas,

11

Le query secondarie correlate sono query secondarie che dipendono dalla query esterna. È come un ciclo for in SQL. La query secondaria verrà eseguita una volta per ogni riga nella query esterna:

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
)

5

EDIT: non importa, Quassnoi ha una risposta migliore.

Per SQL2K, qualcosa del genere:

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

4

Il mio modo preferito per eseguire questa query è con una clausola inesistente. Credo che questo sia il modo più efficiente per eseguire questo tipo di query:

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
    )

Ma non ho testato questo metodo con altri metodi suggeriti qui.


2

Ho provato la croce, funziona bene, ma impiega un po 'più di tempo. Colonne di linea regolate per avere il gruppo massimo e aggiunto che ha mantenuto la velocità e lasciato cadere il record extra.

Ecco la query modificata:

SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber

10
Ma avere max separatamente su due colonne significa che la quantità potrebbe non essere correlata alla descrizione. Se l'ordine fosse composto da 2 widget e 10 gadget, la query restituirebbe 10 widget.
Brianorca,

1

prova questo

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

2
Considera di spiegare cosa fa la tua richiesta per risolvere il problema del PO
Simas Joneliunas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.