सम्मिलित तालिका में कुल मूल्य का वृद्धिशील अंक प्राप्त करें


10

मेरे पास एक MySQL 5.7.22 डेटाबेस में दो टेबल हैं: postsऔर reasons। प्रत्येक पोस्ट पंक्ति में और कई कारण पंक्तियों के अंतर्गत आता है। प्रत्येक कारण में इसके साथ एक भार जुड़ा होता है, और प्रत्येक पद का कुल एकत्र वजन होता है।

वजन के 10 अंकों (यानी 0, 10, 20, 30 आदि) के प्रत्येक वेतन वृद्धि के लिए, मैं उन पदों की गिनती प्राप्त करना चाहता हूं जिनका कुल वजन उस वेतन वृद्धि से कम या बराबर है। मुझे उम्मीद है कि परिणाम कुछ इस तरह दिखेंगे:

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

कुल वज़न लगभग सामान्य रूप से वितरित किए जाते हैं, कुछ बहुत कम मूल्यों के साथ और कुछ बहुत उच्च मूल्यों (अधिकतम वर्तमान में 1277), लेकिन बीच में बहुमत। 120,000 पंक्तियों के नीचे postsऔर 120 के आसपास बस हैं reasons। प्रत्येक पोस्ट में औसतन 5 या 6 कारण होते हैं।

तालिकाओं के संबंधित भाग इस तरह दिखते हैं:

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)
);

अब तक, मैंने पोस्ट आईडी और कुल वजन को एक दृश्य में छोड़ने की कोशिश की है , फिर एक समग्र गणना प्राप्त करने के लिए खुद को उस दृश्य में शामिल कर रहा हूं :

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;

हालांकि, यह असामान्य रूप से धीमा है - मैंने इसे समाप्त किए बिना 15 मिनट तक चलने दिया, जो मैं उत्पादन में नहीं कर सकता।

क्या ऐसा करने का अधिक कुशल तरीका है?

यदि आप संपूर्ण डेटासेट के परीक्षण में रुचि रखते हैं, तो यह यहाँ डाउनलोड करने योग्य है । फ़ाइल 60 एमबी के आसपास है, यह लगभग 250 एमबी तक फैलती है। वैकल्पिक रूप से, यहाँ एक GitHub gist में 12,000 पंक्तियाँ हैं

जवाबों:


8

JOIN परिस्थितियों में कार्यों या अभिव्यक्तियों का उपयोग करना आमतौर पर एक बुरा विचार है, मैं आमतौर पर कहता हूं क्योंकि कुछ आशावादी इसे काफी अच्छी तरह से संभाल सकते हैं और किसी भी तरह अनुक्रमित कर सकते हैं। मेरा सुझाव है कि वज़न के लिए एक तालिका बनाएं। कुछ इस तरह:

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

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

सुनिश्चित करें कि आपके पास अनुक्रमित हैं posts_reasons:

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

एक क्वेरी की तरह:

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;

घर पर मेरी मशीन शायद 5-6 साल पुरानी है, इसमें Intel (R) Core (TM) i5-3470 CPU @ 3.20GHz और 8Gb का RAM है।

uname -a Linux डस्टबाइट 4.16.6-302.fc28.x86_64 # 1 एसएमपी बुध 2 मई 00:07:06 UTC 2018 x86_64 x86_64 x86_64 GNU / Linux

मैंने इसके खिलाफ परीक्षण किया:

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)

यदि प्रदर्शन महत्वपूर्ण है और कुछ नहीं तो आप इसके लिए सारांश तालिका बना सकते हैं:

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

आप इस तालिका को ट्रिगर के माध्यम से बनाए रख सकते हैं

चूंकि वजन में प्रत्येक वजन के लिए कुछ निश्चित कार्य करने की आवश्यकता होती है, इसलिए इस तालिका को सीमित करना फायदेमंद हो सकता है।

    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

चूँकि मेरी भार तालिका (अधिकतम 2590) में बहुत सारी अप्राकृतिक पंक्तियाँ थीं, इसलिए ऊपर दिए गए प्रतिबंध ने निष्पादन समय को 9 से 4 सेकंड तक काट दिया।


स्पष्टता: ऐसा लगता है कि यह वजन कम होने के कारणों को गिन रहा है w.weight- क्या यह सही है? मैं लेटे के कुल वजन (उनके संबंधित कारण पंक्तियों के वजन का योग) के साथ पदों की गणना करना चाहता हूं w.weight
आर्टऑफकोड

आह क्षमा करें। मैं प्रश्न को फिर से
लिखूंगा

यह मुझे बाकी रास्ता मिल गया, हालांकि, धन्यवाद! बस उस मौजूदा post_weightsदृश्य से चयन करने की आवश्यकता है जिसे मैंने पहले से बनाया था reasons
आर्टऑफकोड

@ArtOfCode, क्या मुझे यह संशोधित क्वेरी के लिए सही लगा? BTW, एक उत्कृष्ट प्रश्न के लिए धन्यवाद। स्पष्ट, संक्षिप्त और बहुत से नमूना डेटा के साथ। ब्रावो
लेनार्ट

7

MySQL में, वैरिएबल का उपयोग प्रश्नों में किया जा सकता है, दोनों को कॉलम में मानों से परिकलित किया जाता है और नए, परिकलित स्तंभों के लिए अभिव्यक्ति में उपयोग किया जा सकता है। इस मामले में, एक कुशल क्वेरी में एक चर परिणाम का उपयोग करते हुए:

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
;

dव्युत्पन्न तालिका वास्तव में अपने है post_weightsदृश्य। इसलिए, यदि आप दृश्य रखने की योजना बना रहे हैं, तो आप व्युत्पन्न तालिका के बजाय इसका उपयोग कर सकते हैं:

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
;

इस समाधान का एक डेमो, जो आपके सेटअप के कम किए गए संस्करण के संक्षिप्त संस्करण का उपयोग करता है , SQL फिडेल के साथ पाया और खेला जा सकता है ।


मैंने पूरे डेटा सेट के साथ आपकी क्वेरी की कोशिश की। मुझे यकीन नहीं है कि (क्वेरी मुझे ठीक क्यों लगती है) लेकिन मारियाडीबी के बारे में शिकायत है कि ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BYअगर ONLY_FULL_GROUP_BY@@ sql_mode में है। इसे अक्षम करने पर मैंने देखा कि आपकी क्वेरी मेरे द्वारा पहली बार चलने की तुलना में धीमी है (~ 11 सेकंड)। एक बार डेटा कैश होने के बाद यह तेज (~ 1 सेकंड) होता है। मेरी क्वेरी हर बार लगभग 4 सेकंड में चलती है।
लेनार्ट

1
@ लेनरार्ट: ऐसा इसलिए है क्योंकि यह वास्तविक क्वेरी नहीं है। मैंने इसे फिडेल में ठीक किया लेकिन उत्तर को अपडेट करना भूल गया। अब इसे अपडेट करना, हेड-अप के लिए धन्यवाद।
एंड्री एम

@ लेन्नर्ट: प्रदर्शन के रूप में, मुझे इस प्रकार के प्रश्न के बारे में गलत धारणा हो सकती है। मैंने सोचा कि इसे कुशलता से काम करना चाहिए क्योंकि गणना तालिका में एक पास में पूरी होगी। शायद यह जरूरी नहीं है कि व्युत्पन्न तालिकाओं के मामले में, विशेष रूप से जो एकत्रीकरण का उपयोग करते हैं। मुझे डर है कि मेरे पास गहराई का विश्लेषण करने के लिए न तो उचित MySQL इंस्टॉलेशन है और न ही पर्याप्त विशेषज्ञता।
एंड्री एम

@Andriy_M, यह मेरे MariaDB संस्करण में एक बग प्रतीत होता है। यह पसंद नहीं है GROUP BY FLOOR(reason_weight / 10)लेकिन स्वीकार करता है GROUP BY reason_weight। प्रदर्शन के लिए, मैं निश्चित रूप से एक विशेषज्ञ नहीं हूं जब यह MySQL की बात आती है, तो यह सिर्फ मेरे भद्दे मशीन पर एक अवलोकन था। चूंकि मैंने अपनी क्वेरी पहली बार चलाई थी, इसलिए सभी डेटा पहले से ही कैश होना चाहिए था, इसलिए मुझे नहीं पता कि यह पहली बार धीमा क्यों था।
Lennart
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.