带有子查询语法的 LEFT OUTER JOIN

Posted

技术标签:

【中文标题】带有子查询语法的 LEFT OUTER JOIN【英文标题】:LEFT OUTER JOIN with subquery syntax 【发布时间】:2014-07-14 03:44:45 【问题描述】:

我正在通过 GalaXQL 教程学习 SQL。

我不知道下面的问题(练习 12):

使用列生成星号低于 100 的星号列表 “starname”、“startemp”、“planetname”和“planettemp”。名单 应该有所有的星星,未知的数据用 NULL 填充。 像往常一样,这些值是虚构的。计算一个温度 具有 ((class+7)*intensity)*1000000 的恒星,行星的温度为 由恒星温度减去轨道距离的 50 倍计算得出。

当您有需要连接在一起的子查询项“AS”时,编写 LEFT OUTER JOIN 查询的语法是什么?

这是我所拥有的:

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

这是数据库架构(抱歉,由于代表低,无法发布图像文件):

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);

【问题讨论】:

实际上,我们希望它不是图像,而不是文本;这使得将其转储到运行我们自己的测试的工具中变得更加容易。除其他外,您似乎有一个语法错误:stars.starid &lt; 100 = planets.planetid(如果数据库接受它,我可以保证您没有得到正确的结果)。将来,我们希望能更好地描述什么不起作用,以及您应该得到什么(“鉴于此起始数据,我的查询结果应该如下所示.. 。”)。不过,这比我见过的一些最初的问题写得更好…… 很好,会记住这一点。谢谢。 “这里是数据库架构(抱歉,由于代表性低,无法发布图像文件)”:您可以现在发布,事后发布 :-) 【参考方案1】:

让我们慢慢建立起来。

首先,让我们看看如何获​​取有关星星的信息:

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

(这应该看起来很熟悉!) 我们得到starId 小于 100 的所有星星的列表(WHERE 子句),获取名称并计算温度。在这一点上,我们不需要对源的明确引用。

接下来,我们需要添加行星信息。 INNER JOIN 怎么样(注意实际的关键字INNER 是可选的)?

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

ON 子句使用=(等于)条件将行星与其环绕的恒星联系起来;否则,我们会说它们绕着不止一颗恒星运行,这是非常不寻常的!每颗恒星都针对它拥有的每个行星列出一次,但这是意料之中的。

...除了现在我们有一个问题:第一次查询中的一些星星消失了! (INNER) JOIN 导致至少有一颗行星的恒星被报告。但是我们仍然需要报告没有任何行星的恒星!那么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

...如果该恒星没有行星,我们将返回所有恒星,planetNamenull(并且只出现一次)。到目前为止还不错!

现在我们需要添加行星温度。应该很简单:

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

...除了在大多数 RDBMS 上,您会收到一个语法错误,指出系统找不到 starTemp。这是怎么回事?问题是,新的列别名(名称)(通常)直到语句的SELECT 部分运行之后才可用。这意味着我们需要重新计算:

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

(请注意,db 可能实际上足够聪明,每行仅执行一次 starTemp 计算,但在编写时您必须在此上下文中提及两次)。 好吧,这有点混乱,但它有效。希望如果有必要,您会记得更改这两个引用...

幸运的是,我们可以将其中的 Stars 部分移动到子查询中。我们只需列出一次starTemp 的计算!

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

是的,这看起来像我会这样写。应该适用于基本上任何 RDBMS。

请注意,Stars.starTemp - (50 * Planets.orbitDistance) 中的括号只是为了清楚读者,如果删除它们,数学的含义将保持不变。无论您对运算符优先级规则有多了解,在混合运算时始终放在括号中。这在处理JOINWHERE 条件下的ORs 和ANDs 时尤其有用 - 许多人不知道会发生什么。 另请注意,隐式连接语法(逗号分隔的FROM 子句)通常被认为是不好的做法,或者在某些平台上完全不推荐使用(查询仍会运行,但数据库可能会责骂你)。它还使某些事情——比如LEFT JOINs——变得难以做到,并增加了意外破坏自己的可能性。所以,请避免它。

【讨论】:

@Clockwore-Muse,碰碰朋友。您的回答很透彻且解释清楚。 很好的解释。非常感谢您的宝贵时间。 所以当我们运行子查询并将其分配给别名时,我们仍然必须在 SELECT 中将其称为 table.alias? 哦,不,如果您愿意,您可以将其命名为 someLongNameToSummonElderGodsFromTheDeep(受数据库限制)。如果我在子查询中有一个联接,系统如何知道我“必须”选择哪一个?重要的是我必须给 a 名字,这并不重要。我只是在这里重用了Stars,因为那基本上是您仍在处理的数据;对于更复杂的情况,只需选择一个最能解释数据代表的名称。 @xaume - 单独的问题,但实际上是greatest-n-per-group(子)问题。【参考方案2】:
SELECT * FROM (SELECT [...]) as Alias1
LEFT OUTER JOIN 
    (SELECT [...]) as Alias2
ON Alias1.id = Alias2.id

【讨论】:

这是语法。 :D【参考方案3】:
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;

或者,可以构造一个子查询(我使用了一个公用表表达式)来完成相同的任务,如下所示:

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;

【讨论】:

谢谢!能否请您使用 LEFT OUTER JOIN 完成此操作? 实际上,因为查询没有使用LEFT JOIN,它只会打印颗行星的行,而不是如果不存在则忽略该信息。跨度> @Clockwork-Muse 你是对的,快速阅读问题,更新答案以包括所有星星。 @verkter 希望对您有所帮助! 由于确实支持 CTE 的系统(它不是通用的)被允许为它们构建临时表,因此最好在其中放置尽可能多的限制条件(即,starid &lt; 100)。 USING() 也不是通用的,实际上是有潜在危险的:当你试图找出哪些女演员去过哪些星球并且她们的 id 也是 starId 时会发生什么?更不用说您需要加入多个Stars 的时候了,但是在其他一些列上?请始终明确列出连接条件。不过,这确实回答了这个问题,所以 +1。

以上是关于带有子查询语法的 LEFT OUTER JOIN的主要内容,如果未能解决你的问题,请参考以下文章

多表关联查询语法?

CROSS JOIN + LEFT JOIN 子查询的替代策略?

使用方法语法实现的 LEFT OUTER JOIN

SQL Server 在视图查询中将 LEFT JOIN 替换为 LEFT OUTER JOIN

SQL中的left outer join,inner join,right outer join用法详解

MySQL 的子查询和left join的比较,啥时候用子查询效率高,啥时候用left join效率高?