7 điểm bởi GN⁺ 2024-09-26 | 2 bình luận | Chia sẻ qua WhatsApp
  • Đây là danh sách tổng hợp thói quen viết SQL và các mẫu truy vấn thường dùng trong công việc phân tích dữ liệu, với lưu ý rằng không phải nội dung nào cũng áp dụng giống nhau cho mọi RDBMS
  • Về tính dễ đọc, bài viết khuyến nghị các cách giúp truy vấn dễ đọc và dễ chỉnh sửa hơn như dùng dấu phẩy đầu dòng, WHERE 1=1, thụt lề, CTE, chú thích, và USING
  • Trong xử lý dữ liệu, bài viết đưa ra ví dụ về các cú pháp dùng trong thực tế để lọc kết quả, tạo tổng và kiểm tra khác biệt giữa các bảng như anti-join, QUALIFY, GROUP BY ROLLUP, và EXCEPT
  • Về hiệu năng và độ chính xác, NOT IN khi trộn với NULL, ép kiểu ngầm, và xung đột alias của trường tính toán có thể làm sai lệch kết quả hoặc ảnh hưởng tốc độ truy vấn
  • Với các truy vấn phức tạp, những thói quen cơ bản như hiểu thứ tự thực thi, đọc tài liệu, ghi rõ nguồn gốc cột, và đặt tên truy vấn đã lưu rất quan trọng để cải thiện việc debug và khả năng tái sử dụng

Thói quen giúp tăng tính dễ đọc khi viết SQL

  • Kho lưu trữ này là danh sách tổng hợp mẹo và thủ thuật SQL được đúc kết qua nhiều năm, tập trung vào những điều hữu ích trong công việc hằng ngày của nhà phân tích dữ liệu và những điều đáng lẽ nên biết khi mới bắt đầu dùng SQL
  • Một số mẹo có thể không phù hợp với mọi RDBMS
  • Dấu phẩy đầu dòng và AND đầu dòng

    • Khuyến nghị dùng dấu phẩy đầu dòng thay vì dấu phẩy cuối dòng để phân tách các trường trong mệnh đề SELECT
    • Cách này giúp nhìn rõ hơn đâu là cột mới và đâu là phần mã được xuống dòng
    • Dễ phát hiện việc thiếu dấu phẩy hơn ngay cả khi độ dài các dòng khác nhau
    • Vì cùng lý do đó, cũng có thể đặt AND ở đầu mỗi điều kiện trong mệnh đề WHERE
  • Dùng WHERE 1=1 để kiểm thử điều kiện dễ hơn

    • Nếu thêm điều kiện giả 1=1 vào mệnh đề WHERE, việc comment bớt điều kiện khi kiểm thử sẽ không làm truy vấn bị lỗi
    • Ngay cả khi comment toàn bộ điều kiện khác, 1=1 vẫn còn nên truy vấn vẫn có thể chạy
  • Thụt lề và formatter

    • Thụt lề giúp đồng nghiệp và cả chính bạn trong tương lai đọc truy vấn dễ hơn
    • Nếu nhóm hoặc công ty có guideline thì nên làm theo, còn nếu không có thì nên dùng cách phù hợp với bản thân
    • Có thể dùng formatter online poorsql hoặc linter sqlfluff
  • Cân nhắc CTE cho truy vấn phức tạp

    • Nếu lồng inline view từ 2–3 tầng trở lên, truy vấn rất dễ trở nên khó hiểu khi nhìn lại sau vài tuần
    • CTE được đưa ra như một cách giúp các truy vấn dài trở nên gọn gàng hơn, đồng thời hỗ trợ tái sử dụng và debug
  • Chú thích nên giải thích “vì sao”

    • Sau một thời gian, bạn có thể khó nhớ vì sao mình lại xử lý theo một cách cụ thể
    • Thông thường, chú thích nên giải thích vì sao làm như vậy hơn là mã hoạt động như thế nào
    • Ví dụ trong bài gắn chú thích cho điều kiện loại trừ nội dung archive vì CMS mới không xử lý được định dạng video archive
  • Dùng USING khi join theo cột cùng tên

    • Khi join hai bảng bằng cột có cùng tên, dùng USING có thể diễn đạt ngắn gọn hơn ON
    • USING sẽ loại bỏ trùng lặp cột chung trong kết quả và chỉ trả về một cột
    • Nếu dùng ON mà không chỉ rõ cột chung, có thể gặp lỗi ambiguous column name

Các cú pháp hữu ích cho xử lý dữ liệu

  • Tìm các dòng không có trong bảng khác bằng anti-join

    • anti-join được dùng khi cần trả về các dòng có trong một bảng nhưng không có bản ghi khớp ở bảng còn lại
    • Ví dụ trong bài là trường hợp chỉ lấy video_id của nội dung chưa được archive
    • Có nhiều cách để triển khai
    • Dùng LEFT JOIN rồi lọc các dòng có khóa của bảng được join là NULL
    • Dùng NOT IN cùng subquery
    • Dùng NOT EXISTS cùng correlated subquery
    • Không khuyến nghị dùng NOT IN vì có thể không hoạt động đúng như mong muốn do giá trị NULL
  • Lọc kết quả hàm cửa sổ bằng QUALIFY

    • QUALIFY cho phép lọc kết quả truy vấn dựa trên kết quả của hàm cửa sổ
    • Có thể lọc mà không cần inline view, từ đó giảm số dòng code
    • Ví dụ trong bài chọn 10 thị trường hàng đầu theo từng sản phẩm bằng DENSE_RANK() rồi lọc bằng QUALIFY
    • Có giới hạn là QUALIFY dường như chỉ có ở các kho dữ liệu lớn như Snowflake, Amazon Redshift, và Google BigQuery
  • GROUP BYORDER BY theo vị trí cột

    • Có thể viết GROUP BY 1, ORDER BY 2 dựa trên vị trí cột thay vì tên cột
    • Điều này có thể hữu ích cho truy vấn tạm thời hoặc dùng một lần
    • Tuy nhiên với code production, luôn nên tham chiếu trực tiếp tên cột
  • Tạo tổng bằng GROUP BY ROLLUP

    • GROUP BY ROLLUP có thể được dùng để tạo subtotal và grand total
    • Ví dụ trong bài tính tổng lương theo từng phòng ban đồng thời tạo thêm dòng tổng lương toàn bộ
    • Tài liệu Transact-SQL giải thích rằng ROLLUP tạo các nhóm theo tổ hợp biểu thức cột và giảm dần số lượng nhóm từ phải sang trái để tạo subtotal và grand total
    • Có thể áp dụng COALESCE để hiển thị dòng tổng là Total
    • Cần chú ý cột sắp xếp để dòng tổng nằm ở cuối kết quả
  • Tìm khác biệt giữa hai tập kết quả bằng EXCEPT

    • EXCEPT trả về các dòng có trong kết quả của truy vấn thứ nhất nhưng không có trong truy vấn thứ hai
    • Kết hợp EXCEPT với UNION ALL có thể dùng để kiểm tra hai bảng có chứa cùng dữ liệu hay không
    • Nếu không trả về dòng nào thì hai bảng là giống nhau
    • Nếu có dòng được trả về thì chính các dòng đó là nguyên nhân tạo ra khác biệt

Những mẫu gây hại cho hiệu năng và độ chính xác

  • Với cột có thể NULL, NOT EXISTS tốt hơn NOT IN

    • Nếu cột đem ra so sánh cho phép NULL, NOT IN thường có thể chậm hơn NOT EXISTS
    • Tác giả từng gặp hiện tượng này trên Snowflake, và trang Don’t Do This của PostgreSQL Wiki cũng ghi rằng NOT IN (SELECT ...) không được tối ưu tốt
    • NOT IN sẽ không hoạt động đúng như mong đợi nếu trong giá trị so sánh có NULL
    • Việc cột cho phép NULL không có nghĩa là dữ liệu thực sự có NULL, nhưng khi làm việc với bảng không thể chỉnh sửa thì dùng NOT EXISTS có thể giúp cải thiện tốc độ
  • Ép kiểu ngầm có thể làm chậm hoặc gây lỗi

    • Nếu đưa vào điều kiện một giá trị có kiểu dữ liệu khác với cột, cơ sở dữ liệu có thể thử ép kiểu ngầm
    • Ví dụ trong bài là so sánh số nguyên 200050 với cột video_id kiểu chuỗi
    • Phụ thuộc vào ép kiểu ngầm có thể gây ra vấn đề
    • Nếu có giá trị không thể chuyển đổi, truy vấn có thể phát sinh lỗi
    • Truy vấn cũng có thể chậm hơn do phải làm thêm công việc chuyển đổi từng giá trị sang kiểu được chỉ định
    • Nên dùng kiểu dữ liệu giống với cột, hoặc để tránh lỗi có thể dùng các hàm như TRY_TO_NUMBER của Snowflake
    • Mức độ ảnh hưởng đến tốc độ còn tùy vào kích thước dataset được xử lý

Những lỗi thường gặp

  • NOT INNULL

    • NOT IN sẽ không hoạt động nếu trong các giá trị dùng để so sánh có NULL
    • NULL biểu thị Unknown, SQL engine không thể xác minh rằng giá trị cần kiểm tra không nằm trong danh sách
    • Trong trường hợp này, NOT EXISTS là một phương án thay thế
  • Xung đột alias của trường tính toán

    • Nếu đặt tên trường tính toán trùng với cột có sẵn, có thể xuất hiện hành vi ngoài dự đoán
    • Tài liệu GROUP BY của Snowflake cho biết nếu tên trong mệnh đề GROUP BY khớp cả tên cột lẫn alias thì tên cột sẽ được ưu tiên
    • Trong ví dụ, nếu tạo alias bằng LEFT(product, 1) AS product rồi viết GROUP BY product, truy vấn sẽ nhóm theo cột product gốc thay vì ký tự đầu tiên, nên trả về 3 dòng
    • Có hai cách giải quyết
    • Dùng alias duy nhất như product_letter
    • Hoặc ghi rõ biểu thức như GROUP BY LEFT(product, 1)
    • Vấn đề alias cũng có thể xảy ra với hàm cửa sổ
    • Trong ví dụ, CASE được dùng để đổi revenue của Robot thành 0, nhưng thay đổi này chỉ được áp dụng sau khi hàm cửa sổ chạy xong nên thứ hạng không đúng như kỳ vọng
    • Khi có thể, nên dùng alias duy nhất hoặc đưa trực tiếp biểu thức tính toán vào ORDER BY của hàm cửa sổ
  • Chỉ rõ cột thuộc bảng nào

    • Với truy vấn phức tạp có nhiều phép join, cần có khả năng lần ngược vấn đề dữ liệu về tới bảng nguồn
    • Khi hai bảng dùng chung tên cột mà không ghi rõ cột thuộc bảng nào, RDBMS có thể báo lỗi
    • Ví dụ trong bài dùng alias bảng như vc.video_id, metadata.season để làm rõ nguồn gốc cột

Thứ tự thực thi, tài liệu và tên lưu trữ

  • Hiểu thứ tự thực thi của SQL

    • Một trong những lời khuyên quan trọng nhất cho người học SQL là hiểu thứ tự thực thi của các mệnh đề
    • Khi biết thứ tự thực thi, cách bạn viết truy vấn có thể thay đổi rất nhiều
    • Tài liệu tham khảo được đề xuất là A beginner’s guide to the true order of SQL operations
  • Đọc tài liệu đến cùng

    • Có trường hợp trên Snowflake cần trả về ngày mới nhất trong nhiều cột ngày nên đã dùng GREATEST()
    • GREATEST() sẽ trả về NULL nếu một trong các đối số là NULL
    • Nếu đọc kỹ tài liệu hơn, thay vì COALESCE(GREATEST(...), ...) có thể dùng GREATEST_IGNORE_NULLS()
    • Trong nhiều trường hợp, chỉ mất chưa đến 1 phút để lướt tài liệu và tránh được công sức tìm nguyên nhân vì sao kết quả lại khác mong đợi
  • Đặt tên mô tả cho truy vấn đã lưu

    • Để tránh tình huống không tìm lại được truy vấn cần chạy lại hoặc tham khảo, nên lưu bằng tên mô tả rõ ràng
    • Tên lưu thường bao gồm chủ đề truy vấn, tháng chạy, và tên người yêu cầu
    • Ví dụ theo định dạng Lapsed users analysis - 2023-09-01 - Olivia Roberts

2 bình luận

 
hiyama 2024-09-26

Các dấu phẩy đặt trước trong bài viết này đều được viết thành dấu phẩy đặt sau. Trong nguyên văn, chúng được nhập ở dạng đặt trước.

-- Good:  
SELECT   
timeslot_date  
, timeslot_channel   
, overnight_fta_share  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) > 7, -- First argument of IFF.  
	LAG(overnight_fta_share, 1) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity), -- Second argument of IFF.  
		NULL) AS C7_fta_share -- Third argument of IFF.  
, IFF(DATEDIFF(DAY, timeslot_date, CURRENT_DATE()) >= 29,   
		LAG(overnight_fta_share, 2) OVER (PARTITION BY timeslot_date, timeslot_channel ORDER BY timeslot_activity),   
			NULL) AS C28_fta_share  
FROM timeslot_data  
;  
 
GN⁺ 2024-09-26
Các ý kiến trên Hacker News
  • Mẹo tôi muốn bổ sung là: hãy học kỹ DB server của bạn và thường xuyên kiểm tra execution plan. Có thể xuất hiện kết quả ngoài dự đoán, nên tốt nhất là điều chỉnh rồi kiểm tra lại
    Thông thường EXISTS nhanh hơn IN, còn NOT EXISTS xử lý NULL khác với EXCEPT. Thay vì join các bảng rồi lọc bớt dòng bằng những thứ như DISTINCT, đôi khi dùng cột truy vấn con trong danh sách SELECT sẽ nhanh hơn nhiều. Điều này vẫn đúng ngay cả khi lấy hơn 10 giá trị từ cùng một bảng, và cũng có thể đúng kể cả khi DB server hỗ trợ lateral join. Tuy nhiên truy vấn con chỉ được trả về tối đa một dòng
    Những query không phải chạy một lần rồi bỏ thì nên tránh quét toàn bộ bảng. Table scan hôm nay có thể trở thành sự cố ngày mai, nên cần thêm index. Cũng nên nhớ rằng mệnh đề GROUP BY thường quyết định việc có dùng index hay không
    Nếu phải lọc bằng biểu thức, ví dụ kiểm tra một chuỗi con có bằng một giá trị cụ thể hay không, bạn có thể thêm cột được tính toán rồi tạo index trên đó. Một số DB hỗ trợ trực tiếp index trên biểu thức. Dùng UNION ALL thay cho OR thường có thể nhanh hơn nhiều, cả với query phức tạp hoặc nhiều điều kiện OR
    Khi DB không đủ thông minh để chọn thứ tự lọc, việc JOIN với truy vấn con để ép thứ tự cũng hữu ích

    • Điều hữu ích nhất là học chính DBMS. Không thể tránh được chuyện mỗi DB có những điểm đặc thù về hiệu năng và mức cô lập, cũng như các tính năng bonus khác nhau
      Một điểm thú vị ở Postgres, và các DB khác cũng có thể như vậy, là nếu tự shard thủ công một tác vụ INSERT (SELECT ...) theo số lượng CPU core thì tốc độ có thể tăng gần như tuyến tính. Ngay cả khi có khoảng 10 join cũng làm được. Trước hết xem EXPLAIN, tìm join trong cùng hoặc ngoài cùng, rồi chạy các query song song riêng cho từng khoảng dòng (id >= start AND id < end). Trong một công việc 6 năm trước, vì một lý do kỳ lạ, tôi đã dùng cách này rất nhiều. Postgres 10+ đã thêm tính song song, nhưng theo tôi biết thì vẫn chưa tiến bộ đến mức này
    • Tôi không rõ chính xác ý “cột” truy vấn con trong danh sách SELECT là gì
      Ví dụ nếu chạy SELECT column1, (SELECT column2, column3, ... FROM table_b WHERE table_a.id = table_b.a_id) FROM table_a thì đúng như dự đoán sẽ báo “subquery must return only one column”. Ý là trả về nhiều cột dưới dạng record/composite type à?
      Lý do mệnh đề GROUP BY thường quyết định việc dùng index chưa lập tức rõ với tôi, nhưng bài này giải thích từng bước khá tốt cho ai quan tâm: https://www.brentozar.com/archive/2015/06/indexing-for-group...
    • Đồng ý. Cần dùng EXPLAIN và học cách diễn giải nó bằng công cụ bạn ưa thích. Query cũng cần được monitor
      Ở startup trước đây, chúng tôi cài PgHero, và nó thực sự giúp ích rất nhiều cho việc tối ưu hiệu năng và xác định ưu tiên
    • Ngay cả query được thiết kế tốt cũng thường hoạt động khác với dự đoán. Các trường hợp điển hình là thống kê cột chưa được cập nhật, hoặc dữ liệu trên bảng lớn bị phân mảnh. Ví dụ như chèn khóa chính ngẫu nhiên
    • Tôi không đồng ý với câu “query không phải chạy một lần rồi bỏ thì không được quét toàn bộ bảng. Table scan hôm nay có thể trở thành sự cố ngày mai”
      Có những query mà quét toàn bộ bảng là chiến lược truy cập hiệu quả nhất. Thường là các query phân tích/tổng hợp đọc toàn bộ bảng, và đôi khi ngay cả khi chỉ lấy 50% tổng số dòng thì table scan vẫn tốt hơn.
      Tôi cũng không rõ một table scan chỉ đọc sẽ dẫn đến sự cố như thế nào, vì nó không chặn truy cập đồng thời. Nhược điểm duy nhất là tải I/O lớn hơn; nếu server không chịu nổi mức đó thì ngay từ đầu đã là cấu hình thiếu nghiêm trọng
  • Ba ví dụ trong phần “tính dễ đọc” khá kỳ lạ. Hai ví dụ đầu đúng nghĩa là hy sinh tính dễ đọc để dễ viết hơn, còn ví dụ cuối là một con quái vật khó đọc đến mức thụt lề cũng gần như không cứu nổi

    • Dạng dấu phẩy ở đầu dòng có ưu điểm ngoài tính dễ đọc. Ví dụ trong hệ thống quản lý phiên bản, kiểu mỗi đối số một dòng + dấu phẩy ở đầu dòng khiến thay đổi một đối số chỉ hiện thành diff một dòng
      Tôi nghĩ lập trình viên xem lịch sử commit cũng nhiều không kém gì xem mã nguồn thực tế
    • Tôi không hẳn là rất thích hình thức của hai quy ước đầu, nhưng đó là những quy ước mà người viết SQL thực tế vẫn dùng. Tôi cũng hiểu vì sao chúng tồn tại
      Gặp đủ thường xuyên nên giờ tôi không còn thấy khó chịu lắm
    • Một lựa chọn khác là viết SQL bừa bộn như một đứa trẻ ba tuổi lần đầu phát hiện MSPaint, rồi bấm nút “beautifier” và đi ăn trưa sớm
    • Tôi không hiểu vì sao bạn nghĩ nó tệ hơn
      Tôi cũng không thấy vấn đề gì
      Và có vẻ cũng chẳng có gì sai
    • Ai mà trong khối SELECT thì tách từng cột ra một dòng nhưng vẫn để nguyên dòng dài 150 ký tự vậy? Đây là định nghĩa hỏng của tính dễ đọc. Chuyện dấu phẩy thì tôi còn chưa muốn bắt đầu
      Trong code review, chẳng ai đọc kỹ các dòng dài cả. Đó từng là vấn đề lớn nhất của AngularJS. Merge bị xử lý sai và mọi thứ vỡ tung, vì đến khoảng cột thứ 90 thì mắt bắt đầu lờ đi. Tôi đã trải qua hơn nửa tá team có code review và lần nào cũng vậy. Dù rất ý thức và cố tránh vấn đề này, chính tôi vẫn mắc lỗi với tần suất khoảng một nửa so với người khác
      Hãy chia nhỏ ra mà viết. Đặc biệt là khi đưa ví dụ cho người khác xem
  • Mẹo khi xử lý các stored procedure phức tạp là như sau

    1. Ngay ở phần đầu procedure, lập tức sao chép bảng cố định sang bảng tạm, đồng thời chỉ định/giới hạn/lọc chỉ những hàng cần thiết
    2. Ở phần giữa, thao tác với các bảng tạm theo nhu cầu
    3. Ở phần cuối, cập nhật bảng cố định bên trong một transaction. Nếu phát hiện lỗi thì rollback transaction ngay lập tức và kết thúc procedure. Làm theo ba bước này sẽ cải thiện tính đồng thời, và có thể chạy lại procedure mà không cần tự tay dọn các dữ liệu thừa
    4. Khi xử lý bảng từ xa, phải cực kỳ cẩn thận. Vì bảng từ xa không nằm trong RDBMS hiện tại, rất có thể bạn hầu như không tận dụng được thống kê hay index của RDBMS đó. Trong nhiều trường hợp, dump/sao chép toàn bộ bảng từ xa vào bảng tạm rồi xử lý sẽ nhanh hơn. Mức tối đa có thể kỳ vọng ở bảng từ xa chỉ khoảng thực thi mệnh đề WHERE. Nếu thử JOIN hoặc các thao tác phức tạp, khả năng cao sẽ bị timeout
    5. Execution plan rất dễ gây nhầm lẫn. Trong một số trường hợp, execution plan có thể rơi xuống xử lý theo từng hàng và làm hiệu năng khựng lại. Nhiều khi tốt hơn là chia stored procedure phức tạp thành các bước nhỏ dùng bảng tạm
    6. Để biết RDBMS thực sự đang làm gì, luôn phải kiểm tra execution plan
    • Tôi từng cải thiện đáng kể hiệu năng truy vấn bằng cách hoàn tác đoạn code áp dụng mục 5 trong một tình huống không thật sự cần thiết. Đôi khi việc tách một truy vấn thành nhiều truy vấn nhỏ lại kém hiệu quả hơn nhiều so với đưa toàn bộ truy vấn cho query optimizer và để nó tìm đường tối ưu
      Nếu làm mục 5 mà không có mục 6, rất có thể bạn sẽ không thấy rằng mình đang làm việc không tối ưu. Lời khuyên của tôi là tránh tối ưu hóa sớm, trước hết hãy viết theo cách trực quan nhất, rồi chỉ tối ưu khi cần. Điều quan trọng nhất là không viết SQL theo kiểu thủ tục. Bạn đang mô tả dữ liệu mình muốn, chứ không ra lệnh cho engine cách lấy dữ liệu
    • Tôi không thích phải dùng cả đống bảng tạm, nhưng thường xuyên gặp những truy vấn mà nếu phó mặc cho query planner thì sẽ không bao giờ kết thúc. Cũng như compiler, năng lực của query planner bị đánh giá quá cao
      Trong khi đó Microsoft thì liên tục gắn cảnh báo kiểu như query planner biết rõ nhất, đừng cố tune nó
    • Những quy tắc này có thể hoàn toàn đúng với DB của một vendor nào đó, nhưng ở DB khác thì ưu tiên, đặc tính và trade-off có thể rất khác
      Phiên bản DB cũng có thể ảnh hưởng
    • Mục 1~3 ổn nếu có thể bảo đảm kích thước dữ liệu là hợp lý. Nhưng nếu dữ liệu trở nên quá lớn so với khả năng chịu tải của phần cứng, việc sao chép dataset lớn rồi lại cập nhật dataset lớn có thể tạo thêm overhead đáng kể
  • Tôi không thích kiểu phát triển “phòng khi cần”. Interface cũng vậy, các placeholder như where 1=1 cũng vậy
    Khi nào cần thì hãy làm. Đừng làm chỉ vì một ngày nào đó trong tương lai có thể sẽ cần. Production code không phải là nơi để để lại các công cụ hỗ trợ phát triển. Trong lúc phát triển thì muốn làm gì cũng được, nhưng trong production code, tính dễ đọc và ý định rõ ràng quan trọng hơn nhiều

    • Bạn có thường viết đầy đủ định danh cho mọi tham chiếu tên bảng và tên cột không? Làm vậy trong nhiều trường hợp cải thiện khả năng đọc hơn cả một bậc, nhưng nó nhanh chóng trở nên cực kỳ dài dòng và việc viết thì nhàm chán đến mức khó tin
  • Thêm một ý về “anti join”. Nếu chỉ muốn kiểm tra xem có hàng nào thỏa điều kiện tồn tại trong một bảng lớn khác hoặc subquery hay không, nên dùng EXISTS thay vì IN hoặc LEFT JOIN
    EXISTS trả về true ngay khi tìm thấy mục khớp. Với LEFT JOININ, engine sẽ gom tất cả kết quả trước khi đánh giá

    • Phần đó hơi khó hiểu. Trong mọi trường hợp tôi đã thử, (NOT) EXISTS tạo ra execution plan tốt hơn hoặc giống với (LEFT) JOIN hay (NOT) IN
      Hơn nữa ý định cũng rõ ràng hơn
  • Liên quan đến “hãy chú thích code”, ít nhất trong MSSQL, người ta thường khuyên dùng /**/ thay vì -- trong comment. Lý do là các tính năng như Query Store thường lưu truy vấn không có xuống dòng, nên khi lấy truy vấn từ đó ra, thay vì dùng ngay formatter của IDE, bạn phải tự tay sửa toàn bộ

    • Nghe giống bug của Query Store
    • Có cast sang XML được không? Tôi dùng cách đó với OBJECT_DEFINITION
      select name,cast((select OBJECT_DEFINITION(object_id) for xml path('')) as xml) from sys.procedures
      Vì các xuống dòng được giữ lại nên có thể dễ dọn dẹp hơn. Tuy nhiên các ký tự XML khác sẽ bị hỏng, như > bị đổi thành >. Một lựa chọn khác là dùng VARBINARY và một thứ gì đó để giải mã lại nó
  • Mọi người đều làm ầm lên với đề xuất về dấu phẩy, còn 1=1 trong mệnh đề WHERE thì được xem là ý hay sao? Nếu thấy thứ đó trong code review, tôi không biết phải nghĩ gì về người viết

    • Có thể biện minh bằng cùng lý do như dấu phẩy ở cuối. Tức là thay đổi câu lệnh WHERE không ảnh hưởng đến các dòng khác nên code review dễ hơn
      Nhưng nếu lý do là để thêm điều kiện động như trong trường hợp này, thì ở nơi tôi làm việc chắc chắn sẽ bị sa thải
  • Có ai có thể chia sẻ hướng dẫn chung về việc nên kẻ ranh giới ở đâu giữa cách tăng tốc bằng cấu hình DB, gần như là “mua”, và cách gần như tự triển khai thủ công, tức “xây”? Theo kinh nghiệm hạn chế của tôi, vì các DBA giỏi thường được trả lương cao hơn nhiều và làm ở nơi khác, việc này hay rơi vào tay lập trình viên ứng dụng. Như đã nói ở trên, hiểu về DB là rất quan trọng
    Ví dụ điển hình là loại dữ liệu tích lũy số lượng lớn theo thời gian và dữ liệu mới nhất được truy cập thường xuyên nhất. DBA có thể giữ tốc độ truy cập nhanh bằng partitioning hoặc partial index, nhưng lập trình viên ứng dụng cũng có thể âm thầm chuyển các bản ghi sang bảng lưu trữ riêng, trong khi vẫn tiếp tục hỗ trợ các chức năng như tìm kiếm cuối cùng trên toàn bộ tập dữ liệu. Tôi cũng có cảm giác rằng trong các trường hợp bị giới hạn bởi việc thiếu tính năng của DB đám mây, công cụ có thể tự động hóa khá nhiều công việc ban đầu để tách một bảng thành nhiều bảng vào thời điểm phù hợp
    Một lựa chọn quản trị khác là lưu tất cả blob/file lớn vào một cơ sở dữ liệu riêng, hoặc vào file system, để dùng cấu hình lưu trữ khác. Việc này cũng có thể do DB đảm nhiệm hoặc được xử lý thủ công
    Cực đoan hơn, có lẽ có thể đi tới mức tự triển khai index. Tức là có một bảng khổng lồ với một khóa chính tự tăng và rất nhiều cột, rồi tạo một bảng riêng chứa ID đó cùng vài cột có thể tìm kiếm. Thậm chí có thể đi tới full-text search hoặc vector
    Một mẹo hữu ích khi triển khai thủ công mẫu materialized view trên MSSQL 2016+ là dùng kèm partition switching. Điều này được giải thích và triển khai tốt tại https://github.com/cajuncoding/SqlBulkHelpers?tab=readme-ov-.... Đây là một thư viện nhỏ tôi tình cờ tìm thấy, hữu ích nhất về mặt thương mại nhưng thứ hạng tìm kiếm thấp và ít sao, tập trung vào bulk insert từ .NET vào MSSQL. Tôi nghĩ đây là một ví dụ tốt về việc kẻ ranh giới hợp lý giữa mua/xây thông qua tự động hóa partition switching

  • Nội dung còn thiếu: nên ngừng dùng SELECT *. Gần như chắc chắn bạn không cần toàn bộ bề rộng của bảng, và làm vậy sẽ khiến dữ liệu cần lọc và truyền đi nhiều hơn, đồng thời còn chặn một tính năng hay là semi join

    • Người dùng SQL đại khái có hai nhóm: nhà phân tích và lập trình viên
      Nếu là lập trình viên thì đúng. SELECT * có cạm bẫy, và gần như luôn nên chỉ rõ các cột hoặc dùng query builder làm việc đó thay bạn
      Nhưng nếu là nhà phân tích thì đời ngắn lắm, đôi khi bạn có thể không muốn gõ hết tất cả các cột. SELECT * cũng ổn
  • Có thể hơi lệch chủ đề, nhưng việc người quản trị đóng pull request mà không có bất kỳ bình luận hay thảo luận nào có phải là cách làm chấp nhận được không?
    Tôi hỏi với tư cách là người thỉnh thoảng đã đóng góp hoặc đã cố gắng đóng góp cho repository
    Ví dụ: https://github.com/ben-n93/SQL-tips-and-tricks/pulls?q=is%3A...