sql - Prolog to SQL:有什么方法可以改进单元测试的 SQL 代码并优雅地修复边缘情况?
问题描述
受这个 StackOverflow 问题的启发:
我们有以下
问题陈述
给定一个“主演电影的演员”数据库(例如,starsin 是将演员“bob”与电影“a”联系起来的关系)
starsin(a,bob). starsin(c,bob). starsin(a,maria). starsin(b,maria). starsin(c,maria). starsin(a,george). starsin(b,george). starsin(c,george). starsin(d,george).
给定一组电影M,找出那些出演过M的所有电影的演员。
这个问题最初是针对 Prolog 的。
序言解决方案
在 Prolog 中,一个优雅的解决方案涉及 predicate
setof/3
,它将可能的变量实例收集到一个集合中(实际上是没有重复值的列表):
actors_appearing_in_movies(MovIn,ActOut) :-
setof(
Ax,
MovAx^(setof(Mx,starsin(Mx,Ax),MovAx), subset(MovIn,MovAx)),
ActOut
).
我不会对此进行详细介绍,但让我们看一下测试代码,这里很感兴趣。以下是五个测试用例:
actors_appearing_in_movies([],ActOut),permutation([bob, george, maria],ActOut),!.
actors_appearing_in_movies([a],ActOut),permutation([bob, george, maria],ActOut),!.
actors_appearing_in_movies([a,b],ActOut),permutation([george, maria],ActOut),!.
actors_appearing_in_movies([a,b,c],ActOut),permutation([george, maria],ActOut),!.
actors_appearing_in_movies([a,b,c,d],ActOut),permutation([george],ActOut),!.
测试是对谓词的调用actors_appearing_in_movies/2
,它给出了电影的输入列表(例如[a,b]
),并捕获了结果中的演员列表ActOut
。
随后,我们只需要测试是否ActOut
是预期参与者集合的排列,例如:
permutation([george, maria],ActOut)`
“ActOut
列表是列表的排列[george,maria]
吗?
如果该调用成功(想想,不返回false
),则测试通过。
终端!
是剪切操作符,用于告诉 Prolog 引擎不要重新尝试寻找更多解决方案,因为我们擅长这一点。
请注意,对于空电影集,我们获取所有演员。这可以说是正确的:每个演员都出演了空集的所有电影(Vacuous Truth)。
现在在 SQL 中。
这个问题完全属于关系代数领域,并且有 SQL,所以让我们尝试一下。在这里,我使用的是 MySQL。
首先,摆正事实。
DROP TABLE IF EXISTS starsin;
CREATE TABLE starsin (movie CHAR(20) NOT NULL, actor CHAR(20) NOT NULL);
INSERT INTO starsin VALUES
( "a" , "bob" ),
( "c" , "bob" ),
( "a" , "maria" ),
( "b" , "maria" ),
( "c" , "maria" ),
( "a" , "george" ),
( "b" , "george" ),
( "c" , "george" ),
( "d", "george" );
关于作为输入给出的电影集,以(临时)表格的形式给出它们听起来很自然。在 MySQL 中,“临时表”是会话本地的。好的。
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b");
方法:
现在可以通过为每个演员获取由 表示的电影movies_in
集与演员曾经出现过的电影集的交集(通过内部连接为每个演员创建),然后计数(对于每个演员) 结果集是否至少具有与集合一样多的条目movies_in
。
出于实际原因,将查询包装到一个过程中。分隔符在这里很有用:
DELIMITER $$
DROP PROCEDURE IF EXISTS actors_appearing_in_movies;
CREATE PROCEDURE actors_appearing_in_movies()
BEGIN
SELECT
d.actor
FROM
starsin d, movies_in q
WHERE
d.movie = q.movie
GROUP BY
actor
HAVING
COUNT(*) >= (SELECT COUNT(*) FROM movies_in);
END$$
DELIMITER ;
运行!
出现问题A:
有没有比编辑 + 复制粘贴表创建代码更好的方法,发出 aCALL
并“手动”检查结果?
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
CALL actors_appearing_in_movies();
空集!
问题B出现:
以上是不需要的,我想要“所有参与者”,与 Prolog 解决方案相同。由于我不想在代码中添加奇怪的边缘案例异常,因此我的方法一定是错误的。是否有一个自然涵盖这种情况但不会变得太复杂的情况? T-SQL和 PostgreSQL 单行也很好!
其他测试用例产生预期数据:
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
| maria |
+--------+
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b"), ("c");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
| maria |
+--------+
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b"), ("c"), ("d");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
+--------+
解决方案
给定一组电影 M,找出那些出演过 M 的所有电影的演员。
我会使用:
select si.actor
from starsin si
where si.movie in (<M>)
group by si.actor
having count(*) = <n>;
如果你必须处理一个空集,那么你需要一个left join
:
select a.actor
from actors a left join
starsin si
on a.actor = si.actor and si.movie in (<M>)
group by a.actor
having count(si.movie) = <n>;
<n>
这是 中的电影数量<M>
。
更新:扩展形式的第二种方法
create or replace temporary table
actor (actor char(20) primary key)
as select distinct actor from starsin;
select
a.actor,
si.actor,si.movie -- left in for docu
from
actor a left join starsin si
on a.actor = si.actor
and si.movie in (select * from movies_in)
group
by a.actor
having
count(si.movie) = (select count(*) from movies_in);
然后为空movies_in
:
+--------+-------+-------+
| actor | actor | movie |
+--------+-------+-------+
| bob | NULL | NULL |
| george | NULL | NULL |
| maria | NULL | NULL |
+--------+-------+-------+
例如movies_in
:
+-------+
| movie |
+-------+
| a |
| b |
+-------+
movie
这是该组的顶部:
+--------+--------+-------+
| actor | actor | movie |
+--------+--------+-------+
| george | george | a |
| maria | maria | a |
+--------+--------+-------+
推荐阅读
- javascript - 有人可以帮我制作一个循环,其中详细信息和状态的文本每秒都会从 Text 更改为 Text2 并返回到 Text
- swift - Metal 命令缓冲区内部错误:什么是内部错误(IOAF 代码 2067)?
- javascript - js中的工厂模式是否违反了开闭原则?
- universe - Rocket Universe 挂起删除多部分文件
- amazon-waf - 将 AWS WAF 迁移到 Azure WAF 的问题
- android - 如何在 Android JAVA 中使用 Firebase 推送通知更改某些文档字段?
- go - Go中的无死锁锁定多个锁
- python - KIVY:在 App 初始化期间获取添加的小部件
- mobile - 移动 UX 中的折叠菜单
- java - CSC - 使用不同端点的 pdf 签名