MySQL: How to find sequence of values in column - mysql

I have long list of rows with random values:
| id | value |
|----|-------|
| 1 | abcd |
| 2 | qwer |
| 3 | jklm |
| 4 | yxcv |
| 5 | tzui |
Then I have an array of few values:
array('qwer', 'jklm');
And I need to know, if this sequence of values from array already exists in table in given order. In this case the sequence of values exists.
I tried to concat all values from table and array and match two strings, which works great with few rows but there are actually hundred of thousand of rows in table. I believe there should be a better solution.

If your list is short, you could just do a self-join and spell out the conditions for each joined table reference:
select t1.id from MyTable as t1 join MyTable as t2
where t1.value='qwer' and t2.value='jklm' and t1.id=t2.id-1;
This returns an empty set if there's no such sequence. And of course it assumes that the id numbers are consecutive (they are in your example, but in general that's a risky assumption).
This doesn't work well if your list gets really long. There's a hard limit of 63 table references MySQL supports in a single query.
Here's another solution, which works for any size list, but only if your id values are known to be consecutive:
select t1.id from MyTable as t1 join MyTable as t2
on t2.id between t1.id and t1.id+1
where t1.value = 'qwer' and t2.value in ('qwer','jklm')
group by t1.id
having group_concat(t2.value order by t2.id) = 'qwer,jklm';
The t1 row is the beginning of the potential matching sequence of rows, so it must match the first value in your list.
Then join to the t2 rows, which are the complete set of potentially matching rows.
The set of t2 rows is also limited to a set no more than N rows, based on the size of your list of N values you're searching for. But SQL has no way of making a group based on the number of rows, we can only limit based on some value in the row. So that's why this works if your id values can be assumed to be consecutive.

This way you can do it for the whole set:
select value1, value2
from
(
select *
from (
SELECT [IMEPAC] value1 , ROW_NUMBER() over(order by [MATBR]) rn1
FROM [PACM]
) a1 join
(
SELECT [IMEPAC] value2 , ROW_NUMBER() over(order by [MATBR]) rn2
FROM [PACM]
) a2 on a1.rn1 = a2.rn2 + 1
) a
group by value1, value2
having count(*) > 1
It is written for MS SQL but you can easily rewrite it to fit mysql too.
I run this against table with > 400000 rows on IMEPAC which is not part of any index and it run (first and only once) for 6 sec.
Here is Mysql version:
select value1, value2, count(*) count
from
(
select *
from (
SELECT #row_number1:= #row_number1 + 1 AS rn1, content as value1
FROM docs,(SELECT #row_number1:=0) AS t
order by id
) a1 join
(
SELECT #row_number2:= #row_number2 + 1 AS rn2, content value2
FROM docs,(SELECT #row_number2:=0) AS t
order by id
) a2 on a1.rn1 = a2.rn2 + 1
) a
group by value1, value2
having count(*) > 1;
SQL Fiddle here

Related

Can't get the right column value for a query containing max()

I have a set of data like :
Nm | item | type | value
21 | 19 | A | 15
22 | 40 | B | 10
21 | 20 | A | 80
32 | 40 | C | 40
I tried several queries and i always get : (for the record Nm = 21)
Nm | item | type | max(value)
21 | 19 | A | 80
which is not what i want ,since the max value is from the item = 20
select
* from table t1 where nm=21
order by value desc
limit 1
You need to find row which is having maximum value for particular nm. For that you need to lookup each nm and find maximum value in sub query and then compare that maximum value with main query.
Query:
select *
from item_table it_o
where it_o.value in
(select max(value)
from item_table it_i
where it_i.nm=it_o.nm)
Output:
nm item type value
22 40 B 10
21 20 A 80
32 40 C 40
SELECT Nm, item, type, value
FROM ( SELECT Nm, MAX( value ) AS value
FROM YourTable
GROUP
BY Nm ) AS m
NATURAL JOIN YourTable
WHERE Nm = 21;
I've been asked to provide an explanation so here goes:
First, you need to find the maximum value (you haven't given a table name so I'm going to use YourTable):
SELECT MAX( value ) AS value
FROM YourTable
WHERE item = 21
Second, you want to project all attributes which requires joining the table expression above back to YourTable but we can't do that because we haven't projected the Nm attribute.
It's tempting to think we can simply project the attribute:
SELECT Nm, MAX( value ) AS value
FROM YourTable
WHERE item = 21
However, this makes SQL barf. To make SQL happy we must say which columns we are summarizing by (no matter how obvious it is!) using SQL's rather clunky GROUP BY syntax:
SELECT Nm, MAX( value ) AS value
FROM YourTable
WHERE item = 21
GROUP
BY Nm
Now we can join back to YourTable but again things aren't so simple:
SELECT Nm, item, type, value
FROM ( SELECT Nm, MAX( value ) AS value
FROM YourTable
WHERE Nm = 21
GROUP
BY Nm )
NATURAL JOIN YourTable;
Again, SQL barfs because we haven't given our derived table a name. Now you may be wondering, what is the point of giving it a name if we are using NATURAL JOIN, of which one of its advantages over, say, INNER JOIN is that we don't need range variables? Well, there is no point, it is not needed. However, the SQL Standards declared it is required. Therefore, we are forced to include a name, pointless though it is:
SELECT Nm, item, type, value
FROM ( SELECT Nm, MAX( value ) AS value
FROM YourTable
WHERE Nm = 21
GROUP
BY Nm ) AS pointless_name
NATURAL JOIN YourTable;
Note my SQL code above is different: one applies one's experience to change the structure of the query to make it generally more useful (sorry, I don't have an explanation beyond intuition for this!).
You have to do order by item desc so max value come first then select only one row by doing limit 1
SELECT * FROM TABLE ORDER BY VALUE DESC LIMIT 1
or you can select max id in subquery and select that id in main query (this query can return multiple rows)
SELECT * FROM TABLE WHERE VALUE IN (SELECT MAX(VALUE) FROM TABLE)
You can try like this
select * from tablename where value = (SELECT MAX(value) FROM tablename )
OR
select top 1 * from tablename order by value desc
SELECT a.*
FROM YourTable a
JOIN
( SELECT nm
, MAX(value) value
FROM YourTable
GROUP
BY nm
) b
ON b.nm = a.nm
AND b.value = a.value

Select all rows that have column value larger than some value

I have a SQL table thus:
username | rank
a | 0
b | 2
c | 5
d | 4
e | 5
f | 7
g | 1
h | 12
I want to use a single select statement that returns all rows that have rank greater than the value of user e's rank.
Is this possible with a single statement?
Yes it is possible.
SELECT * FROM MyTable
WHERE rank > (SELECT Rank FROM MyTable WHERE username = 'e')
or you can also use self-join for the same
SELECT t1.* FROM MyTable t1
JOIN MyTable t2
ON t1.Rank > t2.Rank
AND t2.username = 'e';
See this SQLFiddle
You can use subquery
SELECT * FROM `table` WHERE `rank` > (
SELECT `rank` FROM `table` WHERE `username` ='b' LIMIT 1)
This is just an edit script of #UweB.. It will return the max rank even if there are multiple rows for username='e'
SELECT *
FROM tbl
WHERE
tbl.rank > (
SELECT max(rank) FROM tbl WHERE username = 'e')
SELECT *
FROM tbl
WHERE
tbl.rank > (
SELECT rank FROM tbl WHERE username = 'e'
);
Note that this will only work if the sub-select (the part in brackets) returns a single value (one row, one column so to speak) only.

DELETE a record in relational position in MySQL?

I am trying to clean up records stored in a MySQL table. If a row contains %X%, I need to delete that row and the row immediately below it, regardless of content. E.g. (sorry if the table is insulting anyone's intelligence):
| 1 | leave alone
| 2 | Contains %X% - Delete
| 3 | This row should also be deleted
| 4 | leave alone
| 5 | Contains %X% - Delete
| 6 | This row should also be deleted
| 7 | leave alone
Is there a way to do this using only a couple of queries? Or am I going to have to execute a SELECT query first (using the %x% search parameter) then loop through those results and execute a DELETE...WHERE for each index returned + 1
This should work although its a bit clunky (might want to check the LIKE argument as it uses pattern matching (see comments)
DELETE FROM table.db
WHERE idcol IN
( SELECT idcol FROM db.table WHERE col LIKE '%X%')
OR idcolIN
( SELECTidcol+1 FROMdb.tableWHEREcol` LIKE '%X%')
Let's assume the table was named test and contained to columns named id and data.
We start with a SELECT that gives us the id of all rows that have a preceding row (highest id of all ids lower than id of our current row):
SELECT t1.id FROM test t1
JOIN test t2 ON
( t2.id, true )
=
( SELECT t3.id, t3.data LIKE '%X%' FROM test t3
WHERE t3.id < t1.id ORDER BY id DESC LIMIT 1 )
That gives us the ids 3 and 6. Their preceding rows 2 and 5 contain %X%, so that's good.
Now lets get the ids of the rows that contain %X% and combine them with the previous ones, via UNION:
(SELECT t1.id FROM test t1
JOIN test t2 ON
( t2.id, true )
=
( SELECT t3.id, t3.data LIKE '%X%' FROM test t3
WHERE t3.id < t1.id ORDER BY id DESC LIMIT 1 )
)
UNION
(
SELECT id FROM test WHERE data LIKE '%X%'
)
That gives us 3, 6, 2, 5 - nice!
Now, we can't delete from a table and select from the same table in MySQL - so lets use a temporary table, store our ids that are to be deleted in there, and then read from that temporary table to delete from our original table:
CREATE TEMPORARY TABLE deleteids (id INT);
INSERT INTO deleteids
(SELECT t1.id FROM test t1
JOIN test t2 ON
( t2.id, true )
=
( SELECT t3.id, t3.data LIKE '%X%' FROM test t3
WHERE t3.id < t1.id ORDER BY id DESC LIMIT 1 )
)
UNION
(
SELECT id FROM test WHERE data LIKE '%X%'
);
DELETE FROM test WHERE id in (SELECT * FROM deleteids);
... and we are left with the ids 1, 4 and 7 in our test table!
(And since the previous rows are selected using <, ORDER BY and LIMIT, this also works if the ids are not continuous.)
You can do it all in a single DELETE statement:
Assuming the "row immediately after" is based on the order of your INT-based ID column, you can use MySQL variables to assign row numbers which accounts for gaps in your IDs:
DELETE a FROM tbl a
JOIN (
SELECT a.id, b.id AS nextid
FROM (
SELECT a.id, a.text, #rn:=#rn+1 AS rownum
FROM tbl a
CROSS JOIN (SELECT #rn:=1) rn_init
ORDER BY a.id
) a
LEFT JOIN (
SELECT a.id, #rn2:=#rn2+1 AS rownum
FROM tbl a
CROSS JOIN (SELECT #rn2:=0) rn_init
ORDER BY a.id
) b ON a.rownum = b.rownum
WHERE a.text LIKE '%X%'
) b ON a.id IN (b.id, b.nextid)
SQL Fiddle Demo (added additional data for example)
What this does is it first takes your data and ranks it based on your ID column, then we do an offset LEFT JOIN on an almost identical result set except that the rank column is behind by 1. This gets the rows and their immediate "next" rows side by side so that we can pull both of their id's at the same time in the parent DELETE statement:
SELECT a.id, a.text, b.id AS nextid, b.text AS nexttext
FROM (
SELECT a.id, a.text, #rn:=#rn+1 AS rownum
FROM tbl a
CROSS JOIN (SELECT #rn:=1) rn_init
ORDER BY a.id
) a
LEFT JOIN (
SELECT a.id, a.text, #rn2:=#rn2+1 AS rownum
FROM tbl a
CROSS JOIN (SELECT #rn2:=0) rn_init
ORDER BY a.id
) b ON a.rownum = b.rownum
WHERE a.text LIKE '%X%'
Yields:
ID | TEXT | NEXTID | NEXTTEXT
2 | Contains %X% - Delete | 3 | This row should also be deleted
5 | Contains %X% - Delete | 6 | This row should also be deleted
257 | Contains %X% - Delete | 3434 | This row should also be deleted
4000 | Contains %X% - Delete | 4005 | Contains %X% - Delete
4005 | Contains %X% - Delete | 6000 | Contains %X% - Delete
6000 | Contains %X% - Delete | 6534 | This row should also be deleted
We then JOIN-DELETE that entire statement on the condition that it deletes rows whose IDs are either the "subselected" ID or NEXTID.
There is no reasonable way of doing this in a single query. (It may be possible, but the query you end up having to use will be unreasonably complex, and will almost certainly not be portable to other SQL engines.)
Use the SELECT-then-DELETE approach you described in your question.

MySQL - count values and merge results from multiple tables

I have two tables with one column each, containing names.
Names can have duplicates. One name can be found on every table or only in one.
I want to make an query that count duplicates, for each name in every table an list these values like this:
| name | table1 | table2 |
| john | 12 | 23 |
| mark | 2 | 5 |
| mary | | 10 |
| luke | 4 | |
I tried different strategies using UNION but no luck.
Thanks in advance!!!
SELECT DISTINCT t1.name, t1.cnt1, t2.cnt2
FROM
(SELECT name,count(name) as cnt1 FROM table1 GROUP BY name) t1
LEFT JOIN
(SELECT name,count(name) as cnt2 FROM table2 GROUP BY name) t2
ON t1.name = t2.name
UNION
SELECT DISTINCT t2.name, t1.cnt1, t2.cnt2
FROM
(SELECT name,count(name) as cnt1 FROM table1 GROUP BY name) t1
RIGHT JOIN
(SELECT name,count(name) as cnt2 FROM table2 GROUP BY name) t2
ON t1.name = t2.name
Here's a simpler solution:
You can UNION the names from the two tables together, manually differentiating their origin tables with a tbl column.
Then it's just a simple GROUP BY with conditional aggregation using the differentiating column:
SELECT a.name,
NULLIF(COUNT(CASE a.tbl WHEN 1 THEN 1 END), 0) AS table1,
NULLIF(COUNT(CASE a.tbl WHEN 2 THEN 1 END), 0) AS table2
FROM
(
SELECT name, 1 AS tbl FROM table1 UNION ALL
SELECT name, 2 FROM table2
) a
GROUP BY a.name
In accordance with your desired result-set, we NULL the count value if it turns out to be 0.
SQLFiddle Demo
SELECT SUM(res.cn), name
FROM
(
SELECT name, count(name) as cn from table1 GROUP BY name HAVING count(name) > 1
UNION ALL
SELECT name, count(name) as cn from table2 GROUP BY name HAVING count(name)>1
) as res
GROUP BY nam
e
Try the above :) I made a fiddle for you to test it:
http://sqlfiddle.com/#!3/796b2/3
It has a few double names in each table and will show you which names have doubles and then print them. The names that only appear once are not shown (acheived by the HAVING clause)
After some reading i don't think that it's posibil what i want to do. This situation ca be solved with pivot table in excel or libreoffice.
In fact this is method that i used, combined with some sql stataments to count occurence of names and export as CSV.
UNION definitetly not work. Some chance are with join, but not shure.
I found a post that discusses the same problem as mine.
MySQL - Rows to Columns

How can we find gaps in sequential numbering in MySQL?

We have a database with a table whose values were imported from another system. There is an auto-increment column, and there aren’t any duplicate values, but there are missing values. For example, running this query:
select count(id) from arrc_vouchers where id between 1 and 100
should return 100, but it returns 87 instead. Is there a query I can run that will return the values of the missing numbers? For example, the records may exist for id 1-70 and 83-100, but there aren’t any records with id's of 71-82. I want to return 71, 72, 73, etc.
Is this possible?
A better answer
JustPlainMJS provided a much better answer in terms of performance.
The (not as fast as possible) answer
Here's a version that works on a table of any size (not just on 100 rows):
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 - first id in current gap
gap_ends_at - last id in current gap
This just worked for me to find the gaps in a table with more than 80k rows:
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;
Result:
+------------------+
| missing |
+------------------+
| 1 thru 99 |
| 666 thru 667 |
| 50000 |
| 66419 thru 66456 |
+------------------+
4 rows in set (0.06 sec)
Note that the order of columns expected and got is critical.
If you know that YourCol doesn't start at 1 and that doesn't matter, you can replace
(SELECT #rownum:=0) AS a
with
(SELECT #rownum:=(SELECT MIN(YourCol)-1 FROM YourTable)) AS a
New result:
+------------------+
| missing |
+------------------+
| 666 thru 667 |
| 50000 |
| 66419 thru 66456 |
+------------------+
3 rows in set (0.06 sec)
If you need to perform some kind of shell script task on the missing IDs, you can also use this variant in order to directly produce an expression you can iterate over in Bash.
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;
This produces an output like so
$(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456)
You can then copy and paste it into a for loop in a bash terminal to execute a command for every ID
for ID in $(seq 1 99) $(seq 666 667) 50000 $(seq 66419 66456); do
echo $ID
# Fill the gaps
done
It's the same thing as above, only that it's both readable and executable. By changing the "CONCAT" command above, syntax can be generated for other programming languages. Or maybe even SQL.
A quick-and-dirty query that should do the trick:
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
This will give you a table showing the id that has ids missing above it, and next_id that exists, and how many are missing between... E.g.,
id next_id missing_inbetween
1 4 2
68 70 1
75 87 11
If you are using a MariaDB database, you have a faster (800%) option using the sequence storage engine:
SELECT * FROM seq_1_to_50000 WHERE SEQ NOT IN (SELECT COL FROM TABLE);
If there is a sequence having gap of maximum one between two numbers (like
1,3,5,6) then the query that can be used is:
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);
table_name - source1
column_name - id
An alternative solution that requires a query + some code doing some processing would be:
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;
Note that the query does not contain any subselect that we know it's not handled performantly by MySQL's planner.
That will return one entry per centralValue (cValue) that does not have a smaller value (lValue) or a greater value (rValue), i.e.:
lValue |cValue|rValue
-------+------+-------
{null} | 2 | 3
8 | 9 | {null}
{null} | 22 | 23
23 | 24 | {null}
{null} | 29 | {null}
{null} | 33 | {null}
Without going into further details (we'll see them in next paragraphs) this output means that:
No values between 0 and 2
No values between 9 and 22
No values between 24 and 29
No values between 29 and 33
No values between 33 and MAX VALUE
So the basic idea is to do a RIGHT and LEFT joins with the same table seeing if we have adjacents values per value (i.e., if central value is '3' then we check for 3-1=2 at left and 3+1 at right), and when a ROW has a NULL value at RIGHT or LEFT then we know there is no adjacent value.
The complete raw output of my table is:
select * from arrc_vouchers order by id asc;
0
2
3
4
5
6
7
8
9
22
23
24
29
33
Some notes:
The SQL IF statement in the join condition is needed if you define the 'id' field as UNSIGNED, therefore it will not allow you to decrease it under zero. This is not strictly necessary if you keep the c.value > 0 as it's stated in the next note, but I'm including it just as doc.
I'm filtering the zero central value as we are not interested in any previous value and we can derive the post value from the next row.
I tried it in a different manner, and the best performance that I found was this simple query:
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
;
... one left join to check if the next id exists, only if next if is not found, then the subquery finds the next id that exists to find the end of gap. I did it because the query with equal (=) is better performance than the greater than (>) operator.
Using the sqlfiddle it does not show so a different performance compared to the other queries, but in a real database this query above results in 3 times faster than the others.
The schema:
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)
;
Follow below all the queries that I made to compare the performance:
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
;
You can see and test my query using this SQL Fiddle:
http://sqlfiddle.com/#!9/6bdca7/1
It is probably not relevant, but I was looking for something like this to list the gaps in a sequence of numbers and found this post that has multiple different solutions depending upon exactly what you are looking for. I was looking for the first available gap in the sequence (i.e., next available number), and this seems to work fine.
SELECT MIN(l.number_sequence + 1) as nextavabile
from patients as l
LEFT OUTER JOIN patients as r on l.number_sequence + 1 = r.number_sequence
WHERE r.number_sequence is NULL
Several other scenarios and solutions discussed there, from 2005!
How to Find Missing Values in a Sequence With SQL
Create a temporary table with 100 rows and a single column containing the values 1-100.
Outer Join this table to your arrc_vouchers table and select the single column values where the arrc_vouchers id is null.
This should work:
select tempid from temptable
left join arrc_vouchers on temptable.tempid = arrc_vouchers.id
where arrc_vouchers.id is null
Although these all seem to work, the result set returns in a very lengthy time when there are 50,000 records.
I used this, and it find the gap or the next available (last used + 1) with a much faster return from the query.
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;
Based on the answer given by matt, this stored procedure allows you to specify the table and column names that you wish to test to find non-contiguous records - thus answering the original question and also demonstrating how one could use #var to represent tables &/or columns in a stored procedure.
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
A simple, yet effective, solution to find the missing auto-increment values:
SELECT `id`+1
FROM `table_name`
WHERE `id`+1 NOT IN (SELECT id FROM table_name)
Another simple answer that identifies the gaps. We do a query selecting just the odd numbers and we right join it to a query with all the even numbers. As long as you're not missing id 1; this should give you a comprehensive list of where the gaps start.
You'll still have to take a look at that place in the database to figure out how many numbers the gap is. I found this way easier than the solution proposed and much easier to customize to unique situations.
SELECT *
FROM (SELECT * FROM MyTABLE WHERE MYFIELD % 2 > 0) AS A
RIGHT JOIN FROM (SELECT * FROM MyTABLE WHERE MYFIELD % 2 = 0) AS B
ON A.MYFIELD=(B.MYFIELD+1)
WHERE a.id IS NULL;
This works for me:
SELECT distinct(l.membership_no + 1) as nextavabile
from Tablename as l
LEFT OUTER JOIN Tablename as r on l.membership_no + 1 = r.membership_no
WHERE r.membership_no is NULL and l.membership_no is not null order by nextavabile asc;
Starting from the comment posted by user933161,
select l.id + 1 as start from sequence as l inner join sequence as r on l.id + 1 = r.id where r.id is null;
is better in that it will not produce a false positive for the end of the list of records. (I'm not sure why so many are using left outer joins.)
Also,
insert into sequence (id) values (#);
where # is the start value for a gap will fill that start value. (If there are fields that cannot be null, you will have to add those with dummy values.)
You could alternate between querying for start values and filling in each start value until the query for start values returns an empty set.
Of course, this approach would only be helpful if you're working with a small enough data set that manually iterating like that is reasonable. I don't know enough about things like phpMyAdmin to come up with ways to automate it for larger sets with more and larger gaps.
CREATE TABLE arrc_vouchers (id int primary key);
INSERT INTO `arrc_vouchers` (`id`) VALUES (1),(4),(5),(7),(8),(9),(10),(11),(15),(16);
WITH RECURSIVE odd_num_cte (id) AS
(
SELECT (select min(id) from arrc_vouchers)
union all
SELECT id+1 from odd_num_cte where id <(SELECT max(id) from arrc_vouchers)
)
SELECT cte.id
from arrc_vouchers ar right outer join odd_num_cte cte on ar.id=cte.id
where ar.id is null;