Obtenez plusieurs colonnes d'une sous-requête sélectionnée

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)

Comme vous pouvez le voir, je répète la même sous-requête juste pour extraire une autre colonne. Je me demande s'il existe une meilleure façon de procéder?

id est la clé primaire dans les deux tables. Je n'ai aucun problème à rendre product_special.priority unique si cela peut aider.

Sparctus
la source

Réponses:

11

En supposant que la combinaison product_special.id, product_special.priorityest unique

 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
la source
5

sauf si vous avez l'intention de renvoyer les champs en tant que special_price.price et date.date, pourquoi ne pas alias les noms dans la sous-requête? par exemple

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

Votre langage de requête a-t-il une fonction d'agrégation FIRST ()? Je ne sais pas si vous pourriez faire du PK de product_special un composite entre l'id et la priorité (les deux sont de type ASC) et changer la clause ORDER enGROUP BY id, psi.priority

vous POUVEZ être en mesure de supprimer entièrement la clause ORDER BY et d'utiliser HAVING MIN(psi.priority)

mpag
la source
2

Notez que le mécanisme "cross-apply" de SQL Server résoudrait cela, mais il n'est pas disponible dans PostgreSQL. Fondamentalement, c'était leur solution pour savoir comment passer des paramètres (qui ont tendance à être des références à des colonnes externes à l'expression de table actuelle) aux fonctions appelées expressions de table dans la clause FROM. Mais il s'est avéré utile pour toutes sortes de situations où vous souhaitez éviter un autre niveau d'imbrication de sous-requête ou de déplacement de choses de la clause FROM vers la clause SELECT. PostgreSQL a rendu cela possible en faisant une sorte d'exception - vous pouvez passer des paramètres comme ça si l'expression est un simple appel de fonction mais pas à proprement parler un SELECT intégré. Alors

left join highestPriorityProductSpecial(p.id) on true

est ok, mais pas

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

même si la définition de la fonction est précisément cela.

Donc, c'est en fait une solution pratique (en 9.1 au moins): créer une fonction pour extraire votre ligne de priorité la plus élevée en faisant la limite à l'intérieur de la fonction.

Mais les fonctions ont l'inconvénient que le plan de requête ne montrera pas ce qui se passe à l'intérieur et je pense qu'il choisira toujours une jointure en boucle imbriquée, même si ce n'est pas le meilleur.

Paul Vaughan
la source
6
cross apply est disponible dans Postgres à partir de la version 9.3 (sortie en 2013) mais ils ont choisi d'adhérer au standard SQL et d'utiliser l' lateralopérateur standard . Dans votre deuxième requête, remplacez left joinparleft join lateral
a_horse_with_no_name
2

Essayez la commande SQL suivante:

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
la source
1
pouvez-vous s'il vous plaît ajouter plus d'informations à votre réponse
Ahmad Abuhasna
Le code en question utilise LIMITet n'est pas étiqueté avec un SGBD (il pourrait donc s'agir de MySQL ou Postgres ou SQLite ou éventuellement d'autres dbms). Le code dans la réponse utilise OUTER APPLYet TOPdonc il ne fonctionnera que dans SQL Server (et Sybase) qui n'en ont pas LIMIT.
ypercubeᵀᴹ
Celui-ci est applicable au serveur sql uniquement pour les autres bases de données que nous pouvons utiliser la requête interne dans l'instruction select.
SANTOSH APPANA
À Postgres, il n'y en a pas OUTER APPLY, mais il y a LATÉRAL , qui devrait être équivalent. Un exemple en l'utilisant: stackoverflow.com/a/47926042/4850646
Lucas Basquerotto
2

Inspiré par la réponse de dezso /dba//a/222471/127433 Je résous le problème dans PostgreSQL en utilisant des tableaux, comme ceci:

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)

Certes, ce n'est encore qu'une colonne, mais dans mon code, je peux facilement accéder aux deux valeurs. J'espère que cela fonctionne aussi pour vous.

tobi42
la source
1

Je veux juste mettre cela ici en dernier recours, pour tous ceux qui utilisent un moteur de base de données qui ne prend pas en charge une ou plusieurs des autres réponses ...

Vous pouvez utiliser quelque chose comme:

SELECT (col1 || col2) as col3 

(Avec séparateur, ou formatage col1 et col2 à une longueur spécifique.) Et dessinez ensuite vos données à l'aide de sous-chaînes.

J'espère que quelqu'un le trouvera utile.

HAH
la source
0

Dans DB2 pour z / OS, utilisez packet unpackfonctions pour renvoyer plusieurs colonnes dans une sous-sélection.

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
la source