कई स्तंभों पर DISTINCT का चयन करें


23

मान लें कि हमारे पास एक (a,b,c,d)ही डेटा प्रकार के चार स्तंभों वाली एक तालिका है ।

क्या कॉलम में डेटा के भीतर सभी अलग-अलग मूल्यों का चयन करना और उन्हें एक ही कॉलम के रूप में वापस करना संभव है या क्या मुझे इसे प्राप्त करने के लिए फ़ंक्शन बनाना होगा?


7
तुम्हारा मतलब है SELECT a FROM tablename UNION SELECT b FROM tablename UNION SELECT c FROM tablename UNION SELECT d FROM tablename ;?
ypercube y

हाँ। ऐसा होगा लेकिन मुझे 4 प्रश्न चलाने होंगे। यह एक प्रदर्शन अड़चन नहीं होगा?
फबरीज़ो माज़ोनी

6
यह एक प्रश्न है, 4. नहीं
ypercube query

1
मैं क्वेरी को लिखने के कई तरीके देख सकता हूं, जिसमें अलग-अलग प्रदर्शन हो सकते हैं, जो कि उपलब्ध अनुक्रमों के आधार पर हो सकते हैं, आदि, लेकिन मैं कल्पना नहीं कर सकता कि एक फ़ंक्शन कैसे मदद करेगा
ypercubeᵀᴹ

1
ठीक है। इसे एक साथ देते हुएUNION
फैब्रीज़ियो माज़ोनी

जवाबों:


24

अद्यतन: SQLKiddle में 100K पंक्तियों (और 2 अलग-अलग मामलों में से एक, कुछ के साथ एक (25) अलग मूल्यों और दूसरे के साथ बहुत सारे (25K मान) के साथ सभी 5 प्रश्नों का परीक्षण किया ।

एक बहुत ही सरल क्वेरी का उपयोग करना होगा UNION DISTINCTमुझे लगता है कि अगर चार कॉलम में से प्रत्येक पर एक अलग सूचकांक है तो यह सबसे अधिक कुशल होगा। यह चार कॉलम में से प्रत्येक पर एक अलग सूचकांक के साथ कुशल होगा, अगर पोस्टग्रेज ने लूज़ इंडेक्स स्कैन ऑप्टिमाइज़ेशन को लागू किया था , जो यह नहीं है। तो यह क्वेरी कुशल नहीं होगी क्योंकि इसमें तालिका के 4 स्कैन की आवश्यकता होती है (और कोई इंडेक्स का उपयोग नहीं किया जाता है):

-- Query 1. (334 ms, 368ms) 
SELECT a AS abcd FROM tablename 
UNION                           -- means UNION DISTINCT
SELECT b FROM tablename 
UNION 
SELECT c FROM tablename 
UNION 
SELECT d FROM tablename ;

एक UNION ALLऔर पहले और फिर उपयोग करने के लिए किया जाएगा DISTINCT। इसके लिए 4 टेबल स्कैन (और इंडेक्स का कोई उपयोग नहीं) की आवश्यकता होगी। खराब दक्षता नहीं जब मान कुछ कम होते हैं, और अधिक मूल्यों के साथ मेरे (सबसे व्यापक) परीक्षण में सबसे तेज हो जाता है:

-- Query 2. (87 ms, 117 ms)
SELECT DISTINCT a AS abcd
FROM
  ( SELECT a FROM tablename 
    UNION ALL 
    SELECT b FROM tablename 
    UNION ALL
    SELECT c FROM tablename 
    UNION ALL
    SELECT d FROM tablename 
  ) AS x ;

अन्य उत्तरों ने सरणी फ़ंक्शंस या LATERALसिंटैक्स का उपयोग करके अधिक विकल्प प्रदान किए हैं । जैक की क्वेरी ( 187 ms, 261 ms) में उचित प्रदर्शन है लेकिन एंड्रीएम की क्वेरी अधिक कुशल ( 125 ms, 155 ms) लगती है । वे दोनों तालिका का एक क्रमिक स्कैन करते हैं और किसी भी सूचकांक का उपयोग नहीं करते हैं।

वास्तव में जैक की क्वेरी के परिणाम ऊपर दिखाए गए (अगर हम हटाते हैं order by) की तुलना में थोड़ा बेहतर हैं और 4 आंतरिक को हटाकर distinctऔर केवल बाहरी एक को छोड़कर आगे सुधार किया जा सकता है ।


अंत में, अगर - और केवल अगर - 4 कॉलम के अलग-अलग मान अपेक्षाकृत कम हैं, तो आप WITH RECURSIVEऊपर दिए गए लूज़ इंडेक्स स्कैन पेज में वर्णित हैक / ऑप्टिमाइज़ेशन का उपयोग कर सकते हैं और सभी 4 इंडेक्स का उपयोग कर सकते हैं, उल्लेखनीय रूप से तेज़ परिणाम के साथ! एक ही 100K पंक्तियों और लगभग 25 अलग-अलग मानों के साथ 4 कॉलम में फैले (केवल 2 एमएस में रन!) का परीक्षण किया गया, जबकि 25K अलग-अलग मूल्यों के साथ यह 368 एमएस के साथ सबसे धीमा है:

-- Query 3.  (2 ms, 368ms)
WITH RECURSIVE 
    da AS (
       SELECT min(a) AS n  FROM observations
       UNION ALL
       SELECT (SELECT min(a) FROM observations
               WHERE  a > s.n)
       FROM   da AS s  WHERE s.n IS NOT NULL  ),
    db AS (
       SELECT min(b) AS n  FROM observations
       UNION ALL
       SELECT (SELECT min(b) FROM observations
               WHERE  b > s.n)
       FROM   db AS s  WHERE s.n IS NOT NULL  ),
   dc AS (
       SELECT min(c) AS n  FROM observations
       UNION ALL
       SELECT (SELECT min(c) FROM observations
               WHERE  c > s.n)
       FROM   dc AS s  WHERE s.n IS NOT NULL  ),
   dd AS (
       SELECT min(d) AS n  FROM observations
       UNION ALL
       SELECT (SELECT min(d) FROM observations
               WHERE  d > s.n)
       FROM   db AS s  WHERE s.n IS NOT NULL  )
SELECT n 
FROM 
( TABLE da  UNION 
  TABLE db  UNION 
  TABLE dc  UNION 
  TABLE dd
) AS x 
WHERE n IS NOT NULL ;

SQLfiddle


सारांशित करने के लिए, जब अलग-अलग मूल्य कुछ होते हैं, तो पुनरावर्ती क्वेरी पूर्ण विजेता होती है, जबकि बहुत सारे मान, मेरा दूसरा, जैक का (नीचे का उन्नत संस्करण) और एंड्रीएम के प्रश्नों का सबसे अच्छा प्रदर्शन होता है।


देर से जोड़, 1 क्वेरी पर एक भिन्नता जो अतिरिक्त अलग-अलग कार्यों के बावजूद, मूल 1 से बहुत बेहतर प्रदर्शन करती है और केवल 2 के मुकाबले थोड़ी खराब होती है:

-- Query 1b.  (85 ms, 149 ms)
SELECT DISTINCT a AS n FROM observations 
UNION 
SELECT DISTINCT b FROM observations 
UNION 
SELECT DISTINCT c FROM observations 
UNION 
SELECT DISTINCT d FROM observations ;

और जैक में सुधार:

-- Query 4b.  (104 ms, 128 ms)
select distinct unnest( array_agg(a)||
                        array_agg(b)||
                        array_agg(c)||
                        array_agg(d) )
from t ;

12

आप इस क्वेरी में LATERAL का उपयोग कर सकते हैं :

SELECT DISTINCT
  x.n
FROM
  atable
  CROSS JOIN LATERAL (
    VALUES (a), (b), (c), (d)
  ) AS x (n)
;

LATERAL कीवर्ड बाईं ओर से संदर्भ ऑब्जेक्ट में शामिल होने के दाईं ओर अनुमति देता है। इस स्थिति में, दाईं ओर एक वाल्व निर्माता होता है जो एकल स्तंभों में आपके द्वारा रखे जाने वाले स्तंभ मानों में से एकल-स्तंभ सबसेट बनाता है। मुख्य प्रश्न केवल नए कॉलम को संदर्भित करता है, इसमें DISTINCT भी लागू होता है।


10

स्पष्ट होने के लिए, मैं उपयोग करूँगा unionजैसा कि ypercube बताता है , लेकिन यह सरणियों के साथ भी संभव है:

select distinct unnest( array_agg(distinct a)||
                        array_agg(distinct b)||
                        array_agg(distinct c)||
                        array_agg(distinct d) )
from t
order by 1;
| अनावश्यक |
| : ----- |
| 0 |
| 1 |
| 2 |
| 3 |
| 5 |
| 6 |
| 8 |
| 9 |

यहाँ dbfiddle


7

सबसे छोटा

SELECT DISTINCT n FROM observations, unnest(ARRAY[a,b,c,d]) n;

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

SELECT DISTINCT n FROM observations, LATERAL (VALUES (a),(b),(c),(d)) t(n);

सबसे तेजी से

प्रत्येक शामिल कॉलम पर एक सूचकांक के साथ!
के लिए कुछ अलग / कई डुप्लिकेट मानों:

WITH RECURSIVE
  ta AS (
   (SELECT a FROM observations ORDER BY a LIMIT 1)  -- parentheses required!
   UNION ALL
   SELECT o.a FROM ta t
    , LATERAL (SELECT a FROM observations WHERE a > t.a ORDER BY a LIMIT 1) o
   )
, tb AS (
   (SELECT b FROM observations ORDER BY b LIMIT 1)
   UNION ALL
   SELECT o.b FROM tb t
    , LATERAL (SELECT b FROM observations WHERE b > t.b ORDER BY b LIMIT 1) o
   )
, tc AS (
   (SELECT c FROM observations ORDER BY c LIMIT 1)
   UNION ALL
   SELECT o.c FROM tc t
    , LATERAL (SELECT c FROM observations WHERE c > t.c ORDER BY c LIMIT 1) o
   )
, td AS (
   (SELECT d FROM observations ORDER BY d LIMIT 1)
   UNION ALL
   SELECT o.d FROM td t
    , LATERAL (SELECT d FROM observations WHERE d > t.d ORDER BY d LIMIT 1) o
   )
SELECT a
FROM  (
       TABLE ta
 UNION TABLE tb
 UNION TABLE tc
 UNION TABLE td
 ) sub;

यह एक और आरसीटीई संस्करण है, जो पहले से ही पोस्ट किए गए @ypercube के समान है , लेकिन मैं ORDER BY 1 LIMIT 1इसके बजाय min(a)आमतौर पर थोड़ा तेज है। मुझे NULL मूल्यों को बाहर करने के लिए किसी अतिरिक्त अतिरिक्त की आवश्यकता नहीं है।
और LATERALएक सहसंबद्ध उपशम के बजाय, क्योंकि यह क्लीनर है (जरूरी नहीं कि तेज)।

इस तकनीक का जवाब देने के लिए मेरे जाने में विस्तृत विवरण:

मैंने ypercube की SQL Fiddle को अपडेट किया और मुझे प्लेलिस्ट में जोड़ा।


क्या आप EXPLAIN (ANALYZE, TIMING OFF)सर्वश्रेष्ठ समग्र प्रदर्शन को सत्यापित करने के लिए परीक्षण कर सकते हैं ? (कैशिंग प्रभाव को बाहर करने के लिए 5 में से सर्वश्रेष्ठ।)
इरविन ब्रान्डेसटेटर

दिलचस्प। मुझे लगा कि कॉमा जॉइन हर लिहाज से क्रोस जॉइन के बराबर होगा, यानी प्रदर्शन के लिहाज से भी। क्या अंतर LATERAL का उपयोग करने के लिए विशिष्ट है?
एंड्री एम

या शायद मुझे गलत समझा। जब आपने मेरे सुझाव के कम वर्बोज़ संस्करण के बारे में "तेज़" कहा, तो क्या मेरा मतलब था कि आप मेरी तुलना में तेज़ हैं या अनावश्यक के साथ SELIST DISTINCT की तुलना में तेज़ हैं?
एंड्री एम

1
@AndriyM: अल्पविराम है समकक्ष (सिवाय इसके कि स्पष्ट `क्रॉस JOIN` वाक्य रचना बांध मजबूत जब हल करने अनुक्रम में शामिल होने)। हां, मेरा मतलब है कि आपका विचार इससे VALUES ...तेज है unnest(ARRAY[...])। सूची LATERALमें सेट-रिटर्निंग कार्यों के लिए निहित है FROM
इरविन ब्रान्डेसटेटर

सुधार के लिए Thnx! मैंने ऑर्डर / लिमिट -1 वेरिएंट की कोशिश की, लेकिन कोई ध्यान देने योग्य अंतर नहीं था। LATERAL का उपयोग करना बहुत अच्छा है, बहुत से बचने के लिए पूरी जाँच नहीं है, बढ़िया है। आपको यह संस्करण पोस्टग्रेज लोगों को सुझाए जाने चाहिए, जिन्हें लूज़-इंडेक्स-स्कैन पेज में जोड़ा जाना चाहिए।
ypercube y

3

आप कर सकते हैं, लेकिन जैसा कि मैंने लिखा था और उस फ़ंक्शन का परीक्षण किया जो मुझे गलत लगा। यह संसाधनों की बर्बादी है।
कृपया एक संघ और अधिक चयन का उपयोग करें। केवल लाभ (यदि यह है), मुख्य तालिका से एक एकल स्कैन।

Sql fiddle में आपको विभाजक को $ से कुछ और में बदलने की जरूरत है , जैसे /

CREATE TABLE observations (
    id         serial
  , a int not null
  , b int not null
  , c int not null
  , d int not null
  , created_at timestamp
  , foo        text
);

INSERT INTO observations (a, b, c, d, created_at, foo)
SELECT (random() * 20)::int        AS a          -- few values for a,b,c,d
     , (15 + random() * 10)::int 
     , (10 + random() * 10)::int 
     , ( 5 + random() * 20)::int 
     , '2014-01-01 0:0'::timestamp 
       + interval '1s' * g         AS created_at -- ascending (probably like in real life)
     , 'aöguihaophgaduigha' || g   AS foo        -- random ballast
FROM generate_series (1, 10) g;               -- 10k rows

CREATE INDEX observations_a_idx ON observations (a);
CREATE INDEX observations_b_idx ON observations (b);
CREATE INDEX observations_c_idx ON observations (c);
CREATE INDEX observations_d_idx ON observations (d);

CREATE OR REPLACE FUNCTION fn_readuniqu()
  RETURNS SETOF text AS $$
DECLARE
    a_array     text[];
    b_array     text[];
    c_array     text[];
    d_array     text[];
    r       text;
BEGIN

    SELECT INTO a_array, b_array, c_array, d_array array_agg(a), array_agg(b), array_agg(c), array_agg(d)
    FROM observations;

    FOR r IN
        SELECT DISTINCT x
        FROM
        (
            SELECT unnest(a_array) AS x
            UNION
            SELECT unnest(b_array) AS x
            UNION
            SELECT unnest(c_array) AS x
            UNION
            SELECT unnest(d_array) AS x
        ) AS a

    LOOP
        RETURN NEXT r;
    END LOOP;

END;
$$
  LANGUAGE plpgsql STABLE
  COST 100
  ROWS 1000;

SELECT * FROM fn_readuniqu();

आप वास्तव में एक फ़ंक्शन के रूप में सही हैं, फिर भी एक संघ का उपयोग करेगा। किसी भी स्थिति में प्रयास के लिए +1।
फबरीज़ियो माज़ोनी

2
आप यह सरणी और कर्सर जादू क्यों कर रहे हैं? @ ypercube का समाधान काम करता है और इसे SQL भाषा फ़ंक्शन में लपेटना बहुत आसान है।
dezso

क्षमा करें, मैं आपके कार्य को संकलित नहीं कर सका। मैंने शायद मूर्खतापूर्ण कुछ किया। यदि आप इसे यहां काम करने का प्रबंधन करते हैं , तो कृपया मुझे एक लिंक प्रदान करें और मैं अपने उत्तर को परिणामों के साथ अपडेट कर दूंगा, इसलिए हम अन्य उत्तरों के साथ तुलना कर सकते हैं।
ypercube y

@ypercube संपादित समाधान काम करना चाहिए। याद रखें कि विभाजक को फ़िडल में बदलें। मैंने टेबल निर्माण के साथ अपने स्थानीय डीबी पर परीक्षण किया और ठीक काम करता है।
user_0
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.