Truy vấn SQL trả về dữ liệu từ nhiều bảng


434

Tôi muốn biết như sau:

  • Làm thế nào để lấy dữ liệu từ nhiều bảng trong cơ sở dữ liệu của tôi?
  • Có những loại phương pháp nào để làm điều này?
  • tham gia và đoàn thể là gì và chúng khác nhau như thế nào?
  • Khi nào tôi nên sử dụng mỗi cái so với những người khác?

Tôi đang dự định sử dụng điều này trong ứng dụng (ví dụ - PHP) của mình, nhưng không muốn chạy nhiều truy vấn đối với cơ sở dữ liệu, tôi có các tùy chọn nào để nhận dữ liệu từ nhiều bảng trong một truy vấn?

Lưu ý: Tôi đang viết điều này vì tôi muốn có thể liên kết đến một hướng dẫn được viết tốt về nhiều câu hỏi mà tôi liên tục gặp trong hàng đợi PHP, vì vậy tôi có thể liên kết với điều này để biết thêm chi tiết khi tôi đăng câu trả lời.

Các câu trả lời bao gồm những điều sau đây:

  1. Phần 1 - Tham gia và Liên hiệp
  2. Phần 2 - Truy vấn con
  3. Phần 3 - Thủ thuật và mã hiệu quả
  4. Phần 4 - Truy vấn con trong khoản từ
  5. Phần 5 - Túi hỗn hợp các thủ thuật của John

Câu trả lời:


469

Phần 1 - Tham gia và Liên hiệp

Câu trả lời này bao gồm:

  1. Phần 1
    • Tham gia hai hoặc nhiều bảng bằng cách sử dụng kết nối bên trong (Xem mục wikipedia để biết thêm thông tin)
    • Cách sử dụng truy vấn công đoàn
    • Các liên kết ngoài trái và phải ( câu trả lời stackOverflow này là tuyệt vời để mô tả các loại liên kết)
    • Truy vấn xen kẽ (và cách tái tạo chúng nếu cơ sở dữ liệu của bạn không hỗ trợ chúng) - đây là chức năng của SQL-Server ( xem thông tin ) và một phần lý do tôi đã viết toàn bộ điều này ngay từ đầu.
  2. Phần 2
    • Truy vấn con - chúng là gì, chúng có thể được sử dụng ở đâu và những gì cần chú ý
    • Cartesian gia nhập AKA - Ôi, khốn khổ!

Có một số cách để lấy dữ liệu từ nhiều bảng trong cơ sở dữ liệu. Trong câu trả lời này, tôi sẽ sử dụng cú pháp nối ANSI-92. Điều này có thể khác với một số hướng dẫn khác ngoài đó sử dụng cú pháp ANSI-89 cũ hơn (và nếu bạn đã quen với 89, có vẻ ít trực quan hơn - nhưng tất cả những gì tôi có thể nói là thử nó) vì nó dễ hơn nhiều để hiểu khi các truy vấn bắt đầu trở nên phức tạp hơn. Tại sao sử dụng nó? Có đạt được hiệu suất? Các trả lời ngắn gọn là không, nhưng nó dễ dàng hơn để đọc khi bạn đã quen với nó. Việc đọc các truy vấn được viết bởi những người khác sử dụng cú pháp này sẽ dễ dàng hơn.

Tôi cũng sẽ sử dụng khái niệm về một caryard nhỏ có cơ sở dữ liệu để theo dõi những chiếc xe có sẵn. Chủ sở hữu đã thuê bạn làm anh chàng Máy tính CNTT của anh ấy và hy vọng bạn có thể gửi cho anh ấy dữ liệu mà anh ấy yêu cầu khi thả mũ.

Tôi đã thực hiện một số bảng tra cứu sẽ được sử dụng bởi bảng cuối cùng. Điều này sẽ cho chúng ta một mô hình hợp lý để làm việc. Để bắt đầu, tôi sẽ chạy các truy vấn của mình dựa trên cơ sở dữ liệu mẫu có cấu trúc sau. Tôi sẽ cố gắng nghĩ về những lỗi phổ biến được tạo ra khi bắt đầu và giải thích những gì sai với chúng - cũng như tất nhiên chỉ ra cách sửa lỗi.

Bảng đầu tiên chỉ đơn giản là một danh sách màu sắc để chúng ta biết chúng ta có những màu gì trong sân xe.

mysql> create table colors(id int(3) not null auto_increment primary key, 
    -> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)

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    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql> insert into colors (color, paint) values ('Red', 'Metallic'), 
    -> ('Green', 'Gloss'), ('Blue', 'Metallic'), 
    -> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from colors;
+----+-------+----------+
| id | color | paint    |
+----+-------+----------+
|  1 | Red   | Metallic |
|  2 | Green | Gloss    |
|  3 | Blue  | Metallic |
|  4 | White | Gloss    |
|  5 | Black | Gloss    |
+----+-------+----------+
5 rows in set (0.00 sec)

Bảng thương hiệu xác định các nhãn hiệu khác nhau của những chiếc xe mà caryard có thể bán.

mysql> create table brands (id int(3) not null auto_increment primary key, 
    -> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| brand | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> insert into brands (brand) values ('Ford'), ('Toyota'), 
    -> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from brands;
+----+--------+
| id | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  3 | Nissan |
|  4 | Smart  |
|  5 | BMW    |
+----+--------+
5 rows in set (0.00 sec)

Bảng mô hình sẽ bao gồm các loại xe khác nhau, sẽ đơn giản hơn cho việc sử dụng các loại xe khác nhau hơn là các mẫu xe thực tế.

mysql> create table models (id int(3) not null auto_increment primary key, 
    -> model varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| model | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from models;
+----+--------+
| id | model  |
+----+--------+
|  1 | Sports |
|  2 | Sedan  |
|  3 | 4WD    |
|  4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)

Và cuối cùng, để buộc tất cả các bảng khác, bảng liên kết mọi thứ lại với nhau. Trường ID thực sự là số lô duy nhất được sử dụng để xác định ô tô.

mysql> create table cars (id int(3) not null auto_increment primary key, 
    -> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type   | Null | Key | Default | Extra          |
+-------+--------+------+-----+---------+----------------+
| id    | int(3) | NO   | PRI | NULL    | auto_increment |
| color | int(3) | YES  |     | NULL    |                |
| brand | int(3) | YES  |     | NULL    |                |
| model | int(3) | YES  |     | NULL    |                |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), 
    -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
|  1 |     1 |     2 |     1 |
|  2 |     3 |     1 |     2 |
|  3 |     5 |     3 |     1 |
|  4 |     4 |     4 |     2 |
|  5 |     2 |     2 |     3 |
|  6 |     3 |     5 |     4 |
|  7 |     4 |     1 |     3 |
|  8 |     2 |     2 |     1 |
|  9 |     5 |     2 |     3 |
| 10 |     4 |     5 |     1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)

Điều này sẽ cung cấp cho chúng tôi đủ dữ liệu (tôi hy vọng) để che đi các ví dụ dưới đây về các loại liên kết khác nhau và cũng cung cấp đủ dữ liệu để làm cho chúng có giá trị.

Vì vậy, nhận được sự quan tâm của nó, ông chủ muốn biết ID của tất cả những chiếc xe thể thao mà ông có .

Đây là một hai bảng đơn giản tham gia. Chúng tôi có một bảng xác định mô hình và bảng có cổ phiếu có sẵn trong đó. Như bạn có thể thấy, dữ liệu trong modelcột của carsbảng liên quan đến modelscột của carsbảng chúng ta có. Bây giờ, chúng ta biết rằng các mô hình bảng có ID của 1cho Sportsnên cho phép viết tham gia.

select
    ID,
    model
from
    cars
        join models
            on model=ID

Vì vậy, truy vấn này có vẻ tốt phải không? Chúng tôi đã xác định hai bảng và chứa thông tin chúng tôi cần và sử dụng phép nối xác định chính xác cột nào sẽ tham gia.

ERROR 1052 (23000): Column 'ID' in field list is ambiguous

Ôi không! Một lỗi trong truy vấn đầu tiên của chúng tôi! Vâng, và nó là một quả mận. Bạn thấy đấy, truy vấn thực sự đã có đúng cột, nhưng một số trong số chúng tồn tại trong cả hai bảng, vì vậy cơ sở dữ liệu bị nhầm lẫn về cột thực tế chúng ta muốn nói là gì và ở đâu. Có hai giải pháp để giải quyết điều này. Đầu tiên là tốt đẹp và đơn giản, chúng ta có thể sử dụng tableName.columnNameđể nói với cơ sở dữ liệu chính xác những gì chúng ta muốn nói, như thế này:

select
    cars.ID,
    models.model
from
    cars
        join models
            on cars.model=models.ID

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
|  2 | Sedan  |
|  4 | Sedan  |
|  5 | 4WD    |
|  7 | 4WD    |
|  9 | 4WD    |
|  6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)

Cái khác có lẽ thường được sử dụng hơn và được gọi là răng cưa bảng. Các bảng trong ví dụ này có các tên đơn giản và ngắn gọn, nhưng gõ một cái gì đó giống như KPI_DAILY_SALES_BY_DEPARTMENTcó thể sẽ nhanh chóng bị cũ, vì vậy một cách đơn giản là đặt biệt danh cho bảng như thế này:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID

Bây giờ, trở lại yêu cầu. Như bạn có thể thấy chúng tôi có thông tin chúng tôi cần, nhưng chúng tôi cũng có thông tin không được yêu cầu, vì vậy chúng tôi cần bao gồm một mệnh đề where trong tuyên bố để chỉ nhận được những chiếc xe Thể thao như đã hỏi. Vì tôi thích phương thức bí danh bảng hơn là sử dụng tên bảng nhiều lần, tôi sẽ sử dụng nó từ thời điểm này trở đi.

Rõ ràng, chúng ta cần thêm một mệnh đề where vào truy vấn của chúng tôi. Chúng tôi có thể xác định xe thể thao bằng ID=1hoặc model='Sports'. Vì ID được lập chỉ mục và khóa chính (và nó sẽ ít gõ hơn), hãy sử dụng nó trong truy vấn của chúng tôi.

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Chơi lô tô! Sếp vui lắm. Tất nhiên, là một ông chủ và không bao giờ hài lòng với những gì anh ta yêu cầu, anh ta xem thông tin, sau đó nói rằng tôi cũng muốn màu sắc .

Được rồi, vì vậy chúng tôi có một phần tốt của truy vấn của chúng tôi đã được viết, nhưng chúng tôi cần sử dụng bảng thứ ba là màu sắc. Bây giờ, bảng thông tin chính của chúng tôi carslưu ID màu xe và liên kết này trở lại cột ID màu. Vì vậy, theo cách tương tự như bản gốc, chúng ta có thể tham gia bảng thứ ba:

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Chết tiệt, mặc dù bảng đã được nối chính xác và các cột liên quan được liên kết, chúng tôi đã quên lấy thông tin thực tế từ bảng mới mà chúng tôi vừa liên kết.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)

Phải, đó là ông chủ ra khỏi lưng chúng tôi một lát. Bây giờ, để giải thích một số điều này chi tiết hơn một chút. Như bạn có thể thấy, frommệnh đề trong câu lệnh của chúng tôi liên kết bảng chính của chúng tôi (tôi thường sử dụng bảng chứa thông tin thay vì bảng tra cứu hoặc bảng thứ nguyên. Truy vấn sẽ hoạt động tốt với tất cả các bảng được chuyển đổi, nhưng ít có ý nghĩa hơn khi Chúng tôi quay lại truy vấn này để đọc nó trong một vài tháng, vì vậy tốt nhất là cố gắng viết một truy vấn sẽ hay và dễ hiểu - đặt nó ra một cách trực giác, sử dụng thụt lề tốt để mọi thứ rõ ràng như Nếu bạn tiếp tục dạy người khác, hãy cố gắng thấm nhuần những đặc điểm này trong các truy vấn của họ - đặc biệt nếu bạn sẽ khắc phục sự cố cho họ.

Hoàn toàn có thể tiếp tục liên kết nhiều bảng hơn theo cách này.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Trong khi tôi quên bao gồm một bảng nơi chúng tôi có thể muốn tham gia nhiều hơn một cột trong jointuyên bố, đây là một ví dụ. Nếu modelsbảng có các mô hình dành riêng cho thương hiệu và do đó cũng có một cột được gọi là brandliên kết ngược với brandsbảng trên IDtrường, thì có thể thực hiện như sau:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
            and b.brand=d.ID
where
    b.ID=1

Bạn có thể thấy, truy vấn ở trên không chỉ liên kết các bảng đã tham gia với bảng chính carsmà còn chỉ định các phép nối giữa các bảng đã tham gia. Nếu điều này không được thực hiện, kết quả được gọi là tham gia cartesian - đó là dba nói xấu. Tham gia cartesian là một trong đó các hàng được trả về vì thông tin không cho cơ sở dữ liệu biết cách giới hạn kết quả, vì vậy truy vấn trả về tất cả các hàng phù hợp với tiêu chí.

Vì vậy, để đưa ra một ví dụ về tham gia cartesian, hãy chạy truy vấn sau:

select
    a.ID,
    b.model
from
    cars a
        join models b

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  1 | Sedan  |
|  1 | 4WD    |
|  1 | Luxury |
|  2 | Sports |
|  2 | Sedan  |
|  2 | 4WD    |
|  2 | Luxury |
|  3 | Sports |
|  3 | Sedan  |
|  3 | 4WD    |
|  3 | Luxury |
|  4 | Sports |
|  4 | Sedan  |
|  4 | 4WD    |
|  4 | Luxury |
|  5 | Sports |
|  5 | Sedan  |
|  5 | 4WD    |
|  5 | Luxury |
|  6 | Sports |
|  6 | Sedan  |
|  6 | 4WD    |
|  6 | Luxury |
|  7 | Sports |
|  7 | Sedan  |
|  7 | 4WD    |
|  7 | Luxury |
|  8 | Sports |
|  8 | Sedan  |
|  8 | 4WD    |
|  8 | Luxury |
|  9 | Sports |
|  9 | Sedan  |
|  9 | 4WD    |
|  9 | Luxury |
| 10 | Sports |
| 10 | Sedan  |
| 10 | 4WD    |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)

Chúa ơi, thật là xấu xí. Tuy nhiên, theo như cơ sở dữ liệu, đó chính xác là những gì được yêu cầu. Trong truy vấn, chúng tôi đã yêu cầu IDtừ carsmodeltừ models. Tuy nhiên, vì chúng tôi không chỉ định cách tham gia các bảng, cơ sở dữ liệu đã khớp mọi hàng từ bảng đầu tiên với mọi hàng từ bảng thứ hai.

Được rồi, vì vậy ông chủ đã trở lại, và ông muốn biết thêm thông tin một lần nữa. Tôi muốn cùng một danh sách, nhưng cũng bao gồm 4WD trong đó .

Tuy nhiên, điều này cho chúng ta một cái cớ tuyệt vời để xem xét hai cách khác nhau để thực hiện điều này. Chúng ta có thể thêm một điều kiện khác vào mệnh đề where như thế này:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
    or b.ID=3

Mặc dù ở trên sẽ hoạt động hoàn hảo, nhưng hãy nhìn nó khác đi, đây là một lý do tuyệt vời để chỉ ra cách union truy vấn sẽ hoạt động .

Chúng tôi biết rằng sau đây sẽ trả lại tất cả các xe thể thao:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Và sau đây sẽ trả về tất cả các 4WD:

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

Vì vậy, bằng cách thêm một union allmệnh đề giữa chúng, kết quả của truy vấn thứ hai sẽ được thêm vào kết quả của truy vấn đầu tiên.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
union all
select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
|  5 | 4WD    | Green |
|  7 | 4WD    | White |
|  9 | 4WD    | Black |
+----+--------+-------+
7 rows in set (0.00 sec)

Như bạn có thể thấy, kết quả của truy vấn đầu tiên được trả về trước, tiếp theo là kết quả của truy vấn thứ hai.

Trong ví dụ này, tất nhiên sẽ dễ dàng hơn nhiều khi chỉ cần sử dụng truy vấn đầu tiên, nhưng unioncác truy vấn có thể tuyệt vời cho các trường hợp cụ thể. Chúng là một cách tuyệt vời để trả về kết quả cụ thể từ các bảng từ các bảng không dễ dàng kết hợp với nhau - hoặc hoàn toàn là vấn đề không liên quan đến các bảng. Có một vài quy tắc để làm theo tuy nhiên.

  • Các loại cột từ truy vấn đầu tiên phải khớp với các loại cột từ mọi truy vấn khác bên dưới.
  • Tên của các cột từ truy vấn đầu tiên sẽ được sử dụng để xác định toàn bộ tập hợp kết quả.
  • Số lượng cột trong mỗi truy vấn phải giống nhau.

Bây giờ, bạn có thể tự hỏi sự khác biệt giữa việc sử dụng unionunion all. Một uniontruy vấn sẽ loại bỏ trùng lặp, trong khi một truy vấn union allsẽ không. Điều này không có nghĩa là có một hiệu suất nhỏ khi sử dụng unionhơnunion all nhưng kết quả có thể được giá trị nó - Tôi sẽ không suy đoán trên đó đại loại như vậy trong mặc dù điều này.

Về lưu ý này, có thể đáng chú ý một số ghi chú bổ sung ở đây.

  • Nếu chúng tôi muốn đặt hàng kết quả, chúng tôi có thể sử dụng order bynhưng bạn không thể sử dụng bí danh nữa. Trong truy vấn trên, việc nối thêm order by a.IDsẽ dẫn đến lỗi - theo như kết quả có liên quan, cột được gọi IDchứ không phải làa.ID - mặc dù cùng một bí danh đã được sử dụng trong cả hai truy vấn.
  • Chúng ta chỉ có thể có một order bytuyên bố, và nó phải là tuyên bố cuối cùng.

Đối với các ví dụ tiếp theo, tôi sẽ thêm một vài hàng vào bảng của chúng tôi.

Tôi đã thêm vào Holdenbảng thương hiệu. Tôi cũng đã thêm một hàng vào carscolorgiá trị 12- không có tham chiếu trong bảng màu.

Được rồi, ông chủ đã trở lại một lần nữa, sủa yêu cầu - * Tôi muốn đếm từng thương hiệu chúng tôi mang theo và số lượng xe trong đó! `- Điển hình, chúng tôi chỉ cần đến một phần thú vị của cuộc thảo luận của chúng tôi và ông chủ muốn làm việc nhiều hơn .

Rightyo, vì vậy điều đầu tiên chúng ta cần làm là có được một danh sách đầy đủ các thương hiệu có thể.

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

Bây giờ, khi chúng ta tham gia vào bảng ô tô của mình, chúng ta sẽ nhận được kết quả như sau:

select
    a.brand
from
    brands a
        join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Nissan |
| Smart  |
| Toyota |
+--------+
5 rows in set (0.00 sec)

Tất nhiên đó là một vấn đề - chúng tôi không thấy bất kỳ đề cập nào về Holdenthương hiệu đáng yêu mà tôi đã thêm.

Điều này là do một phép nối tìm các hàng khớp trong cả hai bảng. Vì không có dữ liệu trong xe ô tô thuộc loại Holdennào nên nó không được trả lại. Đây là nơi chúng ta có thể sử dụng một outertham gia. Điều này sẽ trả về tất cả các kết quả từ một bảng cho dù chúng có khớp với bảng khác hay không:

select
    a.brand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Holden |
| Nissan |
| Smart  |
| Toyota |
+--------+
6 rows in set (0.00 sec)

Bây giờ chúng ta đã có điều đó, chúng ta có thể thêm một hàm tổng hợp đáng yêu để đếm và đưa ông chủ ra khỏi lưng trong giây lát.

select
    a.brand,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+--------------+
| brand  | countOfBrand |
+--------+--------------+
| BMW    |            2 |
| Ford   |            2 |
| Holden |            0 |
| Nissan |            1 |
| Smart  |            1 |
| Toyota |            5 |
+--------+--------------+
6 rows in set (0.00 sec)

Và với điều đó, đi các ông chủ skulks.

Bây giờ, để giải thích điều này chi tiết hơn, các phép nối ngoài có thể là lefthoặc rightkiểu. Trái hoặc Phải xác định bảng nào được bao gồm đầy đủ . A left outer joinsẽ bao gồm tất cả các hàng từ bảng bên trái, trong khi (bạn đoán nó) aright outer join mang tất cả các kết quả từ bảng bên phải vào kết quả.

Một số cơ sở dữ liệu sẽ cho phép một full outer joinkết quả sẽ mang lại kết quả (dù có khớp hay không) từ cả hai bảng, nhưng điều này không được hỗ trợ trong tất cả các cơ sở dữ liệu.

Bây giờ, tôi có thể tính đến thời điểm này, bạn đang tự hỏi liệu bạn có thể hợp nhất các loại tham gia trong một truy vấn hay không - và câu trả lời là có, bạn hoàn toàn có thể.

select
    b.brand,
    c.color,
    count(a.id) as countOfBrand
from
    cars a
        right outer join brands b
            on b.ID=a.brand
        join colors c
            on a.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)

Vì vậy, tại sao đó không phải là kết quả đã được mong đợi? Đó là bởi vì mặc dù chúng tôi đã chọn phép nối ngoài từ ô tô đến nhãn hiệu, nhưng nó không được chỉ định trong phép nối với màu sắc - vì vậy phép nối cụ thể đó sẽ chỉ mang lại kết quả khớp với cả hai bảng.

Đây là truy vấn sẽ hoạt động để có được kết quả mà chúng tôi mong đợi:

select
    a.brand,
    c.color,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
        left outer join colors c
            on b.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Holden | NULL  |            0 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| Toyota | NULL  |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)

Như chúng ta có thể thấy, chúng ta có hai phép nối ngoài trong truy vấn và kết quả sẽ đạt được như mong đợi.

Bây giờ, làm thế nào về những loại tham gia khác mà bạn yêu cầu? Còn giao lộ thì sao?

Chà, không phải tất cả các cơ sở dữ liệu đều hỗ trợ intersectionnhưng hầu như tất cả các cơ sở dữ liệu sẽ cho phép bạn tạo một giao điểm thông qua một phép nối (hoặc ít nhất là có cấu trúc trong đó câu lệnh).

Giao lộ là một loại tham gia hơi giống với unionmô tả ở trên - nhưng sự khác biệt là nó chỉ trả về các hàng dữ liệu giống hệt nhau (và tôi có nghĩa là giống hệt nhau) giữa các truy vấn riêng lẻ được liên kết bởi liên kết. Chỉ các hàng giống hệt nhau trong mọi vấn đề sẽ được trả lại.

Một ví dụ đơn giản sẽ là như vậy:

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

Trong khi một uniontruy vấn bình thường sẽ trả về tất cả các hàng của bảng (truy vấn đầu tiên trả về bất cứ thứ gì ID>2và thứ hai có bất kỳ thứ gì ID<4) sẽ dẫn đến một bộ đầy đủ, một truy vấn giao nhau sẽ chỉ trả về hàng khớp id=3khi nó đáp ứng cả hai tiêu chí.

Bây giờ, nếu cơ sở dữ liệu của bạn không hỗ trợ intersecttruy vấn, phần trên có thể dễ dàng được cung cấp với truy vấn sau:

select
    a.ID,
    a.color,
    a.paint
from
    colors a
        join colors b
            on a.ID=b.ID
where
    a.ID>2
    and b.ID<4

+----+-------+----------+
| ID | color | paint    |
+----+-------+----------+
|  3 | Blue  | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)

Nếu bạn muốn thực hiện một giao lộ qua hai bảng khác nhau bằng cơ sở dữ liệu vốn không hỗ trợ truy vấn giao nhau, bạn sẽ cần tạo một liên kết trên mỗi cột của các bảng.


2
@Fluffeh Câu trả lời hay. Tôi có một gợi ý: Nếu bạn muốn biến nó thành Hướng dẫn SQL giết người, bạn chỉ thiếu để thêm sơ đồ Venn; Tôi hiểu ngay trái và phải tham gia nhờ họ. Yêu cầu cá nhân: Bạn có bất kỳ hướng dẫn về các lỗi phổ biến / điều chỉnh hiệu suất?
StrayChild01

25
Ôi trời. Bánh xe cuộn của tôi bị hỏng. Câu hỏi và câu trả lời tuyệt vời Tôi ước tôi có thể nâng cấp này 10 lần.
Amal Murali

3
Hehe, cảm ơn vì những phản hồi tích cực. Tiếp tục di chuyển, đây chỉ là câu trả lời đầu tiên. SO nói rằng câu trả lời của tôi quá dài để đưa nó vào một "câu trả lời" vì vậy tôi đã phải sử dụng một vài câu :)
Fluffeh

7
Thành thật mà nói, tôi nghĩ rằng câu trả lời này cần phải được rút ngắn phần nào.
einpoklum

Bài viết tuyệt vời. Cơ sở dữ liệu tham gia 101.
maqs

101

Ok, tôi thấy bài đăng này rất thú vị và tôi muốn chia sẻ một số kiến ​​thức của tôi về việc tạo một truy vấn. Cảm ơn vì Fluffeh này . Những người khác có thể đọc được điều này và có thể cảm thấy rằng tôi sai thì miễn phí 101% để chỉnh sửa và chỉ trích câu trả lời của tôi. ( Thành thật mà nói, tôi cảm thấy rất biết ơn vì đã sửa chữa (các) lỗi của mình. )

Tôi sẽ đăng một số câu hỏi thường gặp trong MySQLthẻ.


Thủ thuật số 1 ( hàng khớp với nhiều điều kiện )

Đưa ra lược đồ này

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');

CÂU HỎI

Tìm tất cả các bộ phim thuộc về ít nhất cả hai ComedyRomancethể loại.

Giải pháp

Câu hỏi này đôi khi có thể rất khó khăn. Có vẻ như một truy vấn như thế này sẽ là câu trả lời: -

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName = 'Comedy' AND
        b.CategoryName = 'Romance'

Trình diễn SQLFiddle

Điều này chắc chắn rất sai vì nó không tạo ra kết quả . Giải thích cho điều này là chỉ có một giá trị hợp lệ CategoryNametrên mỗi hàng . Chẳng hạn, điều kiện thứ nhất trả về true , điều kiện thứ hai luôn luôn là false. Do đó, bằng cách sử dụng ANDtoán tử, cả hai điều kiện phải đúng; nếu không, nó sẽ sai. Một truy vấn khác là như thế này,

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')

Trình diễn SQLFiddle

và kết quả vẫn không chính xác vì nó khớp với bản ghi có ít nhất một trận đấu trên categoryName. Các giải pháp thực tế sẽ bằng cách đếm số lượng các trường hợp hồ sơ cho mỗi phim . Số lượng phiên bản phải khớp với tổng số giá trị được cung cấp trong điều kiện.

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

Trình diễn SQLFiddle (câu trả lời)


Thủ thuật số 2 ( bản ghi tối đa cho mỗi mục )

Lược đồ đã cho,

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');

CÂU HỎI

Tìm phiên bản mới nhất trên mỗi phần mềm. Hiển thị các cột sau: SoftwareName, Descriptions, LatestVersion( từ cột VersionNo ),DateReleased

Giải pháp

Một số nhà phát triển SQL sử dụng nhầm MAX()hàm tổng hợp. Họ có xu hướng tạo ra như thế này,

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

Trình diễn SQLFiddle

( hầu hết RDBMS tạo ra lỗi cú pháp về điều này do không chỉ định một số cột không tổng hợp trong group bymệnh đề ) kết quả tạo ra chính xác LatestVersiontrên mỗi phần mềm nhưng rõ ràng DateReleasedlà không chính xác. MySQLkhông hỗ trợ Window FunctionsCommon Table Expressionnhư một số RDBMS đã làm. Cách giải quyết cho vấn đề này là tạo ra một subquerymức tối đa riêng versionNocho từng phần mềm và sau đó được nối vào các bảng khác.

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

Trình diễn SQLFiddle (câu trả lời)


Vì vậy, đó là nó. Tôi sẽ đăng bài khác ngay khi tôi nhớ bất kỳ Câu hỏi thường gặp nào khác trênMySQL thẻ. Cảm ơn bạn đã đọc bài viết nhỏ này. Tôi hy vọng rằng bạn ít nhất cũng có được một chút kiến ​​thức từ việc này.

CẬP NHẬT 1


Thủ thuật số 3 ( Tìm bản ghi mới nhất giữa hai ID )

Schema đưa ra

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');

CÂU HỎI

Tìm cuộc trò chuyện mới nhất giữa hai người dùng.

Giải pháp

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
)

Trình diễn SQLFiddle


Tuyệt vời! Hãy cẩn thận John, giải pháp đầu tiên của bạn chỉ hoạt động vì có một ràng buộc duy nhất trên hai trường. Bạn có thể đã sử dụng một giải pháp tổng quát hơn để giúp giải quyết một vấn đề chung. Theo tôi, giải pháp duy nhất là chọn từng cá nhân comedyromance. Havingkhông phù hợp rồi ..
nawfal

@nawfal không thực sự, nếu không thêm ràng buộc duy nhất, thì bạn cần thêm distinct vào mệnh đề SQLFiddle Demo : D
John Woo

63

Phần 2 - Truy vấn con

Được rồi, bây giờ ông chủ đã bùng nổ trở lại - Tôi muốn một danh sách tất cả những chiếc xe của chúng tôi với thương hiệu và tổng cộng có bao nhiêu thương hiệu chúng tôi có!

Đây là một cơ hội tuyệt vời để sử dụng thủ thuật tiếp theo trong túi các tính năng SQL của chúng tôi - truy vấn con. Nếu bạn không quen thuộc với thuật ngữ này, truy vấn con là một truy vấn chạy bên trong một truy vấn khác. Có nhiều cách khác nhau để sử dụng chúng.

Đối với yêu cầu của chúng tôi, trước tiên chúng ta hãy đặt một truy vấn đơn giản sẽ liệt kê từng chiếc xe và thương hiệu:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Bây giờ, nếu chúng ta chỉ muốn có một số lượng xe được sắp xếp theo nhãn hiệu, tất nhiên chúng ta có thể viết điều này:

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

Vì vậy, chúng ta có thể chỉ cần thêm vào hàm đếm vào truy vấn ban đầu của chúng tôi phải không?

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)

Đáng buồn thay, không, chúng ta không thể làm điều đó. Lý do là khi chúng ta thêm ID xe (cột a.ID), chúng ta phải thêm nó vào nhóm - vì vậy, bây giờ, khi chức năng đếm hoạt động, chỉ có một ID trùng khớp cho mỗi ID.

Tuy nhiên, đây là nơi chúng ta có thể sử dụng truy vấn con - trên thực tế chúng ta có thể thực hiện hai loại truy vấn hoàn toàn khác nhau sẽ trả về cùng kết quả mà chúng ta cần cho việc này. Đầu tiên là chỉ cần đặt truy vấn con trong selectmệnh đề. Điều này có nghĩa là mỗi khi chúng tôi nhận được một hàng dữ liệu, truy vấn con sẽ chạy, lấy một cột dữ liệu và sau đó đưa nó vào hàng dữ liệu của chúng tôi.

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)

Và Bam!, Điều này sẽ làm chúng ta. Nếu bạn nhận thấy, truy vấn phụ này sẽ phải chạy cho từng hàng dữ liệu chúng tôi trả về. Ngay cả trong ví dụ nhỏ này, chúng tôi chỉ có năm Thương hiệu xe hơi khác nhau, nhưng truy vấn phụ đã chạy mười một lần vì chúng tôi có mười một hàng dữ liệu mà chúng tôi sẽ trả lại. Vì vậy, trong trường hợp này, nó dường như không phải là cách hiệu quả nhất để viết mã.

Đối với một cách tiếp cận khác, hãy chạy một truy vấn con và giả vờ đó là một bảng:

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)

Được rồi, vì vậy chúng tôi có cùng kết quả (được sắp xếp hơi khác nhau - có vẻ như cơ sở dữ liệu muốn trả về kết quả được đặt theo cột đầu tiên chúng tôi đã chọn lần này) - nhưng cùng một số đúng.

Vậy, sự khác biệt giữa hai loại - và khi nào chúng ta nên sử dụng từng loại truy vấn con? Đầu tiên, hãy chắc chắn rằng chúng tôi hiểu cách truy vấn thứ hai hoạt động. Chúng tôi đã chọn hai bảng trong frommệnh đề của truy vấn của mình và sau đó viết một truy vấn và nói với cơ sở dữ liệu rằng thực tế nó là một bảng - cơ sở dữ liệu hoàn toàn hài lòng. Có thể có một số lợi ích khi sử dụng phương pháp này (cũng như một số hạn chế). Điều quan trọng nhất là truy vấn con này đã chạy một lần . Nếu cơ sở dữ liệu của chúng tôi chứa một khối lượng dữ liệu lớn, thì cũng có thể có một sự cải tiến lớn so với phương pháp đầu tiên. Tuy nhiên, vì chúng tôi đang sử dụng bảng này như một bảng, chúng tôi phải đưa thêm các hàng dữ liệu - để chúng thực sự có thể được nối lại vào các hàng dữ liệu của chúng tôi. Chúng tôi cũng phải chắc chắn rằng có đủcác hàng dữ liệu nếu chúng ta sẽ sử dụng một phép nối đơn giản như trong truy vấn trên. Nếu bạn nhớ lại, phép nối sẽ chỉ kéo lại các hàng có dữ liệu trùng khớp ở cả hai phía của phép nối. Nếu chúng tôi không cẩn thận, điều này có thể dẫn đến dữ liệu hợp lệ không được trả về từ bảng ô tô của chúng tôi nếu không có hàng phù hợp trong truy vấn phụ này.

Bây giờ, nhìn lại truy vấn con đầu tiên, có một số hạn chế là tốt. bởi vì chúng tôi đang kéo dữ liệu trở lại thành một hàng, chúng tôi CHỈ có thể kéo lại một hàng dữ liệu. Truy vấn con được sử dụng trong các selectđiều khoản của một truy vấn rất thường chỉ sử dụng một chức năng tổng hợp như sum, count, maxhay cách khác chức năng tổng hợp tương tự. Họ không đến, nhưng đó thường là cách thức chúng được viết ra.

Vì vậy, trước khi chúng ta tiếp tục, hãy xem nhanh những nơi khác chúng ta có thể sử dụng truy vấn con. Chúng ta có thể sử dụng nó trong wheremệnh đề - bây giờ, ví dụ này có một chút giả định như trong cơ sở dữ liệu của chúng ta, có nhiều cách tốt hơn để lấy dữ liệu sau, nhưng xem như nó chỉ là một ví dụ, hãy xem:

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

Điều này trả về cho chúng tôi danh sách ID thương hiệu và Tên thương hiệu (cột thứ hai chỉ được thêm vào để hiển thị cho chúng tôi các nhãn hiệu) có chứa chữ cái otrong tên.

Bây giờ, chúng ta có thể sử dụng kết quả của truy vấn này trong mệnh đề where này:

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)

Như bạn có thể thấy, mặc dù truy vấn con đã trả lại ba ID thương hiệu, bảng ô tô của chúng tôi chỉ có mục nhập cho hai trong số chúng.

Trong trường hợp này, để biết thêm chi tiết, truy vấn con hoạt động như thể chúng ta đã viết đoạn mã sau:

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)

Một lần nữa, bạn có thể thấy cách truy vấn phụ so với đầu vào thủ công đã thay đổi thứ tự của các hàng khi trở về từ cơ sở dữ liệu.

Trong khi chúng ta đang thảo luận về các truy vấn con, hãy xem chúng ta có thể làm gì khác với truy vấn con:

  • Bạn có thể đặt một truy vấn con trong một truy vấn con khác, v.v. Có một giới hạn phụ thuộc vào cơ sở dữ liệu của bạn, nhưng thiếu các chức năng đệ quy của một số lập trình viên điên rồ và điên rồ, hầu hết mọi người sẽ không bao giờ đạt đến giới hạn đó.
  • Bạn có thể đặt một số truy vấn con vào một truy vấn duy nhất, một số trong selectmệnh đề, một số trong frommệnh đề và một vài câu nữa trong wheremệnh đề - chỉ cần nhớ rằng mỗi truy vấn bạn đặt vào sẽ khiến truy vấn của bạn phức tạp hơn và có thể mất nhiều thời gian hơn hành hình.

Nếu bạn cần viết một số mã hiệu quả, có thể có ích khi viết truy vấn một số cách và xem (bằng cách định thời gian hoặc bằng cách sử dụng gói giải thích), đó là truy vấn tối ưu để nhận kết quả của bạn. Cách đầu tiên hoạt động có thể không phải lúc nào cũng là cách tốt nhất để làm điều đó.


Rất quan trọng đối với các nhà phát triển mới: các truy vấn con có thể chạy một lần cho mỗi kết quả trừ khi bạn có thể sử dụng truy vấn phụ làm liên kết (hiển thị ở trên).
Xeoncross

59

Phần 3 - Thủ thuật và mã hiệu quả

MySQL trong () hiệu quả

Tôi nghĩ rằng tôi sẽ thêm một số bit, cho các mẹo và thủ thuật đã đưa ra.

Một câu hỏi tôi thấy xuất hiện một chút công bằng, đó là làm cách nào để có được các hàng không khớp từ hai bảng và tôi thấy câu trả lời được chấp nhận phổ biến nhất như sau (dựa trên bảng ô tô và nhãn hiệu của chúng tôi - được Holden liệt kê dưới dạng thương hiệu, nhưng không xuất hiện trong bảng ô tô):

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

vâng, nó sẽ hoạt động.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

Tuy nhiên nó không hiệu quả trong một số cơ sở dữ liệu. Đây là một liên kết đến một câu hỏi Stack Overflow hỏi về nó, và đây là một bài viết chuyên sâu tuyệt vời nếu bạn muốn đi sâu vào vấn đề khó chịu.

Câu trả lời ngắn gọn là, nếu trình tối ưu hóa không xử lý hiệu quả, có thể tốt hơn nhiều khi sử dụng truy vấn như sau để nhận các hàng không khớp:

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)

Bảng cập nhật với cùng bảng trong truy vấn con

Ahhh, một người cũ khác nhưng tốt bụng - người cũ Bạn không thể chỉ định 'nhãn hiệu' của bảng mục tiêu để cập nhật trong mệnh đề TỪ .

MySQL sẽ không cho phép bạn chạy một update...truy vấn với một mục phụ trên cùng một bảng. Bây giờ, bạn có thể nghĩ, tại sao không tát nó vào mệnh đề where phải không? Nhưng nếu bạn chỉ muốn cập nhật hàng với max()ngày có một loạt các hàng khác thì sao? Bạn không thể chính xác làm điều đó trong một mệnh đề 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

Vì vậy, chúng ta không thể làm điều đó hả? Không hẳn là chính xác lắm. Có một cách giải quyết lén lút mà một số lượng lớn người dùng đáng ngạc nhiên không biết đến - mặc dù nó bao gồm một số tin tặc mà bạn sẽ cần phải chú ý.

Bạn có thể dính truy vấn con trong một truy vấn con khác, điều này đặt đủ khoảng cách giữa hai truy vấn để nó hoạt động. Tuy nhiên, lưu ý rằng có thể an toàn nhất khi dán truy vấn trong giao dịch - điều này sẽ ngăn mọi thay đổi khác được thực hiện đối với các bảng trong khi truy vấn đang chạy.

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

3
Chỉ muốn lưu ý rằng cấu trúc WHERE NOT EXISTS () khá giống nhau từ 'quan điểm hiệu quả' nhưng theo tôi thì dễ đọc / dễ hiểu hơn nhiều. Sau đó, một lần nữa, kiến ​​thức của tôi bị giới hạn ở MSSQL và tôi không thể thề nếu điều tương tự là đúng trên các nền tảng khác.
deroby

Tôi mới thử loại so sánh này vào một ngày khác, trong đó NOT IN () có một danh sách khoảng vài trăm ID và không có sự khác biệt giữa nó và phiên bản tham gia của truy vấn. Có lẽ nó làm cho một sự khác biệt khi bạn lên đến hàng ngàn hoặc hàng tỷ.
Butussy Butkus

18

Bạn có thể sử dụng khái niệm nhiều truy vấn trong từ khóa TỪ. Hãy để tôi chỉ cho bạn một ví dụ:

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

Bạn có thể sử dụng nhiều bảng như bạn muốn. Sử dụng các phép nối ngoài và liên kết bất cứ khi nào cần thiết, ngay cả trong các truy vấn con trong bảng.

Đó là một phương pháp rất dễ dàng để liên quan đến nhiều bảng và trường.


8

Hy vọng điều này làm cho nó tìm thấy các bảng khi bạn đọc qua điều này:

jsfiddle

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    |                |
+-------+-------------+------+-----+---------+----------------+
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.