2013-07-09 64 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