web-dev-qa-db-fra.com

LEFT OUTER JOIN avec syntaxe de sous-requête

J'apprends SQL à travers un tutoriel GalaXQL.

Je n'arrive pas à comprendre la question suivante (exercice 12):

Générez une liste d'étoiles avec des identifiants d'étoiles inférieurs à 100 avec les colonnes "starname", "startemp", "planetname" et "planettemp". La liste doit avoir toutes les étoiles, avec les données inconnues remplies avec NULL. Ces valeurs sont, comme d'habitude, fictives. Calculez la température d'une étoile avec ((classe + 7) * intensité) * 1000000, et la température d'une planète est calculée à partir de la température de l'étoile moins 50 fois la distance en orbite.

Quelle est la syntaxe pour écrire une requête LEFT OUTER JOIN lorsque vous avez des éléments de sous-requête "AS" que vous devez joindre?

Voici ce que j'ai:

SELECT stars.name AS starname, startemp, planets.name AS planetname, planettemp 
FROM stars, planets 
LEFT OUTER JOIN (SELECT ((stars.class + 7) * stars.intensity) * 1000000 AS startemp 
                 FROM stars) 
             ON stars.starid < 100 = planets.planetid 
LEFT OUTER JOIN (SELECT (startemp - 50 * planets.orbitdistance) AS planettemp 
                 FROM planets) 
             ON stars.starid < 100

Voici le schéma de la base de données (désolé, je ne peux pas publier le fichier image en raison d'une faible représentation):

CREATE TABLE stars (starid INTEGER PRIMARY KEY,
                    name TEXT,
                    x DOUBLE NOT NULL,
                    y DOUBLE NOT NULL,
                    z DOUBLE NOT NULL,
                    class INTEGER NOT NULL,
                    intensity DOUBLE NOT NULL);

CREATE TABLE hilight (starid INTEGER UNIQUE);

CREATE TABLE planets (planetid INTEGER PRIMARY KEY,
                      starid INTEGER NOT NULL,
                      orbitdistance DOUBLE NOT NULL,
                      name TEXT,
                      color INTEGER NOT NULL,
                      radius DOUBLE NOT NULL);

CREATE TABLE moons (moonid INTEGER PRIMARY KEY,
                    planetid INTEGER NOT NULL,
                    orbitdistance DOUBLE NOT NULL,
                    name TEXT,
                    color INTEGER NOT NULL,
                    radius DOUBLE NOT NULL);

CREATE INDEX planets_starid ON planets (starid);
CREATE INDEX moons_planetid ON moons (planetid);
8
verkter

Permet de construire cela lentement.

Tout d'abord, voyons comment obtenir uniquement les informations sur les étoiles:

SELECT name AS starName, (class + 7) * intensity * 1000000 AS starTemp 
FROM Stars
WHERE starId < 100

(cela devrait sembler familier!)
Nous obtenons une liste de toutes les étoiles dont starId est inférieur à 100 (la clause WHERE), saisissant le nom et calculant la température. À ce stade, nous n'avons pas besoin d'une référence claire à la source.

Ensuite, nous devons ajouter des informations sur la planète. Qu'en est-il d'un INNER JOIN (Notez que le mot clé réel INNER est facultatif)?

SELECT Stars.name as starName, (Stars.class + 7) * Stars.intensity * 1000000 AS starTemp,
       Planets.name as planetName
FROM Stars
INNER JOIN Planets
        ON Planets.starId = Stars.starId
WHERE Stars.starId < 100

La clause ON utilise une condition = (Égale) pour relier les planètes à l'étoile en orbite; sinon, nous dirions qu'ils étaient en orbite autour de plus d'une étoile, ce qui est très inhabituel! Chaque étoile est répertoriée une fois pour chaque planète qu'elle possède, mais c'est prévu.

... Sauf que maintenant nous avons un problème: certaines de nos étoiles de la première requête ont disparu! Le (INNER) JOIN Fait que seulement étoiles avec au moins une planète sont signalées. Mais nous devons toujours signaler les étoiles sans aucune planète! Alors qu'en est-il d'une LEFT (OUTER) JOIN?

SELECT Stars.name as starName, (Stars.class + 7) * Stars.intensity * 1000000 AS starTemp,
       Planets.name as planetName
FROM Stars
LEFT JOIN Planets
       ON Planets.starId = Stars.starId
WHERE Stars.starId < 100

... Et nous avons toutes les étoiles de retour, avec planetName étant null (et n'apparaissant qu'une seule fois) s'il n'y a pas de planètes pour cette étoile. Bon jusqu'à présent!

Maintenant, nous devons ajouter la température de la planète. Devrait être simple:

SELECT Stars.name as starName, (Stars.class + 7) * Stars.intensity * 1000000 AS starTemp,
       Planets.name as planetName, starTemp - (50 * Planets.orbitDistance) as planetTemp
FROM Stars
LEFT JOIN Planets
       ON Planets.starId = Stars.starId
WHERE Stars.starId < 100

... sauf que sur la plupart des SGBDR, vous obtiendrez une erreur de syntaxe indiquant que le système ne peut pas trouver starTemp. Que se passe-t-il? Le problème est que le nouvel alias de colonne (nom) n'est (généralement) disponible que après la partie SELECT de l'instruction s'exécute. Ce qui signifie que nous devons refaire le calcul:

SELECT Stars.name as starName, (Stars.class + 7) * Stars.intensity * 1000000 AS starTemp,
       Planets.name as planetName, 
       ((Stars.class + 7) * Stars.intensity * 1000000) - (50 * Planets.orbitDistance) as planetTemp
FROM Stars
LEFT JOIN Planets
       ON Planets.starId = Stars.starId
WHERE Stars.starId < 100

(notez que la base de données peut être suffisamment intelligente pour effectuer le calcul starTemp une seule fois par ligne, mais lors de l'écriture, vous devez le mentionner deux fois dans ce contexte).
Eh bien, c'est un peu désordonné, mais ça marche. J'espère que vous vous souviendrez de changer les deux références si cela est nécessaire ...

Heureusement, nous pouvons déplacer la partie Stars de ceci dans une sous-requête. Nous n'aurons à lister le calcul pour starTemp qu'une seule fois!

SELECT Stars.starName, Stars.starTemp,
       Planets.name as planetName, 
       Stars.starTemp - (50 * Planets.orbitDistance) as planetTemp
FROM (SELECT starId, name AS starName, (class + 7) * intensity * 1000000 AS starTemp 
      FROM Stars
      WHERE starId < 100) Stars
LEFT JOIN Planets
       ON Planets.starId = Stars.starId

Ouais, ça ressemble à comment je l'écrirais. Devrait fonctionner sur pratiquement n'importe quel SGBDR.

Notez que la parenthèse dans Stars.starTemp - (50 * Planets.orbitDistance) n'est là que pour plus de clarté pour le lecteur , la signification des mathématiques resterait inchangée si elles étaient supprimé. Quelle que soit votre connaissance des règles de priorité des opérateurs, mettez toujours des parenthèses lors du mélange des opérations. Cela devient particulièrement avantageux lorsque vous traitez avec OR et AND dans des conditions JOIN et WHERE - de nombreuses personnes perdent la trace de ce qui va se produire.
Notez également que la syntaxe de jointure implicite (la clause FROM séparée par des virgules) est considérée comme une mauvaise pratique en général, ou carrément déconseillée sur certaines plates-formes (les requêtes seront toujours exécutées, mais la base de données peut gronder tu). Cela rend également certaines choses - comme LEFT JOIN - difficiles à faire et augmente la possibilité de vous saboter accidentellement. Alors s'il vous plaît, évitez-le.

15
Clockwork-Muse
SELECT * FROM (SELECT [...]) as Alias1
LEFT OUTER JOIN 
    (SELECT [...]) as Alias2
ON Alias1.id = Alias2.id
8
fieven
WITH( 
 SELECT 
   stars.name AS starname, ((star.class+7)*star.intensity)*1000000) AS startemp,
   stars.starid
 FROM
   stars
) AS star_temps
SELECT 
   planets.name AS planetname, (startemp-50*planets.orbitdistance) AS planettemp
   star_temps.starname, star_temps.startemp
FROM 
   star_temps LEFT OUTER JOIN planets USING (star_id)
WHERE
   star_temps.starid < 100;

Alternativement, on peut construire une sous-requête (j'ai utilisé une expression de table commune) pour accomplir la même tâche que celle illustrée ci-dessous:

SELECT 
   planets.name AS planetname, (startemp-50*planets.orbitdistance) AS planettemp
   star_temps.starname, star_temps.startemp
FROM 
   (SELECT 
      stars.name AS starname, ((star.class+7)*star.intensity)*1000000) AS startemp,
      stars.starid
    FROM
      stars
 ) AS star_temps
LEFT OUTER JOIN planets USING (star_id)
WHERE
   star_temps.starid < 100;
2
SystemFun