我有一个多对多的数据库,这三个表,电影,氛围,电影_Ambience:
CREATE TABLE Films (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
Title VARCHAR(255));
CREATE TABLE Ambiences (
id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(id),
ambienceName VARCHAR(255));
CREATE TABLE Films_Ambiences (
film_id INT NOT NULL,
ambience_id INT NOT NULL,
PRIMARY KEY (film_id, ambience_id),
FOREIGN KEY (film_id) REFERENCES Films(id) ON UPDATE CASCADE,
FOREIGN KEY (ambience_id) REFERENCES Ambiences(id) ON UPDATE CASCADE);
我正在使用表格中的信息来搜索特定的电影(例如同时有趣且可怕的电影)。表单只是简单的' ticks'给定名称旁边。信息由$ _POST发送。
问题在于我不知道会有多少要求。我知道用户可以选择的最大数量,但我不知道他们实际会选择多少或哪些(我可以通过检查isset($_POST['somethin'])
来做到这一点但是如果我有,例如20,那将是非常单调的不同的选择。所以我不能做任何事情:
$ambience1 = $_POST["a1"];
$ambience2 = $_POST["a2"];
$ambience3 = $_POST["a2"];
...
...
...
和:
SELECT *,GROUP_CONCAT(ambienceName SEPARATOR ' ') AS ambiences
FROM Films AS f
INNER JOIN Films_Ambiences as fa ON f.id = fa.film_id
INNER JOIN Ambiences AS a ON a.id = fa.ambience_id
GROUP BY Title
HAVING (ambiences LIKE '%$ambience1%' AND ambiences LIKE '%$ambience2%' AND ...
我甚至不知道从哪里开始。我可以用SQL或PHP来做吗?
如果你喜欢的话,这里有 SQLFiddle 。
答案 0 :(得分:1)
我认为您可以使用PHP来构建SQL,如下所示:
$ambienceWhere = '1=1 ';
for ($i=0;$i<NUMBER_OF_POSSIBLE_AMBIENCES;$i++) {
if (isset($_POST['a' . $i])) { // or another criteria to avoid processing this one
$ambienceWhere .= ' AND ';
$ambienceWhere .= ' a.ambiences LIKE \'%' . $_POST['a'. $i] '%\' ';
}
}
$query = 'SELECT ....... WHERE ('. $ambienceWhere.') .....'
答案 1 :(得分:1)
您的表单应该具有元素数组中的氛围,可能是多重选择。然后,这将变成PHP中的数组$_POST['ambience'][]
。然后你可以写:
$ambience_query = implode(' AND ', array_map(function($a) use($mysqli) {
return "'%" . mysqli_real_escape_string($mysqli, $a) . "'";
}, $_POST['ambience']));
$query = "SELECT *, GROUP_CONCAT(ambienceName SEPARATOR ' ') AS ambiences
FROM FROM Films AS f
INNER JOIN Films_Ambiences as fa ON f.id = fa.film_id
INNER JOIN Ambiences AS a ON a.id = fa.ambience_id
GROUP BY Title
HAVING $ambience_query";
这是一个非常昂贵的查询。在过滤到你想要的电影之前,它必须计算数据库中每部电影的GROUP_CONCAT(ambienceName)
。结构化查询会更好:
SELECT f.*
FROM Films f
INNER JOIN Films_Ambiences fa1 ON f.id = fa1.film_id
INNER JOIN Ambiences a1 ON a1.id = f1.ambience_id
INNER JOIN Films_Ambiences fa2 ON f.id = fa2.film_id
INNER JOIN Ambiences a2 ON a2.id = f2.ambience_id
INNER JOIN Films_Ambiences fa3 ON f.id = fa3.film_id
INNER JOIN Ambiences a3 ON a3.id = f3.ambience_id
...
WHERE a1.ambienceName = '$_POST[ambience][1]'
AND a2.ambienceName = '$_POST[ambience][2]'
AND a3.ambienceName = '$_POST[ambience][3]'
...
您可以使用类似于上面的循环或Josejulio的答案来构建此查询。
答案 2 :(得分:1)
使用关键字搜索谓词LIKE '%pattern%'
是sure way to cause poor performance,因为它强制进行表扫描。
执行relational division查询的最佳方法是仅匹配所有三个条件匹配的电影,即为每个条件查找单独的行,然后将它们连接在一起。
SELECT f.*, CONCAT_WS(' ', a1.ambienceName, a2.ambienceName, a3.ambienceName) AS ambiences
FROM Films AS f
INNER JOIN Films_Ambiences as fa1 ON f.id = fa1.film_id
INNER JOIN Ambiences AS a1 ON a1.id = fa1.ambience_id
INNER JOIN Films_Ambiences as fa2 ON f.id = fa2.film_id
INNER JOIN Ambiences AS a2 ON a2.id = fa2.ambience_id
INNER JOIN Films_Ambiences as fa3 ON f.id = fa3.film_id
INNER JOIN Ambiences AS a3 ON a3.id = fa3.ambience_id
WHERE (a1.ambienceName, a2.ambienceName, a3.ambienceName) = (?, ?, ?);
对于每个搜索字词,您还需要额外的JOIN
到Films_Ambiences
和Ambiences
。
你应该在ambienceName
上有一个索引,然后所有三个查找都会更有效率。
ALTER TABLE Ambiences ADD KEY (ambienceName);
我在最近的一次演讲中比较了关系划分的不同解决方案:
重新评论:
是否有办法更改此查询,以便在找到条件后显示其余的氛围?
是的,但你必须再加入一段时间才能获得电影的全部氛围:
SELECT f.*, GROUP_CONCAT(a_all.ambienceName) AS ambiences
FROM Films AS f
INNER JOIN Films_Ambiences as fa1 ON f.id = fa1.film_id
INNER JOIN Ambiences AS a1 ON a1.id = fa1.ambience_id
INNER JOIN Films_Ambiences as fa2 ON f.id = fa2.film_id
INNER JOIN Ambiences AS a2 ON a2.id = fa2.ambience_id
INNER JOIN Films_Ambiences as fa3 ON f.id = fa3.film_id
INNER JOIN Ambiences AS a3 ON a3.id = fa3.ambience_id
INNER JOIN Films_Ambiences AS fa_all ON f.id = fa_all.film_id
INNER JOIN Ambiences AS a_all ON a_all.id = fa_all.ambience_id
WHERE (a1.ambienceName, a2.ambienceName, a3.ambienceName) = (?, ?, ?)
GROUP BY f.id;
有没有办法改变这个查询,以便结果只是具有所需氛围但不再需要的影片?
上面的查询应该这样做。
我认为,查询的作用是寻找包含特定氛围的电影(因此它也会找到更具氛围的电影)。
是的,除非匹配搜索条件中的所有三个氛围,否则查询与电影不匹配。但是这部电影可能还有其他超出搜索条件的氛围,所有电影的氛围(搜索条件加上其他内容)都被收集为GROUP_CONCAT(a_all.ambienceName)
。
我测试了这个例子:
mysql> INSERT INTO Ambiences (ambienceName)
VALUES ('funny'), ('scary'), ('1950s'), ('London'), ('bank'), ('crime'), ('stupid');
mysql> INSERT INTO Films (title)
VALUES ('Mary Poppins'), ('Heist'), ('Scary Movie'), ('Godzilla'), ('Signs');
mysql> INSERT INTO Films_Ambiences
VALUES (1,1),(1,2),(1,4),(1,5), (2,1),(2,2),(2,5),(2,6), (3,1),(3,2),(3,7), (4,2),(4,3), (5,2),(5,7);
mysql> SELECT f.*, GROUP_CONCAT(a_all.ambienceName) AS ambiences
FROM Films AS f
INNER JOIN Films_Ambiences as fa1 ON f.id = fa1.film_id
INNER JOIN Ambiences AS a1 ON a1.id = fa1.ambience_id
INNER JOIN Films_Ambiences as fa2 ON f.id = fa2.film_id
INNER JOIN Ambiences AS a2 ON a2.id = fa2.ambience_id
INNER JOIN Films_Ambiences as fa3 ON f.id = fa3.film_id
INNER JOIN Ambiences AS a3 ON a3.id = fa3.ambience_id
INNER JOIN Films_Ambiences AS fa_all ON f.id = fa_all.film_id
INNER JOIN Ambiences AS a_all ON a_all.id = fa_all.ambience_id
WHERE (a1.ambienceName, a2.ambienceName, a3.ambienceName) = ('funny','scary','bank')
GROUP BY f.id;
+----+--------------+-------------------------+
| id | Title | ambiences |
+----+--------------+-------------------------+
| 1 | Mary Poppins | funny,scary,London,bank |
| 2 | Heist | funny,scary,bank,crime |
+----+--------------+-------------------------+
顺便说一句,这里是EXPLAIN显示索引的用法:
+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+
| 1 | SIMPLE | a1 | ref | PRIMARY,ambienceName | ambienceName | 258 | const | 1 | Using where; Using index; Using temporary; Using filesort |
| 1 | SIMPLE | a2 | ref | PRIMARY,ambienceName | ambienceName | 258 | const | 1 | Using where; Using index |
| 1 | SIMPLE | a3 | ref | PRIMARY,ambienceName | ambienceName | 258 | const | 1 | Using where; Using index |
| 1 | SIMPLE | fa1 | ref | PRIMARY,ambience_id | ambience_id | 4 | test.a1.id | 1 | Using index |
| 1 | SIMPLE | f | eq_ref | PRIMARY | PRIMARY | 4 | test.fa1.film_id | 1 | NULL |
| 1 | SIMPLE | fa2 | eq_ref | PRIMARY,ambience_id | PRIMARY | 8 | test.fa1.film_id,test.a2.id | 1 | Using index |
| 1 | SIMPLE | fa3 | eq_ref | PRIMARY,ambience_id | PRIMARY | 8 | test.fa1.film_id,test.a3.id | 1 | Using index |
| 1 | SIMPLE | fa_all | ref | PRIMARY,ambience_id | PRIMARY | 4 | test.fa1.film_id | 1 | Using index |
| 1 | SIMPLE | a_all | eq_ref | PRIMARY | PRIMARY | 4 | test.fa_all.ambience_id | 1 | NULL |
+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+
我有一部可怕,有趣,愚蠢的电影1。当我搜索一部只有可怕的电影时,无论如何我都会得到电影1。如果我不想要那个怎么办?
哦,好吧,我完全不明白这就是你的意思,在这些类型的问题上这是一个不寻常的要求。
这是一个解决方案:
mysql> SELECT f.*, GROUP_CONCAT(a_all.ambienceName) AS ambiences
FROM Films AS f
INNER JOIN Films_Ambiences as fa1 ON f.id = fa1.film_id
INNER JOIN Ambiences AS a1 ON a1.id = fa1.ambience_id
INNER JOIN Films_Ambiences as fa2 ON f.id = fa2.film_id
INNER JOIN Ambiences AS a2 ON a2.id = fa2.ambience_id
INNER JOIN Films_Ambiences AS fa_all ON f.id = fa_all.film_id
WHERE (a1.ambienceName, a2.ambienceName) = ('scary','stupid')
GROUP BY f.id
HAVING COUNT(*) = 2
+----+-------+--------------+
| id | Title | ambiences |
+----+-------+--------------+
| 5 | Signs | scary,stupid |
+----+-------+--------------+
在这种情况下没有必要加入a_all
,因为我们不需要环境名称列表,我们只需要环境数量,我们只需加入{{1}即可获得}。