2009-08-19 34 views
11

Cơ sở dữ liệu phân tích trang web MySQL của chúng tôi chứa bảng tóm tắt được cập nhật suốt cả ngày khi hoạt động mới được nhập. Chúng tôi sử dụng ON CẬP NHẬT KEY CẬP NHẬT để tóm tắt ghi đè các tính toán trước đó, nhưng gặp khó khăn vì một trong các cột trong UNIQUE KEY của bảng tóm tắt là một FK tùy chọn và chứa các giá trị NULL.MySQL ON CẬP NHẬT KEY TUYÊN BỐ với cột nullable trong khóa duy nhất

Các giá trị NULL này có nghĩa là "không có mặt và tất cả các trường hợp như vậy là tương đương". Tất nhiên, MySQL thường xử lý NULL như là "không xác định, và tất cả các trường hợp như vậy không tương đương".

cấu trúc cơ bản như sau:

An "Hoạt động" bảng chứa một mục nhập cho mỗi phiên, mỗi thuộc vào chiến dịch, với tùy chọn bộ lọc và giao dịch ID cho một số mục.

CREATE TABLE `Activity` (
    `session_id` INTEGER AUTO_INCREMENT 
    , `campaign_id` INTEGER NOT NULL 
    , `filter_id` INTEGER DEFAULT NULL 
    , `transaction_id` INTEGER DEFAULT NULL 
    , PRIMARY KEY (`session_id`) 
); 

Bảng "Tóm tắt" chứa tổng số phiên trong đó có chứa ID giao dịch. Các tóm tắt này được phân tách, với một bản tóm tắt cho mỗi bộ lọc chiến dịch và (tùy chọn). Đây là một bảng phi giao dịch sử dụng MyISAM.

CREATE TABLE `Summary` (
    `day` DATE NOT NULL 
    , `campaign_id` INTEGER NOT NULL 
    , `filter_id` INTEGER DEFAULT NULL 
    , `sessions` INTEGER UNSIGNED DEFAULT NULL 
    , `transactions` INTEGER UNSIGNED DEFAULT NULL 
    , UNIQUE KEY (`day`, `campaign_id`, `filter_id`) 
) ENGINE=MyISAM; 

Truy vấn tóm tắt thực tế giống như sau, đếm số lượng phiên và giao dịch, sau đó nhóm theo bộ lọc chiến dịch và (tùy chọn).

INSERT INTO `Summary` 
    (`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`) 
    SELECT `day`, `campaign_id`, `filter_id 
     , COUNT(`session_id`) AS `sessions` 
     , COUNT(`transaction_id` IS NOT NULL) AS `transactions` 
    FROM Activity 
    GROUP BY `day`, `campaign_id`, `filter_id` 
ON DUPLICATE KEY UPDATE 
    `sessions` = VALUES(`sessions`) 
    , `transactions` = VALUES(`transactions`) 
; 

Mọi thứ hoạt động tốt, ngoại trừ tóm tắt các trường hợp mà filter_id là NULL. Trong những trường hợp này, mệnh đề ON DUPLICATE KEY UPDATE không khớp với hàng hiện tại, và một hàng mới được viết mỗi lần. Điều này là do thực tế là "NULL! = NULL". Tuy nhiên, cái chúng ta cần là "NULL = NULL" khi so sánh các khóa duy nhất.

Tôi đang tìm ý tưởng về cách giải quyết hoặc phản hồi về những điều chúng tôi đã đưa ra cho đến thời điểm này. Cách giải quyết chúng tôi đã nghĩ đến cho đến nay theo.

  1. Xóa tất cả các mục tóm tắt chứa giá trị khóa NULL trước khi chạy tóm tắt. (Đây là những gì chúng tôi đang làm bây giờ) Điều này có tác dụng phụ tiêu cực của việc trả về kết quả với dữ liệu bị thiếu nếu một truy vấn được thực hiện trong quá trình tổng kết.

  2. Thay đổi cột DEFAULT NULL thành DEFAULT 0, cho phép khóa UNIQUE được kết hợp nhất quán. Điều này có tác dụng phụ tiêu cực của quá phức tạp sự phát triển của các truy vấn đối với bảng tóm tắt. Nó buộc chúng ta phải sử dụng rất nhiều "CASE filter_id = 0 THEN NULL ELSE filter_id END", và làm cho việc tham gia khó xử vì tất cả các bảng khác có NULLs thực tế cho filter_id.

  3. Tạo chế độ xem trả về "Bộ lọc CASE_id = 0 THEN NULL ELSE filter_id END" và sử dụng chế độ xem này thay vì trực tiếp bảng. Bảng tóm tắt chứa hàng trăm nghìn hàng và tôi đã được cho biết hiệu suất xem khá kém.

  4. Cho phép tạo các mục trùng lặp và xóa các mục nhập cũ sau khi tóm tắt hoàn tất. Có vấn đề tương tự khi xóa chúng trước thời hạn.

  5. Thêm cột thay thế có chứa 0 cho NULL và sử dụng thay thế đó trong UNIQUE KEY (thực tế chúng tôi có thể sử dụng PRIMARY KEY nếu tất cả các cột KHÔNG NULL).
    Giải pháp này có vẻ hợp lý, ngoại trừ ví dụ trên chỉ là một ví dụ; cơ sở dữ liệu thực tế chứa một nửa tá bảng tóm tắt, một trong số đó chứa bốn cột vô giá trị trong UNIQUE KEY. Có một số lo ngại rằng chi phí quá cao.

Bạn có cách giải quyết tốt hơn, cấu trúc bảng, quy trình cập nhật hoặc thực hành tốt nhất của MySQL có thể giúp ích?

EDIT: Để làm rõ "ý nghĩa của null"

Dữ liệu trong các hàng tóm tắt chứa các cột NULL được coi thuộc về nhau chỉ theo nghĩa là trở thành một single "catch-all" hàng trong báo cáo tóm tắt , tóm tắt những mục mà điểm dữ liệu đó không tồn tại hoặc không xác định. Vì vậy, trong bối cảnh của bảng tóm tắt chính nó, ý nghĩa là "tổng của những mục mà không có giá trị nào được biết". Trong các bảng quan hệ, mặt khác, đây thực sự là các kết quả NULL.

Lý do duy nhất để đưa chúng vào khóa duy nhất trên bảng tóm tắt là cho phép cập nhật tự động (theo ON DUPLICATE KEY UPDATE) khi tính lại các báo cáo tóm tắt.

Có thể một cách tốt hơn để mô tả nó là ví dụ cụ thể mà một trong các bảng tóm tắt nhóm kết quả theo địa lý bằng tiền tố mã zip của địa chỉ doanh nghiệp do người trả lời đưa ra. Không phải tất cả người trả lời đều cung cấp địa chỉ doanh nghiệp, do đó mối quan hệ giữa bảng giao dịch và bảng địa chỉ là khá chính xác NULL. Trong bảng tóm tắt cho dữ liệu này, một hàng được tạo cho mỗi tiền tố mã zip, chứa tóm tắt dữ liệu trong khu vực đó. Một hàng bổ sung được tạo để hiển thị tóm tắt dữ liệu mà không có tiền tố mã zip nào được biết.

Thay đổi phần còn lại của bảng dữ liệu để có giá trị 0 "THERE_IS_NO_ZIP_CODE" rõ ràng và đặt một bản ghi đặc biệt trong bảng ZipCodePrefix đại diện cho giá trị này là không đúng - mối quan hệ đó thực sự là NULL.

Trả lời

4

Tôi nghĩ rằng điều gì đó dọc theo dòng (2) thực sự là đặt cược tốt nhất - hoặc, ít nhất, nó sẽ là nếu bạn bắt đầu từ đầu. Trong SQL, NULL có nghĩa là không xác định. Nếu bạn muốn một số ý nghĩa khác, bạn thực sự nên sử dụng một giá trị đặc biệt cho điều đó, và 0 chắc chắn là một sự lựa chọn OK.

Bạn nên làm điều này trên toàn bộ cơ sở dữ liệu , không chỉ một bảng này. Sau đó, bạn không nên gió lên với các trường hợp đặc biệt kỳ lạ. Trong thực tế, bạn sẽ có thể loại bỏ nhiều thứ hiện tại của bạn (ví dụ: hiện tại, nếu bạn muốn hàng tóm tắt không có bộ lọc, bạn có trường hợp đặc biệt "bộ lọc rỗng" trái với trường hợp thông thường "filter =?".)

Bạn cũng nên tiếp tục và tạo mục nhập "không hiện diện" trong bảng được đề cập, để giữ ràng buộc FK hợp lệ (và tránh các trường hợp đặc biệt).

PS: Bảng w/o khóa chính không phải là bảng quan hệ và thực sự nên tránh.

chỉnh sửa 1

Hmmm, trong trường hợp đó, bạn có thực sự cần cập nhật khóa trùng lặp không? Nếu bạn đang làm một INSERT ... SELECT, sau đó bạn có thể làm. Nhưng nếu ứng dụng của bạn đang cung cấp dữ liệu, chỉ cần thực hiện bằng tay - thực hiện cập nhật (ánh xạ zip = null đến zip is null), kiểm tra số hàng đã được thay đổi (MySQL trả về điều này), nếu 0 thực hiện chèn.

+0

Vâng, bảng tóm tắt là khá rõ ràng không phải là một bảng quan hệ. Nó chỉ đơn giản là một container thuận tiện để giữ kết quả báo cáo. Tuyên bố của tôi rằng "Những NULL này có nghĩa là 'không có mặt, và tất cả các trường hợp như vậy là tương đương'", có lẽ là gây hiểu nhầm. Trong các bảng quan hệ có chứa dữ liệu chuẩn hóa, filter_id và các mối quan hệ rỗng khác mà tôi đề cập là một phần của khóa duy nhất trong bảng tóm tắt thực sự có nghĩa "không xác định" và không phải là một phần của bất kỳ khóa chính hoặc khóa duy nhất nào. Xem chỉnh sửa ở trên. – ryandenki

+0

Chính xác. Chúng tôi sử dụng INSERT ... SELECT, sử dụng mệnh đề ON DUPLICATE KEY ở đó để cập nhật các mục trong suốt cả ngày. Thực ra, triển khai đầu tiên cách đây hai năm là như bạn đề xuất - trước tiên hãy chọn dữ liệu, thực hiện một số thao tác bổ sung, sau đó phát hành INSERTS riêng lẻ, với mệnh đề WHERE có tính đến trường hợp IS NULL. Cách tiếp cận đó có lợi thế là các khóa chèn các hàng riêng lẻ ngắn hơn cho phương thức CHỌN CHỌN ... SELECT. Nhưng những khóa này chỉ dựa trên bản gốc bằng cách sử dụng sao chép hàng, và chúng ta có thể thay thế tất cả mã phía ứng dụng bằng một câu lệnh SQL đơn. – ryandenki

0

Thay đổi cột DEFAULT NULL thành DEFAULT 0, cho phép khớp UNIQUE KEY một cách nhất quán. Điều này có tác dụng phụ tiêu cực của quá phức tạp sự phát triển của các truy vấn đối với bảng tóm tắt. Nó buộc chúng ta phải sử dụng rất nhiều "CASE filter_id = 0 THEN NULL ELSE filter_id END", và làm cho việc tham gia khó xử vì tất cả các bảng khác có NULLs thực tế cho filter_id.

Tạo chế độ xem trả về "Bộ lọc CASE_id = 0 THEN NULL ELSE filter_id END" và sử dụng chế độ xem này thay vì trực tiếp bảng. Bảng tóm tắt chứa hàng trăm nghìn hàng và tôi được cho biết hiệu suất xem khá kém.

Xem hiệu suất trong MySQL 5.x sẽ ổn, vì chế độ xem không có gì ngoài thay thế số không bằng giá trị rỗng. Trừ khi bạn sử dụng tổng hợp/sắp xếp trong chế độ xem, hầu hết mọi truy vấn đối với chế độ xem sẽ được viết lại bởi trình tối ưu hóa truy vấn để chỉ cần nhấn vào bảng bên dưới.

Và tất nhiên, vì đây là FK, bạn sẽ phải tạo mục nhập trong bảng được tham chiếu với id là 0.

0

Với phiên bản hiện đại của MariaDB (trước đây là MySQL), upserts có thể được thực hiện đơn giản bằng cách chèn vào các câu lệnh cập nhật khóa trùng lặp nếu bạn đi với tuyến đường thay thế cột # 5. Thêm các cột được lưu trữ của MySQL hoặc các cột ảo liên tục của MariaDB để áp dụng ràng buộc duy nhất trên các trường nullable gián tiếp giữ dữ liệu vô nghĩa ra khỏi cơ sở dữ liệu để đổi lấy một số bloat.

ví dụ:

 
CREATE TABLE IF NOT EXISTS bar (
    id INT PRIMARY KEY AUTO_INCREMENT, 
    datebin DATE NOT NULL, 
    baz1_id INT DEFAULT NULL, 
    vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED, 
    baz2_id INT DEFAULT NULL, 
    vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED, 
    blam DOUBLE NOT NULL, 
    UNIQUE(datebin, vbaz1_id, vbaz2_id) 
); 

INSERT INTO bar (datebin, baz1_id, baz2_id, blam) 
    VALUES ('2016-06-01', null, null, 777) 
ON DUPLICATE KEY UPDATE 
    blam = VALUES(blam); 

Đối với MariaDB thay thế LƯU TRỮ với CHỈ ĐỊNH, chỉ mục yêu cầu phải kiên trì.

MySQL Generated Columns MariaDB Virtual Columns

Các vấn đề liên quan