0

我有一个包含这三个表的多对多数据库,Films、Ambiences、Films_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);

我正在使用表格中的信息来搜索特定的电影(例如同时有趣和可怕的电影)。该表格只是给定名称旁边的“刻度”。信息由 $_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

4

3 回答 3

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.') .....'
于 2013-07-09T21:22:47.230 回答
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 的回答来构建此查询。

于 2013-07-09T21:25:06.873 回答
1

使用关键字搜索谓词LIKE '%pattern%'肯定会导致性能下降,因为它会强制进行表扫描。

进行关系除法查询(即仅匹配所有三个条件都匹配的电影)的最佳方法是为每个条件查找单独的行,然后将它们连接在一起。

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  |
+----+--------------+-------------------------+

顺便说一句,这是显示索引用法的说明:

+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+
| 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                                                      |
+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+

我有一部恐怖、有趣、愚蠢的电影。当我搜索一部只可怕的电影时,愚蠢的我无论如何都会得到film1。如果我不想要那个怎么办?

哦,好吧,我完全不明白你的意思,在这类问题中这是一个不寻常的要求。

这是一个解决方案:

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 |
+----+-------+--------------+

在这种情况下不需要加入 to a_all,因为我们不需要环境名称列表,我们只需要环境的数量,我们可以通过加入来获得fa_all

于 2013-07-09T21:36:10.660 回答