Tham gia so với truy vấn phụ


837

Tôi là một người dùng MySQL trường học cũ và luôn thích JOINtruy vấn phụ hơn. Nhưng ngày nay mọi người đều sử dụng truy vấn phụ, và tôi ghét nó; Tôi không biết tại sao.

Tôi thiếu kiến ​​thức lý thuyết để tự đánh giá nếu có sự khác biệt. Là một truy vấn phụ tốt như một JOINvà do đó không có gì phải lo lắng?


23
Truy vấn con đôi khi là tuyệt vời. Họ hút hiệu năng-khôn ngoan trong MySQL. Đừng sử dụng chúng.
run run

8
Tôi luôn có ấn tượng rằng các truy vấn phụ được thực hiện dưới dạng tham gia khi có sẵn trong các công nghệ DB nhất định.
Kezzer

18
Các truy vấn phụ không phải lúc nào cũng hút, khi tham gia với các bảng khá lớn, cách ưa thích là thực hiện chọn phụ từ bảng lớn đó (giới hạn số lượng hàng) và sau đó tham gia.
ovais.tariq

136
"ngày nay mọi người đều sử dụng truy vấn phụ" [cần dẫn nguồn]
Piskvor rời khỏi tòa nhà vào

3
Có khả năng liên quan (mặc dù cụ thể hơn nhiều): stackoverflow.com/questions/141278/subqueries-vs-joins/
mẹo

Câu trả lời:


191

Lấy từ hướng dẫn sử dụng MySQL ( 13.2.10.11 Viết lại các truy vấn con khi tham gia ):

THAM GIA TRÊN [NGOÀI] THAM GIA có thể nhanh hơn truy vấn con tương đương vì máy chủ có thể tối ưu hóa nó tốt hơn một thực tế không chỉ dành riêng cho Máy chủ MySQL.

Vì vậy, các truy vấn con có thể chậm hơn LEFT [OUTER] JOIN, nhưng theo tôi sức mạnh của chúng là khả năng đọc cao hơn một chút.


45
@ user1735921 IMO nó phụ thuộc ... Nói chung, điều rất quan trọng là tính dễ đọc của mã, bởi vì nó rất quan trọng đối với việc quản lý sau này ... Hãy nhớ câu nói nổi tiếng của Donald Knuth: "Tối ưu hóa sớm là gốc rễ của tất cả cái ác (hoặc ít nhất là phần lớn) trong lập trình " . Tuy nhiên, tự nhiên có những lĩnh vực lập trình mà hiệu suất là tối quan trọng ... Lý tưởng nhất là khi một người thành công trong việc dung hòa cái này với cái khác :)
simhumileco

30
Trong các truy vấn phức tạp hơn, tôi thấy các phép nối dễ đọc hơn nhiều so với các truy vấn phụ. truy vấn phụ biến thành một bát mì trong đầu tôi.
Zahra

6
@ user1735921 chắc chắn, đặc biệt là khi truy vấn trở nên phức tạp đến mức nó làm sai và bạn mất một ngày để sửa nó ... như một sự cân bằng ở giữa, như thường lệ.
fabio.sussetto

6
@ user1735921 Chỉ khi mức tăng hiệu suất đáng để tăng thời gian bảo trì cần thiết trong tương lai
Joshua Schlichting

3
Ý kiến ​​của tôi Joinsub querycó cú pháp khác nhau, vì vậy chúng tôi không thể so sánh, cả hai đều có khả năng đọc cao hơn miễn là bạn giỏi cú pháp SQL. Hiệu suất là quan trọng hơn.
Thavaprakash Swaminathan

842

Truy vấn phụ là cách chính xác về mặt logic để giải quyết các vấn đề của biểu mẫu, "Nhận sự kiện từ A, có điều kiện dựa trên sự kiện từ B". Trong các trường hợp như vậy, sẽ có ý nghĩa logic hơn khi gắn B vào truy vấn phụ hơn là thực hiện nối. Theo nghĩa thực tế, nó cũng an toàn hơn, vì bạn không cần phải thận trọng về việc nhận các sự kiện trùng lặp từ A do nhiều trận đấu với B.

Thực tế mà nói, câu trả lời thường đi vào hiệu suất. Một số trình tối ưu hóa hút chanh khi được nối với một truy vấn phụ và một số khác hút chanh theo cách khác, và đây là đặc trưng của trình tối ưu hóa, cụ thể theo phiên bản DBMS và dành riêng cho truy vấn.

Trong lịch sử, các phép nối rõ ràng thường giành chiến thắng, do đó trí tuệ được thiết lập tham gia tốt hơn, nhưng các trình tối ưu hóa đang trở nên tốt hơn mọi lúc, và vì vậy tôi thích viết các truy vấn trước theo cách hợp lý, và sau đó cơ cấu lại nếu các ràng buộc về hiệu suất đảm bảo điều này.


105
Câu trả lời chính xác. Tôi cũng nói thêm rằng các nhà phát triển (đặc biệt là những người nghiệp dư) không phải lúc nào cũng thành thạo SQL.
Álvaro González

4
+1 Tìm kiếm một số lời giải thích hợp lý cho vấn đề này trong một thời gian dài, đây chỉ là câu trả lời có vẻ hợp lý với tôi
Ali Umair

1
@Marcelo Cantos, bạn có thể vui lòng cho một ví dụ về câu nói của bạn "Nó cũng an toàn hơn, theo nghĩa thực tế, vì bạn không phải thận trọng về việc nhận các sự kiện trùng lặp từ A do nhiều trận đấu với B."? Tôi thấy điều này rất sâu sắc nhưng hơi quá trừu tượng. Cảm ơn.
Jinghui Niu

6
@JinghuiNiu Khách hàng mua đồ đắt tiền : select custid from cust join bought using (custid) where price > 500. Nếu một khách hàng mua nhiều mặt hàng đắt tiền, bạn sẽ nhận được gấp đôi. Để khắc phục điều này , select custid from cust where exists (select * from bought where custid = cust.custid and price > 500). Bạn có thể sử dụng select distinct …thay thế, nhưng nó thường làm việc nhiều hơn, cho trình tối ưu hóa hoặc người đánh giá.
Marcelo Cantos

1
@MatTheWhale vâng Tôi đã sử dụng một câu trả lời đơn giản vì tôi lười biếng. Trong một kịch bản thực tế, bạn sẽ kéo nhiều cột hơn là chỉ có quyền giám hộ.
Marcelo Cantos

357

Trong hầu hết các trường hợp, JOINs nhanh hơn truy vấn phụ và rất hiếm khi truy vấn phụ nhanh hơn.

Trong JOINRDBMS có thể tạo một kế hoạch thực hiện tốt hơn cho truy vấn của bạn và có thể dự đoán dữ liệu nào sẽ được tải để xử lý và tiết kiệm thời gian, không giống như truy vấn phụ nơi nó sẽ chạy tất cả các truy vấn và tải tất cả dữ liệu của chúng để xử lý .

Điều tốt trong các truy vấn phụ là chúng dễ đọc hơn JOINs: đó là lý do tại sao hầu hết những người SQL mới thích chúng; đó là cách dễ dàng; nhưng khi nói đến hiệu suất, THAM GIA tốt hơn trong hầu hết các trường hợp mặc dù chúng cũng không khó đọc.


14
Có, do đó, hầu hết các cơ sở dữ liệu bao gồm nó như một bước tối ưu hóa để chuyển đổi các truy vấn con thành các phép nối khi nó đang phân tích truy vấn của bạn.
Cine

16
Câu trả lời này là một chút quá đơn giản cho câu hỏi đã được hỏi. Như bạn nêu: các truy vấn con nhất định là ok và nhất định là không. Câu trả lời không thực sự giúp phân biệt hai. (cũng là "rất hiếm" thực sự phụ thuộc vào dữ liệu / ứng dụng của bạn).
Không hợp lý

21
bạn có thể chứng minh bất kỳ điểm nào của bạn với tài liệu tham khảo hoặc kết quả kiểm tra?
Uur Gümüşhan

62
Tôi đã có những trải nghiệm rất tốt với các truy vấn phụ có chứa tham chiếu ngược đến truy vấn trên, đặc biệt là khi nó có số lượng hàng trên 100.000. Điều này có vẻ là việc sử dụng bộ nhớ và phân trang cho tệp hoán đổi. Một phép nối sẽ tạo ra một lượng dữ liệu rất lớn, có thể không vừa với bộ nhớ và phải được phân trang vào tệp hoán đổi. Bất cứ khi nào đây là trường hợp thời gian truy vấn của các lựa chọn phụ nhỏ như thế select * from a where a.x = (select b.x form b where b.id = a.id)là cực kỳ nhỏ so với tham gia. Đây là một vấn đề rất cụ thể, nhưng trong một số trường hợp, nó mang lại cho bạn từ vài giờ đến vài phút.
zuloo

13
Tôi có kinh nghiệm với Oracle và tôi có thể nói, các truy vấn phụ sẽ tốt hơn nhiều trên các bảng lớn nếu bạn không có bất kỳ bộ lọc hoặc sắp xếp nào trên chúng.
Amir Pashazadeh

130

Sử dụng GIẢI THÍCH để xem cách cơ sở dữ liệu của bạn thực hiện truy vấn trên dữ liệu của bạn. Có một "nó phụ thuộc" rất lớn trong câu trả lời này ...

PostgreSQL có thể viết lại một truy vấn con thành một tham gia hoặc tham gia vào một truy vấn phụ khi nó nghĩ rằng một truy vấn nhanh hơn một truy vấn phụ. Tất cả phụ thuộc vào dữ liệu, chỉ mục, tương quan, lượng dữ liệu, truy vấn, v.v.


6
đây chính xác là lý do tại sao postgresql rất tốt và hữu ích, nó hiểu mục tiêu là gì và sẽ khắc phục một truy vấn dựa trên những gì nó nghĩ là tốt hơn và postgresql rất giỏi trong việc biết cách xem dữ liệu của nó
WojonsTech 17/214

đẽo Tôi đoán không cần phải viết lại hàng tấn truy vấn cho tôi! postgresql cho chiến thắng.
Daniel Shin

77

Vào năm 2010, tôi đã tham gia với tác giả của câu hỏi này và đã bỏ phiếu mạnh mẽ JOIN, nhưng với nhiều kinh nghiệm hơn (đặc biệt là trong MySQL) tôi có thể nói: Có các câu hỏi con có thể tốt hơn. Tôi đã đọc nhiều câu trả lời ở đây; một số truy vấn con được nêu nhanh hơn, nhưng nó thiếu một lời giải thích tốt. Tôi hy vọng tôi có thể cung cấp một câu trả lời muộn (rất) này:

Trước hết, hãy để tôi nói điều quan trọng nhất: Có các hình thức truy vấn phụ khác nhau

Và tuyên bố quan trọng thứ hai: Kích thước quan trọng

Nếu bạn sử dụng các truy vấn phụ, bạn nên biết cách DB-Server thực hiện truy vấn phụ. Đặc biệt nếu truy vấn phụ được đánh giá một lần hoặc cho mỗi hàng! Mặt khác, một DB-Server hiện đại có thể tối ưu hóa rất nhiều. Trong một số trường hợp, truy vấn con giúp tối ưu hóa truy vấn, nhưng phiên bản mới hơn của DB-Server có thể khiến việc tối ưu hóa trở nên lỗi thời.

Truy vấn phụ trong trường chọn

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Xin lưu ý rằng một truy vấn phụ được thực thi cho mỗi hàng kết quả từ foo.
Tránh điều này nếu có thể; nó có thể làm chậm đáng kể truy vấn của bạn trên các bộ dữ liệu lớn. Tuy nhiên, nếu truy vấn phụ không có tham chiếu đến foonó thì máy chủ DB có thể được tối ưu hóa dưới dạng nội dung tĩnh và chỉ có thể được đánh giá một lần.

Các truy vấn phụ trong câu lệnh Where

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Nếu bạn may mắn, DB tối ưu hóa nội bộ này thành a JOIN. Nếu không, truy vấn của bạn sẽ trở nên rất, rất chậm trên các bộ dữ liệu khổng lồ vì nó sẽ thực hiện truy vấn phụ cho mỗi hàng foo, không chỉ các kết quả như trong kiểu chọn.

Truy vấn phụ trong Tuyên bố tham gia

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Hay đấy. Chúng tôi kết hợp JOINvới một truy vấn phụ. Và ở đây chúng ta có được sức mạnh thực sự của các truy vấn phụ. Hãy tưởng tượng một tập dữ liệu với hàng triệu hàng trong wilconhưng chỉ có một vài khác biệt me. Thay vì tham gia chống lại một bảng lớn, giờ đây chúng ta có một bảng tạm thời nhỏ hơn để tham gia. Điều này có thể dẫn đến các truy vấn nhanh hơn nhiều tùy thuộc vào kích thước cơ sở dữ liệu. Bạn có thể có cùng hiệu ứng với CREATE TEMPORARY TABLE ...INSERT INTO ... SELECT ..., có thể cung cấp khả năng đọc tốt hơn cho các truy vấn rất phức tạp (nhưng có thể khóa các bộ dữ liệu ở mức cô lập đọc lặp lại).

Các truy vấn phụ lồng nhau

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  ORDER BY bar

Bạn có thể lồng các truy vấn phụ theo nhiều cấp độ. Điều này có thể giúp trên các bộ dữ liệu lớn nếu bạn phải nhóm hoặc sắp xếp kết quả. Thông thường DB-Server tạo một bảng tạm thời cho việc này, nhưng đôi khi bạn không cần sắp xếp trên toàn bộ bảng, chỉ trên tập kết quả. Điều này có thể cung cấp hiệu suất tốt hơn nhiều tùy thuộc vào kích thước của bảng.

Phần kết luận

Các truy vấn phụ không thay thế cho a JOINvà bạn không nên sử dụng chúng như thế này (mặc dù có thể). Theo ý kiến ​​khiêm tốn của tôi, việc sử dụng đúng một truy vấn phụ là việc sử dụng như một sự thay thế nhanh chóng CREATE TEMPORARY TABLE .... Một truy vấn phụ tốt làm giảm tập dữ liệu theo cách bạn không thể thực hiện được trong ONcâu lệnh của a JOIN. Nếu một truy vấn phụ có một trong các từ khóa GROUP BYhoặc DISTINCTtốt nhất là không nằm trong các trường được chọn hoặc câu lệnh where, thì nó có thể cải thiện hiệu suất rất nhiều.


3
Đối với Sub-queries in the Join-statement: (1) tạo bảng dẫn xuất từ ​​chính truy vấn phụ có thể mất nhiều thời gian. (2) bảng dẫn xuất kết quả không được lập chỉ mục. chỉ riêng hai điều này có thể làm chậm đáng kể SQL.
jxc

@jxc Tôi chỉ có thể nói cho MySQL (1) Có một bảng tạm thời tương tự như tham gia. Thời gian phụ thuộc vào lượng dữ liệu. Nếu bạn không thể giảm dữ liệu bằng truy vấn con, hãy sử dụng phép nối. (2) Điều này đúng, nó phụ thuộc vào yếu tố bạn có thể giảm dữ liệu trong bảng tạm thời. Tôi đã có các trường hợp trong thế giới thực, nơi tôi có thể giảm kích thước tham gia từ vài triệu xuống còn vài trăm và giảm thời gian truy vấn từ nhiều giây (với việc sử dụng chỉ mục đầy đủ) xuống một phần tư giây với truy vấn phụ.
Trendfischer

IMO: (1) bảng tạm thời như vậy (bảng dẫn xuất) không được cụ thể hóa, do đó mỗi lần bạn chạy SQL, bảng tạm thời phải được tạo lại, có thể rất tốn kém và cổ chai thực sự (nghĩa là chạy một nhóm trên hàng triệu của các bản ghi) (2) ngay cả khi bạn có thể giảm kích thước của bảng tạm thời thành 10các bản ghi, vì không có chỉ mục, điều đó vẫn có nghĩa là có khả năng truy vấn các bản ghi dữ liệu nhiều hơn 9 lần so với bảng tạm thời khi THAM GIA các bảng khác. BTW Tôi đã gặp vấn đề này trước đây với db (MySQL) của tôi, trong trường hợp của tôi, sử dụng truy vấn phụ trong SELECT listcó thể nhanh hơn nhiều.
jxc

@jxc Tôi không nghi ngờ rằng có rất nhiều ví dụ, trong đó sử dụng truy vấn con ít tối ưu hơn. Khi thực hành tốt, bạn nên sử dụng EXPLAINtrên một truy vấn trước khi tối ưu hóa. Với cái cũ set profiling=1bạn có thể dễ dàng nhìn thấy, nếu một bảng tạm thời là một nút cổ chai. Và ngay cả một chỉ mục cần thời gian xử lý, B-Plants tối ưu hóa truy vấn cho các bản ghi, nhưng một bảng 10 bản ghi có thể nhanh hơn nhiều so với một chỉ mục cho hàng triệu bản ghi. Nhưng nó phụ thuộc vào nhiều yếu tố như kích cỡ và loại trường.
Trendfischer

1
Tôi thực sự rất thích lời giải thích của bạn. Cảm ơn bạn.
tuyệt vời nhất

43

Trước hết, để so sánh hai cái đầu tiên, bạn nên phân biệt các truy vấn với các truy vấn con với:

  1. một lớp các truy vấn con luôn có truy vấn tương đương được viết bằng các phép nối
  2. một lớp các truy vấn con không thể được viết lại bằng cách sử dụng các phép nối

Đối với lớp truy vấn đầu tiên, RDBMS tốt sẽ thấy các phép nối và truy vấn con tương đương và sẽ tạo ra các gói truy vấn giống nhau.

Ngày nay, ngay cả mysql cũng làm điều đó.

Tuy nhiên, đôi khi không, nhưng điều này không có nghĩa là các tham gia sẽ luôn giành chiến thắng - Tôi đã gặp trường hợp khi sử dụng các truy vấn con trong mysql cải thiện hiệu suất. (Ví dụ: nếu có thứ gì đó ngăn cản trình lập kế hoạch mysql ước tính chính xác chi phí và nếu trình hoạch định không thấy biến thể nối và biến thể truy vấn giống nhau thì các truy vấn con có thể vượt trội hơn các phép nối bằng cách buộc một đường dẫn nhất định).

Kết luận là bạn nên kiểm tra các truy vấn của mình cho cả hai biến thể tham gia và truy vấn con nếu bạn muốn chắc chắn cái nào sẽ hoạt động tốt hơn.

Đối với lớp thứ hai, việc so sánh không có ý nghĩa vì các truy vấn đó không thể được viết lại bằng cách sử dụng các phép nối và trong các trường hợp này, các truy vấn con là cách tự nhiên để thực hiện các tác vụ được yêu cầu và bạn không nên phân biệt đối xử với chúng.


1
bạn có thể cung cấp một ví dụ về một truy vấn được viết bằng các truy vấn phụ không thể chuyển đổi thành các phép nối (lớp thứ hai, như bạn gọi nó) không?
Zahra

24

Tôi nghĩ rằng những gì đã được nhấn mạnh trong các câu trả lời được trích dẫn là vấn đề trùng lặp và kết quả có vấn đề có thể phát sinh từ các trường hợp (sử dụng) cụ thể.

(mặc dù Marcelo Cantos có đề cập đến nó)

Tôi sẽ trích dẫn ví dụ từ các khóa học Lagunita của Stanford về SQL.

Bảng sinh viên

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Áp dụng bảng

(các ứng dụng được thực hiện cho các trường đại học và chuyên ngành cụ thể)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Hãy thử tìm điểm GPA cho sinh viên đã nộp đơn vào CSchuyên ngành (không phân biệt trường đại học)

Sử dụng truy vấn con:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Giá trị trung bình cho tập kết quả này là:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Sử dụng tham gia:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

giá trị trung bình cho kết quả này:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Rõ ràng là lần thử thứ hai mang lại kết quả sai lệch trong trường hợp sử dụng của chúng tôi, với điều kiện là nó tính các lần trùng lặp để tính toán giá trị trung bình. Một điều hiển nhiên là việc sử dụng distinctvới tuyên bố dựa trên tham gia sẽ không loại bỏ được vấn đề, với điều kiện là nó sẽ giữ sai một trong ba lần xuất hiện của 3.9điểm số. Trường hợp chính xác là tính đến TWO (2) lần xuất hiện của 3.9điểm số mà chúng tôi thực sự có TWO (2) học sinh với điểm số đó tuân thủ các tiêu chí truy vấn của chúng tôi.

Có vẻ như trong một số trường hợp, truy vấn phụ là cách an toàn nhất, bên cạnh bất kỳ vấn đề về hiệu suất.


Tôi nghĩ bạn không thể sử dụng truy vấn phụ ở đây. Đây không phải là trường hợp bạn có thể sử dụng một cách hợp lý nhưng người ta trả lời sai vì thực hiện kỹ thuật của nó. Đây là trường hợp bạn KHÔNG THỂ sử dụng truy vấn phụ vì một học sinh không thuộc CS có thể đạt điểm 3.9 trong danh sách điểm IN. Bối cảnh của CS bị mất khi truy vấn phụ được thực thi, đó không phải là điều chúng ta muốn một cách hợp lý. Vì vậy, đây không phải là một ví dụ tốt trong đó có thể được sử dụng. Việc sử dụng truy vấn phụ là sai về mặt khái niệm / logic cho trường hợp sử dụng này ngay cả khi may mắn là nó mang lại kết quả đúng cho một tập dữ liệu khác.
Saurabh Patil

22

Tài liệu MSDN cho SQL Server cho biết

Nhiều câu lệnh Transact-SQL bao gồm các truy vấn con có thể được tạo thành một cách khác như các phép nối. Các câu hỏi khác chỉ có thể được đặt ra với các truy vấn con. Trong Transact-SQL, thường không có sự khác biệt về hiệu năng giữa một câu lệnh bao gồm một truy vấn con và một phiên bản tương đương về mặt ngữ nghĩa mà không có. Tuy nhiên, trong một số trường hợp phải kiểm tra sự tồn tại, phép nối mang lại hiệu suất tốt hơn. Mặt khác, truy vấn lồng nhau phải được xử lý cho từng kết quả của truy vấn bên ngoài để đảm bảo loại bỏ trùng lặp. Trong những trường hợp như vậy, một cách tiếp cận tham gia sẽ mang lại kết quả tốt hơn.

vì vậy nếu bạn cần một cái gì đó như

select * from t1 where exists select * from t2 where t2.parent=t1.id

cố gắng sử dụng tham gia thay thế. Trong các trường hợp khác, nó không làm cho sự khác biệt.

Tôi nói: Tạo các hàm cho các truy vấn con loại bỏ vấn đề của cluttter và cho phép bạn triển khai logic bổ sung cho các truy vấn con. Vì vậy, tôi khuyên bạn nên tạo chức năng cho các truy vấn con bất cứ khi nào có thể.

Sự lộn xộn trong mã là một vấn đề lớn và ngành công nghiệp đã cố gắng tránh nó trong nhiều thập kỷ.


9
Thay thế các truy vấn con bằng các hàm là một ý tưởng rất tệ về hiệu năng trong một số RDBMS (ví dụ: Oracle), vì vậy tôi khuyên bạn chỉ nên ngược lại - sử dụng các truy vấn con / tham gia thay vì các chức năng bất cứ khi nào có thể.
Frank Schmitt

3
@FrankSchmitt vui lòng hỗ trợ lập luận của bạn với các tài liệu tham khảo.
Uğur Gümüşhan

2
Cũng có trường hợp bạn nên sử dụng truy vấn phụ thay vì tham gia ngay cả khi bạn kiểm tra sự tồn tại: nếu bạn kiểm tra NOT EXISTS. Một NOT EXISTSchiến thắng trên một LEFT OUTER JOIN vì nhiều lý do: sự phù hợp, không an toàn (trong trường hợp các cột có thể điều chỉnh được) và khả năng đọc. sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Tim Schmelter

16

Chạy trên một cơ sở dữ liệu rất lớn từ một CMS Mambo cũ:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 giây

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 giây

Một EXPLAIN cho thấy rằng họ kiểm tra chính xác số lượng hàng, nhưng một mất 3 giây và một hàng gần như ngay lập tức. Đạo đức của câu chuyện? Nếu hiệu suất là quan trọng (khi không phải là nó?), Hãy thử nhiều cách và xem cách nào là nhanh nhất.

Và ...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 giây

Một lần nữa, cùng kết quả, cùng một số hàng được kiểm tra. Tôi đoán là DISTINCT mos_content.catid mất nhiều thời gian hơn để tìm ra so với DISTINCT mos_c loại.id.


1
Tôi muốn biết nhiều hơn về những gì bạn đang cố gắng chỉ ra trong dòng cuối cùng "Tôi đoán là DISTINCT mos_content.catid mất nhiều thời gian hơn để tìm ra so với DISTINCT mos_c chuyên.id." . Bạn đang nói rằng một id chỉ nên được đặt tên idvà không được đặt tên như thế catidnào? Cố gắng tối ưu hóa truy cập db của tôi và việc học của bạn có thể giúp ích.
bool.dev

2
sử dụng SQL IN trong trường hợp đó là một thực tiễn tồi và nó không chứng minh được điều gì.
Uğur Gümüşhan

15

Theo quan sát của tôi như hai trường hợp, nếu một bảng có ít hơn 100.000 bản ghi thì phép nối sẽ hoạt động nhanh.

Nhưng trong trường hợp một bảng có hơn 100.000 bản ghi thì một truy vấn con là kết quả tốt nhất.

Tôi có một bảng có 500.000 bản ghi mà tôi đã tạo bên dưới truy vấn và thời gian kết quả của nó là như thế

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Kết quả: 13,3 giây

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Kết quả: 1,65 giây


Tôi đồng ý, đôi khi phá vỡ truy vấn cũng hoạt động, khi bạn có hàng triệu bản ghi, bạn không muốn sử dụng các phép nối vì chúng tồn tại mãi mãi. Thay vì xử lý nó trong mã và bản đồ trong mã là tốt hơn.
dùng1735921

1
Liên kết các liên kết của bạn không hoạt động đủ nhanh, bạn có thể thiếu một chỉ mục. Phân tích truy vấn có thể khá hữu ích trong việc so sánh hiệu suất thực tế.
số.aaron

Tôi đồng ý với Ajay Gajera, tôi đã thấy điều này cho chính mình.
dùng1735921

14
Làm thế nào có ý nghĩa để so sánh hiệu suất của hai truy vấn trả về kết quả khác nhau?
Paul Spiegel

Vâng, đó là những truy vấn khác nhau nhưng trả về cùng một kết quả
king neo

12

Các truy vấn con thường được sử dụng để trả về một hàng đơn dưới dạng giá trị nguyên tử, mặc dù chúng có thể được sử dụng để so sánh các giá trị với nhiều hàng với từ khóa IN. Chúng được cho phép tại gần như bất kỳ điểm có ý nghĩa nào trong câu lệnh SQL, bao gồm danh sách đích, mệnh đề WHERE, v.v. Một truy vấn phụ đơn giản có thể được sử dụng làm điều kiện tìm kiếm. Ví dụ: giữa một cặp bảng:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Lưu ý rằng việc sử dụng toán tử giá trị bình thường trên kết quả của truy vấn phụ yêu cầu chỉ phải trả về một trường. Nếu bạn muốn kiểm tra sự tồn tại của một giá trị trong một tập hợp các giá trị khác, hãy sử dụng IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Điều này rõ ràng khác với việc nói TRÁI-THAM GIA khi bạn chỉ muốn tham gia công cụ từ bảng A và B ngay cả khi điều kiện tham gia không tìm thấy bất kỳ bản ghi khớp nào trong bảng B, v.v.

Nếu bạn chỉ lo lắng về tốc độ, bạn sẽ phải kiểm tra với cơ sở dữ liệu của mình và viết một truy vấn tốt và xem liệu có sự khác biệt đáng kể nào về hiệu suất.


11

Phiên bản MySQL: 5.5.28-0ubfox0.12.04.2-log

Tôi cũng có ấn tượng rằng THAM GIA luôn tốt hơn một truy vấn phụ trong MySQL, nhưng GIẢI THÍCH là một cách tốt hơn để đưa ra đánh giá. Dưới đây là một ví dụ trong đó các truy vấn phụ hoạt động tốt hơn THAM GIA.

Đây là truy vấn của tôi với 3 truy vấn phụ:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

GIẢI THÍCH cho thấy:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

Truy vấn tương tự với THAM GIA là:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

và đầu ra là:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Một so sánh của rowscột cho biết sự khác biệt và truy vấn với THAM GIA đang sử dụng Using temporary; Using filesort.

Tất nhiên khi tôi chạy cả hai truy vấn, thì truy vấn đầu tiên được thực hiện trong 0,02 giây, lần thứ hai không hoàn thành ngay cả sau 1 phút, vì vậy GIẢI THÍCH đã giải thích chính xác các truy vấn này.

Nếu tôi không có INNER THAM GIA trên list_tagbàn tức là nếu tôi xóa

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

từ truy vấn đầu tiên và tương ứng:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

từ truy vấn thứ hai, sau đó EXPLAIN trả về cùng một số hàng cho cả hai truy vấn và cả hai truy vấn này đều chạy nhanh như nhau.


Tôi có tình huống tương tự, nhưng với nhiều người tham gia hơn bạn, sẽ cố gắng giải thích một lần
pahnin

Trong Oracle hoặc PostgreSQL tôi đã thử: VÀ KHÔNG EXISTS (CHỌN 1 TỪ list_tag WHERE list_id = l.list_id VÀ tag_id trong (43, 55, 246403))
David Aldridge

11

Các truy vấn con có khả năng tính toán các hàm tổng hợp khi đang bay. Ví dụ: Tìm giá tối thiểu của cuốn sách và nhận tất cả các sách được bán với giá này. 1) Sử dụng các truy vấn con:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) sử dụng THAM GIA

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;

Một trường hợp khác: nhiều GROUP BYs với các bảng khác nhau: stackoverflow.com/questions/11415284/ Truy vấn con dường như hoàn toàn tổng quát hơn. Xem thêm người đàn ông MySQL: dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
Ciro Santilli 病毒 审查 六四

6
-1 Điều này gây hiểu lầm khi bạn đang sử dụng truy vấn con và tham gia cả hai ví dụ. Rằng bạn đã rút truy vấn con ra một truy vấn thứ hai để xác định giá đặt hàng thấp nhất không có hiệu lực vì cơ sở dữ liệu sẽ thực hiện chính xác điều tương tự. Ngoài ra, bạn không viết lại việc tham gia bằng cách sử dụng truy vấn con; cả hai truy vấn sử dụng một tham gia. Bạn đúng khi cho rằng các truy vấn con cho phép chức năng tổng hợp, nhưng ví dụ này không chứng minh thực tế đó.
David Harkness

Tôi đồng ý với David và bạn có thể sử dụng nhóm để có được mức giá tối thiểu.
dùng1735921

9
  • Một quy tắc chung là tham gia nhanh hơn trong hầu hết các trường hợp (99%).
  • Các bảng dữ liệu càng có nhiều, các truy vấn con chậm hơn.
  • Các bảng dữ liệu ít hơn, các truy vấn con có tốc độ tương đương như tham gia .
  • Các truy vấn con đơn giản hơn, dễ hiểu hơn và dễ đọc hơn.
  • Hầu hết các khung web và ứng dụng cũng như các "ORM" và "Bản ghi hoạt động" của chúng tạo ra các truy vấn với các truy vấn con , bởi vì với các truy vấn con sẽ dễ phân chia trách nhiệm hơn, duy trì mã, v.v.
  • Đối với các trang web nhỏ hơn hoặc truy vấn ứng dụng là OK, nhưng đối với các trang web và ứng dụng lớn hơn, bạn thường sẽ phải viết lại các truy vấn đã tạo để tham gia truy vấn, đặc biệt nếu một truy vấn sử dụng nhiều truy vấn phụ trong truy vấn.

Một số người nói "một số RDBMS có thể viết lại một subquery để một tham gia hoặc tham gia vào một subquery khi nó nghĩ rằng một là nhanh hơn so với người kia.", Nhưng tuyên bố này áp dụng đối với các trường hợp đơn giản, chắc chắn không cho các truy vấn phức tạp với các truy vấn con đó thực sự gây ra một vấn đề trong hiệu suất.


> nhưng tuyên bố này áp dụng cho các trường hợp đơn giản Tôi hiểu rằng đó là một trường hợp đơn giản có thể được viết lại thành "THAM GIA" bởi RDBMS, hoặc đó là một trường hợp phức tạp mà các truy vấn con phù hợp ở đây. :-) Điểm hay trên ORM. Tôi nghĩ rằng điều này có tác động lớn nhất.
pilat

4

Sự khác biệt chỉ được nhìn thấy khi bảng tham gia thứ hai có nhiều dữ liệu hơn đáng kể so với bảng chính. Tôi đã có một trải nghiệm như dưới đây ...

Chúng tôi đã có một bảng người dùng gồm một trăm nghìn mục và dữ liệu thành viên của họ (tình bạn) khoảng 3 trăm nghìn mục. Đó là một tuyên bố tham gia để lấy bạn bè và dữ liệu của họ, nhưng với một sự chậm trễ lớn. Nhưng nó đã hoạt động tốt khi chỉ có một lượng nhỏ dữ liệu trong bảng thành viên. Khi chúng tôi thay đổi nó để sử dụng truy vấn phụ, nó hoạt động tốt.

Nhưng trong thời gian đó, các truy vấn nối đang hoạt động với các bảng khác có ít mục hơn bảng chính.

Vì vậy, tôi nghĩ rằng các câu lệnh truy vấn nối và phụ đang hoạt động tốt và nó phụ thuộc vào dữ liệu và tình huống.


3

Ngày nay, nhiều dbs có thể tối ưu hóa các truy vấn con và tham gia. Vì vậy, bạn chỉ cần kiểm tra truy vấn của bạn bằng cách giải thích và xem cái nào nhanh hơn. Nếu không có nhiều khác biệt về hiệu suất, tôi thích sử dụng truy vấn con vì chúng đơn giản và dễ hiểu hơn.


1

Tôi chỉ nghĩ về cùng một vấn đề, nhưng tôi đang sử dụng truy vấn con trong phần TỪ. Tôi cần kết nối và truy vấn từ các bảng lớn, bảng "nô lệ" có 28 triệu bản ghi nhưng kết quả chỉ là 128 nên kết quả rất nhỏ dữ liệu lớn! Tôi đang sử dụng hàm MAX () trên nó.

Đầu tiên tôi đang sử dụng LEFT THAM GIA vì tôi nghĩ đó là cách chính xác, mysql có thể tối ưu hóa, v.v. Lần thứ hai chỉ để thử nghiệm, tôi viết lại để chọn phụ so với THAM GIA.

Thời gian chạy TRỰC TIẾP: 1.12 giây Thời gian chạy SUB-SELECT: 0,06 giây

Đăng ký nhanh hơn 18 lần so với tham gia! Chỉ cần trong chokito adv. Subelect có vẻ khủng khiếp nhưng kết quả ...


-1

Nếu bạn muốn tăng tốc truy vấn của mình bằng cách sử dụng tham gia:

Đối với "tham gia / tham gia bên trong", Đừng sử dụng điều kiện thay vì sử dụng nó trong điều kiện "BẬT". Ví dụ:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Đối với "Tham gia trái / phải", không sử dụng trong điều kiện "BẬT", bởi vì nếu bạn sử dụng nối trái / phải, nó sẽ nhận được tất cả các hàng cho bất kỳ một bảng nào. Vì vậy, không sử dụng sử dụng trong "Bật". Vì vậy, hãy thử sử dụng điều kiện "Ở đâu"


Điều này phụ thuộc vào máy chủ SQL và vào độ phức tạp của truy vấn. Rất nhiều triển khai SQL sẽ tối ưu hóa các truy vấn đơn giản như thế này để có hiệu suất tốt nhất. Có lẽ cung cấp một tên máy chủ ví dụ và phiên bản nơi hành vi này xảy ra để cải thiện câu trả lời?
Trendfischer
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.