Nhận tổng số gia tăng của một giá trị tổng hợp trong bảng đã tham gia


10

Tôi có hai bảng trong cơ sở dữ liệu MySQL 5.7.22: postsreasons. Mỗi hàng bài có và thuộc nhiều hàng lý do. Mỗi lý do có trọng số liên quan đến nó và do đó mỗi bài đăng có tổng trọng số liên quan đến nó.

Đối với mỗi lần tăng 10 điểm trọng lượng (ví dụ: 0, 10, 20, 30, v.v.), tôi muốn nhận được số lượng bài đăng có tổng trọng lượng nhỏ hơn hoặc bằng mức tăng đó. Tôi mong đợi kết quả cho điều đó trông giống như thế này:

 weight | post_count
--------+------------
      0 | 0
     10 | 5
     20 | 12
     30 | 18
    ... | ...
    280 | 20918
    290 | 21102
    ... | ...
   1250 | 118005
   1260 | 118039
   1270 | 118040

Tổng trọng lượng được phân phối xấp xỉ bình thường, với một vài giá trị rất thấp và một vài giá trị rất cao (tối đa hiện là 1277), nhưng phần lớn ở giữa. Chỉ có dưới 120.000 hàng trong postsvà khoảng 120 in reasons. Mỗi bài viết có trung bình 5 hoặc 6 lý do.

Các phần có liên quan của các bảng trông như thế này:

CREATE TABLE `posts` (
  id BIGINT PRIMARY KEY
);

CREATE TABLE `reasons` (
  id BIGINT PRIMARY KEY,
  weight INT(11) NOT NULL
);

CREATE TABLE `posts_reasons` (
  post_id BIGINT NOT NULL,
  reason_id BIGINT NOT NULL,
  CONSTRAINT fk_posts_reasons_posts (post_id) REFERENCES posts(id),
  CONSTRAINT fk_posts_reasons_reasons (reason_id) REFERENCES reasons(id)
);

Cho đến nay, tôi đã thử thả ID bài đăng và tổng trọng lượng vào một chế độ xem, sau đó tham gia chế độ xem đó vào chính nó để có được tổng số:

CREATE VIEW `post_weights` AS (
    SELECT 
        posts.id,
        SUM(reasons.weight) AS reason_weight
    FROM posts
    INNER JOIN posts_reasons ON posts.id = posts_reasons.post_id
    INNER JOIN reasons ON posts_reasons.reason_id = reasons.id
    GROUP BY posts.id
);

SELECT
    FLOOR(p1.reason_weight / 10) AS weight,
    COUNT(DISTINCT p2.id) AS cumulative
FROM post_weights AS p1
INNER JOIN post_weights AS p2 ON FLOOR(p2.reason_weight / 10) <= FLOOR(p1.reason_weight / 10)
GROUP BY FLOOR(p1.reason_weight / 10)
ORDER BY FLOOR(p1.reason_weight / 10) ASC;

Tuy nhiên, đó là chậm một cách khó tin - tôi để nó chạy trong 15 phút mà không bị chấm dứt, điều mà tôi không thể làm trong sản xuất.

Có cách nào hiệu quả hơn để làm điều này?

Trong trường hợp bạn muốn thử nghiệm toàn bộ dữ liệu, có thể tải xuống tại đây . Các tập tin là khoảng 60 MB, nó mở rộng đến khoảng 250 MB. Thay phiên, có 12.000 hàng trong một ý chính GitHub ở đây .

Câu trả lời:


8

Sử dụng các hàm hoặc biểu thức trong điều kiện THAM GIA thường là một ý tưởng tồi, tôi nói thường bởi vì một số trình tối ưu hóa có thể xử lý nó khá tốt và sử dụng các chỉ mục bằng mọi cách. Tôi sẽ đề nghị tạo một bảng cho trọng lượng. Cái gì đó như:

CREATE TABLE weights
( weight int not null primary key 
);

INSERT INTO weights (weight) VALUES (0),(10),(20),...(1270);

Hãy chắc chắn rằng bạn có các chỉ mục trên posts_reasons:

CREATE UNIQUE INDEX ... ON posts_reasons (reason_id, post_id);

Một truy vấn như:

SELECT w.weight
     , COUNT(1) as post_count
FROM weights w
JOIN ( SELECT pr.post_id, SUM(r.weight) as sum_weight     
       FROM reasons r
       JOIN posts_reasons pr
             ON r.id = pr.reason_id
       GROUP BY pr.post_id
     ) as x
    ON w.weight > x.sum_weight
GROUP BY w.weight;

Máy của tôi ở nhà có lẽ đã 5-6 tuổi, nó có CPU Intel (R) Core (TM) i5-3470 @ 3,20GHz và 8Gb ram.

uname -a Linux thùng rác 4.16.6-302.fc28.x86_64 # 1 SMP Thứ tư ngày 2 tháng 5 00:07:06 UTC 2018 x86_64 x86_64 x86_64 GNU / Linux

Tôi đã thử nghiệm chống lại:

https://drive.google.com/open?id=1q3HZXW_qIZ01gU-Krms7qMJW3GCsOUP5

MariaDB [test3]> select @@version;
+-----------------+
| @@version       |
+-----------------+
| 10.2.14-MariaDB |
+-----------------+
1 row in set (0.00 sec)


SELECT w.weight
     , COUNT(1) as post_count
FROM weights w
JOIN ( SELECT pr.post_id, SUM(r.weight) as sum_weight     
       FROM reasons r
       JOIN posts_reasons pr
             ON r.id = pr.reason_id
       GROUP BY pr.post_id
     ) as x
    ON w.weight > x.sum_weight
GROUP BY w.weight;

+--------+------------+
| weight | post_count |
+--------+------------+
|      0 |          1 |
|     10 |       2591 |
|     20 |       4264 |
|     30 |       4386 |
|     40 |       5415 |
|     50 |       7499 |
[...]   
|   1270 |     119283 |
|   1320 |     119286 |
|   1330 |     119286 |
[...]
|   2590 |     119286 |
+--------+------------+
256 rows in set (9.89 sec)

Nếu hiệu suất là quan trọng và không có gì khác giúp bạn có thể tạo một bảng tóm tắt cho:

SELECT pr.post_id, SUM(r.weight) as sum_weight     
FROM reasons r
JOIN posts_reasons pr
    ON r.id = pr.reason_id
GROUP BY pr.post_id

Bạn có thể duy trì bảng này thông qua kích hoạt

Vì có một số lượng công việc nhất định cần phải được thực hiện cho mỗi trọng lượng theo trọng lượng, có thể có ích để hạn chế bảng này.

    ON w.weight > x.sum_weight 
WHERE w.weight <= (select MAX(sum_weights) 
                   from (SELECT SUM(weight) as sum_weights 
                   FROM reasons r        
                   JOIN posts_reasons pr
                       ON r.id = pr.reason_id 
                   GROUP BY pr.post_id) a
                  ) 
GROUP BY w.weight

Vì tôi có rất nhiều hàng không cần thiết trong bảng cân nặng của mình (tối đa 2590), hạn chế ở trên đã cắt giảm thời gian thực hiện từ 9 xuống còn 4 giây.


Làm rõ: Có vẻ như đó là lý do đếm trọng lượng thấp hơn w.weight- có đúng không? Tôi đang tìm cách đếm các bài đăng có tổng trọng số (tổng trọng số của các hàng lý do liên quan của chúng) của lte w.weight.
ArtOfCode

Ồ xin lỗi. Tôi sẽ viết lại truy vấn
Lennart

Điều này đã cho tôi phần còn lại của con đường, mặc dù vậy, cảm ơn! Chỉ cần chọn từ post_weightschế độ xem hiện tại mà tôi đã tạo thay vì reasons.
ArtOfCode

@ArtOfCode, tôi đã hiểu đúng về truy vấn đã sửa đổi chưa? BTW, cảm ơn cho một câu hỏi tuyệt vời. Rõ ràng, súc tích và với rất nhiều dữ liệu mẫu. Bravo
Lennart

7

Trong MySQL, các biến có thể được sử dụng trong các truy vấn cả được tính từ các giá trị trong các cột và được sử dụng trong biểu thức cho các cột được tính toán mới. Trong trường hợp này, sử dụng một biến dẫn đến một truy vấn hiệu quả:

SELECT
  weight,
  @cumulative := @cumulative + post_count AS post_count
FROM
  (SELECT @cumulative := 0) AS x,
  (
    SELECT
      FLOOR(reason_weight / 10) * 10 AS weight,
      COUNT(*)                       AS post_count
    FROM
      (
        SELECT 
          p.id,
          SUM(r.weight) AS reason_weight
        FROM
          posts AS p
          INNER JOIN posts_reasons AS pr ON p.id = pr.post_id
          INNER JOIN reasons AS r ON pr.reason_id = r.id
        GROUP BY
          p.id
      ) AS d
    GROUP BY
      FLOOR(reason_weight / 10)
    ORDER BY
      FLOOR(reason_weight / 10) ASC
  ) AS derived
;

Bảng ddẫn xuất thực sự là post_weightsquan điểm của bạn . Do đó, nếu bạn dự định giữ chế độ xem, bạn có thể sử dụng nó thay vì bảng dẫn xuất:

SELECT
  weight,
  @cumulative := @cumulative + post_count AS post_count
FROM
  (SELECT @cumulative := 0),
  (
    SELECT
      FLOOR(reason_weight / 10) * 10 AS weight,
      COUNT(*)                       AS post_count
    FROM
      post_weights
    GROUP BY
      FLOOR(reason_weight / 10)
    ORDER BY
      FLOOR(reason_weight / 10) ASC
  ) AS derived
;

Bản demo của giải pháp này, sử dụng phiên bản ngắn gọn của phiên bản rút gọn của thiết lập của bạn, có thể được tìm thấy và chơi cùng với SQL Fiddle .


Tôi đã thử truy vấn của bạn với bộ dữ liệu đầy đủ. Tôi không chắc tại sao (truy vấn có vẻ ổn với tôi) nhưng MariaDB phàn nàn về việc ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BYnếu ONLY_FULL_GROUP_BYở trong @@ sql_mode. Vô hiệu hóa tôi nhận thấy rằng truy vấn của bạn chậm hơn so với lần đầu tiên tôi chạy (~ 11 giây). Khi dữ liệu được lưu trữ, nó sẽ nhanh hơn (~ 1 giây). Truy vấn của tôi chạy trong khoảng 4 giây mỗi lần.
Lennart

1
@Lennart: Đó là vì đó không phải là truy vấn thực tế. Tôi đã sửa nó trong fiddle nhưng quên cập nhật câu trả lời. Cập nhật nó bây giờ, cảm ơn cho các head-up.
Andriy M

@Lennart: Về hiệu suất, tôi có thể có một quan niệm sai lầm về loại truy vấn này. Tôi nghĩ rằng nó nên hoạt động hiệu quả bởi vì các tính toán sẽ được hoàn thành trong một lần vượt qua bảng. Có lẽ đó không nhất thiết là trường hợp với các bảng dẫn xuất, đặc biệt là các bảng sử dụng tổng hợp. Tuy nhiên, tôi e rằng tôi không có bản cài đặt MySQL phù hợp cũng như không đủ chuyên môn để phân tích sâu hơn.
Andriy M

@Andriy_M, có vẻ như là một lỗi trong phiên bản MariaDB của tôi. Nó không thích GROUP BY FLOOR(reason_weight / 10)nhưng chấp nhận GROUP BY reason_weight. Về hiệu năng, tôi chắc chắn không phải là một chuyên gia khi nói đến MySQL, nó chỉ là một quan sát trên cỗ máy xảo quyệt của tôi. Vì tôi đã chạy truy vấn của mình trước tiên nên tất cả dữ liệu đã được lưu vào bộ nhớ cache, vì vậy tôi không biết tại sao lần đầu tiên nó chạy chậm hơn.
Lennart
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.