SQL ज्वाइन: एक-से-कई संबंधों में अंतिम रिकॉर्ड का चयन करना


298

मान लीजिए कि मेरे पास ग्राहकों की एक तालिका है और खरीदारी की एक तालिका है। प्रत्येक खरीद एक ग्राहक की है। मैं एक सेलेक्ट स्टेटमेंट में अपनी अंतिम खरीद के साथ सभी ग्राहकों की एक सूची प्राप्त करना चाहता हूं। सबसे अच्छा अभ्यास क्या है? सूचकांक बनाने पर कोई सलाह?

कृपया अपने उत्तर में इन तालिका / स्तंभ नामों का उपयोग करें:

  • ग्राहक: आईडी, नाम
  • खरीद: आईडी, customer_id, item_id, दिनांक

और अधिक जटिल स्थितियों में, क्या ग्राहक की तालिका में अंतिम खरीद डालकर डेटाबेस को निरूपित करना (प्रदर्शन-वार) फायदेमंद होगा?

यदि खरीद (खरीद) आईडी को तिथि के अनुसार क्रमबद्ध करने की गारंटी दी जाती है, तो क्या कुछ का उपयोग करके बयानों को सरल बनाया जा सकता है LIMIT 1?


हां, यह मूल्यहीन करने के लायक हो सकता है (यदि यह प्रदर्शन में बहुत सुधार करता है, जिसे आप केवल दोनों संस्करणों का परीक्षण करके पता लगा सकते हैं)। लेकिन अपभ्रंश के पतन आमतौर पर टालने लायक होते हैं।
विंस बॉड्रेन 21:10

जवाबों:


449

यह greatest-n-per-groupStackOverflow पर नियमित रूप से दिखाई देने वाली समस्या का एक उदाहरण है ।

यहां बताया गया है कि मैं आमतौर पर इसे हल करने की सलाह देता हूं:

SELECT c.*, p1.*
FROM customer c
JOIN purchase p1 ON (c.id = p1.customer_id)
LEFT OUTER JOIN purchase p2 ON (c.id = p2.customer_id AND 
    (p1.date < p2.date OR (p1.date = p2.date AND p1.id < p2.id)))
WHERE p2.id IS NULL;

स्पष्टीकरण: एक पंक्ति दी गई है p1, p2उसी ग्राहक और बाद की तारीख के साथ कोई पंक्ति नहीं होनी चाहिए (या संबंधों के मामले में, बाद में id)। जब हमें पता चलता है कि यह सच है, तो p1उस ग्राहक के लिए सबसे हाल की खरीदारी है।

अनुक्रमित के बारे में, मैं में एक यौगिक सूचकांक बनाएंगे, purchaseस्तंभों पर ( customer_id, date, id)। यह एक कवरिंग इंडेक्स का उपयोग करके बाहरी जुड़ने की अनुमति दे सकता है। अपने प्लेटफ़ॉर्म पर परीक्षण करना सुनिश्चित करें, क्योंकि अनुकूलन कार्यान्वयन-निर्भर है। अनुकूलन योजना का विश्लेषण करने के लिए अपने RDBMS की सुविधाओं का उपयोग करें। जैसे EXPLAINMySQL पर।


कुछ लोग उपर्युक्त समाधान का उपयोग करते हैं जो मैं ऊपर दिखाता हूं, लेकिन मुझे लगता है कि मेरे समाधान से संबंधों को हल करना आसान हो जाता है।


3
अनुकूल रूप से, सामान्य रूप से। लेकिन यह आपके द्वारा उपयोग किए जाने वाले डेटाबेस के ब्रांड और आपके डेटाबेस में डेटा की मात्रा और वितरण पर निर्भर करता है। एक सटीक उत्तर प्राप्त करने का एकमात्र तरीका यह है कि आप अपने डेटा के विरुद्ध दोनों समाधानों का परीक्षण करें।
बिल कार्विन 21:10

27
यदि आप उन ग्राहकों को शामिल करना चाहते हैं जिन्होंने कभी खरीदारी नहीं की है, तो JOIN खरीद p1 ON (c.id = p1.customer_id) को LEFT JOIN खरीद p1 ON (c.id = p1.customer_id) पर करें
गॉर्डन

5
@rdsds, आपको टाई को हल करने के लिए उपयोग किए जाने वाले कुछ अनूठे कॉलम की आवश्यकता होगी। यह संबंधपरक डेटाबेस में दो समान पंक्तियों के होने का कोई मतलब नहीं है।
बिल कारविन

6
"WHERE P2.id IS NULL" का उद्देश्य क्या है?
क्लू

3
यह समाधान केवल तभी काम करता है, जब 1 से अधिक खरीद रिकॉर्ड हों। ist वहाँ 1: 1 लिंक है, यह काम नहीं करता है। वहां इसे "WHERE (P2.id IS NULL or p1.id = P2.id) होना है
ब्रूनो जेनरिच

126

आप उप-चयन का उपयोग करके भी ऐसा करने का प्रयास कर सकते हैं

SELECT  c.*, p.*
FROM    customer c INNER JOIN
        (
            SELECT  customer_id,
                    MAX(date) MaxDate
            FROM    purchase
            GROUP BY customer_id
        ) MaxDates ON c.id = MaxDates.customer_id INNER JOIN
        purchase p ON   MaxDates.customer_id = p.customer_id
                    AND MaxDates.MaxDate = p.date

चयन सभी ग्राहकों और उनकी अंतिम खरीद तिथि में शामिल होना चाहिए ।


4
धन्यवाद यह सिर्फ मुझे बचाया - यह समाधान और अधिक व्यावहारिक और बनाए रखने के लिए लगता है तो दूसरों को सूचीबद्ध + इसके उत्पाद विशिष्ट नहीं
डेवियो

अगर मैं कोई ग्राहक नहीं खरीदना चाहता था, तो मैं इसे कैसे संशोधित करूंगा?
क्लू

3
@clu: बदलें INNER JOINएक करने के लिए LEFT OUTER JOIN
साशा चोडगोव

3
ऐसा लगता है कि उस दिन केवल एक खरीद है। अगर दो थे तो आपको एक ग्राहक के लिए दो आउटपुट पंक्तियाँ मिलेंगी, मुझे लगता है?
Artfulrobot

1
@IstiaqueAhmed - अंतिम INNER JOIN उस अधिकतम (दिनांक) मान को लेता है और उसे वापस स्रोत तालिका में जोड़ता है। इसके साथ जुड़ने के बिना, purchaseतालिका से आपके पास एकमात्र जानकारी दिनांक और customer_id होगी, लेकिन क्वेरी तालिका से सभी फ़ील्ड के लिए पूछती है।
लाफिंग वर्गिल

26

आपने डेटाबेस निर्दिष्ट नहीं किया है। यदि यह एक ऐसा है जो विश्लेषणात्मक कार्यों की अनुमति देता है तो ग्रुप बी वन की तुलना में इस दृष्टिकोण का उपयोग करना तेज हो सकता है (निश्चित रूप से ओरेकल में तेज, सबसे संभावित रूप से देर से SQL सर्वर संस्करणों में, दूसरों के बारे में नहीं जानते हैं)।

SQL सर्वर में सिंटैक्स होगा:

SELECT c.*, p.*
FROM customer c INNER JOIN 
     (SELECT RANK() OVER (PARTITION BY customer_id ORDER BY date DESC) r, *
             FROM purchase) p
ON (c.id = p.customer_id)
WHERE p.r = 1

10
यह प्रश्न का गलत उत्तर है क्योंकि आप "ROW_NUMBER ()" के बजाय "RANK ()" का उपयोग कर रहे हैं। आरआईसी आपको अभी भी संबंधों की एक ही समस्या देगा जब दो खरीद में एक ही तारीख होगी। यही रैंकिंग कार्य करता है; यदि शीर्ष 2 का मिलान होता है, तो वे दोनों को 1 का मान दिया जाता है और 3rd रिकॉर्ड को 3. का मान मिलता है। Row_Number के साथ, कोई टाई नहीं है, यह पूरे विभाजन के लिए अद्वितीय है।
माइक्टिवेई

4
मैडलिना के दृष्टिकोण के खिलाफ बिल कारविन के दृष्टिकोण की कोशिश करना, निष्पादन योजनाओं के साथ एसक्यूएल सर्वर 2008 के तहत सक्षम होना मैंने पाया कि बिल कारविन के मूल्यांकन में मैडलिना के दृष्टिकोण के विपरीत 43% की क्वेरी लागत थी, जिसका उपयोग 57% था - इसलिए इस उत्तर के अधिक सुरुचिपूर्ण वाक्यविन्यास के बावजूद, मैंने अभी भी बिल के पक्ष में होगा!
शॉसन

26

एक और तरीका यह होगा कि आप NOT EXISTSबाद की खरीद के लिए परीक्षण करने के लिए अपनी सम्मिलित स्थिति में एक शर्त का उपयोग करें:

SELECT *
FROM customer c
LEFT JOIN purchase p ON (
       c.id = p.customer_id
   AND NOT EXISTS (
     SELECT 1 FROM purchase p1
     WHERE p1.customer_id = c.id
     AND p1.id > p.id
   )
)

क्या आप AND NOT EXISTSभाग को आसान शब्दों में समझा सकते हैं ?
इस्तियाक अहमद

उप चयन करता है कि अगर कोई उच्च आईडी के साथ पंक्ति है। आपको केवल अपने परिणाम सेट में एक पंक्ति मिल जाएगी, यदि कोई भी उच्च आईडी नहीं मिलती है। यह अद्वितीय उच्चतम होना चाहिए।
स्टीफन हैबरल

2
यह मेरे लिए सबसे पठनीय समाधान है। अगर यह महत्वपूर्ण है।
मेघालय

:) धन्यवाद। मैं हमेशा सबसे पठनीय समाधान के लिए प्रयास करता हूं, क्योंकि यह महत्वपूर्ण है।
स्टीफन हैबरल

19

मैंने इस धागे को अपनी समस्या के समाधान के रूप में पाया।

लेकिन जब मैंने कोशिश की तो उनका प्रदर्शन कम था। बेहतर प्रदर्शन के लिए बोलो मेरा सुझाव है।

With MaxDates as (
SELECT  customer_id,
                MAX(date) MaxDate
        FROM    purchase
        GROUP BY customer_id
)

SELECT  c.*, M.*
FROM    customer c INNER JOIN
        MaxDates as M ON c.id = M.customer_id 

आशा है कि यह मददगार होगा।


केवल 1 का उपयोग करने के लिए top 1और ordered it byMaxDatedesc
ओमर

1
यह आसान और सीधा समाधान है, मेरे मामले में (कई ग्राहक, कुछ खरीदारी) 10% तेजी से तब @Stefan Haberl का समाधान और स्वीकृत उत्तर की तुलना में 10 गुना अधिक बेहतर है
जुराज बेजुर्का

इस समस्या को हल करने के लिए सामान्य टेबल एक्सप्रेशन (CTE) का उपयोग करते हुए शानदार सुझाव। इसने कई स्थितियों में प्रश्नों के प्रदर्शन में नाटकीय रूप से सुधार किया है।
AdamsTips

सर्वश्रेष्ठ उत्तर imo, पढ़ने में आसान, मैक्स () खंड के आदेश द्वारा + सीमा 1 comparted शानदार प्रदर्शन देता है
MRJ

10

यदि आप PostgreSQL का उपयोग कर रहे हैं, तो आप DISTINCT ONसमूह में पहली पंक्ति खोजने के लिए उपयोग कर सकते हैं ।

SELECT customer.*, purchase.*
FROM customer
JOIN (
   SELECT DISTINCT ON (customer_id) *
   FROM purchase
   ORDER BY customer_id, date DESC
) purchase ON purchase.customer_id = customer.id

PostgreSQL डॉक्स - डिस्टिंक्ट ऑन

ध्यान दें कि DISTINCT ONफ़ील्ड (s) - यहाँ customer_id- ORDER BYक्लॉज़ में बाएं सबसे अधिक फ़ील्ड (s) से मेल खाना चाहिए ।

कैविएट: यह एक अमानक क्लॉज है।


8

यह कोशिश करो, यह मदद करेगा।

मैंने अपने प्रोजेक्ट में इसका इस्तेमाल किया है।

SELECT 
*
FROM
customer c
OUTER APPLY(SELECT top 1 * FROM purchase pi 
WHERE pi.customer_id = c.Id order by pi.Id desc) AS [LastPurchasePrice]

उर्फ "पी" कहां से आता है?
टिआगो

यह अच्छा प्रदर्शन नहीं करता है .... हमेशा के लिए जहां अन्य उदाहरणों ने मेरे पास मौजूद डेटा सेट पर 2 सेकंड का समय लिया ....
जोएल_जैग

3

SQLite पर परीक्षण किया गया:

SELECT c.*, p.*, max(p.date)
FROM customer c
LEFT OUTER JOIN purchase p
ON c.id = p.customer_id
GROUP BY c.id

max()समेकित फ़ंक्शन सुनिश्चित करें कि नवीनतम खरीद प्रत्येक समूह से चयन किया जाता है (- जो आम तौर पर मामला है लेकिन यह मानता है कि तारीख स्तंभ एक प्रारूप में है जिसके तहत अधिकतम () नवीनतम देता है) कर देगा। अगर आप उसी तारीख से खरीदारी करना चाहते हैं तो आप उपयोग कर सकते हैं max(p.date, p.id)

अनुक्रमणिका के संदर्भ में, मैं खरीद के साथ एक सूचकांक का उपयोग करेगा (customer_id, तिथि, [आपके चयन में वापस आने के लिए कोई अन्य खरीद कॉलम]]।

LEFT OUTER JOIN(के रूप में करने का विरोध किया INNER JOIN) यह सुनिश्चित करें कि ग्राहकों को खरीदारी करने वाले कभी नहीं किया है भी शामिल किए गए हैं कर देगा।


t-sql में सेलेक्ट c के रूप में रन न करें। * कॉलम में समूह में क्लॉज नहीं है
Joel_J

1

कृपया यह प्रयास करें,

SELECT 
c.Id,
c.name,
(SELECT pi.price FROM purchase pi WHERE pi.Id = MAX(p.Id)) AS [LastPurchasePrice]
FROM customer c INNER JOIN purchase p 
ON c.Id = p.customerId 
GROUP BY c.Id,c.name;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.