Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Các nguyên tắc cơ bản về biểu thức bảng, Phần 2 - Các bảng có nguồn gốc, cân nhắc logic

Tháng trước, tôi đã cung cấp nền tảng cho biểu thức bảng trong T-SQL. Tôi đã giải thích ngữ cảnh từ lý thuyết quan hệ và tiêu chuẩn SQL. Tôi đã giải thích cách một bảng trong SQL là một nỗ lực để biểu diễn một mối quan hệ từ lý thuyết quan hệ. Tôi cũng đã giải thích rằng một biểu thức quan hệ là một biểu thức hoạt động trên một hoặc nhiều quan hệ làm đầu vào và dẫn đến một quan hệ. Tương tự, trong SQL, một biểu thức bảng là một biểu thức hoạt động trên một hoặc nhiều bảng đầu vào và dẫn đến một bảng. Biểu thức có thể là một truy vấn, nhưng không nhất thiết phải như vậy. Ví dụ:biểu thức có thể là một hàm tạo giá trị bảng, như tôi sẽ giải thích ở phần sau của bài viết này. Tôi cũng giải thích rằng trong loạt bài này, tôi tập trung vào bốn loại biểu thức bảng được đặt tên cụ thể mà T-SQL hỗ trợ:bảng dẫn xuất, biểu thức bảng chung (CTE), chế độ xem và hàm giá trị bảng nội tuyến (TVF).

Nếu bạn đã làm việc với T-SQL một thời gian, bạn có thể gặp phải một số trường hợp trong đó bạn phải sử dụng biểu thức bảng hoặc bằng cách nào đó nó thuận tiện hơn so với các giải pháp thay thế không sử dụng chúng. Dưới đây chỉ là một số ví dụ cho các trường hợp sử dụng mà bạn cần lưu ý:

  • Tạo một giải pháp mô-đun bằng cách chia nhỏ các nhiệm vụ phức tạp thành các bước, mỗi tác vụ được biểu thị bằng một biểu thức bảng khác nhau.
  • Kết hợp các kết quả của các truy vấn được nhóm và chi tiết, trong trường hợp bạn quyết định không sử dụng các chức năng cửa sổ cho mục đích này.
  • Xử lý truy vấn logic xử lý các mệnh đề truy vấn theo thứ tự sau:FROM> WHERE> GROUP BY> HAVING> SELECT> ORDER BY. Do đó, trong cùng một mức lồng, bí danh cột mà bạn xác định trong mệnh đề CHỌN chỉ khả dụng cho mệnh đề ORDER BY. Chúng không có sẵn cho phần còn lại của các mệnh đề truy vấn. Với biểu thức bảng, bạn có thể sử dụng lại các bí danh mà bạn xác định trong truy vấn bên trong trong bất kỳ mệnh đề nào của truy vấn bên ngoài và bằng cách này, tránh lặp lại các biểu thức dài / phức tạp.
  • Các hàm cửa sổ chỉ có thể xuất hiện trong mệnh đề SELECT và ORDER BY của truy vấn. Với biểu thức bảng, bạn có thể gán bí danh cho một biểu thức dựa trên một hàm cửa sổ, sau đó sử dụng bí danh đó trong một truy vấn đối với biểu thức bảng.
  • Một toán tử PIVOT liên quan đến ba yếu tố:nhóm, phân tán và tổng hợp. Toán tử này xác định phần tử nhóm một cách ngầm định bằng cách loại bỏ. Bằng cách sử dụng biểu thức bảng, bạn có thể chiếu chính xác ba phần tử được cho là có liên quan và truy vấn bên ngoài sử dụng biểu thức bảng làm bảng đầu vào của toán tử PIVOT, do đó kiểm soát phần tử nào là phần tử nhóm.
  • Các sửa đổi với TOP không hỗ trợ điều khoản ORDER BY. Bạn có thể kiểm soát hàng nào được chọn gián tiếp bằng cách xác định biểu thức bảng dựa trên truy vấn CHỌN với bộ lọc TOP hoặc OFFSET-FETCH và mệnh đề ORDER BY, đồng thời áp dụng sửa đổi đối với biểu thức bảng.

Đây không phải là một danh sách đầy đủ. Tôi sẽ trình bày một số trường hợp sử dụng ở trên và những trường hợp khác trong loạt bài này. Tôi chỉ muốn đề cập đến một số trường hợp sử dụng ở đây để minh họa tầm quan trọng của các biểu thức bảng trong mã T-SQL của chúng tôi và lý do tại sao việc đầu tư vào việc hiểu rõ các nguyên tắc cơ bản của chúng là điều đáng giá.

Trong bài viết của tháng này, tôi tập trung vào việc xử lý hợp lý các bảng dẫn xuất một cách cụ thể.

Trong các ví dụ của mình, tôi sẽ sử dụng cơ sở dữ liệu mẫu có tên là TSQLV5. Bạn có thể tìm thấy tập lệnh tạo và điền nó tại đây và sơ đồ ER của nó tại đây.

Các bảng có nguồn gốc

Thuật ngữ bảng dẫn xuất được sử dụng trong SQL và T-SQL với nhiều hơn một ý nghĩa. Vì vậy, trước tiên tôi muốn nói rõ tôi đang đề cập đến cái nào trong bài viết này. Tôi đang đề cập đến một cấu trúc ngôn ngữ cụ thể mà bạn thường xác định, nhưng không chỉ, trong mệnh đề FROM của một truy vấn bên ngoài. Tôi sẽ sớm cung cấp cú pháp cho cấu trúc này.

Việc sử dụng thuật ngữ bảng dẫn xuất tổng quát hơn trong SQL là đối chứng với quan hệ dẫn xuất từ ​​lý thuyết quan hệ. Quan hệ dẫn xuất là quan hệ kết quả được suy ra từ một hoặc nhiều quan hệ cơ sở đầu vào, bằng cách áp dụng các toán tử quan hệ từ đại số quan hệ như phép chiếu, giao điểm và các phép toán khác cho các quan hệ cơ sở đó. Tương tự, theo nghĩa chung, bảng dẫn xuất trong SQL là bảng kết quả được dẫn xuất từ ​​một hoặc nhiều bảng cơ sở, bằng cách đánh giá các biểu thức dựa trên các bảng cơ sở đầu vào đó.

Ngoài ra, tôi đã kiểm tra cách tiêu chuẩn SQL định nghĩa một bảng cơ sở và ngay lập tức xin lỗi vì tôi đã làm phiền.

4.15.2 Bảng cơ sở

Bảng cơ sở là một bảng cơ sở liên tục hoặc một bảng tạm thời.

Bảng cơ sở liên tục là một bảng cơ sở ổn định thông thường hoặc một bảng được tạo phiên bản hệ thống.

Bảng cơ sở thông thường là một bảng cơ sở ổn định thông thường hoặc một bảng tạm thời. ”

Đã thêm vào đây mà không có thêm bình luận nào…

Trong T-SQL, bạn có thể tạo bảng cơ sở bằng câu lệnh CREATE TABLE, nhưng vẫn có các tùy chọn khác, ví dụ:CHỌN VÀO VÀ KHAI BÁO @T AS TABLE.

Dưới đây là định nghĩa của tiêu chuẩn cho các bảng dẫn xuất theo nghĩa chung:

4.15.3 Bảng có nguồn gốc

Bảng dẫn xuất là một bảng được dẫn xuất trực tiếp hoặc gián tiếp từ một hoặc nhiều bảng khác bằng cách đánh giá một biểu thức, chẳng hạn như , , hoặc . Một có thể chứa một tùy chọn. Thứ tự của các hàng trong bảng được chỉ định bởi chỉ được đảm bảo cho chứa ngay . ”

Có một số điều thú vị cần lưu ý ở đây về các bảng dẫn xuất theo nghĩa chung. Người ta phải làm gì với nhận xét về việc đặt hàng. Tôi sẽ nói đến vấn đề này sau trong bài viết. Một điều khác là bảng dẫn xuất trong SQL có thể là một biểu thức bảng độc lập hợp lệ, nhưng không nhất thiết phải như vậy. Ví dụ:biểu thức sau đại diện cho một bảng dẫn xuất và is cũng được coi là một biểu thức bảng độc lập hợp lệ (bạn có thể chạy nó):

SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'

Ngược lại, biểu thức sau đại diện cho một bảng dẫn xuất, nhưng không phải một biểu thức bảng độc lập hợp lệ:

T1 INNER JOIN T2
  ON T1.keycol = T2.keycol

T-SQL hỗ trợ một số toán tử bảng mang lại bảng dẫn xuất, nhưng không được hỗ trợ dưới dạng biểu thức độc lập. Đó là:THAM GIA, PIVOT, UNPIVOT và ÁP DỤNG. Bạn cần một mệnh đề để chúng hoạt động bên trong (thường là FROM, nhưng cũng có thể là mệnh đề SỬ DỤNG của câu lệnh MERGE) và một truy vấn máy chủ.

Kể từ đây, tôi sẽ sử dụng thuật ngữ bảng dẫn xuất để mô tả cấu trúc ngôn ngữ cụ thể hơn chứ không phải theo nghĩa chung được mô tả ở trên.

Cú pháp

Một bảng dẫn xuất có thể được định nghĩa như một phần của câu lệnh SELECT bên ngoài trong mệnh đề FROM của nó. Nó cũng có thể được định nghĩa là một phần của câu lệnh DELETE và UPDATE trong mệnh đề FROM của chúng, và như một phần của câu lệnh MERGE trong mệnh đề USING của nó. Tôi sẽ cung cấp thêm chi tiết về cú pháp khi được sử dụng trong các câu lệnh sửa đổi ở phần sau của bài viết này.

Đây là cú pháp cho truy vấn SELECT đơn giản đối với bảng dẫn xuất:

CHỌN
TỪ () [AS] [()] ;

Định nghĩa bảng dẫn xuất xuất hiện khi một bảng cơ sở thường có thể xuất hiện, trong mệnh đề FROM của truy vấn bên ngoài. Nó có thể là đầu vào cho toán tử bảng như THAM GIA, ÁP DỤNG, PIVOT và UNPIVOT. Khi được sử dụng làm đầu vào phù hợp cho toán tử ÁP DỤNG, phần của bảng dẫn xuất được phép có mối tương quan với các cột từ bảng bên ngoài (thêm về điều này trong một bài viết chuyên dụng trong tương lai của loạt bài này). Nếu không, biểu thức bảng phải là độc lập.

Câu lệnh bên ngoài có thể có tất cả các phần tử truy vấn thông thường. Trong trường hợp câu lệnh SELECT:WHERE, GROUP BY, HAVING, ORDER BY và như đã đề cập, các toán tử bảng trong mệnh đề FROM.

Dưới đây là một ví dụ cho một truy vấn đơn giản đối với một bảng dẫn xuất đại diện cho khách hàng Hoa Kỳ:

SELECT custid, companyname
FROM ( SELECT custid, companyname
       FROM Sales.Customers
       WHERE country = N'USA' ) AS UC;

Truy vấn này tạo ra kết quả sau:

custid  companyname
------- ---------------
32      Customer YSIQX
36      Customer LVJSO
43      Customer UISOJ
45      Customer QXPPT
48      Customer DVFMB
55      Customer KZQZT
65      Customer NYUHS
71      Customer LCOUJ
75      Customer XOJYP
77      Customer LCYBZ
78      Customer NLTYP
82      Customer EYHKM
89      Customer YBQTI

Có ba phần chính cần xác định trong một câu lệnh liên quan đến định nghĩa bảng dẫn xuất:

  1. Biểu thức bảng (truy vấn bên trong)
  2. Tên bảng dẫn xuất, hay chính xác hơn, trong lý thuyết quan hệ được coi là biến phạm vi
  3. Câu lệnh bên ngoài

Biểu thức bảng được cho là đại diện cho một bảng và như vậy, phải đáp ứng các yêu cầu nhất định mà một truy vấn thông thường không nhất thiết phải đáp ứng. Tôi sẽ cung cấp chi tiết ngay trong phần “Biểu thức bảng là một bảng”.

Đối với tên bảng dẫn xuất đích; một giả định phổ biến giữa các nhà phát triển T-SQL là nó chỉ đơn thuần là tên hoặc bí danh mà bạn gán cho bảng đích. Tương tự, hãy xem xét truy vấn sau:

SELECT custid, companyname
FROM Sales.Customers AS C
WHERE country = N'USA';

Cũng ở đây, giả định phổ biến là AS C chỉ là một cách để đổi tên, hoặc bí danh, bảng Khách hàng cho mục đích của truy vấn này, bắt đầu với bước xử lý truy vấn logic nơi tên được chỉ định trở đi. Tuy nhiên, từ quan điểm của lý thuyết quan hệ, có một ý nghĩa sâu sắc hơn đối với những gì C đại diện. C là những gì được gọi là một biến phạm vi. C là một biến quan hệ dẫn xuất có phạm vi trên các bộ giá trị trong biến quan hệ đầu vào Khách hàng. Trong ví dụ trên, C phạm vi trên các bộ giá trị trong Khách hàng và đánh giá vị từ quốc gia =N'USA '. Các bộ giá trị mà vị từ đánh giá là true trở thành một phần của quan hệ kết quả C.

Biểu thức bảng là một bảng

Với thông tin cơ bản mà tôi đã cung cấp cho đến nay, điều tôi sắp giải thích tiếp theo sẽ khiến bạn không khỏi ngạc nhiên. một phần của định nghĩa bảng dẫn xuất là một bảng . Đó là trường hợp ngay cả khi nó được thể hiện dưới dạng một truy vấn. Hãy nhớ thuộc tính đóng của đại số quan hệ? Điều tương tự cũng áp dụng cho phần còn lại của các biểu thức bảng được đặt tên nói trên (CTE, chế độ xem và TVF nội tuyến). Như bạn đã học, bảng của SQL là bản đối chiếu với mối quan hệ của lý thuyết quan hệ , mặc dù không phải là một đối tác hoàn hảo. Do đó, một biểu thức bảng cần phải đáp ứng các yêu cầu nhất định để đảm bảo rằng kết quả là một bảng — những yêu cầu mà truy vấn không được sử dụng làm biểu thức bảng không nhất thiết phải có. Dưới đây là ba yêu cầu cụ thể:

  • Tất cả các cột của biểu thức bảng phải có tên
  • Tất cả các tên cột của biểu thức bảng phải là duy nhất
  • Các hàng của biểu thức bảng không có thứ tự

Hãy chia nhỏ từng yêu cầu này, thảo luận về mức độ phù hợp với cả lý thuyết quan hệ và SQL.

Tất cả các cột phải có tên

Hãy nhớ rằng một quan hệ có một tiêu đề và một phần thân. Đầu đề của một quan hệ là một tập hợp các thuộc tính (cột trong SQL). Thuộc tính có tên và tên kiểu, được xác định bằng tên của nó. Truy vấn không được sử dụng làm biểu thức bảng không nhất thiết phải gán tên cho tất cả các cột mục tiêu. Hãy xem xét truy vấn sau đây làm ví dụ:

SELECT empid, firstname, lastname,
  CONCAT_WS(N'/', country, region, city)
FROM HR.Employees;

Truy vấn này tạo ra kết quả sau:

empid  firstname  lastname   (No column name)
------ ---------- ---------- -----------------
1      Sara       Davis      USA/WA/Seattle
2      Don        Funk       USA/WA/Tacoma
3      Judy       Lew        USA/WA/Kirkland
4      Yael       Peled      USA/WA/Redmond
5      Sven       Mortensen  UK/London
6      Paul       Suurs      UK/London
7      Russell    King       UK/London
8      Maria      Cameron    USA/WA/Seattle
9      Patricia   Doyle      UK/London

Kết quả truy vấn có một cột ẩn danh là kết quả của việc ghép các thuộc tính vị trí bằng cách sử dụng hàm CONCAT_WS. (Nhân tiện, chức năng này đã được thêm vào SQL Server 2017, vì vậy nếu bạn đang chạy mã trong phiên bản cũ hơn, vui lòng thay thế tính toán này bằng một tính toán thay thế mà bạn chọn.) Do đó, truy vấn này không trả về một bảng, không phải để nói về một mối quan hệ. Do đó, sẽ không hợp lệ khi sử dụng truy vấn như biểu thức bảng / phần truy vấn bên trong của định nghĩa bảng dẫn xuất.

Hãy thử nó:

SELECT *
FROM ( SELECT empid, firstname, lastname,
         CONCAT_WS(N'/', country, region, city)
       FROM HR.Employees ) AS D;

Bạn gặp lỗi sau:

Msg 8155, Mức 16, Trạng thái 2, Dòng 50
Không có tên cột nào được chỉ định cho cột 4 của 'D'.

Ngoài ra, hãy để ý điều gì thú vị về thông báo lỗi? Nó phàn nàn về cột 4, làm nổi bật sự khác biệt giữa các cột trong SQL và các thuộc tính trong lý thuyết quan hệ.

Tất nhiên, giải pháp là đảm bảo rằng bạn chỉ định rõ ràng tên cho các cột là kết quả của các phép tính. T-SQL hỗ trợ khá nhiều kỹ thuật đặt tên cột. Tôi sẽ đề cập đến hai trong số họ.

Bạn có thể sử dụng kỹ thuật đặt tên nội dòng trong đó bạn chỉ định tên cột mục tiêu sau khi tính toán và một mệnh đề AS tùy chọn, như trong < expression > [ AS ] < column name > , như vậy:

SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
         CONCAT_WS(N'/', country, region, city) AS custlocation
       FROM HR.Employees ) AS D;

Truy vấn này tạo ra kết quả sau:

empid  firstname  lastname   custlocation
------ ---------- ---------- ----------------
1      Sara       Davis      USA/WA/Seattle
2      Don        Funk       USA/WA/Tacoma
3      Judy       Lew        USA/WA/Kirkland
4      Yael       Peled      USA/WA/Redmond
5      Sven       Mortensen  UK/London
6      Paul       Suurs      UK/London
7      Russell    King       UK/London
8      Maria      Cameron    USA/WA/Seattle
9      Patricia   Doyle      UK/London

Sử dụng kỹ thuật này, rất dễ dàng khi xem lại mã để biết tên cột mục tiêu nào được gán cho biểu thức nào. Ngoài ra, bạn chỉ cần đặt tên cho các cột chưa có tên.

Bạn cũng có thể sử dụng kỹ thuật đặt tên cột bên ngoài hơn trong đó bạn chỉ định tên cột mục tiêu trong dấu ngoặc đơn ngay sau tên bảng dẫn xuất, như sau:

SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
         CONCAT_WS(N'/', country, region, city)
       FROM HR.Employees ) AS D(empid, firstname, lastname, custlocation);

Với kỹ thuật này, bạn phải liệt kê tên cho tất cả các cột — kể cả những cột đã có tên. Việc gán tên cột mục tiêu được thực hiện theo vị trí, từ trái sang phải, tức là, tên cột mục tiêu đầu tiên đại diện cho biểu thức đầu tiên trong danh sách SELECT của truy vấn bên trong; tên cột mục tiêu thứ hai đại diện cho biểu thức thứ hai; và như thế.

Lưu ý rằng trong trường hợp có sự mâu thuẫn giữa tên cột bên trong và cột bên ngoài, chẳng hạn như do lỗi trong mã, phạm vi của các tên bên trong là truy vấn bên trong — hoặc chính xác hơn là biến phạm vi bên trong (ở đây ngầm hiểu là HR. AS Nhân viên) —và phạm vi của các tên bên ngoài là biến phạm vi bên ngoài (trong trường hợp của chúng tôi là D). Có một chút liên quan đến việc xác định phạm vi tên cột liên quan đến xử lý truy vấn logic, nhưng đó là một mục cho các cuộc thảo luận sau này.

Khả năng có lỗi với cú pháp đặt tên bên ngoài được giải thích tốt nhất bằng một ví dụ.

Kiểm tra kết quả đầu ra của truy vấn trước đó, với toàn bộ nhân viên từ bảng HR.E Employees. Sau đó, hãy xem xét truy vấn sau và trước khi chạy, hãy cố gắng tìm ra những nhân viên mà bạn mong đợi sẽ thấy trong kết quả:

SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
         CONCAT_WS(N'/', country, region, city)
       FROM HR.Employees
       WHERE lastname LIKE N'D%' ) AS D(empid, lastname, firstname, custlocation)
WHERE firstname LIKE N'D%';

Nếu bạn mong đợi truy vấn trả về một tập hợp trống cho dữ liệu mẫu đã cho, vì không có nhân viên nào hiện có cả họ và tên bắt đầu bằng chữ D, bạn đang thiếu lỗi trong mã.

Bây giờ hãy chạy truy vấn và kiểm tra đầu ra thực tế:

empid  firstname  lastname  custlocation
------ ---------- --------- ---------------
1      Davis      Sara      USA/WA/Seattle
9      Doyle      Patricia  UK/London

Điều gì đã xảy ra?

Truy vấn bên trong chỉ định họ là cột thứ hai và họ là cột thứ ba trong danh sách CHỌN. Mã chỉ định tên cột mục tiêu của bảng dẫn xuất trong truy vấn bên ngoài chỉ định họ thứ hai và họ thứ ba. Tên mã đặt tên là họ và họ dưới dạng tên đầu tiên trong biến phạm vi D. Một cách hiệu quả, bạn chỉ lọc những nhân viên có họ bắt đầu bằng chữ D. Bạn không lọc những nhân viên có cả họ và tên bắt đầu với chữ D.

Cú pháp răng cưa nội dòng không dễ mắc phải những lỗi như vậy. Đối với một, bạn thường không đặt bí danh cho một cột đã có tên mà bạn hài lòng. Thứ hai, ngay cả khi bạn muốn chỉ định một bí danh khác cho một cột đã có tên, thì rất có thể với cú pháp AS , bạn sẽ chỉ định sai bí danh. Hãy suy nghĩ về nó; khả năng bạn sẽ viết như thế này là bao nhiêu:

SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid AS empid, firstname AS lastname, lastname AS firstname,
         CONCAT_WS(N'/', country, region, city) AS custlocation
       FROM HR.Employees
       WHERE lastname LIKE N'D%' ) AS D
WHERE firstname LIKE N'D%';

Rõ ràng là không có nhiều khả năng.

Tất cả các tên cột phải là duy nhất

Quay lại thực tế là tiêu đề của một quan hệ là một tập hợp các thuộc tính và cho rằng một thuộc tính được xác định bằng tên, các tên thuộc tính phải là duy nhất cho cùng một quan hệ. Trong một truy vấn nhất định, bạn luôn có thể tham chiếu đến một thuộc tính bằng cách sử dụng tên gồm hai phần với tên biến phạm vi làm định tính, như trong . . Khi tên cột không có định nghĩa rõ ràng, bạn có thể bỏ qua tiền tố tên biến phạm vi. Điều quan trọng cần nhớ là điều tôi đã nói trước đó về phạm vi của tên cột. Trong mã liên quan đến biểu thức bảng được đặt tên, với cả truy vấn bên trong (biểu thức bảng) và truy vấn bên ngoài, phạm vi tên cột trong truy vấn bên trong là các biến phạm vi bên trong và phạm vi tên cột ở bên ngoài truy vấn là các biến phạm vi bên ngoài. Nếu truy vấn bên trong liên quan đến nhiều bảng nguồn có cùng tên cột, bạn vẫn có thể tham chiếu đến các cột đó theo cách rõ ràng bằng cách thêm tên biến phạm vi làm tiền tố. Nếu bạn không chỉ định tên biến phạm vi một cách rõ ràng, bạn sẽ được gán một cách ngầm định, như thể bạn đã sử dụng AS .

Hãy xem xét truy vấn độc lập sau làm ví dụ:

SELECT C.custid, O.custid, O.orderid
FROM Sales.Customers AS C
  LEFT OUTER JOIN Sales.Orders AS O
    ON C.custid = O.custid;

Truy vấn này không bị lỗi với lỗi tên cột trùng lặp vì một cột custid thực sự được đặt tên là C.custid và O.custid khác trong phạm vi của truy vấn hiện tại. Truy vấn này tạo ra kết quả sau:

custid      custid      orderid
----------- ----------- -----------
1           1           10643
1           1           10692
1           1           10702
1           1           10835
1           1           10952
1           1           11011
2           2           10308
2           2           10625
2           2           10759
2           2           10926
...

Tuy nhiên, hãy thử sử dụng truy vấn này dưới dạng biểu thức bảng trong định nghĩa của bảng dẫn xuất có tên CO, như sau:

SELECT *
FROM ( SELECT C.custid, O.custid, O.orderid
       FROM Sales.Customers AS C
         LEFT OUTER JOIN Sales.Orders AS O
           ON C.custid = O.custid ) AS CO;

Đối với truy vấn bên ngoài có liên quan, bạn có một biến phạm vi có tên là CO và phạm vi của tất cả các tên cột trong truy vấn bên ngoài là biến phạm vi đó. Tên của tất cả các cột trong một biến phạm vi nhất định (hãy nhớ rằng biến phạm vi là một biến quan hệ) phải là duy nhất. Do đó, bạn gặp lỗi sau:

Msg 8156, Mức 16, Trạng thái 1, Dòng 80
Cột 'custid' được chỉ định nhiều lần cho 'CO'.

Tất nhiên, cách khắc phục là gán các tên cột khác nhau cho hai cột custid trong phạm vi liên quan đến biến phạm vi CO, như sau:

SELECT *
FROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid
       FROM Sales.Customers AS C
         LEFT OUTER JOIN Sales.Orders AS O
           ON C.custid = O.custid ) AS CO;

Truy vấn này tạo ra kết quả sau:

custcustid  ordercustid orderid
----------- ----------- -----------
1           1           10643
1           1           10692
1           1           10702
1           1           10835
1           1           10952
1           1           11011
2           2           10308
2           2           10625
2           2           10759
2           2           10926
...

Nếu bạn làm theo các phương pháp hay, bạn sẽ liệt kê rõ ràng các tên cột trong danh sách CHỌN của truy vấn ngoài cùng. Vì chỉ có một biến phạm vi liên quan, bạn không phải sử dụng tên hai phần cho các tham chiếu cột bên ngoài. Nếu bạn muốn sử dụng tên hai phần, bạn đặt tiền tố tên cột bằng tên biến phạm vi bên ngoài CO, như sau:

SELECT CO.custcustid, CO.ordercustid, CO.orderid
FROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid
       FROM Sales.Customers AS C
         LEFT OUTER JOIN Sales.Orders AS O
           ON C.custid = O.custid ) AS CO;

Không có đơn đặt hàng

Có khá nhiều điều tôi phải nói về các biểu thức bảng được đặt tên và thứ tự — đủ cho một bài viết theo đúng nghĩa của nó — vì vậy tôi sẽ dành một bài viết trong tương lai cho chủ đề này. Tuy nhiên, tôi muốn đề cập đến chủ đề này một cách ngắn gọn vì nó rất quan trọng. Nhớ lại rằng phần thân của một quan hệ là một tập hợp các bộ giá trị và tương tự, phần nội dung của một bảng là một tập hợp các hàng. Một bộ không có thứ tự. Tuy nhiên, SQL cho phép truy vấn ngoài cùng có mệnh đề ORDER BY phục vụ ý nghĩa thứ tự bản trình bày, như truy vấn sau thể hiện:

SELECT orderid, val
FROM Sales.OrderValues
ORDER BY val DESC;

Tuy nhiên, điều bạn cần hiểu là kết quả truy vấn này không trả về một mối quan hệ. Ngay cả từ quan điểm của SQL, kết quả là truy vấn không trả về một bảng và do đó nó không phải được coi là một biểu thức bảng. Do đó, sẽ không hợp lệ nếu sử dụng truy vấn như vậy làm phần biểu thức bảng của định nghĩa bảng dẫn xuất.

Hãy thử chạy đoạn mã sau:

SELECT orderid, val
FROM ( SELECT orderid, val
       FROM Sales.OrderValues
       ORDER BY val DESC ) AS D;

Bạn gặp lỗi sau:

Msg 1033, Mức 15, Trạng thái 1, Dòng 124
Mệnh đề ORDER BY không hợp lệ trong các dạng xem, hàm nội tuyến, bảng dẫn xuất, truy vấn con và biểu thức bảng thông thường, trừ khi TOP, OFFSET hoặc FOR XML cũng được chỉ định.

Tôi sẽ giải quyết trừ khi một phần của thông báo lỗi ngay sau đó.

Nếu bạn muốn truy vấn ngoài cùng trả về kết quả có thứ tự, bạn cần chỉ định mệnh đề ORDER BY trong truy vấn ngoài cùng, như sau:

SELECT orderid, val
FROM ( SELECT orderid, val
       FROM Sales.OrderValues ) AS D
ORDER BY val DESC;

Đối với trừ khi một phần của thông báo lỗi; T-SQL hỗ trợ bộ lọc TOP độc quyền cũng như bộ lọc OFFSET-FETCH tiêu chuẩn. Cả hai bộ lọc đều dựa vào mệnh đề ORDER BY trong cùng một phạm vi truy vấn để xác định cho chúng những hàng trên cùng cần lọc. Rất tiếc, đây là kết quả của một cái bẫy trong thiết kế của các tính năng này, không tách thứ tự bản trình bày khỏi thứ tự bộ lọc. Điều đó có thể xảy ra, cả Microsoft với bộ lọc TOP và tiêu chuẩn với bộ lọc OFFSET-FETCH, đều cho phép chỉ định mệnh đề ORDER BY trong truy vấn bên trong miễn là nó cũng chỉ định bộ lọc TOP hoặc OFFSET-FETCH tương ứng. Vì vậy, truy vấn này hợp lệ, ví dụ:

SELECT orderid, val
FROM ( SELECT TOP (3) orderid, val
       FROM Sales.OrderValues
       ORDER BY val DESC ) AS D;

Khi tôi chạy truy vấn này trên hệ thống của mình, nó đã tạo ra kết quả sau:

orderid  val
-------- ---------
10865    16387.50
10981    15810.00
11030    12615.05

Tuy nhiên, điều quan trọng cần nhấn mạnh là lý do duy nhất mà mệnh đề ORDER BY được cho phép trong truy vấn bên trong là để hỗ trợ bộ lọc TOP. Đó là đảm bảo duy nhất mà bạn có được khi đặt hàng. Vì truy vấn bên ngoài cũng không có mệnh đề ORDER BY, nên bạn không nhận được đảm bảo cho bất kỳ thứ tự trình bày cụ thể nào từ truy vấn này, bất chấp hành vi được quan sát là gì. Đó là cả trường hợp trong T-SQL, cũng như trong tiêu chuẩn. Dưới đây là trích dẫn từ tiêu chuẩn giải quyết phần này:

“Thứ tự của các hàng trong bảng được chỉ định bởi chỉ được đảm bảo cho chứa ngay .”

Như đã đề cập, còn rất nhiều điều để nói về biểu thức bảng và thứ tự mà tôi sẽ thực hiện trong một bài viết trong tương lai. Tôi cũng sẽ cung cấp các ví dụ chứng minh việc thiếu mệnh đề ORDER BY trong truy vấn bên ngoài có nghĩa là bạn không nhận được bất kỳ đảm bảo nào về thứ tự bản trình bày.

Vì vậy, một biểu thức bảng, ví dụ:một truy vấn bên trong trong định nghĩa bảng dẫn xuất, là một bảng. Tương tự, bản thân một bảng dẫn xuất (theo nghĩa cụ thể) cũng là một bảng. Nó không phải là một cái bàn cơ bản, nhưng nó vẫn là một cái bàn. Điều tương tự cũng áp dụng cho CTE, lượt xem và TVF nội tuyến. Chúng không phải là các bảng cơ sở, đúng hơn là các bảng dẫn xuất (theo nghĩa chung hơn), nhưng chúng vẫn là các bảng.

Lỗi thiết kế

Các bảng có nguồn gốc có hai khuyết điểm chính trong thiết kế của chúng. Cả hai đều liên quan đến thực tế là bảng dẫn xuất được xác định trong mệnh đề FROM của truy vấn bên ngoài.

Một lỗ hổng thiết kế liên quan đến thực tế là nếu bạn cần truy vấn một bảng dẫn xuất từ ​​một truy vấn bên ngoài và đến lượt nó, sử dụng truy vấn đó làm biểu thức bảng trong một định nghĩa bảng dẫn xuất khác, bạn sẽ phải lồng các truy vấn bảng dẫn xuất đó. Trong máy tính, việc lồng mã rõ ràng liên quan đến nhiều cấp độ lồng nhau có xu hướng dẫn đến mã phức tạp khó bảo trì.

Dưới đây là một ví dụ rất cơ bản chứng minh điều này:

SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
       FROM ( SELECT YEAR(orderdate) AS orderyear, custid
              FROM Sales.Orders ) AS D1
       GROUP BY orderyear ) AS D2
WHERE numcusts > 70;

Mã này trả về số năm đặt hàng và số lượng khách hàng đã đặt hàng trong mỗi năm, chỉ đối với những năm có số lượng khách hàng đã đặt hàng nhiều hơn 70.

Động lực chính để sử dụng biểu thức bảng ở đây là để có thể tham chiếu đến bí danh cột nhiều lần. Truy vấn trong cùng được sử dụng làm biểu thức bảng cho bảng dẫn xuất D1 truy vấn bảng Sales.Orders và gán tên cột theo thứ tự cho biểu thức YEAR (orderdate), đồng thời trả về cột custid. Truy vấn đối với D1 nhóm các hàng từ D1 theo năm thứ tự và trả về năm thứ tự cũng như số lượng khác biệt của khách hàng đã đặt hàng trong năm được đề cập có bí danh là numcusts. Mã xác định một bảng dẫn xuất được gọi là D2 dựa trên truy vấn này. Truy vấn ngoài cùng hơn các truy vấn D2 và chỉ lọc những năm có số lượng khách hàng đã đặt hàng nhiều hơn 70.

Cố gắng xem lại mã này hoặc khắc phục sự cố trong trường hợp có vấn đề là khó khăn do có nhiều cấp lồng ghép. Thay vì xem lại mã theo cách tự nhiên hơn từ trên xuống dưới, bạn thấy mình phải phân tích nó bắt đầu với đơn vị trong cùng và dần dần ra bên ngoài, vì điều đó thực tế hơn.

Toàn bộ điểm về việc sử dụng các bảng dẫn xuất trong ví dụ này là đơn giản hóa mã bằng cách tránh sự cần thiết phải lặp lại các biểu thức. Nhưng tôi không chắc rằng giải pháp này đạt được mục tiêu này. Trong trường hợp này, có lẽ bạn nên lặp lại một số biểu thức, tránh phải sử dụng hoàn toàn các bảng dẫn xuất, như vậy:

SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts
FROM Sales.Orders
GROUP BY YEAR(orderdate)
HAVING COUNT(DISTINCT custid) > 70;

Hãy nhớ rằng tôi đang hiển thị một ví dụ rất đơn giản ở đây cho mục đích minh họa. Hãy tưởng tượng mã sản xuất với nhiều mức lồng ghép hơn và với mã dài hơn, phức tạp hơn và bạn có thể thấy cách nó trở nên phức tạp hơn đáng kể để duy trì.

Một lỗ hổng khác trong thiết kế các bảng dẫn xuất liên quan đến trường hợp bạn cần tương tác với nhiều phiên bản của cùng một bảng dẫn xuất. Hãy xem xét truy vấn sau đây làm ví dụ:

SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS CUR
  LEFT OUTER JOIN
     ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Mã này tính toán số lượng đơn đặt hàng được xử lý trong mỗi năm, cũng như sự khác biệt so với năm trước. Bỏ qua thực tế là có những cách đơn giản hơn để đạt được cùng một nhiệm vụ với các hàm cửa sổ — Tôi đang sử dụng mã này để minh họa một điểm nhất định, vì vậy bản thân nhiệm vụ và các cách khác nhau để giải quyết nó không có ý nghĩa.

Phép nối là một toán tử bảng coi hai đầu vào của nó như một tập hợp — nghĩa là không có thứ tự nào giữa chúng. Chúng được gọi là đầu vào bên trái và bên phải để bạn có thể đánh dấu một trong số chúng (hoặc cả hai) dưới dạng bảng được bảo toàn trong phép nối bên ngoài, nhưng vẫn không có đầu vào đầu tiên và thứ hai trong số đó. Bạn được phép sử dụng bảng dẫn xuất làm đầu vào nối, nhưng tên biến phạm vi mà bạn gán cho đầu vào bên trái không thể truy cập được trong định nghĩa của đầu vào bên phải. Đó là bởi vì cả hai đều được xác định về mặt khái niệm trong cùng một bước logic, như thể ở cùng một thời điểm. Do đó, khi kết hợp các bảng dẫn xuất, bạn không thể xác định hai biến phạm vi dựa trên một biểu thức bảng. Thật không may, bạn phải lặp lại mã, xác định hai biến phạm vi dựa trên hai bản sao giống hệt nhau của mã. Tất nhiên, điều này làm phức tạp khả năng bảo trì của mã và tăng khả năng xuất hiện lỗi. Mọi thay đổi bạn thực hiện đối với một biểu thức bảng cũng cần được áp dụng cho biểu thức bảng khác.

Như tôi sẽ giải thích trong một bài viết tới, CTE, trong thiết kế của chúng, không mắc phải hai sai sót mà các bảng dẫn xuất mắc phải.

Hàm tạo giá trị bảng

Một hàm tạo giá trị bảng cho phép bạn tạo một giá trị bảng dựa trên các biểu thức vô hướng độc lập. You can then use such a table in an outer query just like you use a derived table that is based on an inner query. In a future article I discuss lateral derived tables and correlations in detail, and I’ll show more sophisticated forms of table value constructors. In this article, though, I’ll focus on a simple form that is based purely on self-contained scalar expressions.

The general syntax for a query against a table value constructor is as follows:

SELECT