2013-07-09 48 views
0

的变化量一个简单的搜索我有很多对多分贝这三张表,制片,环境音效,Films_Ambience:表演在MySQL数据库与输入

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如果你喜欢。

+0

即使您知道环境的数量,您的查询也不正确。单一的氛围无法同时兼顾伤感和趣味。 – Barmar

+1

并没有列'a.ambiences'。 – Barmar

+0

我的意思是只是氛围,我相信。编辑。 – Sharkz

回答

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) = (?, ?, ?); 

你需要一个额外的JOINFilms_AmbiencesAmbiences每个搜索词

您应该有一个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              | 
+----+-------------+--------+--------+----------------------+--------------+---------+-----------------------------+------+-----------------------------------------------------------+ 

我有一个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 | 
+----+-------+--------------+ 

有没有必要加入到a_all在这种情况下,因为我们不需要氛围名称的列表,我们只需要氛围的数量,我们可以得到只需加入fa_all即可。

+0

然而,这里的问题是,电影可能会有更多的氛围,而不仅仅是那些需要的环境。在这种情况下,我不会显示它们。有没有办法改变这个查询,以便在找到标准后显示其余的氛围?另外,有没有办法改变这个查询,以便结果只有具有所需氛围但没有更多的电影? – Sharkz

+0

感谢第一部分。但关于第二个问题:我想这个问题的作用是寻找包含特定氛围的电影(因此它也会找到有更多氛围的电影)。希望澄清我的问题。 – Sharkz

+0

举例说明我的意思: 我有一个'film1',它是'可怕的,有趣的,愚蠢的'。 当我搜索一部只有“可怕,愚蠢”的电影时,我会得到'film1'。如果我不想要那个怎么办? – Sharkz

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.') .....' 
+0

这看起来像一个解决方案但它不会对性能产生负面影响? – Sharkz

+1

正如其他答案所指出的那样,LIKE查询可能非常昂贵,尽管PHP循环并不是非常昂贵,除非您有太多的NUMBER_OF_POSSIBLE_AMBIENCES,但我对此表示怀疑。 – Josejulio

+0

感谢您的想法和示例:) – Sharkz

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的回答循环来构建这个查询。

+0

因此,如果$ _POST ['ambience']有一个空元素,它只会搜索'%'的表列? – Sharkz

+0

你应该在循环之前过滤掉那些。 – Barmar

+0

我修正了第二个版本。它不需要使用'LIKE',它可以完全匹配。 – Barmar