सर्वोत्तम पठन प्रदर्शन के लिए आपको एक बहु-स्तंभ अनुक्रमणिका की आवश्यकता है:
CREATE INDEX log_combo_idx
ON log (user_id, log_date DESC NULLS LAST);
केवल अनुक्रमणिका स्कैन करने के लिए संभव है, अन्यथा आवश्यक नहीं कॉलम जोड़ें payload
एक कवरिंग इंडेक्स में INCLUDE
. के साथ क्लॉज (11 या बाद के संस्करण पोस्ट करें):
CREATE INDEX log_combo_covering_idx
ON log (user_id, log_date DESC NULLS LAST) INCLUDE (payload);
देखें:
- क्या PostgreSQL में इंडेक्स को कवर करने से जॉइन कॉलम में मदद मिलती है?
पुराने संस्करणों के लिए फ़ॉलबैक:
CREATE INDEX log_combo_covering_idx
ON log (user_id, log_date DESC NULLS LAST, payload);
क्यों DESC NULLS LAST
?
- दिनांक क्वेरी की श्रेणी में अप्रयुक्त अनुक्रमणिका
कुछ . के लिए पंक्तियाँ प्रति user_id
या छोटी टेबल DISTINCT ON
आमतौर पर सबसे तेज़ और सरल होता है:
- समूह द्वारा प्रत्येक समूह में पहली पंक्ति का चयन करें?
कई . के लिए पंक्तियाँ प्रति user_id
एक इंडेक्स स्किप स्कैन (या ढीला इंडेक्स स्कैन ) (बहुत) अधिक कुशल है। पोस्टग्रेज 12 तक इसे लागू नहीं किया गया है - पोस्टग्रेज 14 के लिए काम जारी है। लेकिन इसे कुशलता से अनुकरण करने के तरीके हैं।
सामान्य तालिका अभिव्यक्तियों के लिए पोस्टग्रेज की आवश्यकता होती है 8.4+ .LATERAL
पोस्टग्रेज की आवश्यकता है 9.3+ .
निम्न समाधान Postgres Wiki में शामिल किए गए समाधान से आगे जाते हैं ।
एक अलग users
के साथ तालिका, समाधान 2. . में नीचे आमतौर पर सरल और तेज़ होते हैं। आगे बढ़ें।
1a. LATERAL
के साथ पुनरावर्ती सीटीई शामिल हों
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT user_id, log_date, payload
FROM log
WHERE log_date <= :mydate
ORDER BY user_id, log_date DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT l.*
FROM cte c
CROSS JOIN LATERAL (
SELECT l.user_id, l.log_date, l.payload
FROM log l
WHERE l.user_id > c.user_id -- lateral reference
AND log_date <= :mydate -- repeat condition
ORDER BY l.user_id, l.log_date DESC NULLS LAST
LIMIT 1
) l
)
TABLE cte
ORDER BY user_id;
मनमाना कॉलम पुनर्प्राप्त करना आसान है और शायद वर्तमान पोस्टग्रेस में सबसे अच्छा है। अध्याय 2a. . में अधिक स्पष्टीकरण नीचे।
<एच4>1बी. सहसंबद्ध सबक्वेरी के साथ पुनरावर्ती सीटीईWITH RECURSIVE cte AS (
( -- parentheses required
SELECT l AS my_row -- whole row
FROM log l
WHERE log_date <= :mydate
ORDER BY user_id, log_date DESC NULLS LAST
LIMIT 1
)
UNION ALL
SELECT (SELECT l -- whole row
FROM log l
WHERE l.user_id > (c.my_row).user_id
AND l.log_date <= :mydate -- repeat condition
ORDER BY l.user_id, l.log_date DESC NULLS LAST
LIMIT 1)
FROM cte c
WHERE (c.my_row).user_id IS NOT NULL -- note parentheses
)
SELECT (my_row).* -- decompose row
FROM cte
WHERE (my_row).user_id IS NOT NULL
ORDER BY (my_row).user_id;
एकल कॉलम retrieve को पुनः प्राप्त करने के लिए सुविधाजनक या पूरी पंक्ति . उदाहरण तालिका की संपूर्ण पंक्ति प्रकार का उपयोग करता है। अन्य प्रकार संभव हैं।
यह सुनिश्चित करने के लिए कि पिछले पुनरावृत्ति में एक पंक्ति पाई गई थी, एक एकल NOT NULL कॉलम (प्राथमिक कुंजी की तरह) का परीक्षण करें।
इस प्रश्न के लिए अध्याय 2ख में अधिक स्पष्टीकरण। नीचे।
संबंधित:
- प्रति पंक्ति अंतिम N संबंधित पंक्तियों को क्वेरी करें
- एक कॉलम के आधार पर ग्रुप करें, जबकि PostgreSQL में दूसरे कॉलम को सॉर्ट करें
users
. के साथ टेबल
तालिका लेआउट शायद ही तब तक मायने रखता है जब तक कि प्रति प्रासंगिक user_id
. में ठीक एक पंक्ति हो यह गारंटीशुदा है। उदाहरण:
CREATE TABLE users (
user_id serial PRIMARY KEY
, username text NOT NULL
);
आदर्श रूप से, तालिका को log
. के साथ सिंक में भौतिक रूप से क्रमबद्ध किया जाता है टेबल। देखें:
- पोस्टग्रेज टाइमस्टैम्प क्वेरी श्रेणी को ऑप्टिमाइज़ करें
या यह काफी छोटा है (कम कार्डिनैलिटी) कि यह शायद ही मायने रखता है। अन्यथा, क्वेरी में पंक्तियों को क्रमबद्ध करने से प्रदर्शन को और अधिक अनुकूलित करने में मदद मिल सकती है। गैंग लिआंग का जोड़ देखें। यदि users
. का भौतिक क्रम तालिका log
पर अनुक्रमणिका से मेल खाने के लिए होती है , यह अप्रासंगिक हो सकता है।
2a. LATERAL
शामिल हों
SELECT u.user_id, l.log_date, l.payload
FROM users u
CROSS JOIN LATERAL (
SELECT l.log_date, l.payload
FROM log l
WHERE l.user_id = u.user_id -- lateral reference
AND l.log_date <= :mydate
ORDER BY l.log_date DESC NULLS LAST
LIMIT 1
) l;
JOIN LATERAL
FROM
. से पहले के संदर्भ की अनुमति देता है समान क्वेरी स्तर पर आइटम। देखें:
- लेटरल जॉइन और PostgreSQL में सबक्वेरी में क्या अंतर है?
प्रति उपयोगकर्ता एक अनुक्रमणिका (-केवल) लुक-अप में परिणाम।
users
. में अनुपलब्ध उपयोगकर्ताओं के लिए कोई पंक्ति नहीं देता है टेबल। आमतौर पर, एक विदेशी कुंजी संदर्भात्मक अखंडता को लागू करने वाली बाधा इसे खारिज कर देगी।
साथ ही, log
. में मिलान प्रविष्टि के बिना उपयोगकर्ताओं के लिए कोई पंक्ति नहीं - मूल प्रश्न के अनुरूप। उन उपयोगकर्ताओं को परिणाम में रखने के लिए LEFT JOIN LATERAL ... ON true
का उपयोग करें इसके बजाय CROSS JOIN LATERAL
:
- एक सरणी तर्क के साथ एक सेट-रिटर्निंग फ़ंक्शन को कई बार कॉल करें
LIMIT n
. का प्रयोग करें LIMIT 1
. के बजाय एक से अधिक पंक्तियों को पुनः प्राप्त करने के लिए (लेकिन सभी नहीं) प्रति उपयोगकर्ता।
प्रभावी रूप से, ये सभी ऐसा ही करते हैं:
JOIN LATERAL ... ON true
CROSS JOIN LATERAL ...
, LATERAL ...
हालांकि, पिछले वाले की प्राथमिकता कम है। स्पष्ट JOIN
अल्पविराम से पहले बांधता है। अधिक जुड़ने वाली तालिकाओं के साथ वह सूक्ष्म अंतर मायने रखता है। देखें:
- पोस्टग्रेज क्वेरी में "तालिका के लिए FROM-खंड प्रविष्टि का अमान्य संदर्भ"
एकल कॉलम retrieve को पुनः प्राप्त करने का अच्छा विकल्प एक एकल पंक्ति . से . कोड उदाहरण:
- समूहवार अधिकतम क्वेरी अनुकूलित करें
एकाधिक कॉलम . के लिए भी ऐसा ही संभव है , लेकिन आपको अधिक स्मार्ट की आवश्यकता है:
CREATE TEMP TABLE combo (log_date date, payload int);
SELECT user_id, (combo1).* -- note parentheses
FROM (
SELECT u.user_id
, (SELECT (l.log_date, l.payload)::combo
FROM log l
WHERE l.user_id = u.user_id
AND l.log_date <= :mydate
ORDER BY l.log_date DESC NULLS LAST
LIMIT 1) AS combo1
FROM users u
) sub;
जैसे LEFT JOIN LATERAL
ऊपर, इस संस्करण में सभी . शामिल हैं उपयोगकर्ता, log
. में प्रविष्टियों के बिना भी . आपको NULL
मिलता है combo1
. के लिए , जिसे आप WHERE
. से आसानी से फ़िल्टर कर सकते हैं यदि आवश्यक हो तो बाहरी क्वेरी में क्लॉज। आपको एक NOT NULL
की आवश्यकता है इस अस्पष्टता से बचने के लिए सबक्वेरी में कॉलम।
एक सहसंबद्ध उपश्रेणी केवल एक एकल मान return लौटा सकती है . आप कई स्तंभों को एक समग्र प्रकार में लपेट सकते हैं। लेकिन बाद में इसे विघटित करने के लिए, पोस्टग्रेस एक प्रसिद्ध समग्र प्रकार की मांग करता है। अनाम रिकॉर्ड को केवल एक कॉलम परिभाषा सूची प्रदान करके विघटित किया जा सकता है।
एक पंजीकृत प्रकार का उपयोग करें जैसे किसी मौजूदा तालिका के पंक्ति प्रकार का उपयोग करें। या CREATE TYPE
. के साथ एक कंपोजिट टाइप को स्पष्ट रूप से (और स्थायी रूप से) रजिस्टर करें . या अस्थायी रूप से अपनी पंक्ति प्रकार को पंजीकृत करने के लिए एक अस्थायी तालिका (सत्र के अंत में स्वचालित रूप से गिरा दी गई) बनाएं। कास्ट सिंटैक्स:(log_date, payload)::combo
अंत में, हम combo1
. को विघटित नहीं करना चाहते हैं एक ही क्वेरी स्तर पर। क्वेरी प्लानर में कमजोरी के कारण यह प्रत्येक कॉलम के लिए एक बार सबक्वायरी का मूल्यांकन करेगा (अभी भी पोस्टग्रेस 12 में सच है)। इसके बजाय, इसे एक सबक्वेरी बनाएं और बाहरी क्वेरी में विघटित करें।
संबंधित:
- प्रति समूह पहली और अंतिम पंक्ति से मान प्राप्त करें
100k लॉग प्रविष्टियों और 1k उपयोगकर्ताओं के साथ सभी 4 प्रश्नों को प्रदर्शित करना:
db<>fiddle here - पृष्ठ 11
<उप>पुराना sqlfiddle