समूह द्वारा प्रत्येक ग्रुप में पहली पंक्ति का चयन करें?


1320

जैसा कि शीर्षक से पता चलता है, मैं एक के साथ समूहीकृत पंक्तियों के प्रत्येक सेट की पहली पंक्ति का चयन करना चाहता हूं GROUP BY

विशेष रूप से, अगर मुझे एक purchasesतालिका मिली है जो इस तरह दिखती है:

SELECT * FROM purchases;

मेरा आउटपुट:

आईडी | ग्राहक | संपूर्ण
--- + ---------- + ------
 1 | जो | 5
 2 | सैली | 3
 3 | जो | 2
 4 | सैली | 1

मैं प्रत्येक द्वारा की गई idसबसे बड़ी खरीद ( total) के लिए क्वेरी करना चाहूंगा customer। कुछ इस तरह:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

अपेक्षित उत्पादन:

FIRST (आईडी) | ग्राहक | सबसे पहले (कुल)
---------- + ---------- + -------------
        1 | जो | 5
        2 | सैली | 3

चूँकि आप केवल प्रत्येक सबसे बड़े की तलाश कर रहे हैं, तो क्वेरी के लिए क्यों नहीं MAX(total)?
दर्शन 294

4
@ phil294 अधिकतम (कुल) के लिए क्वेरी पंक्ति के 'आईडी' मान के साथ उस कुल को संबद्ध नहीं करेगी, जिस पर यह हुआ था।
गुरुद्वारा

जवाबों:


1113

Oracle 9.2+ पर (8i + जैसा कि मूल रूप से कहा गया है), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rk
      FROM PURCHASES p)
SELECT s.*
  FROM summary s
 WHERE s.rk = 1

किसी भी डेटाबेस द्वारा समर्थित:

लेकिन आपको संबंधों को तोड़ने के लिए तर्क जोड़ने की जरूरत है:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

2
Informix 12.x भी विंडो फ़ंक्शंस का समर्थन करता है (CTE को एक व्युत्पन्न तालिका में परिवर्तित करने की आवश्यकता है)। और फायरबर्ड 3.0 भी विंडो फ़ंक्शंस का समर्थन करेगा
a_horse_with_no_name

37
ROW_NUMBER() OVER(PARTITION BY [...])कुछ अन्य अनुकूलन के साथ मुझे कुछ सेकंड से लेकर कुछ मिलीसेकंड तक एक क्वेरी प्राप्त करने में मदद मिली। धन्यवाद! (PostgreSQL 9.2)
सैम

8
यदि totalएक ग्राहक के लिए समान रूप से उच्चतम के साथ कई खरीद हैं , तो 1 क्वेरी एक मनमाना विजेता (कार्यान्वयन विवरण के आधार पर id, हर निष्पादन के लिए बदल सकती है!) लौटाती है । आमतौर पर (हमेशा नहीं) आप प्रति ग्राहक एक पंक्ति चाहते हैं , जिसे "सबसे छोटे के साथ एक" जैसे अतिरिक्त मानदंडों द्वारा परिभाषित किया गया है id। ठीक करने के लिए, idकी ORDER BYसूची में संलग्न करें row_number()। फिर आपको दूसरी क्वेरी के समान परिणाम मिलता है , जो इस मामले के लिए बहुत ही अक्षम है । इसके अलावा, आपको हर अतिरिक्त कॉलम के लिए एक और सबक्वेरी की आवश्यकता होगी।
एरविन ब्रान्डसेट्टर

2
Google का BigQuery पहली क्वेरी के ROW_NUMBER () कमांड का भी समर्थन करता है। हमारे लिए एक आकर्षण की तरह काम किया
Praxiteles

2
ध्यान दें कि विंडो फ़ंक्शन के साथ पहला संस्करण SQLite संस्करण 3.25.0 के रूप में काम करता है: sqlite.org/windowfunctions.html#history
branz

1146

में PostgreSQL इस आम तौर पर है सरल और तेजी से (नीचे और अधिक प्रदर्शन अनुकूलन):

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

या कम (यदि स्पष्ट नहीं है) आउटपुट कॉलम के क्रमिक संख्या के साथ:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

अगर totalNULL हो सकता है (किसी भी तरह से चोट नहीं पहुंचेगी, लेकिन आप मौजूदा अनुक्रमितों से मेल खाना चाहेंगे ):

...
ORDER  BY customer, total DESC NULLS LAST, id;

प्रमुख बिंदु

  • DISTINCT ONमानक का एक PostgreSQL विस्तार है (जहां केवल DISTINCTपूरी SELECTसूची में परिभाषित किया गया है)।

  • DISTINCT ONक्लाज में किसी भी संख्या के भावों को सूचीबद्ध करें , संयुक्त पंक्ति मान डुप्लिकेट को परिभाषित करता है। नियम पुस्तिका:

    जाहिर है, दो पंक्तियों को अलग माना जाता है यदि वे कम से कम एक स्तंभ मान में भिन्न होते हैं। इस तुलना में शून्य मान को समान माना जाता है।

    बोल्ड जोर मेरा।

  • DISTINCT ONके साथ जोड़ा जा सकता है ORDER BY। अभिव्यक्तियों में अग्रणी अभिव्यक्तियों ORDER BYके सेट में होना चाहिए DISTINCT ON, लेकिन आप उन लोगों के बीच स्वतंत्र रूप से आदेश पुनर्व्यवस्थित कर सकते हैं। उदाहरण। आप साथियों के प्रत्येक समूह से एक विशेष पंक्ति चुनने के लिए अतिरिक्त भाव जोड़ सकते हैं ORDER BY। या, मैनुअल इसे डालता है :

    DISTINCT ONअभिव्यक्ति (रों) वाम-पंथी से मेल खाना चाहिए ORDER BY अभिव्यक्ति (रों)। ORDER BYखंड सामान्य रूप से अतिरिक्त अभिव्यक्ति (रों) कि प्रत्येक के भीतर पंक्तियों की वांछित पूर्वता निर्धारित शामिल होंगे DISTINCT ONसमूह।

    मैंने idसंबंधों को तोड़ने के लिए अंतिम आइटम के रूप में जोड़ा :
    " idउच्चतम साझा करने वाले प्रत्येक समूह से सबसे छोटी के साथ पंक्ति उठाओ total।"

    इस तरह से परिणाम प्राप्त करने के लिए जो पहले समूह के अनुसार निर्धारित क्रम क्रम से असहमत है, आप क्वेरी को एक बाहरी क्वेरी में दूसरे से ऊपर कर सकते हैं ORDER BYउदाहरण।

  • यदि totalआप NULL हो सकते हैं, तो आप शायद सबसे बड़ी गैर-शून्य मान वाली पंक्ति चाहते हैं। NULLS LASTप्रदर्शन की तरह जोड़ें । देख:

  • SELECTसूची में भाव से विवश नहीं कर रहा है DISTINCT ONया ORDER BYकिसी भी तरह से। (ऊपर सरल मामले में आवश्यक नहीं):

    • आप की जरूरत नहीं है में भाव से कोई भी शामिल DISTINCT ONया ORDER BY

    • आप सूची में किसी भी अन्य अभिव्यक्ति को शामिल कर सकते हैं SELECT। यह सबक्वेरीज़ और एग्रीगेट / विंडो फ़ंक्शंस के साथ बहुत अधिक जटिल प्रश्नों को बदलने के लिए महत्वपूर्ण है।

  • मैंने पोस्टग्रेज संस्करण 12..३ - १२ के साथ परीक्षण किया। लेकिन यह फीचर कम से कम संस्करण es.१ के बाद से है, इसलिए मूल रूप से हमेशा।

सूची

आदर्श ऊपर क्वेरी के लिए सूचकांक एक होगा बहु-स्तंभ सूचकांक अनुक्रम मिलान में और मिलान सॉर्ट क्रम के साथ सभी तीन स्तंभ तक फैली:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

विशेष भी हो सकता है। लेकिन इसका उपयोग करें यदि विशेष क्वेरी के लिए पठन प्रदर्शन महत्वपूर्ण है। यदि आपके पास हैDESC NULLS LAST क्वेरी है, तो इंडेक्स में उसी का उपयोग करें ताकि क्रम क्रम मेल हो और इंडेक्स लागू हो।

प्रभावशीलता / प्रदर्शन अनुकूलन

प्रत्येक क्वेरी के लिए अनुरूप अनुक्रमणिका बनाने से पहले वजन और लाभ। उपरोक्त सूचकांक की क्षमता काफी हद तक डेटा वितरण पर निर्भर करती है

सूचकांक का उपयोग किया जाता है क्योंकि यह पूर्व-सॉर्ट किए गए डेटा को वितरित करता है। Postgres 9.2 में या बाद में क्वेरी भी एक इंडेक्स से स्कैन कर लाभ उठा सकती है यदि इंडेक्स अंतर्निहित तालिका से छोटा हो। सूचकांक को इसकी संपूर्णता में स्कैन किया जाना है, हालांकि।

बेंचमार्क

मेरे पास एक साधारण बेंचमार्क था जो अब तक पुराना है। मैंने इसे एक अलग बेंचमार्क के साथ इस अलग उत्तर में बदल दिया ।


28
यह अधिकांश डेटाबेस आकारों के लिए एक शानदार उत्तर है, लेकिन मैं यह बताना चाहता हूं कि जैसे-जैसे आप ~ मिलियन पंक्तियां DISTINCT ONबेहद धीमी होती जाती हैं। कार्यान्वयन हमेशा पूरी तालिका को छांटता है और डुप्लिकेट के लिए इसके माध्यम से स्कैन करता है, सभी सूचकांकों की अनदेखी करता है (भले ही आपने आवश्यक मल्टी-कॉलम इंडेक्स बनाया हो)। संभावित समाधान के लिए explainextended.com/2009/05/03/postgresql-optimizing-distinct देखें ।
मियोकी

14
"कोड को कम करने" के लिए अध्यादेशों का उपयोग करना एक भयानक विचार है। कॉलम नामों को पढ़ने योग्य बनाने के लिए छोड़ने के बारे में कैसे?
KOTJMF

13
@KOTJMF: मेरा सुझाव है कि आप तब अपनी व्यक्तिगत पसंद के साथ जाएं। मैं शिक्षित करने के लिए दोनों विकल्पों को प्रदर्शित करता हूं। सिंटैक्स शॉर्टहैंड SELECTसूची में लंबी अभिव्यक्तियों के लिए उपयोगी हो सकता है ।
एरविन ब्रान्डस्टेट्टर

1
@ जांगोरेकी: मूल बेंचमार्क 2011 से है, मेरे पास सेटअप नहीं है। लेकिन यह पीजी 9.4 और पीजी 9.5 के साथ परीक्षण चलाने का समय था। अतिरिक्त उत्तर में विवरण देखें। । आप नीचे अपनी स्थापना के परिणाम के साथ एक टिप्पणी जोड़ सकते हैं?
इरविन ब्रान्डेसटेटर

2
@PirateApp: मेरे सिर के ऊपर से नहीं। DISTINCT ONकेवल एक पंक्ति प्रति साथियों के समूह के लिए अच्छा है ।
इरविन ब्रांडस्टेटर

134

बेंचमार्क

9.4 और 9.5 के साथ सबसे दिलचस्प उम्मीदवारों का परीक्षण के एक आधे रास्ते यथार्थवादी तालिका के साथ 200k पंक्तियों में purchasesऔर 10k अलगcustomer_id ( औसत। प्रति ग्राहक 20 पंक्तियों )।

पोस्टग्रैज 9.5 के लिए मैंने 86446 अलग-अलग ग्राहकों के साथ एक दूसरा परीक्षण किया। नीचे देखें ( औसत ग्राहक प्रति 2.3 पंक्तियाँ )।

सेट अप

मुख्य तालिका

CREATE TABLE purchases (
  id          serial
, customer_id int  -- REFERENCES customer
, total       int  -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);

मैं एक serial(पीके बाधा नीचे जोड़ा गया) और एक पूर्णांक का उपयोग करता हूं customer_idक्योंकि यह एक अधिक विशिष्ट सेटअप है। को भी जोड़ाsome_columnआम तौर पर अधिक स्तंभों के लिए बनाने के लिए गया।

डमी डेटा, पीके, इंडेक्स - एक विशिष्ट तालिका में कुछ मृत ट्यूपल भी हैं:

INSERT INTO purchases (customer_id, total, some_column)    -- insert 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9; -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);

VACUUM ANALYZE purchases;

customer तालिका - बेहतर क्वेरी के लिए

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

9.5 के लिए अपने दूसरे परीक्षण में मैंने एक ही सेटअप का उपयोग किया, लेकिन प्रति कुछ ही पंक्तियों को प्राप्त करने के random() * 100000लिए उत्पन्न customer_idकरने के लिएcustomer_id

तालिका के लिए ऑब्जेक्ट आकार purchases

इस क्वेरी के साथ जनरेट किया गया ।

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

प्रश्नों

1. row_number()सीटीई में, ( अन्य उत्तर देखें )

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

2. row_number()वश में (मेरा अनुकूलन)

SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3. DISTINCT ON( अन्य उत्तर देखें )

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4. अधीनता के साथ rCTE LATERAL( यहाँ देखें )

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5. customerतालिका LATERAL( यहां देखें )

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. array_agg()साथ ORDER BY( अन्य उत्तर देखें )

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

परिणाम

उपरोक्त प्रश्नों के लिए निष्पादन समय EXPLAIN ANALYZE(और सभी विकल्प बंद ), सर्वश्रेष्ठ 5 रन

सभी प्रश्नों ने एक सूचकांक केवल स्कैन पर उपयोग कियाpurchases2_3c_idx (अन्य कदम के अलावा)। उनमें से कुछ सिर्फ सूचकांक के छोटे आकार के लिए, दूसरों को अधिक प्रभावी ढंग से।

A. 200k पंक्तियों के साथ 9.4 और ~ 20 प्रति पोस्टग्रे customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B. पोस्टग्रेज 9.5 के साथ भी ऐसा ही है

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  
5.  33.944 ms  -- winner
6. 211.540 ms  

C. बी के समान, लेकिन ~ 2.3 पंक्तियों प्रति के साथ customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

संबंधित बेंचमार्क

यहाँ "OGR" के साथ परीक्षण करके एक नया है 10M पंक्तियों और 60k अद्वितीय "ग्राहकों" पर Postgres 11.5 (सितम्बर 2019 के रूप में वर्तमान)। परिणाम अभी भी हम क्या देखा है के साथ लाइन में हैं:

2011 से मूल (पुराना) बेंचमार्क

मैंने 65579 पंक्तियों की एक वास्तविक जीवन तालिका पर पोस्टग्रेसीक्यू 9.1 के साथ तीन परीक्षण चलाए और इसमें शामिल प्रत्येक तीन स्तंभों पर एकल-स्तंभ बीटीआरई अनुक्रमित किया और 5 रन का सर्वश्रेष्ठ निष्पादन समय लिया ।
तुलना @OMGPonies ' पहली क्वेरी ( Aकरने के लिए) से ऊपर DISTINCT ONसमाधान ( B):

  1. पूरी तालिका का चयन करें, इस मामले में 5958 पंक्तियों में परिणाम।

    A: 567.218 ms
    B: 386.673 ms
  2. WHERE customer BETWEEN x AND y1000 पंक्तियों के परिणामस्वरूप स्थिति का उपयोग करें ।

    A: 249.136 ms
    B:  55.111 ms
  3. के साथ एक एकल ग्राहक का चयन करें WHERE customer = x

    A:   0.143 ms
    B:   0.072 ms

अन्य उत्तर में वर्णित सूचकांक के साथ एक ही परीक्षण दोहराया गया

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

5
एक महान बेंचमार्क के लिए धन्यवाद। मैं सोच रहा था कि अगर इवेंट डेटा को क्वेरी करना जहां आपके पास कुल के बजाय एक टाइमस्टैम्प है, तो नए BRIN इंडेक्स से लाभ होगा। यह संभावित रूप से अस्थायी प्रश्नों के लिए स्पीडअप दे सकता है।
जंगोरकी

3
@jangorecki: शारीरिक रूप से सॉर्ट किए गए डेटा के साथ कोई भी बड़ी तालिका BRIN इंडेक्स से लाभान्वित हो सकती है।
एरविन ब्रान्डेसटेटर

@ErwinBrandstetter में 2. row_number()और 5. customer table with LATERALउदाहरणों में, यह सुनिश्चित करता है कि आईडी सबसे छोटी होगी?
आर्टेम नोविकोव

@ArtemNovikov: कुछ भी नहीं। उद्देश्य को customer_id उच्चतम के साथ पंक्ति के अनुसार पुनः प्राप्त करना है total। यह प्रश्न के परीक्षण डेटा में एक भ्रामक संयोग है कि idचयनित पंक्तियों में सबसे छोटा प्रति भी होता है customer_id
एरविन ब्रान्डसेट्टर

1
@ArtemNovikov: केवल-केवल स्कैन की अनुमति देने के लिए।
बजे एरविन ब्रान्डस्टेट्टर

55

यह आम बात है समस्या, जो पहले से ही अच्छी तरह से परीक्षण और अत्यधिक अनुकूलित समाधान है । व्यक्तिगत रूप से मैं बिल कारविन ( कई अन्य समाधानों के साथ मूल पोस्ट ) द्वारा बाएं जुड़ाव समाधान पसंद करता हूं ।

ध्यान दें कि इस सामान्य समस्या के समाधान का गुच्छा आश्चर्यजनक रूप से सबसे आधिकारिक स्रोतों, MySQL मैनुअल में से एक में पाया जा सकता है ! सामान्य प्रश्न के उदाहरण देखें: एक निश्चित कॉलम के समूह-वार अधिकतम होल्डिंग पंक्तियाँ


22
पोस्टग्रेज / SQLite (एसक्यूएल का उल्लेख नहीं करने) के प्रश्नों के लिए MySQL मैनुअल किसी भी तरह से "आधिकारिक" कैसे है? इसके अलावा, स्पष्ट होने के लिए, DISTINCT ONसंस्करण बहुत छोटा है, सरल है और आमतौर पर स्वयं LEFT JOINया अर्ध-विरोधी के साथ विकल्पों की तुलना में पोस्टग्रेज में बेहतर प्रदर्शन करता है NOT EXISTS। यह "अच्छी तरह से जांचा हुआ" भी है।
इरविन ब्रांडीसेट्टर

3
इसके अलावा एरविन ने जो लिखा है, मैं कहूंगा कि एक विंडो फ़ंक्शन (जो आजकल आम एसक्यूएल कार्यक्षमता है) का उपयोग करके व्युत्पन्न तालिका के साथ जुड़ने की तुलना में लगभग हमेशा तेज होता है
a_horse_with_no_name

6
महान संदर्भ। मुझे नहीं पता था कि इसे सबसे बड़ी n-per-group समस्या कहा जाता था। धन्यवाद।
डेविड मान

सवाल करता है नहीं के लिए के रूप में सबसे बड़ी समूह प्रति n लेकिन पहले एन।
रीयरियरपोस्ट

1
एक दो आदेश-क्षेत्र के मामले में, मैंने कोशिश की, "बिल कारविन द्वारा छोड़ दिया गया समाधान" खराब प्रदर्शन दें। मेरी टिप्पणी नीचे देखें stackoverflow.com/a/8749095/684229
जॉनी वोंग

30

Postgres में आप array_aggइस तरह का उपयोग कर सकते हैं :

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

यह आपको देगा id प्रत्येक ग्राहक की सबसे बड़ी खरीद देगा।

ध्यान देने योग्य कुछ बातें:

  • array_aggएक समग्र कार्य है, इसलिए यह साथ काम करता है GROUP BY
  • array_aggआपको केवल स्वयं के लिए स्कोप किए गए ऑर्डर को निर्दिष्ट करने देता है, इसलिए यह पूरी क्वेरी की संरचना को बाधित नहीं करता है। यदि आप डिफ़ॉल्ट से कुछ अलग करने की आवश्यकता है, तो आप NULLs को कैसे सॉर्ट करते हैं, इसके लिए भी वाक्यविन्यास है।
  • एक बार जब हम सरणी बनाते हैं, तो हम पहला तत्व लेते हैं। (Postgres arrays 1-indexed हैं, 0-indexed नहीं हैं)।
  • आप array_aggअपने तीसरे आउटपुट कॉलम के लिए एक समान तरीके से उपयोग कर सकते हैं , लेकिन max(total)सरल है।
  • इसके विपरीत DISTINCT ON, यदि आप अन्य कारणों से ऐसा चाहते हैं, array_aggतो आप इसका उपयोग कर सकते हैं GROUP BY

14

SubQs की उपस्थिति के कारण Erwin द्वारा बताया गया यह समाधान बहुत कारगर नहीं है

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;

धन्यवाद, हाँ आपसे सहमत हूँ, सबक और बाहरी क्वेरी के बीच जुड़ाव वास्तव में अधिक समय लेता है। "में" यहाँ एक समस्या नहीं होगी क्योंकि सबक केवल एक पंक्ति का परिणाम देगा। BTW, आप किस सिंटैक्स त्रुटि की ओर इशारा कर रहे हैं ??
user2407394

ओह .. "तेरदता" के लिए उपयोग किया जाता है .. अब संपादित किया गया है..कभी भी संबंध तोड़ने की आवश्यकता नहीं है क्योंकि इसे प्रत्येक ग्राहक के लिए उच्चतम कुल खोजने की आवश्यकता है ..
user2407394

आप जानते हैं कि टाई के मामले में आपको एकल ग्राहक के लिए कई पंक्तियाँ मिलती हैं? चाहे वह वांछनीय हो, सटीक आवश्यकताओं पर निर्भर करता है। आम तौर पर, यह नहीं है। हाथ में सवाल के लिए, शीर्षक बहुत स्पष्ट है।
इरविन ब्रान्डेसटेटर

यह प्रश्न से स्पष्ट नहीं है, अगर एक ही ग्राहक ने 2 अलग-अलग आईडी के लिए = मैक्स की खरीद की है, मुझे लगता है कि हमें दोनों को प्रदर्शित करना चाहिए।
user2407394

10

मैं इस तरह का उपयोग करता हूं (केवल पोस्टग्रैस्कल): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

फिर आपका उदाहरण लगभग काम करना चाहिए :

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

गुफा: यह अनदेखी पंक्तियों को दर्शाता है


संपादित 1 - इसके बजाय पोस्टग्रेज एक्सटेंशन का उपयोग करें

अब मैं इस तरह से उपयोग करता हूं: http://pgxn.org/dist/first_last_agg/

Ubuntu 14.04 पर स्थापित करने के लिए:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

यह एक पोस्टग्रेज एक्सटेंशन है जो आपको पहले और अंतिम फ़ंक्शन देता है; स्पष्ट रूप से उपर्युक्त तरीके से तेज।


2 संपादित करें - आदेश देना और फ़िल्टर करना

यदि आप कुल कार्यों (जैसे ये) का उपयोग करते हैं, तो आप पहले से ही ऑर्डर किए गए डेटा की आवश्यकता के बिना परिणाम ऑर्डर कर सकते हैं:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

तो क्रमबद्ध उदाहरण के साथ, कुछ इस तरह होगा:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

बेशक, आप आदेश और फ़िल्टर कर सकते हैं जैसा कि आप कुल के भीतर फिट होते हैं; यह बहुत शक्तिशाली वाक्य रचना है।


इस कस्टम फ़ंक्शन दृष्टिकोण का उपयोग करना। पर्याप्त रूप से सार्वभौमिक और सरल। क्यों चीजों को जटिल करते हैं, क्या यह दूसरों की तुलना में काफी कम समाधान है?
सर्गेई श्चेरबकोव

9

पूछताछ:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

वह कैसे काम करता है! (मैं वहाँ गया था)

हम यह सुनिश्चित करना चाहते हैं कि हमारे पास प्रत्येक खरीद के लिए केवल उच्चतम कुल है।


कुछ सैद्धांतिक सामग्री (इस भाग को छोड़ें यदि आप केवल क्वेरी को समझना चाहते हैं)

टोटल एक फंक्शन T (ग्राहक, आईडी) होने दें जहां वह नाम और आईडी दिया गया मान लौटाता है यह साबित करने के लिए कि दिया गया कुल (टी (ग्राहक, आईडी)) हमें साबित करना है कि हम या तो साबित करना चाहते हैं।

  • ∀x T (ग्राहक, आईडी)> T (ग्राहक, x) (यह कुल उस ग्राहक के लिए अन्य सभी की तुलना में अधिक है)

या

  • ¬∃x T (ग्राहक, आईडी) <T (ग्राहक, x) (उस ग्राहक के लिए कोई उच्च कुल मौजूद नहीं है)

पहला दृष्टिकोण हमें उस नाम के लिए सभी रिकॉर्ड प्राप्त करने की आवश्यकता होगी जो मुझे वास्तव में पसंद नहीं है।

दूसरे को यह कहने के लिए एक स्मार्ट तरीके की आवश्यकता होगी कि इस से अधिक कोई रिकॉर्ड नहीं हो सकता है।


SQL पर वापस जाएँ

अगर हमने छोड़ दिया तो नाम पर तालिका में शामिल हो गए और कुल मिलाकर शामिल तालिका से कम है:

      LEFT JOIN purchases as p 
      ON 
      p.customer = purchases.customer 
      AND 
      purchases.total < p.total

हम यह सुनिश्चित करते हैं कि सभी रिकॉर्ड जिसमें एक ही उपयोगकर्ता के शामिल होने के लिए उच्च कुल के साथ एक और रिकॉर्ड है:

purchases.id, purchases.customer, purchases.total, p.id, p.customer, p.total
1           , Tom           , 200             , 2   , Tom   , 300
2           , Tom           , 300
3           , Bob           , 400             , 4   , Bob   , 500
4           , Bob           , 500
5           , Alice         , 600             , 6   , Alice   , 700
6           , Alice         , 700

बिना किसी समूहीकरण के प्रत्येक खरीद के लिए उच्चतम फ़िल्टर के लिए हमें मदद मिलेगी:

WHERE p.total IS NULL

purchases.id, purchases.name, purchases.total, p.id, p.name, p.total
2           , Tom           , 300
4           , Bob           , 500
6           , Alice         , 700

और यही जवाब हमें चाहिए।


8

बहुत तेज उपाय

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

और वास्तव में बहुत तेजी से अगर तालिका आईडी द्वारा अनुक्रमित है:

create index purchases_id on purchases (id);

USING क्लॉज बहुत मानक है। यह सिर्फ इतना है कि कुछ छोटे डेटाबेस सिस्टम के पास नहीं है।
होल्गर जेकब जूल


7

SQL सर्वर में आप यह कर सकते हैं:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

स्पष्टीकरण: यहां समूह ग्राहक के आधार पर किया जाता है और फिर उसे कुल मिलाकर आदेश देता है, फिर ऐसे प्रत्येक समूह को StRank के रूप में क्रम संख्या दी जाती है और हम पहले 1 ग्राहक को निकाल रहे हैं जिसका StRank 1 है


धन्यवाद! इसने पूरी तरह से काम किया और इसे समझना और लागू करना बहुत आसान था।
रूबोला


4

PostgreSQL में, first_valueविंडो फ़ंक्शन के साथ संयोजन में उपयोग करने के लिए एक और संभावना है SELECT DISTINCT:

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

मैंने एक समग्र बनाया (id, total), इसलिए दोनों मान एक ही समुच्चय द्वारा लौटाए गए हैं। आप निश्चित रूप से हमेशा first_value()दो बार आवेदन कर सकते हैं ।


3

स्वीकृत OMG पॉनीज़ "किसी भी डेटाबेस द्वारा समर्थित" समाधान में मेरे परीक्षण से अच्छी गति है।

यहां मैं एक समान दृष्टिकोण प्रदान करता हूं, लेकिन किसी भी डेटाबेस समाधान को अधिक पूर्ण और साफ करता हूं। संबंधों पर विचार किया जाता है (प्रत्येक ग्राहक के लिए केवल एक पंक्ति प्राप्त करने की इच्छा मान लें, यहां तक ​​कि अधिकतम प्रति ग्राहक के लिए कई रिकॉर्ड भी), और अन्य खरीद फ़ील्ड (जैसे खरीद_पेमेंट_आईडी) को खरीद तालिका में वास्तविक मिलान पंक्तियों के लिए चुना जाएगा।

किसी भी डेटाबेस द्वारा समर्थित:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

यह क्वेरी विशेष रूप से तेजी से होती है, खासकर जब खरीद तालिका पर एक समग्र सूचकांक (ग्राहक, कुल) होता है।

टिप्पणी:

  1. t1, t2 सबक्वेरी अलायस हैं जिन्हें डेटाबेस के आधार पर हटाया जा सकता है।

  2. कैविएट : इस using (...)खंड को एमएस-एसक्यूएल और ओरेकल डीबी में वर्तमान में समर्थित नहीं किया गया है क्योंकि जनवरी 2017 को इसे संपादित किया गया है। आपको इसे अपने आप को विस्तार करना होगा जैसे कि on t2.id = purchase.idआदि। साइक्लिंग, माईएसक्यूएल और पोस्टग्रेएसक्यूएल में यूएसइंग सिंटैक्स काम करता है।


2

स्नोफ्लेक / टेराडाटा QUALIFYक्लॉज का समर्थन करता है जो HAVINGखिड़की वाले कार्यों के लिए काम करता है:

SELECT id, customer, total
FROM PURCHASES
QUALIFY ROW_NUMBER() OVER(PARTITION BY p.customer ORDER BY p.total DESC) = 1

1
  • यदि आप समुच्चय पंक्तियों के सेट से किसी भी (आपकी कुछ विशिष्ट स्थिति द्वारा) पंक्ति का चयन करना चाहते हैं।

  • यदि आप sum/avgइसके अलावा एक और ( ) एकत्रीकरण फ़ंक्शन का उपयोग करना चाहते हैं max/min। इस प्रकार आप के साथ सुराग का उपयोग नहीं कर सकतेDISTINCT ON

आप अगली उपश्रेणी का उपयोग कर सकते हैं:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

आप प्रतिस्थापित कर सकते हैं amount = MAX( tf.amount ) एक प्रतिबंध के साथ अपनी इच्छित किसी भी स्थिति हैं: इस उपश्रेणी को एक से अधिक पंक्ति में नहीं लौटना चाहिए

लेकिन अगर आप ऐसी चीजें करना चाहते हैं, जो आप शायद खिड़की के कामों में तलाश रहे हैं


1

SQl सर्वर के लिए सबसे कुशल तरीका है:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

और इस्तेमाल किए गए कॉलम के लिए क्लस्टर इंडेक्स बनाना न भूलें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.