Abrufen mehrerer Spalten aus einer ausgewählten Unterabfrage

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)

Wie Sie sehen, wiederhole ich dieselbe Unterabfrage, nur um eine weitere Spalte herauszubekommen. Ich frage mich, gibt es einen besseren Weg, dies zu tun?

id ist der Primärschlüssel in beiden Tabellen. Ich habe kein Problem damit, product_special.priority einzigartig zu machen, wenn das helfen kann.

Sparctus
quelle

Antworten:

11

Vorausgesetzt, die Kombination product_special.id, product_special.priorityist einzigartig

 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)
a1ex07
quelle
5

Es sei denn, Sie möchten die Felder als special_price.price und date.date zurückgeben. Warum nicht einen Alias ​​für die Namen in der Unterabfrage verwenden? z.B

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

Verfügt Ihre Abfragesprache über eine FIRST () - Aggregatfunktion? Wir sind uns nicht sicher, ob Sie die PK von product_special als Zusammensetzung zwischen id und priority (beide ASC-Sortierung) definieren und die ORDER-Klausel in ändern könntenGROUP BY id, psi.priority

Sie KÖNNTEN in der Lage sein, die ORDER BY-Klausel vollständig zu entfernen und zu verwenden HAVING MIN(psi.priority)

mpag
quelle
2

Beachten Sie, dass der "Cross Apply" -Mechanismus von SQL Server dies lösen würde, aber in PostgreSQL nicht verfügbar ist. Grundsätzlich war dies die Lösung für die Übergabe von Parametern (bei denen es sich in der Regel um Verweise auf Spalten außerhalb des aktuellen Tabellenausdrucks handelt) an Funktionen, die in der FROM-Klausel als Tabellenausdrücke bezeichnet werden. Es hat sich jedoch für alle Situationen als nützlich erwiesen, in denen Sie vermeiden möchten, dass Unterabfragen auf einer anderen Ebene verschachtelt werden oder Dinge von der FROM-Klausel in die SELECT-Klausel verschoben werden. PostgreSQL hat dies durch eine Art Ausnahme ermöglicht - Sie können Parameter wie diesen übergeben, wenn der Ausdruck ein einfacher Funktionsaufruf ist, aber nicht unbedingt ein eingebettetes SELECT. So

left join highestPriorityProductSpecial(p.id) on true

ist ok aber nicht

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

obwohl die Definition der Funktion genau das ist.

Das ist in der Tat eine praktische Lösung (mindestens in 9.1): Erstellen Sie eine Funktion, um die Zeile mit der höchsten Priorität zu extrahieren, indem Sie das Limit innerhalb der Funktion ausführen.

Funktionen haben jedoch den Nachteil, dass der Abfrageplan nicht anzeigt, was in ihnen vor sich geht, und ich glaube, dass immer ein Join mit verschachtelten Schleifen ausgewählt wird, auch wenn dies möglicherweise nicht der beste ist.

Paul Vaughan
quelle
6
cross apply ist in Postgres ab Version 9.3 (Veröffentlichung 2013) verfügbar, sie haben sich jedoch dafür entschieden, den SQL-Standard einzuhalten und den Standardoperator zu verwenden lateral. Ersetzen Sie in Ihrer zweiten Abfrage left joinmitleft join lateral
a_horse_with_no_name am
2

Versuchen Sie den folgenden SQL-Befehl:

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
Santosh Appana
quelle
1
können Sie bitte weitere Informationen zu Ihrer Antwort hinzufügen
Ahmad Abuhasna
Der fragliche Code verwendet LIMITund ist nicht mit einem DBMS versehen (es kann sich also um MySQL oder Postgres oder SQLite oder möglicherweise um ein anderes DBMS handeln). Der Code in der Antwort verwendet OUTER APPLYund TOPso funktioniert es nur in SQL Server (und Sybase), die nicht haben LIMIT.
Ypercubeᵀᴹ
Dieser gilt nur für SQL Server für andere Datenbanken, die wir in der select-Anweisung als innere Abfrage verwenden können.
Santosh Appana
In Postgres gibt es keine OUTER APPLY, aber es gibt LATERAL , was äquivalent sein sollte. Ein Beispiel dafür: stackoverflow.com/a/47926042/4850646
Lucas Basquerotto
2

Inspiriert von der Antwort von dezso /dba//a/222471/127433. Ich löse das Problem in PostgreSQL mithilfe von Arrays wie folgt :

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)

Es ist zwar immer noch nur eine Spalte, aber in meinem Code kann ich problemlos auf die beiden Werte zugreifen. Hoffe, es funktioniert auch für Sie.

tobi42
quelle
1

Ich möchte dies hier nur als letztes Mittel anführen, für alle, die eine Datenbank-Engine verwenden, die eine oder mehrere der anderen Antworten nicht unterstützt ...

Sie können etwas verwenden wie:

SELECT (col1 || col2) as col3 

(Mit Trennzeichen oder Formatieren von Spalte 1 und Spalte 2 auf eine bestimmte Länge.) Zeichnen Sie Ihre Daten später mithilfe von Unterzeichenfolgen.

Ich hoffe, jemand findet es nützlich.

HAH
quelle
0

Verwenden Sie packund in DB2 für z / OS, unpackum mehrere Spalten in einer Unterauswahl zurückzugeben.

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);
Keith C
quelle