How can I find unoccupied id numbers in a table? - sql

In my table I want to see a list of unoccupied id numbers in a certain range.
For example there are 10 records in my table with id's: "2,3,4,5,10,12,16,18,21,22" and say that I want to see available ones between 1 and 25. So I want to see a list like:
1,6,7,89,11,13,14,15,17,19,20,23,24,25
How should I write my sql query?

Select the numbers form 1 to 25 and show only those that are not in your table
select n from
( select rownum n from dual connect by level <= 25)
where n not in (select id from table);

Let's say you a #numbers table with three numbers -
CREATE TABLE #numbers (num INT)
INSERT INTO #numbers (num)
SELECT 1
UNION
SELECT 3
UNION
SELECT 6
Now, you can use CTE to generate numbers recursively from 1-25 and deselect those which are in your #numbers table in the WHERE clause -
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 25
)
SELECT n FROM n
WHERE n NOT IN (select num from #numbers)
ORDER BY n
OPTION (MAXRECURSION 25);

You can try using the "NOT IN" clause:
select
u1.user_id + 1 as start
from users as u1
left outer join users as u2 on u1.user_id + 1 = u2.id
where
u2.id is null
see also SQL query to find Missing sequence numbers

You need LISTAGG to get the output in a single row.
SQL> WITH DATA1 AS(
2 SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=25
3 ),
4 data2 AS(
5 SELECT 2 num FROM dual UNION ALL
6 SELECT 3 FROM dual UNION ALL
7 SELECT 4 from dual union all
8 SELECT 5 FROM dual UNION ALL
9 SELECT 10 FROM dual UNION ALL
10 SELECT 12 from dual union all
11 SELECT 16 from dual union all
12 SELECT 18 FROM dual UNION ALL
13 SELECT 21 FROM dual UNION ALL
14 SELECT 22 FROM dual)
15 SELECT listagg(rn, ',')
16 WITHIN GROUP (ORDER BY rn) num_list FROM data1
17 WHERE rn NOT IN(SELECT num FROM data2)
18 /
NUM_LIST
----------------------------------------------------
1,6,7,8,9,11,13,14,15,17,19,20,23,24,25
SQL>

Related

Selecting all values from multivalued field in Oracle c11 - SQL query

I am trying to select all the values from multivalued data column. Values are separated with ý.
This query I wrote does exactly what I intended, but unfortunately I am working on 11g so I can't use CROSS APPLY. Any suggestions how to go around CROSS APPLY? Or any other ideas?
select REGEXP_SUBSTR (MFIELD, '([^ý]+)',1,l.lvl,NULL) AS item
FROM TABLE
CROSS APPLY
(
SELECT LEVEL AS lvl
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( MFIELD, 'ý' )
)l;
Switch to
SQL> WITH
2 my_table (mfield)
3 AS
4 (SELECT 'abcýdef' FROM DUAL
5 UNION ALL
6 SELECT 'xyzýmnoýzzz' FROM DUAL
7 UNION ALL
8 SELECT 'ý18524ý2879' FROM DUAL)
9 SELECT mfield,
10 REGEXP_SUBSTR (mfield,
11 '[^ý]+',
12 1,
13 COLUMN_VALUE) AS item
14 FROM my_table
15 CROSS JOIN
16 TABLE (
17 CAST (
18 MULTISET (
19 SELECT LEVEL
20 FROM DUAL
21 CONNECT BY LEVEL <=
22 REGEXP_COUNT (LTRIM (mfield, 'ý'), 'ý') + 1)
23 AS SYS.odcinumberlist));
MFIELD ITEM
----------- -----------
abcýdef abc
abcýdef def
xyzýmnoýzzz xyz
xyzýmnoýzzz mno
xyzýmnoýzzz zzz
ý18524ý2879 18524
ý18524ý2879 2879
7 rows selected.
SQL>

Usage of combinatorics in Oracle SQL

Could use some help or insights, cause I'm going nuts..
Situation: I have a table of players ID's, with values 1,2,3.. to 7.
Objective: Want to create a roster of 4 players, from available players (in our case there are 7 of them). Its a classical combinatoric task, we need to calculate C(k,n). In our case C(4,7)=840/24=35. So, there are possible 35 ways to build a roster. I want to create a table of rosters with player ID's. Here's the current script, that builds the current roster table:
with comb_tbl as(
select
tmp_out.row_num,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
from(
select
rownum row_num,
substr(tmp.combinations,2) comb_sets
from(
select
sys_connect_by_path(plr.plr_id, ',') combinations
from(
select 1 plr_id from dual union
select 2 plr_id from dual union
select 3 plr_id from dual union
select 4 plr_id from dual union
select 5 plr_id from dual union
select 6 plr_id from dual union
select 7 plr_id from dual) plr
connect by nocycle prior plr.plr_id != plr.plr_id) tmp
where
length(substr(tmp.combinations,2)) = 7) tmp_out)
select
tmp1.*
from
comb_tbl tmp1
Problem:Its creates 840 possibilities, but I need to remove the "identical" ones, for example roster (1,2,3,4) is "identical" to roster (2,1,3,4). Any insights/comments/critics are welcome. Maybe the approach itself is wrong?
There is a 1-1 correspondence between possible rosters and ORDERED subsets of four elements of the seven-element set.
In your CONNECT BY clause, you only check that the player id's be different, rather than that they be in increasing order. Change != to < in CONNECT BY and it will work. (Also, you won't need NOCYCLE anymore.)
This could be a join way:
with plr(plr_id) as
( select level from dual connect by level <= 7)
select p1.plr_id, p2.plr_id, p3.plr_id, p4.plr_id
from plr p1
inner join plr p2
on(p1.plr_id < p2.plr_id)
inner join plr p3
on(p2.plr_id < p3.plr_id)
inner join plr p4
on(p3.plr_id < p4.plr_id)
For example, with n=5 you would have:
PLR_ID PLR_ID PLR_ID PLR_ID
---------- ---------- ---------- ----------
1 2 3 4
1 2 3 5
1 2 4 5
1 3 4 5
2 3 4 5
This is not an answer; it's merely a continuation of my earlier comment:
By making those additional changes, your query could end up something like:
SELECT regexp_substr(tmp_out.comb_sets,'[^,]+',1,1) plr_id_1,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,2) plr_id_2,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,3) plr_id_3,
regexp_substr(tmp_out.comb_sets,'[^,]+',1,4) plr_id_4
FROM (SELECT sys_connect_by_path(plr.plr_id, ',') comb_sets,
LEVEL lvl
FROM (select 1 plr_id from dual union all
select 2 plr_id from dual union all
select 3 plr_id from dual union all
select 4 plr_id from dual union all
select 5 plr_id from dual union all
select 6 plr_id from dual union all
select 7 plr_id from dual) plr
CONNECT BY PRIOR plr.plr_id < plr.plr_id
AND LEVEL <= 4) tmp_out
WHERE lvl = 4;
which is, IMO, easier to read and understand than your original query.

Join two tables with a column with multiple entries for the other table

I have the following problem.
I want to join two tables.
The first table has entries like the following:
T1
PK Info
1 one
2 two
3 three
The second table is build like this:
T2
PK FKT1
1 1,3
2 1,2,3
3 2
My Result should show the following
PK2 FKT1 InfoT1
1 1,3 One,Three
2 1,2,3 One,two,Three
3 2 Two
I just cant get an idea how to solve this.
Is this possible only using sql selects or is a function needed?
kind regards
It's not that difficult, but - as you were told, you'd rather NOT do that.
SQL> with
2 t1 (pk, info) as
3 (select 1, 'one' from dual union
4 select 2, 'two' from dual union
5 select 3, 'three' from dual
6 ),
7 t2 (pk, fkt1) as
8 (select 1, '1,3' from dual union
9 select 2, '1,2,3' from dual union
10 select 3, '2' from dual
11 ),
12 t2rows as
13 (select pk, regexp_substr(fkt1, '[^,]+', 1, column_value) fkt1, column_value rn
14 from t2,
15 table(cast(multiset(select level from dual
16 connect by level <= regexp_count(fkt1, ',') + 1
17 ) as sys.odcinumberlist))
18 )
19 select t2r.pk,
20 listagg(t2r.fkt1, ',') within group (order by t2r.rn) fkt1,
21 listagg(t1.info, ',') within group (order by t2r.rn) infot1
22 from t2rows t2r join t1 on t2r.fkt1 = t1.pk
23 group by t2r.pk
24 order by t2r.pk;
PK FKT1 INFOT1
---------- -------------------- --------------------
1 1,3 one,three
2 1,2,3 one,two,three
3 2 two
SQL>

get the nearest highest value from a list oracle sql

I have a column in the database in the following format: yymmddhh24miss
Sample Data:
140203101241
140202101141
140102101240
143001101244
142801101245
142701131347
142601121542
142101131744
...
I need to get the nearest high value from the list. Ex: If I pass 142701131333, then it should return 142701131347 from the above list.
Any help appreciated!
SELECT data
FROM
(
SELECT data
FROM tbl
WHERE data > '142701131333'
ORDER BY data
) a
WHERE rownum = 1
SQL> with t (x) as (
2 select 140203101241 from dual union all
3 select 140202101141 from dual union all
4 select 140102101240 from dual union all
5 select 143001101244 from dual union all
6 select 142801101245 from dual union all
7 select 142701131347 from dual union all
8 select 142601121542 from dual union all
9 select 142101131744 from dual
10 )
11 select min(x) minx from t where x > 142701131333
12 /
MINX
-----------------
142701131347
SELECT MIN(sample_data)
FROM tableName
WHERE sample_data > 142701131333

Generating a range of numbers in MySQL

How do I generate a range of consecutive numbers (one per line) from a MySQL query so that I can insert them into a table?
For example:
nr
1
2
3
4
5
I would like to use only MySQL for this (not PHP or other languages).
Here is one way to do it set-based without loops. This can also be made into a view for re-use. The example shows the generation of a sequence from 0 through 999, but of course, it may be modified to suit.
INSERT INTO
myTable
(
nr
)
SELECT
SEQ.SeqValue
FROM
(
SELECT
(HUNDREDS.SeqValue + TENS.SeqValue + ONES.SeqValue) SeqValue
FROM
(
SELECT 0 SeqValue
UNION ALL
SELECT 1 SeqValue
UNION ALL
SELECT 2 SeqValue
UNION ALL
SELECT 3 SeqValue
UNION ALL
SELECT 4 SeqValue
UNION ALL
SELECT 5 SeqValue
UNION ALL
SELECT 6 SeqValue
UNION ALL
SELECT 7 SeqValue
UNION ALL
SELECT 8 SeqValue
UNION ALL
SELECT 9 SeqValue
) ONES
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 10 SeqValue
UNION ALL
SELECT 20 SeqValue
UNION ALL
SELECT 30 SeqValue
UNION ALL
SELECT 40 SeqValue
UNION ALL
SELECT 50 SeqValue
UNION ALL
SELECT 60 SeqValue
UNION ALL
SELECT 70 SeqValue
UNION ALL
SELECT 80 SeqValue
UNION ALL
SELECT 90 SeqValue
) TENS
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 100 SeqValue
UNION ALL
SELECT 200 SeqValue
UNION ALL
SELECT 300 SeqValue
UNION ALL
SELECT 400 SeqValue
UNION ALL
SELECT 500 SeqValue
UNION ALL
SELECT 600 SeqValue
UNION ALL
SELECT 700 SeqValue
UNION ALL
SELECT 800 SeqValue
UNION ALL
SELECT 900 SeqValue
) HUNDREDS
) SEQ
Here's a hardware engineer's version of Pittsburgh DBA's solution:
SELECT
(TWO_1.SeqValue + TWO_2.SeqValue + TWO_4.SeqValue + TWO_8.SeqValue + TWO_16.SeqValue) SeqValue
FROM
(SELECT 0 SeqValue UNION ALL SELECT 1 SeqValue) TWO_1
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 2 SeqValue) TWO_2
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 4 SeqValue) TWO_4
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 8 SeqValue) TWO_8
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 16 SeqValue) TWO_16;
If you need the records in a table and you want to avoid concurrency issues, here's how to do it.
First you create a table in which to store your records
CREATE TABLE `incr` (
`Id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Secondly create a stored procedure like this:
DELIMITER ;;
CREATE PROCEDURE dowhile()
BEGIN
DECLARE v1 INT DEFAULT 5;
WHILE v1 > 0 DO
INSERT incr VALUES (NULL);
SET v1 = v1 - 1;
END WHILE;
END;;
DELIMITER ;
Lastly call the SP:
CALL dowhile();
SELECT * FROM incr;
Result
Id
1
2
3
4
5
Let's say you want to insert numbers 1 through 100 into your table. As long as you have some other table that has at least that many rows (doesn't matter the content of the table), then this is my preferred method:
INSERT INTO pivot100
SELECT #ROW := #ROW + 1 AS ROW
FROM someOtherTable t
join (SELECT #ROW := 0) t2
LIMIT 100
;
Want a range that starts with something other than 1? Just change what #ROW gets set to on the join.
As you all understand, this is rather hacky so use with care
SELECT
id % 12 + 1 as one_to_twelve
FROM
any_large_table
GROUP BY
one_to_twelve
;
DECLARE i INT DEFAULT 0;
WHILE i < 6 DO
/* insert into table... */
SET i = i + 1;
END WHILE;
Very similar to the accepted response, but using the new WITH syntax for mysql >= 8.0 which makes a lot more legible and the intent is also clearer
WITH DIGITS (N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL
SELECT 8 UNION ALL SELECT 9)
SELECT
UNITS.N + TENS.N*10 + HUNDREDS.N*100 + THOUSANDS.N*1000
FROM
DIGITS AS UNITS, DIGITS AS TENS, DIGITS AS HUNDREDS, DIGITS AS THOUSANDS;
In MariaDB you can do:
SELECT * FROM seq_i_to_N
For example:
SELECT * FROM seq_0_to_1000
SELECT * FROM seq_1_to_1000000
Reference: https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/
The "shortest" way i know (in MySQL) to create a table with a long sequence is to (cross) join an existing table with itself. Since any (common) MySQL server has the information_schema.COLUMNS table i would use it:
DROP TABLE IF EXISTS seq;
CREATE TABLE seq (i MEDIUMINT AUTO_INCREMENT PRIMARY KEY)
SELECT NULL AS i
FROM information_schema.COLUMNS t1
JOIN information_schema.COLUMNS t2
JOIN information_schema.COLUMNS t3
LIMIT 100000; -- <- set your limit here
Usually one join should be enough to create over 1M rows - But one more join will not hurt :-) - Just don't forget to set a limit.
If you want to include 0, you should "remove" the AUTO_INCEMENT property.
ALTER TABLE seq ALTER i DROP DEFAULT;
ALTER TABLE seq MODIFY i MEDIUMINT;
Now you can insert 0
INSERT INTO seq (i) VALUES (0);
and negative numbers as well
INSERT INTO seq (i) SELECT -i FROM seq WHERE i <> 0;
You can validate the numbers with
SELECT MIN(i), MAX(i), COUNT(*) FROM seq;
All other answers are good, however they all have speed issues for larger ranges because they force MySQL to generate every number then filter them.
The following only makes MySQL generate the numbers that are needed, and therefore is faster:
set #amount = 55; # How many numbers from zero you want to generate
select `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i` as `offset`
from
(select 0 `i` union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) `t0`,
(select 0 `i` union select 10 union select 20 union select 30 union select 40 union select 50 union select 60 union select 70 union select 80 union select 90) `t1`,
(select 0 `i` union select 100 union select 200 union select 300 union select 400 union select 500 union select 600 union select 700 union select 800 union select 900) `t2`,
(select 0 `i` union select 1000 union select 2000 union select 3000 union select 4000 union select 5000 union select 6000 union select 7000 union select 8000 union select 9000) `t3`
where `t3`.`i`<#amount
and `t2`.`i`<#amount
and `t1`.`i`<#amount
and `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i`<#amount;
With the above you can generate upto 10,000 numbers (0 to 9,999) with no slower speed overhead for lower numbers, regardless how low they are.
Here's a way to do it with json_table if you have MySql 8 and above:
set #noRows = 100;
SELECT tt.rowid - 1 AS value
FROM JSON_TABLE(CONCAT('[{}', REPEAT(',{}', #noRows - 1), ']'),
"$[*]" COLUMNS(rowid FOR ORDINALITY)
) AS tt;
(h/t - https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/)
The idea I want to share is not a precise response for the question but can be useful for some so I would like to share it.
If you frequently need only a limited set of numbers then it can be beneficial to create a table with the numbers you may need and just use that table every time. For example:
CREATE TABLE _numbers (num int);
INSERT _numbers VALUES (0), (1), (2), (3), ...;
This can be applied only if you need numbers below a certain reasonable limit, so don't use it for generating sequence 1...1 million but can be used for numbers 1...10k, for example.
If you have this list of numbers in the _numbers table then you can write queries like this, for obtaining the individual characters of a string:
SELECT number, substr(name, num, 1)
FROM users
JOIN _numbers ON num < length(name)
WHERE user_id = 1234
ORDER BY num;
If you need larger numbers than 10k then you can join the table to itself:
SELECT n1.num * 10000 + n2.num
FROM _numbers n1
JOIN _numbers n2
WHERE n1 < 100
ORDER BY n1.num * 10000 + n2.num; -- or just ORDER BY 1 meaning the first column
This is based on a previous answer (https://stackoverflow.com/a/53125278/2009581), but is compatible with MySQL 5.7. It works for replicas and read-only users:
SELECT x1.N + x10.N*10 + x100.N*100 + x1000.N*1000
FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x1,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x10,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x100,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x1000
WHERE x1.N + x10.N*10 + x100.N*100 + x1000.N*1000 <= #max;
It generates integers in the range of [0, #max].
with recursive cte..
with recursive rnums as (
select 1 as n
union all
select n+1 as n from rnums
where n <10
)
select * from rnums
;
Result would be..
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.00 sec)