Ottieni più colonne da una sottoquery selezionata


24
SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
   ( 
       SELECT ps.price 
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS special_price,
   ( 
       SELECT ps.date 
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS date
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id)

Come puoi vedere sto ripetendo la stessa subquery solo per far uscire un'altra colonna. Mi chiedo c'è un modo migliore per farlo?

id è la chiave primaria in entrambe le tabelle. Non ho problemi a rendere unico product_special.priority se questo può aiutare.

Risposte:


11

Supponendo che la combinazione product_special.id, product_special.prioritysia unica

 SELECT p.*, special_price,special_date
 FROM product p
 LEFT JOIN 
 (
     SELECT ps.id, ps.price as special_price, ps.`date` as special_date
     FROM product_special ps
     INNER JOIN 
     (
       SELECT id, MIN(priority) as min_priority 
       FROM product_special
       GROUP BY id
     ) ps2 
     ON (ps2.id = ps.id)
 )a ON (a.id=p.id)

5

a meno che non si intenda restituire i campi come special_price.price e date.date perché non alias i nomi all'interno della sottoquery? per esempio

SELECT p.*, p.name AS  name, p.image, p.price, ps.*
FROM product p
LEFT JOIN
   (SELECT
      psi.price as special_price, psi.date as my_date 
    FROM product_special psi
    WHERE 
      p.id = psi.id AND
      psi.date < NOW()
    ORDER BY psi.priority ASC, LIMIT 1
   ) AS ps ON
  p.id = ps.id

Il tuo linguaggio di query ha una funzione di aggregazione FIRST ()? Non sono sicuro se potresti rendere il PK di product_special un composto tra id e priorità (entrambi ordinamento ASC) e modificare la clausola ORDER inGROUP BY id, psi.priority

POTREBBE essere in grado di rimuovere completamente la clausola ORDER BY e utilizzarla HAVING MIN(psi.priority)


2

Si noti che il meccanismo di "applicazione incrociata" di SQL Server risolverebbe questo problema, ma non è disponibile in PostgreSQL. Fondamentalmente, era la loro soluzione su come passare i parametri (che tendono ad essere riferimenti a colonne esterne all'espressione di tabella corrente) a funzioni chiamate come espressioni di tabella nella clausola FROM. Ma si è rivelato utile per tutti i tipi di situazioni in cui si desidera evitare un altro livello di annidamento di subquery o spostamento di cose dalla clausola FROM alla clausola SELECT. PostgreSQL ha reso possibile farlo facendo una specie di eccezione: è possibile passare parametri del genere se l'espressione è una semplice chiamata di funzione ma non strettamente parlando di un SELECT incorporato. Così

left join highestPriorityProductSpecial(p.id) on true

va bene, ma no

left join (select * from product_special ps where ps.id = p.id order by priority desc limit 1) on true

anche se la definizione della funzione è proprio quella.

Quindi, questa è in effetti una soluzione pratica (almeno in 9.1): crea una funzione per estrarre la riga con la massima priorità facendo il limite all'interno della funzione.

Ma le funzioni hanno lo svantaggio che il piano di query non mostrerà ciò che sta accadendo al loro interno e credo che sceglierà sempre un join loop nidificato, anche quando potrebbe non essere il migliore.


6
cross apply è disponibile in Postgres a partire da 9.3 (rilasciato nel 2013) ma hanno scelto di aderire allo standard SQL e utilizzare l' lateraloperatore standard . Nella tua seconda query sostituisci left joinconleft join lateral
a_horse_with_no_name il

2

Prova il seguente comando SQL:

SELECT p.name,p.image,p.price,pss.price,pss.date
FROM Product p OUTER APPLY(SELECT TOP(1)* 
FROM ProductSpecial ps
WHERE p.Id = ps.Id ORDER BY ps.priority )as pss

1
per favore, puoi aggiungere ulteriori informazioni alla tua risposta
Ahmad Abuhasna,

Il codice in questione utilizza LIMITe non è taggato con un DBMS (quindi potrebbe essere MySQL o Postgres o SQLite o eventualmente altri dbms). Il codice nella risposta usa OUTER APPLYe TOPquindi funzionerà solo in SQL Server (e Sybase) che non hanno LIMIT.
ypercubeᵀᴹ

Questo è applicabile per il server sql solo per altri database che possiamo usare la query interna all'interno dell'istruzione select.
SANTOSH APPANA,

In Postgres non esiste OUTER APPLY, ma esiste LATERAL , che dovrebbe essere equivalente. Un esempio di utilizzarlo: stackoverflow.com/a/47926042/4850646
Lucas Basquerotto

2

Ispirato dalla risposta di dezso /dba//a/222471/127433 Sto risolvendo il problema in PostgreSQL usando array, come questo:

SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
   ( 
       SELECT ARRAY[ps.price, ps.date]
       FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1
   ) AS special_price_and_date
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id)

Certo, è ancora solo una colonna, ma nel mio codice, posso facilmente accedere ai due valori. Spero che funzioni anche per te.


1

Voglio solo metterlo qui per l'ultima risorsa, per tutti coloro che usano il motore di database che non supporta una o più delle altre risposte ...

Puoi usare qualcosa come:

SELECT (col1 || col2) as col3 

(Con separatore o formattazione di col1 e col2 a una lunghezza specifica.) E successivamente disegnare i dati utilizzando le stringhe secondarie.

Spero che qualcuno lo trovi utile.


0

In DB2 per z / OS, utilizzare packe le unpackfunzioni per restituire più colonne in una sottoselezione.

SELECT 
   *, 
   p.name AS name, 
   p.image, 
   p.price,
    unpack((select PACK (CCSID 1028,
               ps.price,
               ps.date)
         FROM product_special ps 
       WHERE p.id = ps.id
         AND ps.date < NOW() 
       ORDER BY ps.priority ASC, LIMIT 1)) .* AS (SPECIAL_PRICE double, DATE date)
FROM product p LEFT JOIN product_special ps ON (p.id = ps.id);
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.