灵感来源于StackOverflow问题:
Find mutual element in different facts in swi-prolog
我们有以下的
问题陈述
给出了一个“演员主演电影”的数据库
(例如,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).
给定一组电影
米
,找到那些在
米
.
这个问题最初是问Prolog的。
Prolog解决方案
在Prolog中,一个优雅的解决方案涉及到谓词
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
.
接下来,我们只需要测试
阿克图
是预期的
一组参与者,例如:
permutation([george, maria],ActOut)`
“是
阿克图
列表的排列
[george,maria]
?.
如果调用成功(思考,不返回
false
),测试通过。
终点站
!
是cut运算符,用于告诉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
一个演员出演过的电影
(通过内部连接为每个参与者创建),然后计算(对于每个参与者)是否
结果集至少具有与该集相同的条目数
电影
.
出于实际原因,将查询包装为过程。
一个
delimiter
在这里很有用:
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:
有没有比编辑+复制粘贴表创建代码更好的方法,
发行a
CALL
然后“亲手”检查结果?
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 |
+--------+