私は以下のことを知りたいのですが。
私は(例えば - PHP)アプリケーションでこれを使用することを計画していますが、データベースに対して複数のクエリを実行したくない、単一のクエリで複数のテーブルからデータを取得するためにどのようなオプションがありますか?
注:PHPキューで頻繁に出くわす多くの質問について、よく書かれたガイドにリンクできるように、これを書いています。答えを投稿します。
答えは次のとおりです。
わかりました。この投稿は非常に興味深いものであり、クエリの作成に関する知識を共有したいと思います。これをありがとうFluffeh。これを読んで、私が間違っていると感じるかもしれない他の人は、私の答えを自由に編集して批判することができます。 (正直なところ、私は自分の間違いを修正してくれたことにとても感謝している。)
MySQL
タグでよくある質問をいくつか投稿します。
このスキーマを考える
CREATE TABLE MovieList
(
ID INT,
MovieName VARCHAR(25),
CONSTRAINT ml_pk PRIMARY KEY (ID),
CONSTRAINT ml_uq UNIQUE (MovieName)
);
INSERT INTO MovieList VALUES (1, 'American Pie');
INSERT INTO MovieList VALUES (2, 'The Notebook');
INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa');
INSERT INTO MovieList VALUES (4, 'Mr. Bean');
INSERT INTO MovieList VALUES (5, 'Expendables 2');
CREATE TABLE CategoryList
(
MovieID INT,
CategoryName VARCHAR(25),
CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName),
CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID)
);
INSERT INTO CategoryList VALUES (1, 'Comedy');
INSERT INTO CategoryList VALUES (1, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Drama');
INSERT INTO CategoryList VALUES (3, 'Documentary');
INSERT INTO CategoryList VALUES (4, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Action');
質問
少なくとも両方Comedy
およびRomance
カテゴリーに属するすべての映画を見つけます。
ソリューション
この質問は非常に難しい場合があります。このようなクエリが答えになると思われるかもしれません:-
SELECT DISTINCT a.MovieName
FROM MovieList a
INNER JOIN CategoryList b
ON a.ID = b.MovieID
WHERE b.CategoryName = 'Comedy' AND
b.CategoryName = 'Romance'
結果なしを生成するため、これは間違いなく間違いです。これの説明は、各行にCategoryName
の有効な値が1つしかないということです。たとえば、最初の条件はtrueを返し、2番目の条件は常にfalseです。したがって、AND
演算子を使用すると、両方の条件が真になります。そうでない場合、falseになります。別のクエリは次のようなものです。
SELECT DISTINCT a.MovieName
FROM MovieList a
INNER JOIN CategoryList b
ON a.ID = b.MovieID
WHERE b.CategoryName IN ('Comedy','Romance')
categoryName
に少なくともが1つ一致するレコードと一致するため、結果は依然として正しくありません。 実際のソリューション映画ごとのレコードインスタンスの数をカウントすることによります。インスタンスの数は、条件で指定された値の総数と一致する必要があります。
SELECT a.MovieName
FROM MovieList a
INNER JOIN CategoryList b
ON a.ID = b.MovieID
WHERE b.CategoryName IN ('Comedy','Romance')
GROUP BY a.MovieName
HAVING COUNT(*) = 2
与えられたスキーマ、
CREATE TABLE Software
(
ID INT,
SoftwareName VARCHAR(25),
Descriptions VARCHAR(150),
CONSTRAINT sw_pk PRIMARY KEY (ID),
CONSTRAINT sw_uq UNIQUE (SoftwareName)
);
INSERT INTO Software VALUES (1,'PaintMe','used for photo editing');
INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world');
INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words');
CREATE TABLE VersionList
(
SoftwareID INT,
VersionNo INT,
DateReleased DATE,
CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo),
CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID)
);
INSERT INTO VersionList VALUES (3, 2, '2009-12-01');
INSERT INTO VersionList VALUES (3, 1, '2009-11-01');
INSERT INTO VersionList VALUES (3, 3, '2010-01-01');
INSERT INTO VersionList VALUES (2, 2, '2010-12-01');
INSERT INTO VersionList VALUES (2, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 3, '2011-12-01');
INSERT INTO VersionList VALUES (1, 2, '2010-12-01');
INSERT INTO VersionList VALUES (1, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 4, '2012-12-01');
質問
各ソフトウェアの最新バージョンを見つけます。次の列を表示します:SoftwareName
、Descriptions
、LatestVersion
(from VersionNo column)、DateReleased
ソリューション
一部のSQL開発者は、MAX()
集約関数を誤って使用します。彼らはこのように作成する傾向があり、
SELECT a.SoftwareName, a.Descriptions,
MAX(b.VersionNo) AS LatestVersion, b.DateReleased
FROM Software a
INNER JOIN VersionList b
ON a.ID = b.SoftwareID
GROUP BY a.ID
ORDER BY a.ID
(ほとんどのRDBMSは、group by
句に非集約列の一部を指定しないため、これに関する構文エラーを生成します)結果は各ソフトウェアで正しいLatestVersion
を生成しますが、明らかにDateReleased
は正しくありません。 MySQL
は、いくつかのRDBMSが既にサポートしているように、Window Functions
およびCommon Table Expression
をまだサポートしていません。この問題の回避策は、各ソフトウェアで個々の最大subquery
を取得し、後で他のテーブルで結合されるversionNo
を作成することです。
SELECT a.SoftwareName, a.Descriptions,
b.LatestVersion, c.DateReleased
FROM Software a
INNER JOIN
(
SELECT SoftwareID, MAX(VersionNO) LatestVersion
FROM VersionList
GROUP BY SoftwareID
) b ON a.ID = b.SoftwareID
INNER JOIN VersionList c
ON c.SoftwareID = b.SoftwareID AND
c.VersionNO = b.LatestVersion
GROUP BY a.ID
ORDER BY a.ID
それで終わりました。 MySQL
タグで他のFAQを思い出すと、すぐに別の投稿を行います。この小さな記事を読んでくれてありがとう。これから少しでも知識を得ていただければ幸いです。
更新1
与えられたスキーマ
CREATE TABLE userList
(
ID INT,
NAME VARCHAR(20),
CONSTRAINT us_pk PRIMARY KEY (ID),
CONSTRAINT us_uq UNIQUE (NAME)
);
INSERT INTO userList VALUES (1, 'Fluffeh');
INSERT INTO userList VALUES (2, 'John Woo');
INSERT INTO userList VALUES (3, 'hims056');
CREATE TABLE CONVERSATION
(
ID INT,
FROM_ID INT,
TO_ID INT,
MESSAGE VARCHAR(250),
DeliveryDate DATE
);
INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01');
INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02');
INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03');
INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04');
INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05');
INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05');
質問
2人のユーザー間の最新の会話を見つけます。
ソリューション
SELECT b.Name SenderName,
c.Name RecipientName,
a.Message,
a.DeliveryDate
FROM Conversation a
INNER JOIN userList b
ON a.From_ID = b.ID
INNER JOIN userList c
ON a.To_ID = c.ID
WHERE (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate)
IN
(
SELECT LEAST(FROM_ID, TO_ID) minFROM,
GREATEST(FROM_ID, TO_ID) maxTo,
MAX(DeliveryDate) maxDate
FROM Conversation
GROUP BY minFROM, maxTo
)
さて、上司が再び破裂しました-ブランドと私たちの車のリストと、そのブランドの合計数が欲しい!
これは、SQLの便利な機能である次のトリックであるサブクエリを使用する絶好の機会です。この用語に慣れていない場合、サブクエリは別のクエリ内で実行されるクエリです。それらを使用する多くの異なる方法があります。
私たちのリクエストでは、まず各車とブランドをリストする簡単なクエリをまとめましょう:
select
a.ID,
b.brand
from
cars a
join brands b
on a.brand=b.ID
さて、単にブランド別に分類された車の数を取得したい場合は、もちろんこれを書くことができます:
select
b.brand,
count(a.ID) as countCars
from
cars a
join brands b
on a.brand=b.ID
group by
b.brand
+--------+-----------+
| brand | countCars |
+--------+-----------+
| BMW | 2 |
| Ford | 2 |
| Nissan | 1 |
| Smart | 1 |
| Toyota | 5 |
+--------+-----------+
それでは、元のクエリにcount関数を単純に追加できるはずです。
select
a.ID,
b.brand,
count(a.ID) as countCars
from
cars a
join brands b
on a.brand=b.ID
group by
a.ID,
b.brand
+----+--------+-----------+
| ID | brand | countCars |
+----+--------+-----------+
| 1 | Toyota | 1 |
| 2 | Ford | 1 |
| 3 | Nissan | 1 |
| 4 | Smart | 1 |
| 5 | Toyota | 1 |
| 6 | BMW | 1 |
| 7 | Ford | 1 |
| 8 | Toyota | 1 |
| 9 | Toyota | 1 |
| 10 | BMW | 1 |
| 11 | Toyota | 1 |
+----+--------+-----------+
11 rows in set (0.00 sec)
残念ながら、それはできません。その理由は、車のID(列a.ID)を追加するときにグループに追加する必要があるためです。したがって、カウント関数が機能するとき、IDごとに一致するIDは1つだけです。
ただし、ここでサブクエリを使用できます。実際には、これに必要な同じ結果を返す2つの完全に異なるタイプのサブクエリを実行できます。最初の方法は、サブクエリをselect
句に単純に入れることです。つまり、データの行を取得するたびに、サブクエリが実行され、データの列を取得してから、データの行にポップします。
select
a.ID,
b.brand,
(
select
count(c.ID)
from
cars c
where
a.brand=c.brand
) as countCars
from
cars a
join brands b
on a.brand=b.ID
+----+--------+-----------+
| ID | brand | countCars |
+----+--------+-----------+
| 2 | Ford | 2 |
| 7 | Ford | 2 |
| 1 | Toyota | 5 |
| 5 | Toyota | 5 |
| 8 | Toyota | 5 |
| 9 | Toyota | 5 |
| 11 | Toyota | 5 |
| 3 | Nissan | 1 |
| 4 | Smart | 1 |
| 6 | BMW | 2 |
| 10 | BMW | 2 |
+----+--------+-----------+
11 rows in set (0.00 sec)
そして、バム!ただし、気付いた場合は、返されるデータの各行ごとにこのサブクエリを実行する必要があります。この小さな例でも、車のブランドは5つしかありませんが、返されるデータの行が11行あるため、サブクエリは11回実行されました。したがって、この場合、コードを記述する最も効率的な方法とは思えません。
別のアプローチでは、サブクエリを実行して、テーブルのふりをしましょう。
select
a.ID,
b.brand,
d.countCars
from
cars a
join brands b
on a.brand=b.ID
join
(
select
c.brand,
count(c.ID) as countCars
from
cars c
group by
c.brand
) d
on a.brand=d.brand
+----+--------+-----------+
| ID | brand | countCars |
+----+--------+-----------+
| 1 | Toyota | 5 |
| 2 | Ford | 2 |
| 3 | Nissan | 1 |
| 4 | Smart | 1 |
| 5 | Toyota | 5 |
| 6 | BMW | 2 |
| 7 | Ford | 2 |
| 8 | Toyota | 5 |
| 9 | Toyota | 5 |
| 10 | BMW | 2 |
| 11 | Toyota | 5 |
+----+--------+-----------+
11 rows in set (0.00 sec)
わかりましたので、結果は同じです(順序は少し異なります-データベースは今回選択した最初の列で並べられた結果を返したいようです)-正しい数字は同じです。
それで、2つの違いは何ですか?また、各タイプのサブクエリをいつ使用する必要がありますか?最初に、その2番目のクエリがどのように機能するかを確認します。クエリのfrom
句で2つのテーブルを選択し、クエリを作成して、実際にはテーブルであるとデータベースに伝えました。データベースはこれで問題ありません。 canこの方法を使用するといくつかの利点があります(いくつかの制限もあります)。何よりもまず、このサブクエリはonceを実行したことです。データベースに大量のデータが含まれている場合、最初の方法よりも大幅に改善される可能性があります。ただし、これをテーブルとして使用しているため、追加のデータ行を取り込む必要があります。これにより、実際にデータ行に結合できるようになります。また、上記のクエリのように単純な結合を使用する場合は、十分な行のデータがあることを確認する必要があります。思い出すと、結合はboth結合の両側に一致するデータがある行のみをプルバックします。注意を怠ると、このサブクエリに一致する行がない場合、有効なデータがcarsテーブルから返されない可能性があります。
さて、最初のサブクエリを振り返ると、いくつかの制限もあります。データを単一の行に戻すので、ONLYデータの1行を戻すことができます。クエリのselect
句で使用されるサブクエリは、sum
、count
、max
などの集計関数または他の同様の集計関数のみを使用することが非常に多くあります。それらはhave toではありませんが、多くの場合、そのように書かれています。
したがって、次に進む前に、サブクエリを使用できる他の場所を簡単に見てみましょう。 where
句で使用できます。現在、この例はデータベースのように少し工夫されています。次のデータを取得するより良い方法がありますが、例としてだけ見て、見てみましょう:
select
ID,
brand
from
brands
where
brand like '%o%'
+----+--------+
| ID | brand |
+----+--------+
| 1 | Ford |
| 2 | Toyota |
| 6 | Holden |
+----+--------+
3 rows in set (0.00 sec)
これにより、名前にo
という文字が含まれるブランドIDとブランド名のリストが返されます(2番目の列はブランドを表示するためにのみ追加されます)。
これで、このクエリの結果をwhere句thisで使用できます。
select
a.ID,
b.brand
from
cars a
join brands b
on a.brand=b.ID
where
a.brand in
(
select
ID
from
brands
where
brand like '%o%'
)
+----+--------+
| ID | brand |
+----+--------+
| 2 | Ford |
| 7 | Ford |
| 1 | Toyota |
| 5 | Toyota |
| 8 | Toyota |
| 9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)
ご覧のとおり、サブクエリは3つのブランドIDを返していましたが、carsテーブルにはそのうち2つのブランドIDのみが含まれていました。
この場合、詳細については、次のコードを記述したかのようにサブクエリが機能しています。
select
a.ID,
b.brand
from
cars a
join brands b
on a.brand=b.ID
where
a.brand in (1,2,6)
+----+--------+
| ID | brand |
+----+--------+
| 1 | Toyota |
| 2 | Ford |
| 5 | Toyota |
| 7 | Ford |
| 8 | Toyota |
| 9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)
繰り返しますが、サブクエリと手動入力がデータベースから戻るときに行の順序をどのように変更したかを確認できます。
サブクエリについて説明している間、サブクエリで他にできることを見てみましょう。
select
句に、いくつかはfrom
句に、さらに2つはwhere
句に配置できます。クエリをより複雑にし、実行に時間がかかる可能性があります。効率的なコードを作成する必要がある場合は、さまざまな方法でクエリを作成し、結果を取得するための最適なクエリを(タイミングを計るか、またはEXPLAIN PLANを使用して)確認することをお勧めします。動作する最初の方法は、常にそれを行う最良の方法であるとは限りません。
出てきたヒントやコツのために、もう少し追加するつもりだ。
2つのテーブルから一致しない行を取得するにはどうすればよいですかです。そして、次のようなものとして最も一般的に受け入れられている答えがわかります(車とブランドのテーブルに基づく) -Holdenがブランドとしてリストされていますが、carsテーブルには表示されません):
select
a.ID,
a.brand
from
brands a
where
a.ID not in(select brand from cars)
そして、yes動作します。
+----+--------+
| ID | brand |
+----+--------+
| 6 | Holden |
+----+--------+
1 row in set (0.00 sec)
ただし、一部のデータベースではnotで効率的です。ここに Stack Overflowの質問へのリンク について質問し、ここに 詳細な記事 がらくたになりたい場合にあります。
簡単な答えは、オプティマイザーがそれを効率的に処理しない場合、次のようなクエリを使用して一致しない行を取得する方がはるかに良い場合があるということです。
select
a.brand
from
brands a
left join cars b
on a.id=b.brand
where
b.brand is null
+--------+
| brand |
+--------+
| Holden |
+--------+
1 row in set (0.00 sec)
ああ、もう一つの古いけれど良いもの-古いFROM句で更新するターゲットテーブル 'brands'を指定することはできません。
MySQLでは、同じテーブルでサブセレクトを使用してupdate...
クエリを実行することはできません。今、あなたは考えているかもしれません、なぜそれを正しいwhere句に平手打ちしないのですか?しかし、max()
日付を持つ行のみを更新し、他の行がたくさんある場合はどうでしょうか? where句で正確に行うことはできません。
update
brands
set
brand='Holden'
where
id=
(select
id
from
brands
where
id=6);
ERROR 1093 (HY000): You can't specify target table 'brands'
for update in FROM clause
それで、私たちはそれをすることができませんね?まあ、正確ではありません。驚くほど多くのユーザーが知らない卑劣な回避策があります-ただし、注意が必要なハッカーがいくつか含まれています。
サブクエリを別のサブクエリ内に固定することができます。これにより、2つのクエリ間に十分なギャップができ、機能します。ただし、クエリをトランザクション内に固定するのが最も安全である可能性があることに注意してください。これにより、クエリの実行中にテーブルに他の変更が加えられなくなります。
update
brands
set
brand='Holden'
where id=
(select
id
from
(select
id
from
brands
where
id=6
)
as updateTable);
Query OK, 0 rows affected (0.02 sec)
Rows matched: 1 Changed: 0 Warnings: 0
FROMキーワードで複数のクエリの概念を使用できます。一例を示しましょう。
SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY
FROM (
SELECT c.id cnty,l.name
FROM county c, location l
WHERE c.id=l.county_id AND l.end_Date IS NOT NULL
) c_loc, emp e
INNER JOIN dept d ON e.deptno =d.id
LEFT JOIN
(
SELECT l.id lappy, c.name cmpy
FROM laptop l, company c
WHERE l.make = c.name
) lap ON e.cmpy_id=lap.cmpy
必要なだけテーブルを使用できます。テーブルサブクエリの中であっても、必要なところであればどこでも外部ジョインと共用体を使用してください。
これは、テーブルやフィールドと同じ数の要素を含めるのが非常に簡単な方法です。
この記事を読んでいるときに、テーブルが見つかることを願います。
mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(3) | NO | PRI | NULL | auto_increment |
| color | varchar(15) | YES | | NULL | |
| Paint | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+