我正在尝试回答以下问题:
从所有类别中选择租借电影(有DVD)的客户的名字和姓氏,按名字和姓氏排序。
数据库包括:
(更好的视图 - 在新标签中打开)
Inventory -> DVD's
Rental -> Rents customers did
分类表:
| category_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(25) | YES | | NULL |
我的疑问是如何指定查询中的字段必须包含来自其他查询(类别)的所有ID。
我的意思是我理解我们可以通过租赁和电影自然地加入库存,然后在单个类别中找到失败的ID,然后我们知道他不包含所有......但我无法完成此操作
我有这个解决方案(但我无法理解):
SELECT first_name, last_name
FROM customer AS C WHERE NOT EXISTS
(SELECT * FROM category AS K WHERE NOT EXISTS
(SELECT * FROM (film NATURAL JOIN inventory) NATURAL JOIN rental
WHERE C.customer_id = customer_id AND K.category_id = category_id));
还有其他解决方案吗?
答案 0 :(得分:4)
在我们的项目中,我们从不使用NATURAL JOIN
。这对我们没有用,因为PRIMARY KEY始终是名为id
的代理列,外键列始终是tablename_id
。
自然加入会将一个表格中的id
与另一个表格中的id
匹配,而这不是我们想要的。我们经常有"家务管理"表中名称相同的列,例如用于乐观锁定模式的version
列。
即使我们的命名约定不同,并且连接列的名称相同,如果我们向表中添加了一列,现有查询中也可能会加入更改它被命名为与另一个表中的列相同。
并且,读取包含NATURAL JOIN
的SQL语句,我们无法查看实际匹配的列,而无需遍历表定义,查找名称相同的列。这似乎给声明的读者带来了不必要的负担。 (一个SQL语句将会是"读取"比它编写的次数多得多......保存击键的语句的作者不会因为模糊而导致额外工作的有益权衡未来的读者。
(我知道其他人对此主题有不同的看法。我确信可以使用NATURAL JOIN
模式编写成功的软件。我只是不够聪明或不够合适我非常重视DBA的观点,他们拥有多年的数据库建模经验,实现模式,编写和调优SQL,支持操作系统,处理不断变化的需求和持续维护。)
我在哪里......哦,是的......回到定期安排的节目......
架构的图像对我来说太小而无法破译,我似乎无法从中复制任何文本。 SHOW CREATE TABLE
的输出很多更容易使用。
您是否有SQL Fiddle设置?
我不会在问题中削弱查询实际上会有效。我认为有一个限制,即" up"相关子查询可以引用外部查询。
对我来说,它看起来像这个谓词
WHERE C.customer_id = customer_id
^^^^^^^^^^^^^
太深。其中的子查询不允许引用C
中的列,该表太高了。 (也许我对此完全错误;也许它的Oracle或SQL Server或Teradata有这种限制。或者MySQL曾经有过这种限制,但后来的版本已经解除了它。)
其他方法
作为另一种方法,我们可以为每位客户提供他所租用的每个类别的清单。
然后,我们可以比较"客户租用类别"有完整的(不同)类别列表。一个相当简单的方法是将每个列表折叠成一个" count"不同类别,然后比较计数。如果客户的计数小于总计数,那么我们知道他没有从每个类别中租用。 (还有一些注意事项,我们需要确保客户"从类别"列表中仅包含总类别列表中的类别。)
另一种方法是获取(不同的)客户列表,并对每个可能的类别执行交叉连接(笛卡尔积)。 (警告:这可能是相当大的设置。)
通过这组"客户交叉产品类别",我们可以消除客户从该类别租用的行(可能使用反连接模式。)
这会让我们留下一组客户以及他们没有租借的类别。
OP没有设置表格和示例数据的SQL小提琴;所以,我也不打算这样做。我会提供一些示例SQL语句,但图像中的表定义不可用;为了证明这些陈述实际有效,我需要在表格中提供一些示例数据。
(同样,我不相信问题中的陈述确实有效。但没有证明它确实有效。)
如果NATURAL JOIN
语法没有,我会更倾向于自己测试一下。如果没有可用的表定义,我就不够聪明。
如果我参与其中,我首先想到的是重写它以删除NATURAL
关键字,并在实际{{1}中添加实际谓词}子句,并限定所有列引用。
查询最终会看起来像这样:
ON
(我认为对SELECT c.first_name
, c.last_name
FROM customer c
WHERE NOT EXISTS
( SELECT 1
FROM category k
WHERE NOT EXISTS
( SELECT 1
FROM film f
JOIN inventory i
ON i.film_id = f.film_id
JOIN rental r
ON r.inventory_id = i.inventory_id
WHERE f.category_id = k.category_id
AND r.customer_id = c.customer_id
)
)
的引用太深,无效。)
修改强>
我坚持认为c.customer_id
对C.customer_id
的引用太多" deep"。该查询不会给我带来错误。
但它似乎也没有返回我们期待的结果集,我可能会以某种方式搞砸了它。哦,好吧。
以下是获得不同租赁类别"计数的示例。对于每个客户(GROUP BY c.customer_id
,以防我们有两个客户使用相同的名字和姓氏)并与category
的计数进行比较。
SELECT c.last_name
, c.first_name
FROM customer c
JOIN rental r
ON r.customer_id = c.customer_id
JOIN inventory i
ON i.inventory_id = r.inventory_id
JOIN film f
ON f.film_id = i.film_id
GROUP
BY c.last_name
, c.first_name
, c.customer_id
HAVING COUNT(DISTINCT f.category_id)
= (SELECT COUNT(DISTINCT a.category_id) FROM category a)
ORDER
BY c.last_name
, c.first_name
, c.customer_id
修改强>
这里展示了另一种方法,生成了所有客户和所有类别的笛卡尔产品(警告:不要在LARGE集上执行此操作!),并查明这些行中是否有任何行?匹配。
-- customers who have rented from EVERY category
-- h = cartesian (cross) product of all customers with all categories
-- g = all categories rented by each customer
-- perform outer join, return all rows from h and matching rows from g
-- if a row from h does not have a "matching" row found in g
-- columns from g will be null, test if any rows have null values from g
SELECT h.last_name
, h.first_name
FROM ( SELECT hi.customer_id
, hi.last_name
, hi.first_name
, hj.category_id
FROM customer hi
CROSS
JOIN category hj
) h
LEFT
JOIN ( SELECT c.customer_id
, f.category_id
FROM customer c
JOIN rental r
ON r.customer_id = c.customer_id
JOIN inventory i
ON i.inventory_id = r.inventory_id
JOIN film f
ON f.film_id = i.film_id
GROUP
BY c.customer_id
, f.category_id
) g
ON g.customer_id = h.customer_id
AND g.category_id = h.category_id
GROUP
BY h.last_name
, h.first_name
, h.customer_id
HAVING MIN(g.category_id IS NOT NULL)
ORDER
BY h.last_name
, h.first_name
, h.customer_id
答案 1 :(得分:0)
我会抓住这个,只因为我很好奇为什么提出的答案看起来如此复杂。首先,提出几个问题。
所以你的问题是:"从所有类别中选择租用电影(有DVD' s)的客户的名字和姓氏,按名字和姓氏排序。&#34 ;
所以,只需通过租赁数据库,加入客户。我不确定类别部分与此有什么关系,因为您没有选择或显示任何类别,因此不需要成为搜索的一部分,它暗示为当他们租DVD时,DVD具有一个类别。
SELECT C.first_name, C.last_name
FROM customer as C JOIN rental as R
ON (C.customer_id = R.customer_id)
WHERE R.return_date IS NOT NULL;
因此,您正在寻找目前租用的电影,并显示有效租借的客户的名字和姓氏。
您还可以使用一些独特的方式来减少列表中显示的重复客户数量。
这有帮助吗?!