Cách tốt nhất để kiểm tra các truy vấn SQL [đã đóng]


109

Tôi đã gặp phải một vấn đề trong đó chúng tôi tiếp tục có các truy vấn SQL phức tạp gặp lỗi. Về cơ bản, điều này dẫn đến việc gửi thư cho khách hàng không chính xác và những 'vấn đề' khác như vậy.

Kinh nghiệm của mọi người với việc tạo truy vấn SQL như vậy là gì? Chúng tôi đang tạo nhóm dữ liệu mới cách tuần một lần.

Vì vậy, đây là một số suy nghĩ của tôi và những hạn chế đối với chúng:

  • Tạo dữ liệu thử nghiệm Trong khi điều này sẽ chứng minh rằng chúng tôi có tất cả dữ liệu chính xác, nó không thực thi việc loại trừ các bất thường trong sản xuất. Đó là dữ liệu được coi là sai ngày nay nhưng có thể đã đúng 10 năm trước; nó không được ghi lại và do đó chúng tôi chỉ biết về nó sau khi dữ liệu được trích xuất.

  • Tạo sơ đồ Venn và bản đồ dữ liệu Đây có vẻ là một cách chắc chắn để kiểm tra thiết kế của một truy vấn, tuy nhiên nó không đảm bảo rằng việc triển khai là chính xác. Nó giúp các nhà phát triển lập kế hoạch trước và suy nghĩ về những gì đang xảy ra khi họ viết.

Cảm ơn vì bất kỳ đầu vào nào bạn có thể cung cấp cho vấn đề của tôi.

Câu trả lời:


164

Bạn sẽ không viết một ứng dụng có hàm dài 200 dòng. Bạn sẽ phân rã các hàm dài đó thành các hàm nhỏ hơn, mỗi hàm có một trách nhiệm được xác định rõ ràng.

Tại sao viết SQL của bạn như vậy?

Phân rã các truy vấn của bạn, giống như bạn phân rã các chức năng của mình. Điều này làm cho chúng ngắn hơn, đơn giản hơn, dễ hiểu hơn , dễ kiểm tra hơn, dễ cấu trúc lại. Và nó cho phép bạn thêm "miếng chêm" vào giữa chúng và "lớp bọc" xung quanh chúng, giống như bạn làm trong mã thủ tục.

Làm thế nào để bạn làm điều này? Bằng cách biến mỗi điều quan trọng mà một truy vấn thực hiện thành một chế độ xem. Sau đó, bạn soạn các truy vấn phức tạp hơn từ các khung nhìn đơn giản này, cũng giống như bạn soạn các hàm phức tạp hơn từ các hàm nguyên thủy hơn.

Và điều tuyệt vời là, đối với hầu hết các thành phần chế độ xem, bạn sẽ nhận được chính xác hiệu suất tương tự từ RDBMS của mình. (Đối với một số bạn thì không; vậy thì sao? Tối ưu hóa sớm là gốc rễ của mọi điều xấu. Trước tiên hãy viết mã chính xác, sau đó tối ưu hóa nếu bạn cần.)

Đây là một ví dụ về việc sử dụng một số dạng xem để phân tách một truy vấn phức tạp.

Trong ví dụ này, vì mỗi chế độ xem chỉ thêm một phép biến đổi, mỗi phép biến đổi có thể được kiểm tra độc lập để tìm lỗi và việc kiểm tra rất đơn giản.

Đây là bảng cơ sở trong ví dụ:

create table month_value( 
    eid int not null, month int, year int,  value int );

Bảng này thiếu sót, vì nó sử dụng hai cột, tháng và năm, để đại diện cho một số liệu, một tháng tuyệt đối. Đây là đặc điểm kỹ thuật của chúng tôi cho cột mới được tính toán:

Chúng tôi sẽ thực hiện điều đó dưới dạng một phép biến đổi tuyến tính, sao cho nó sắp xếp giống như (năm, tháng) và sao cho bất kỳ bộ (năm, tháng) nào đều có một giá trị duy nhất và tất cả các giá trị đều liên tiếp:

create view cm_absolute_month as 
select *, year * 12 + month as absolute_month from month_value;

Bây giờ những gì chúng ta phải kiểm tra đã có trong thông số kỹ thuật của chúng ta, cụ thể là đối với bất kỳ tuple (năm, tháng) nào, chỉ có một và chỉ một (tuyệt đối_tháng), và các (tháng_tuyệt đối) là liên tiếp. Hãy viết một số bài kiểm tra.

Bài kiểm tra của chúng tôi sẽ là một selecttruy vấn SQL , với cấu trúc sau: tên bài kiểm tra và câu lệnh trường hợp được phân loại cùng nhau. Tên kiểm tra chỉ là một chuỗi tùy ý. Câu lệnh trường hợp chỉ là case whencác câu lệnh kiểm tra then 'passed' else 'failed' end.

Các câu lệnh kiểm tra sẽ chỉ là các lựa chọn SQL (truy vấn con) phải đúng để kiểm tra vượt qua.

Đây là thử nghiệm đầu tiên của chúng tôi:

--a select statement that catenates the test name and the case statement
select concat( 
-- the test name
'For every (year, month) there is one and only one (absolute_month): ', 
-- the case statement
   case when 
-- one or more subqueries
-- in this case, an expected value and an actual value 
-- that must be equal for the test to pass
  ( select count(distinct year, month) from month_value) 
  --expected value,
  = ( select count(distinct absolute_month) from cm_absolute_month)  
  -- actual value
  -- the then and else branches of the case statement
  then 'passed' else 'failed' end
  -- close the concat function and terminate the query 
  ); 
  -- test result.

Chạy truy vấn đó tạo ra kết quả sau: For every (year, month) there is one and only one (absolute_month): passed

Miễn là có đủ dữ liệu thử nghiệm trong month_value, thì thử nghiệm này sẽ hoạt động.

Chúng tôi cũng có thể thêm một bài kiểm tra để có đủ dữ liệu kiểm tra:

select concat( 'Sufficient and sufficiently varied month_value test data: ',
   case when 
      ( select count(distinct year, month) from month_value) > 10
  and ( select count(distinct year) from month_value) > 3
  and ... more tests 
  then 'passed' else 'failed' end );

Bây giờ hãy kiểm tra nó liên tiếp:

select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b 
on (     (a.month + 1 = b.month and a.year = b.year) 
      or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )  
where a.absolute_month + 1 <> b.absolute_month ) = 0 
then 'passed' else 'failed' end );

Bây giờ chúng ta hãy đặt các bài kiểm tra của chúng ta, chỉ là các truy vấn, vào một tệp và chạy tập lệnh đó trên cơ sở dữ liệu. Thật vậy, nếu chúng tôi lưu trữ các định nghĩa chế độ xem của mình trong một tập lệnh (hoặc các tập lệnh, tôi khuyên bạn nên chạy một tệp cho mỗi chế độ xem liên quan) để chạy trên cơ sở dữ liệu, chúng tôi có thể thêm các thử nghiệm của mình cho mỗi chế độ xem vào cùng một tập lệnh, để hành động của (lại -) việc tạo chế độ xem của chúng tôi cũng chạy các bài kiểm tra của chế độ xem. Bằng cách đó, cả hai chúng tôi đều nhận được các bài kiểm tra hồi quy khi chúng tôi tạo lại chế độ xem và khi quá trình tạo chế độ xem chạy ngược lại quá trình sản xuất, chế độ xem cũng sẽ được kiểm tra trong quá trình sản xuất.


27
Đây là lần đầu tiên tôi nhìn thấy mã sạch và đơn vị thử nghiệm trong sql, tôi đang hạnh phúc trong ngày :)
Maxime ARNSTAMM

1
hacks sql tuyệt vời
CodeFarmer

13
Điều này thật tuyệt, nhưng tại sao lại sử dụng một tên chữ cái cho các cột và tên chế độ xem khó đọc? Tại sao SQL nên ít tự lập tài liệu hơn hoặc dễ đọc hơn Python?
snl

1
Lời giải thích tuyệt vời cho một cái gì đó hữu ích mà tôi chưa bao giờ xem xét trong thế giới SQL / DB. Tôi cũng thích cách bạn kiểm tra cơ sở dữ liệu ở đây.
Jackstine

Như một cảnh báo, tôi đã thấy các chế độ xem sql tham gia vào các chế độ xem sql hoạt động rất kém trên PostgreSQL. Tuy nhiên, tôi đã sử dụng kỹ thuật này hiệu quả với M $ SQL.
Ben Liyanage

6

Tạo cơ sở dữ liệu hệ thống thử nghiệm mà bạn có thể tải lại thường xuyên nếu muốn. Tải dữ liệu của bạn hoặc tạo dữ liệu của bạn và lưu lại. Tạo ra một cách dễ dàng để tải lại nó. Đính kèm hệ thống phát triển của bạn vào cơ sở dữ liệu đó và xác thực mã của bạn trước khi bạn đưa vào sản xuất. Hãy tự bắt đầu mỗi khi bạn xoay sở để cho một vấn đề đi vào sản xuất. Tạo một bộ thử nghiệm để xác minh các vấn đề đã biết và phát triển bộ thử nghiệm của bạn theo thời gian.


4

Bạn có thể muốn kiểm tra DbUnit , vì vậy bạn có thể thử viết các bài kiểm tra đơn vị cho chương trình của mình với một tập dữ liệu cố định. Bằng cách đó, bạn sẽ có thể viết các truy vấn với ít nhiều kết quả có thể đoán trước được.

Điều khác bạn có thể muốn làm là lập hồ sơ ngăn xếp thực thi SQL Server của bạn và tìm hiểu xem tất cả các truy vấn có thực sự là những truy vấn chính xác hay không, ví dụ: nếu bạn đang sử dụng chỉ một truy vấn trả về cả kết quả đúng và sai, thì rõ ràng truy vấn đang được sử dụng là câu hỏi, nhưng nếu ứng dụng của bạn đang gửi các truy vấn khác nhau tại các điểm khác nhau trong mã thì sao?

Bất kỳ nỗ lực nào để sửa truy vấn của bạn sau đó sẽ vô ích ... dù sao thì các truy vấn giả mạo vẫn có thể là những truy vấn đưa ra kết quả sai.


2

Re: tpdi

case when ( select count(*) from cm_abs_month a join cm_abs_month b  
on (( a.m + 1 = b.m and a.y = b.y) or (a.m = 12 and b.m = 1 and a.y + 1 = b.y) )   
where a.am + 1 <> b.am ) = 0  

Lưu ý rằng điều này chỉ kiểm tra xem các giá trị am trong các tháng liên tiếp sẽ liên tiếp chứ không phải dữ liệu liên tiếp tồn tại (có thể là những gì bạn dự định ban đầu). Điều này sẽ luôn vượt qua nếu không có dữ liệu nguồn nào của bạn là liên tiếp (ví dụ: bạn chỉ có các tháng được đánh số chẵn), ngay cả khi tính toán am của bạn bị tắt hoàn toàn.

Ngoài ra, tôi có thiếu thứ gì đó không, hay nửa sau của điều khoản BẬT đó làm sai giá trị tháng? (tức là kiểm tra rằng 12/2011 đến sau 1/2010)

Điều tồi tệ hơn, nếu tôi nhớ không nhầm, SQL Server ít nhất cho phép bạn ít hơn 10 cấp độ xem trước khi trình tối ưu hóa ném bàn tay ảo của nó vào không khí và bắt đầu quét toàn bộ bảng theo mọi yêu cầu, vì vậy đừng làm quá mức phương pháp này.

Hãy nhớ kiểm tra các trường hợp thử nghiệm của bạn!

Mặt khác, tạo một bộ dữ liệu rất rộng để bao gồm hầu hết hoặc tất cả các dạng đầu vào có thể có, sử dụng SqlUnit hoặc DbUnit hoặc bất kỳ đơn vị * nào khác để tự động kiểm tra kết quả mong đợi so với dữ liệu đó và xem xét, duy trì và cập nhật nó khi cần thiết thường có vẻ là con đường để đi.

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.