带有子查询语法的 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 < 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
...如果该恒星没有行星,我们将返回所有恒星,planetName
是 null
(并且只出现一次)。到目前为止还不错!
现在我们需要添加行星温度。应该很简单:
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)
中的括号只是为了清楚读者,如果删除它们,数学的含义将保持不变。无论您对运算符优先级规则有多了解,在混合运算时始终放在括号中。这在处理JOIN
和WHERE
条件下的OR
s 和AND
s 时尤其有用 - 许多人不知道会发生什么。
另请注意,隐式连接语法(逗号分隔的FROM
子句)通常被认为是不好的做法,或者在某些平台上完全不推荐使用(查询仍会运行,但数据库可能会责骂你)。它还使某些事情——比如LEFT JOIN
s——变得难以做到,并增加了意外破坏自己的可能性。所以,请避免它。
【讨论】:
@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 < 100
)。 USING()
也不是通用的,实际上是有潜在危险的:当你试图找出哪些女演员去过哪些星球并且她们的 id 也是 starId
时会发生什么?更不用说您需要加入多个Stars
的时候了,但是在其他一些列上?请始终明确列出连接条件。不过,这确实回答了这个问题,所以 +1。以上是关于带有子查询语法的 LEFT OUTER JOIN的主要内容,如果未能解决你的问题,请参考以下文章
CROSS JOIN + LEFT JOIN 子查询的替代策略?
SQL Server 在视图查询中将 LEFT JOIN 替换为 LEFT OUTER JOIN