0

Films、Ambiences、Films_Ambience の 3 つのテーブルを持つ多対多のデー​​タベースがあります。

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 の配列に変換されます$_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%'確実にパフォーマンスが低下します。

関係除算クエリを実行する最善の方法、つまり 3 つの条件がすべて一致する映画のみを一致させる方法は、各条件の個々の行を見つけて、それらを JOIN することです。

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に toFilms_Ambiencesとfor を追加する必要があります。Ambiences

にインデックスを作成する必要があります。そうすれば、 ambienceName3 つの検索すべてがより効率的になります。

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;

このクエリを変更して、結果が必要な雰囲気を備えた映画のみになるようにする方法はありますか?

上記のクエリはそれを行う必要があります。


クエリが行うことは、与えられたアンビエンスを含む映画を探すことだと思います (したがって、より多くのアンビエンスを持つ映画も見つけます)。

そうです、検索基準の 3 つの雰囲気すべてに一致しない限り、クエリは映画に一致しません。しかし、フィルムには検索基準以外のアンビエンスが含まれる場合があり、フィルムのすべてのアンビエンス (検索基準にあるものとその他のもの) は として収集され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が表示されます。私がそれを望まない場合はどうなりますか?

ああ、わかりました。それがあなたの言いたいことだとはまったく理解できませんでした。この種の問題では珍しい要件です。

解決策は次のとおりです。

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

于 2013-07-09T21:36:10.660 に答える