Mysql में अनुक्रमिक क्रमांकन में अंतराल कैसे खोजें?


119

हमारे पास एक तालिका वाला एक डेटाबेस है, जिसका मान किसी अन्य सिस्टम से आयात किया गया था। एक ऑटो-इंक्रीमेंट कॉलम है, और कोई डुप्लिकेट मान नहीं हैं, लेकिन लापता मान हैं। उदाहरण के लिए, यह क्वेरी चलाना:

select count(id) from arrc_vouchers where id between 1 and 100

100 वापस करना चाहिए, लेकिन यह बदले 87 देता है। क्या कोई प्रश्न है जो मैं चला सकता हूं जो लापता संख्याओं के मूल्यों को वापस कर देगा? उदाहरण के लिए, आईडी 1-70 और 83-100 के लिए मौजूद हो सकते हैं, लेकिन आईडी के 71-82 के साथ कोई रिकॉर्ड नहीं हैं। मैं 71, 72, 73 आदि वापस करना चाहता हूं।

क्या यह संभव है?


यह MySQL में काम नहीं कर सकता है, लेकिन काम (Oracle) में हमें कुछ इसी तरह की जरूरत थी। हमने एक Stored Proc लिखा, जिसने Max value के रूप में एक नंबर लिया। संग्रहित प्रोक ने तब एक एकल स्तंभ के साथ एक अस्थायी तालिका बनाई। तालिका में 1 से लेकर मैक्स तक की सभी संख्याएँ थीं। तब इसने टेम्‍प टेबल और हमारी अभिरुचि की तालिका में शामिल नहीं किया। यदि आप इसे arrc_vouchers से Max = Select max (id) के साथ कहते हैं, तो यह सभी लापता मानों को वापस कर देगा।
22

2
नंबरिंग में अंतराल होने में क्या गलत है? सरोगेट कुंजी का मूल्य आम तौर पर सार्थक नहीं होता है; यह सब अद्वितीय है। यदि आपका एप्लिकेशन गैर-सन्निहित आईडी को संभाल नहीं सकता है, तो संभवतः यह एप्लिकेशन में बग है, डेटा में नहीं।
Wyzard

4
इस मामले में यह एक मुद्दा है क्योंकि पुराने सिस्टम से हमें जो डेटा विरासत में मिला है, वह एक रिकॉर्ड के साथ जुड़े ऑटो-इंक्रीमेंट नंबर का इस्तेमाल करता है, जो लोगों को सौंपे जा रहे फिजिकल कार्ड पर प्रिंट करने की कुंजी के रूप में है। यह हमारा विचार नहीं था। यह पता लगाने के लिए कि कौन से कार्ड गायब हैं, हमें यह जानना होगा कि अनुक्रमिक क्रम में अंतराल कहां हैं।
एमीस

xaprb.com/blog/2005/12/06/… select l.id + 1 as start from sequence as l left outer join sequence as r on l.id + 1 = r.id where r.id is null;

आप अपनी तालिका के उच्चतम आईडी से 1 से संख्या उत्पन्न करने के लिए श्रृंखला उत्पन्न कर सकते हैं। फिर एक क्वेरी चलाएँ जहाँ आईडी इस श्रृंखला में नहीं है।
Tsvetelin Salutski

जवाबों:


170

अपडेट करें

प्रदर्शन के मामले में कॉन्फिडेंशमैज ने बहुत बेहतर जवाब दिया

(उत्तर के रूप में उपवास के रूप में संभव नहीं)

यहां वह संस्करण है जो किसी भी आकार की तालिका पर काम करता है (न केवल 100 पंक्तियों पर):

SELECT (t1.id + 1) as gap_starts_at, 
       (SELECT MIN(t3.id) -1 FROM arrc_vouchers t3 WHERE t3.id > t1.id) as gap_ends_at
FROM arrc_vouchers t1
WHERE NOT EXISTS (SELECT t2.id FROM arrc_vouchers t2 WHERE t2.id = t1.id + 1)
HAVING gap_ends_at IS NOT NULL
  • gap_starts_at - वर्तमान अंतर में पहली आईडी
  • gap_ends_at - वर्तमान अंतर में अंतिम आईडी

6
मैं अब उस कंपनी के लिए काम नहीं कर रहा हूं, लेकिन यह सबसे अच्छा जवाब है जो मैंने देखा है और यह निश्चित रूप से भविष्य के संदर्भ के लिए याद रखने योग्य है। धन्यवाद!
एमीस

4
इसके साथ एकमात्र समस्या यह है कि यह एक संभावित प्रारंभिक अंतर "रिपोर्ट" नहीं करता है। उदाहरण के लिए, यदि पहले 5 आईडी गायब हैं (5 के माध्यम से 1) यह नहीं दिखाता है कि ... हम बहुत भीख माँगने पर कैसे दयनीय अंतराल दिखा सकते हैं?
डिएगोडीडी

नोट: यह क्वेरी अस्थायी तालिकाओं पर काम नहीं करती है। मेरी समस्या यह थी कि order numberमैं अंतराल के लिए खोज कर रहा था, अलग-अलग नहीं है (तालिका भंडार क्रम रेखाएँ हैं, इसलिए क्रम संख्या वे प्रत्येक पंक्ति के लिए दोहराते हैं)। पहली क्वेरी: सेट में 2812 पंक्तियाँ (1 मिनट 31.09 सेकंड) । अलग क्रम संख्या का चयन करके एक और तालिका बनाई। मेरे दोहराए बिना आपकी क्वेरी: सेट में 1009 पंक्तियाँ (18.04 सेकेंड)
क्रिस के

1
@DiegoDD क्या गलत है SELECT MIN(id) FROM table?
एयर

8
काम किया लेकिन 700000 रिकॉर्ड के साथ एक मेज पर चलने में लगभग 5 घंटे लगे
मैट

98

इसने मेरे लिए 80k से अधिक पंक्तियों वाली तालिका में अंतराल को खोजने का काम किया:

SELECT
 CONCAT(z.expected, IF(z.got-1>z.expected, CONCAT(' thru ',z.got-1), '')) AS missing
FROM (
 SELECT
  @rownum:=@rownum+1 AS expected,
  IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got
 FROM
  (SELECT @rownum:=0) AS a
  JOIN YourTable
  ORDER BY YourCol
 ) AS z
WHERE z.got!=0;

परिणाम:

+------------------+
| missing          |
+------------------+
| 1 thru 99        |
| 666 thru 667     |
| 50000            |
| 66419 thru 66456 |
+------------------+
4 rows in set (0.06 sec)

ध्यान दें कि कॉलम का क्रम expectedऔर gotमहत्वपूर्ण है।

यदि आप जानते हैं कि YourCol1 से शुरू नहीं होता है और कोई फर्क नहीं पड़ता, तो आप बदल सकते हैं

(SELECT @rownum:=0) AS a

साथ में

(SELECT @rownum:=(SELECT MIN(YourCol)-1 FROM YourTable)) AS a

नया परिणाम:

+------------------+
| missing          |
+------------------+
| 666 thru 667     |
| 50000            |
| 66419 thru 66456 |
+------------------+
3 rows in set (0.06 sec)

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

SELECT GROUP_CONCAT(IF(z.got-1>z.expected, CONCAT('$(',z.expected,' ',z.got-1,')'), z.expected) SEPARATOR " ") AS missing
FROM (  SELECT   @rownum:=@rownum+1 AS expected,   IF(@rownum=height, 0, @rownum:=height) AS got  FROM   (SELECT @rownum:=0) AS a   JOIN block   ORDER BY height  ) AS z WHERE z.got!=0;

यह ऐसा आउटपुट उत्पन्न करता है

$(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456)

फिर आप प्रत्येक ID के लिए एक कमांड निष्पादित करने के लिए इसे bash टर्मिनल में लूप में कॉपी और पेस्ट कर सकते हैं

for ID in $(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456); do
  echo $ID
  # fill the gaps
done

यह उपरोक्त के समान ही है, केवल यह पठनीय और निष्पादन योग्य दोनों है। ऊपर "CONCAT" कमांड को बदलकर, अन्य प्रोग्रामिंग भाषाओं के लिए सिंटैक्स उत्पन्न किया जा सकता है। या शायद SQL भी।


8
अच्छा समाधान, मेरे लिए यह पसंदीदा उत्तर से बेहतर है - धन्यवाद
वी ज़ेल

6
स्वीकृत उत्तर की तुलना में इसका बहुत अधिक कुशल है।
सिम्बियन

1
स्वीकृत उत्तर की तुलना में कहीं अधिक तेज़। केवल एक चीज जो मैं CONVERT( YourCol, UNSIGNED )जोड़ूंगा वह यह है कि बेहतर परिणाम देगा यदि YourCol पहले से ही पूर्णांक नहीं है।
बार्टन चित्तेंडेन

1
@AlexandreCassagne: अगर मैं आपके प्रश्न को सही ढंग से समझ रहा हूं, तो मैं बस मिनट खोजने के लिए एम्बेड की तरह एक अलग क्वेरी SELECT MAX(YourCol) FROM YourTable;
करूंगा

1
@temuri यदि आवश्यक हो तो GROUP_CONCAT वैरिएंट पर स्विच करें:SELECT IF((z.got-IF(z.over>0, z.over, 0)-1)>z.expected, CONCAT(z.expected,' thru ',(z.got-IF(z.over>0, z.over, 0)-1)), z.expected) AS missing FROM ( SELECT @rownum:=@rownum+1 AS expected, @target-@missing AS under, (@missing:=@missing+IF(@rownum=YourCol, 0, YourCol-@rownum))-@target AS over, IF(@rownum=YourCol, 0, @rownum:=YourCol) AS got FROM (SELECT @rownum:=0, @missing:=0, @target:=10) AS a JOIN YourTable ORDER BY YourCol ) AS z WHERE z.got!=0 AND z.under>0;
ConfexianMJS

11

क्विक और डर्टी क्वेरी जो करना चाहिए:

SELECT a AS id, b AS next_id, (b - a) -1 AS missing_inbetween
FROM 
 (
SELECT a1.id AS a , MIN(a2.id) AS b 
FROM arrc_vouchers  AS a1
LEFT JOIN arrc_vouchers AS a2 ON a2.id > a1.id
WHERE a1.id <= 100
GROUP BY a1.id
) AS tab

WHERE 
b > a + 1

यह आपको एक तालिका दिखाएगा जिसमें आईडी दिखाई दे रही है जिसमें उसके ऊपर आईडी गायब है, और अगला_आईडी जो मौजूद है, और कितने के बीच गायब हैं ...

 
आईडी next_id गुम_inbetween
 १ ४ २
68 70 1
75 87 11

1
यह मेरे लिए बहुत अच्छा काम किया। धन्यवाद।! मैं अपने उद्देश्यों के लिए इसे आसानी से संशोधित करने में सक्षम था।
रहीम खोजा

ऐसा लगता है कि अंतराल में 'अगली आईडी' की तलाश में यह सबसे अच्छा जवाब है। दुर्भाग्य से यह 10K पंक्तियों के साथ तालिकाओं के लिए बहुत धीमी है। मैं ~ 46K तालिका पर 10 मिनट से अधिक समय तक प्रतीक्षा कर रहा हूं जबकि @ConfexianMJS के साथ मुझे एक सेकंड से भी कम समय में परिणाम मिल गया है!
लाकबेकॉडमोर 64

5

यदि आप उपयोग कर रहे MariaDBहैं तो आपके पास अनुक्रम भंडारण इंजन का उपयोग करके तेज (800%) विकल्प है :

SELECT * FROM seq_1_to_50000 WHERE SEQ NOT IN (SELECT COL FROM TABLE);

2
इस विचार पर विस्तार करने के लिए, अनुक्रम का अधिकतम उपयोग करके "SELECT MAX(column) FROM table"परिणाम की स्थापना की जा सकती है और $ MAX कहे जाने वाले परिणाम से एक चर सेट किया जा सकता है ... sql स्टेटमेंट तब लिखा जा सकता है "SELECT * FROM seq_1_to_". $MAX ." WHERE seq not in (SELECT column FROM table)" मेरा सिंटैक्स php आधारित है
me_

या आप SELECT @var:= max FROM ....; select * from .. WHERE seq < @max;MySQL चर के साथ उपयोग कर सकते हैं ।
मोशे एल

2

100 पंक्तियों के साथ एक अस्थायी तालिका बनाएं और 1-100 मानों वाला एक कॉलम।

बाहरी इस तालिका को अपने arrc_vouchers तालिका में शामिल करें और एकल स्तंभ मानों का चयन करें जहाँ arrc_vouchers आईडी शून्य है।

इस अंधे को कूटना, लेकिन काम करना चाहिए।

select tempid from temptable 
left join arrc_vouchers on temptable.tempid = arrc_vouchers.id 
where arrc_vouchers.id is null

ठीक है, 1 - 100 उदाहरण देने के लिए सिर्फ एक आसान तरीका था। इस मामले में, हम 20,000 - 85,000 देख रहे हैं। तो क्या मैं 20000 - 85000 की संख्या वाली 65,000 पंक्तियों के साथ एक अस्थायी तालिका बनाता हूं? और मैं ऐसा करने के बारे में कैसे जाना? मैं phpMyAdmin का उपयोग कर रहा हूं; यदि मैं कॉलम का डिफ़ॉल्ट मान 25000 पर सेट करता हूं और इसे ऑटो वेतन वृद्धि करता हूं, तो क्या मैं सिर्फ 65,000 पंक्तियां सम्मिलित कर सकता हूं और यह 25000 के साथ ऑटो-वेतन वृद्धि शुरू करेगा?
एम्मीएस

मेरे पास एक समान स्थिति थी (मेरे पास 100 आइटम हैं और 100 में लापता आइटम खोजने की आवश्यकता है)। ऐसा करने के लिए, मैंने 1-100 में एक और तालिका बनाई, फिर इस पर इस कथन को निष्पादित करें और यह खूबसूरती से काम करता है। यह अस्थायी तालिकाओं को बनाने के लिए एक बहुत ही जटिल कार्य को प्रतिस्थापित करता है। किसी ऐसी ही स्थिति में किसी के लिए बस सलाह, कभी-कभी टेम्प टेबल बनाने की तुलना में यह तेजी से होता है।
newshorts

2

एक वैकल्पिक समाधान जिसके लिए क्वेरी + कुछ प्रोसेसिंग करने वाले कुछ कोड की आवश्यकता होगी:

select l.id lValue, c.id cValue, r.id rValue 
  from 
  arrc_vouchers l 
  right join arrc_vouchers c on l.id=IF(c.id > 0, c.id-1, null)
  left  join arrc_vouchers r on r.id=c.id+1
where 1=1
  and c.id > 0 
  and (l.id is null or r.id is null)
order by c.id asc;

ध्यान दें कि क्वेरी में कोई सबसेलेक्ट नहीं है जिसे हम जानते हैं कि यह MySQL के प्लानर द्वारा प्रदर्शन नहीं किया गया है।

वह प्रति सेंट्रलवैल्यू (cValue) में एक प्रविष्टि लौटाएगा, जिसका छोटा मान (lValue) या अधिक मान (rValue) नहीं है, अर्थात:

lValue |cValue|rValue
-------+------+-------
{null} | 2    | 3      
8      | 9    | {null} 
{null} | 22   | 23     
23     | 24   | {null} 
{null} | 29   | {null} 
{null} | 33   | {null} 


आगे के विवरण में जाने के बिना (हम उन्हें अगले पैराग्राफ में देखेंगे) इस आउटपुट का मतलब है कि:

  • 0 और 2 के बीच कोई मान नहीं
  • 9 और 22 के बीच कोई मान नहीं
  • 24 और 29 के बीच कोई मान नहीं
  • 29 और 33 के बीच कोई मान नहीं
  • 33 और MAX VALUE के बीच कोई मान नहीं है

इसलिए मूल विचार यह है कि एक ही तालिका के साथ राइट और लेफ्ट जुड़ते हैं, यह देखने के लिए कि क्या हमारे पास मूल्य प्रति मान है (यानी: यदि केंद्रीय मूल्य '3' है तो हम 3-1 = 2 बाईं ओर और 3 + 1 पर जांच करते हैं। दाईं ओर), और जब ROW के पास RIGHT या LEFT में एक NULL मान होता है तो हम जानते हैं कि कोई आसन्न मान नहीं है।

मेरी मेज का पूरा कच्चा माल है:

select * from arrc_vouchers order by id asc;

0  
2  
3  
4  
5  
6  
7  
8  
9  
22 
23 
24 
29 
33 

कुछ नोट:

  1. यदि आप 'id' फ़ील्ड को UNSIGNED के रूप में परिभाषित करते हैं, तो जॉइन कंडीशन में SQL IF स्टेटमेंट की आवश्यकता होती है, इसलिए यह आपको इसे शून्य के तहत कम करने की अनुमति नहीं देगा। यह कड़ाई से आवश्यक नहीं है यदि आप c.value> 0 रखते हैं जैसा कि अगले नोट में बताया गया है, लेकिन मैं इसे डॉक्स के रूप में शामिल कर रहा हूं।
  2. मैं शून्य केंद्रीय मान को फ़िल्टर कर रहा हूं क्योंकि हम किसी भी पिछले मूल्य में रुचि नहीं रखते हैं और हम अगली पंक्ति से पोस्ट मूल्य प्राप्त कर सकते हैं।

2

यदि कोई क्रम दो संख्याओं (जैसे 1,3,5,6) के बीच अधिकतम एक का अंतर है तो उपयोग की जा सकने वाली क्वेरी निम्न है:

select s.id+1 from source1 s where s.id+1 not in(select id from source1) and s.id+1<(select max(id) from source1);
  • तालिका नाम - source1
  • आम नाम - id

1

लुसेक द्वारा दिए गए उत्तर के आधार पर यह संग्रहित प्रक्रिया आपको तालिका और स्तंभ नाम निर्दिष्ट करने की अनुमति देती है जिसे आप गैर-सन्निहित रिकॉर्ड खोजने के लिए परीक्षण करना चाहते हैं - इस प्रकार मूल प्रश्न का उत्तर देना और यह भी प्रदर्शित करना कि टेबल का प्रतिनिधित्व करने के लिए @var का उपयोग कैसे किया जा सकता है। / या संग्रहीत कार्यविधि में स्तंभ।

create definer=`root`@`localhost` procedure `spfindnoncontiguous`(in `param_tbl` varchar(64), in `param_col` varchar(64))
language sql
not deterministic
contains sql
sql security definer
comment ''
begin
declare strsql varchar(1000);
declare tbl varchar(64);
declare col varchar(64);

set @tbl=cast(param_tbl as char character set utf8);
set @col=cast(param_col as char character set utf8);

set @strsql=concat("select 
    ( t1.",@col," + 1 ) as starts_at, 
  ( select min(t3.",@col,") -1 from ",@tbl," t3 where t3.",@col," > t1.",@col," ) as ends_at
    from ",@tbl," t1
        where not exists ( select t2.",@col," from ",@tbl," t2 where t2.",@col," = t1.",@col," + 1 )
        having ends_at is not null");

prepare stmt from @strsql;
execute stmt;
deallocate prepare stmt;
end

1

मैंने इसे अलग-अलग शिष्टाचारों में आज़माया और मुझे जो सबसे अच्छा प्रदर्शन मिला, वह यह सरल प्रश्न था:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;

... एक बाईं ओर जुड़ने के लिए जाँच करें कि क्या अगला आईडी मौजूद है, केवल अगर अगला नहीं मिला है, तो उपकुंजी अगली आईडी ढूंढें जो अंतराल के अंत का पता लगाने के लिए मौजूद है। मैंने ऐसा किया क्योंकि बराबर (=) के साथ क्वेरी (>) ऑपरेटर से अधिक से बेहतर प्रदर्शन है ।

Sqlfiddle का उपयोग करके यह दूसरों के क्वेरी के इतने अलग-अलग प्रदर्शन नहीं दिखाती है, लेकिन एक वास्तविक डेटाबेस में यह क्वेरी दूसरों के मुकाबले 3 गुना अधिक तेजी से होती है।

स्कीमा:

CREATE TABLE arrc_vouchers (id int primary key)
;
INSERT INTO `arrc_vouchers` (`id`) VALUES (1),(4),(5),(7),(8),(9),(10),(11),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29)
;

प्रदर्शन की तुलना करने के लिए मेरे द्वारा की गई सभी क्वेरी का अनुसरण करें:

select a.id+1 gapIni
    ,(select x.id-1 from arrc_vouchers x where x.id>a.id+1 limit 1) gapEnd
    from arrc_vouchers a
    left join arrc_vouchers b on b.id=a.id+1
    where b.id is null
    order by 1
;
select *, (gapEnd-gapIni) qt
    from (
        select id+1 gapIni
        ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
        from arrc_vouchers a
        order by id
    ) a where gapEnd <> gapIni
;
select id+1 gapIni
    ,(select x.id from arrc_vouchers x where x.id>a.id limit 1) gapEnd
    #,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    where id+1 <> (select x.id from arrc_vouchers x where x.id>a.id limit 1)
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),(select x.id from arrc_vouchers x where x.id>a.id limit 1)) gapEnd
    from arrc_vouchers a
    order by id
;
select id+1 gapIni
    ,coalesce((select id from arrc_vouchers x where x.id=a.id+1),concat('*** GAT *** ',(select x.id from arrc_vouchers x where x.id>a.id limit 1))) gapEnd
    from arrc_vouchers a
    order by id
;

शायद यह किसी को मदद करता है और उपयोगी है।

आप इस sqlfiddle का उपयोग करके मेरी क्वेरी देख और परीक्षण कर सकते हैं :

http://sqlfiddle.com/#!9/6bdca7/1


0

हालांकि ये सभी काम करने लगते हैं, लेकिन परिणाम बहुत लंबा समय में रिटर्न सेट करते हैं जब 50,000 रिकॉर्ड होते हैं।

मैंने इसका उपयोग किया है, और यह क्वेरी से बहुत तेज़ वापसी के साथ अंतर या अगला उपलब्ध (अंतिम उपयोग किया गया + 1) पाता है।

SELECT a.id as beforegap, a.id+1 as avail
FROM table_name a
where (select b.id from table_name b where b.id=a.id+1) is null
limit 1;

यह पहला अंतर पाता है जो वह नहीं है जो सवाल पूछ रहा था।
21

0

शायद प्रासंगिक नहीं है, लेकिन मैं संख्याओं के अनुक्रम में अंतराल को सूचीबद्ध करने के लिए इस तरह की चीज़ की तलाश कर रहा था और इस पद को पाया, जो कि आपके लिए देख रहे हैं, उसके आधार पर कई अलग-अलग समाधान हैं। मैं अनुक्रम में पहला उपलब्ध अंतर (यानी अगले उपलब्ध संख्या) की तलाश में था, और यह ठीक काम करने लगता है।

MIN का चयन करें (l.number_fterence + 1) के रूप में रोगियों से अगलेavabile के रूप में एल LEFT OUTER के रूप में रोगियों को r.number_fterence + 1 = r.number_fterence पर आर के रूप में। 2005 से कई अन्य परिदृश्यों और समाधानों पर चर्चा हुई!

एसक्यूएल के साथ एक अनुक्रम में गुम मान कैसे खोजें

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