SQL Oracle, next free number from numeric column data - sql

I have a table with an numeric column. There are data records, lets take for Example { 1,7, 10, 11, 12, 19, 20}. I want to use SQL to get the next "free" number from a specific x:
>8 for x=7
>8 for x=8
>13 for x=10
>21 for x=20
Does anybody have an idea?
OK: I want to insert something with an 'x'. The column is unique, so I cannot put something with x=7 in the table when there already is a '7' in there. So I want a routine that returns me '8' if there is already a record with '7'. Or '9' if there already is an '8'.
IN Pseudo SQL:
x = 7 // for example
select COL from myTable where COL= (x or if x does not exist, the y : y > x, y - x smallest possible)

SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE NUMBERS
("NUM" int)
;
INSERT ALL
INTO NUMBERS ("NUM")
VALUES (1)
INTO NUMBERS ("NUM")
VALUES (7)
INTO NUMBERS ("NUM")
VALUES (10)
INTO NUMBERS ("NUM")
VALUES (11)
INTO NUMBERS ("NUM")
VALUES (12)
INTO NUMBERS ("NUM")
VALUES (19)
INTO NUMBERS ("NUM")
VALUES (20)
SELECT * FROM dual
;
Query 1:
select min(n.VAL) as NextFree
from (
SELECT LEVEL as VAL
FROM DUAL
CONNECT BY LEVEL <= 100000
ORDER BY LEVEL
) n
left outer join NUMBERS d on n.VAL = d.NUM
where d.NUM is null
and n.VAL >= 10
Results:
| NEXTFREE |
|----------|
| 13 |

Similar to the answer above but there's no need for a left join:
select nvl(nextfree, 10) as nextfree from (
select max(d.NUM) + 1 as nextfree
from NUMBERS d
CONNECT BY PRIOR NUM + 1 = NUM
START WITH NUM = 10
)
| NEXTFREE |
|----------|
| 13 |

Related

Difference in consecutive rows in SQL

I have a table which has any integer number. There is no specific criteria for a number to start but next row will be +2000 in number then above row and so on. So I want to find out through query where the difference of 2 rows are not 2000. Could you please help me on this? Comparison would be as follows:
Row 1 = 1000
2 = 3000
3 4000
4= 6000
5= 7000
Then only 3 and 5 should be output as the difference of Row 3 and Row 5 is not 2000. Row 3 should be compared with 2 and 5 should be compared with 4.
My data looks like :
Label Formorder date
test 480000 3/31/2015
test2 481000 3/31/2014
test3 482000 3/31/2015
test4 483000 3/31/2014
If you have SQL Server 2012 or above, you can use the LAG function.
LAG will give you a value in the previous row, compare this value to see if it is 2000 lower than the current row:
WITH diffs as (
SELECT rowValue,
rowValue - LAG(rowValue) OVER (ORDER BY rowValue) diff
FROM dataTable)
SELECT rowValue
FROM diffs
WHERE diff <> 2000
http://sqlfiddle.com/#!6/59d28/2
Possible solution:
declare #t table(id int, v int)
insert into #t values
(1, 1000),
(2, 3000),
(4, 7000),
(6, 9000),
(11, 11000),
(17, 17000)
select * from #t t1
outer apply(select * from (
select top 1 id as previd, v as prevv
from #t t2
where t2.id < t1.id
order by id desc)t
where t1.v - t.prevv <> 2000) oa
where oa.prevv is not null
Output:
id v previd prevv
4 7000 2 3000
17 17000 11 11000
If ID (row) does not skip
select t2.*
from table t1
jion table t2
on t2.ID = t1.ID + 1
and t2.value <> t1.value + 2000
A JOIN-based approach should work on SQL Server 2008.
In the query below, row numbers are added to the source data. Then, an inner join connects the current row to the previous row if and only if the previous row's value is not exactly 2000 less than the current row.
WITH Data AS (
SELECT *, RowNumber = ROW_NUMBER() OVER (ORDER BY rowValue)
FROM dataTable
)
SELECT n.rowValue
FROM data n
JOIN data p ON p.RowNumber = n.RowNumber - 1
AND p.rowValue != n.rowValue - 2000
http://sqlfiddle.com/#!3/59d28/10

PL Sql Parsing # Character

I have a records in table which contains only one column,and its column contains data like under string.
D#1001111068#112B#0010040130022013012111505444##20130121110800#20130121115054#01#-##240#username#username#20130124171831#20130130#6
Between # chracters every string show one column in other table like that.
D#
1001111068# --Customer Number
112B# --Procut Id
0010040130022013012111505444# --Serial Number
# --Order Number(empty record)
20130121110800# --X Columns
I want to parse these items and insert to other table.
How to write this.
You can extract n-th substring by the following expression:
regexp_substr(source_string, '([^#]*)(#|$)', 1, n, '', 1)
Full query:
create table your_table(
source_string varchar2(4000)
);
insert into your_table
values('D#1001111068#112B#0010040130022013012111505444##20130121110800#20130121115054#01#-##240#username#username#20130124171831#20130130#6');
select
n,
regexp_substr(source_string, '([^#]*)(#|$)', 1, n, '', 1)
from your_table,
(
select level as n from dual
connect by level <= (
select max(regexp_count(source_string, '#')) + 1
from your_table
)
)
where n <= regexp_count(source_string, '#') + 1;
Output:
1 D
2 1001111068
3 112B
4 0010040130022013012111505444
5 (null)
6 20130121110800
7 20130121115054
8 01
9 -
10 (null)
11 240
12 username
13 username
14 20130124171831
15 20130130
16 6
fiddle
The function you are looking for is regexp_substr.

Pivot values of a column based on a search string

Note: I would like to do this in a single SQL statement. not pl/sql, cursor loop, etc.
I have data that looks like this:
ID String
-- ------
01 2~3~1~4
02 0~3~4~6
03 1~4~5~1
I want to provide a report that somehow pivots the values of the String column into distinct rows such as:
Value "Total number in table"
----- -----------------------
1 3
2 1
3 2
4 3
5 1
6 1
How do I go about doing this? It's like a pivot table but I am trying to pivot the data in a column, rather than pivoting the columns in the table.
Note that in real application, I do not actually know what the values of the String column are; I only know that the separation between values is '~'
Given this test data:
CREATE TABLE tt (ID INTEGER, VALUE VARCHAR2(100));
INSERT INTO tt VALUES (1,'2~3~1~4');
INSERT INTO tt VALUES (2,'0~3~4~6');
INSERT INTO tt VALUES (3,'1~4~5~1');
This query:
SELECT VALUE, COUNT(*) "Total number in table"
FROM (SELECT tt.ID, SUBSTR(qq.value, sp, ep-sp) VALUE
FROM (SELECT id, value
, INSTR('~'||value, '~', 1, L) sp -- 1st posn of substr at this level
, INSTR(value||'~', '~', 1, L) ep -- posn of delimiter at this level
FROM tt JOIN (SELECT LEVEL L FROM dual CONNECT BY LEVEL < 20) q -- 20 is max #substrings
ON LENGTH(value)-LENGTH(REPLACE(value,'~'))+1 >= L
) qq JOIN tt on qq.id = tt.id)
GROUP BY VALUE
ORDER BY VALUE;
Results in:
VALUE Total number in table
---------- ---------------------
0 1
1 3
2 1
3 2
4 3
5 1
6 1
7 rows selected
SQL>
You can adjust the maximum number of items in your search string by adjusting the "LEVEL < 20" to "LEVEL < your_max_items".

SQL Aggregation for a smaller result set

I have a database for which I need to aggregate records into another smaller set. This result set should contain the difference between maximum and minumum of specific columns of the original records where they add up to certain SUM, a closed interval constant C.
The constant C determines how the original records are aggregated and no entry in the resulting set ever exceeds it. Naturally I am supposed to run this in natural primary key order..
To illustrate: table has:
[key]
[a]
[b]
[minColumn]
[maxColumn]
[N]
...all are int datatype.
I am after a result set that has entries where the MAX(maxColumn) - MIN(minColumn) for that group such that when their difference is summed up it is less or equal to constant C.
Apart from the MAX(maxColumn) and MIN(minColumn) value I also need the FIRST record column [a] and LAST record column [b] values before creating a new entry in this result set. Finally, the N column should be SUMmed for all original records in a group.
Is there an efficient way to do this without cursors?
-----[Trivial Sample]------------------------------------------------------------
I am attempting to group-by a slightly complicated form of a running sum, constant C.
There is only one table, columns are all of int type and sample data
declare #t table (
PK int primary key
, int a, int b, int minColumn, int maxColumn, int N
)
insert #t values (1,5,6,100,200,1000)
insert #t values (2,7,8,210,300,2000)
insert #t values (3,9,10,420,600,3000)
insert #t values (4,11,12,640,800,4000)
Thus for:
key, a, b, minColumn, maxColumn, N
---------------------------------------
1, 5, 6, 100, 200, 1000
2, 7, 8, 210, 300, 2000
3, 9, 10, 420, 600, 3000
4, 11, 12, 640, 800, 4000
I need the result set to look like, for a constant C of 210 :
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 8 100 300 3000
9 10 420 600 3000
11 12 640 800 4000
[ Adding the bounty and sample as discussed below]
For C = 381, It should contain 2 rows:
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 8 100 300 3000
9 12 420 800 7000
Hope this demonstrates the problem better.. and for a constant C say 1000 you would get 1 record in the result:
firstA | lastB | MIN_minColumn | MAX_maxColumn | SUM_N
5 12 100 800 10000
DECLARE #c int
SELECT #c = 210
SELECT MIN(a) firstA,
MAX(b) lastB,
MIN(minColumn) MIN_minColumn,
MAX(maxColumn) MAX_maxColumn,
SUM(N) SUM_N
FROM #t t
JOIN (SELECT key, floor(sum/#c) as rank
FROM (SELECT key,
(SELECT SUM(t2.maxColumn - t2.minColumn)
FROM #t t2
WHERE t2.key <= t1.key
GROUP BY t1.key) as sum
FROM #t t1) A
) B on B.key = t.key
GROUP BY B.rank
/*
Table A: for each key, calculating SUM[maxColumn-minColumn] of all keys below it.
Table B: for each key, using the sum in A, calculating a rank so that:
sum = (rank + y)*#c where 0 <= y < 1.
ex: #c=210, rank(100) = 0, rank(200) = 0, rank(220) = 1, ...
finally grouping by rank, you'll have what you want.
*/
declare #c int
select #c = 210
select firstA = min(a), lastB = max(b), MIN_minColumn = min(minColumn), MAX_maxColumn = max(maxColumn), SUM_N = sum(N)
from #t
where minColumn <= #c
union all
select a, b, minColumn, maxColumn, N
from #t
where minColumn > #c
I am a little confused on the grouping logic for result you are trying to produce, but from the description of what you are looking for, I think you need a HAVING clause. You should be able to do something like:
SELECT groupingA, groupingB, MAX(a) - MIN(b)
FROM ...
GROUP BY groupingA, groupingB
HAVING (MAX(a) - MIN(b)) < C
...in order to filter out the difference between your max and min values, once you've determined your grouping. Hope this is helpful

SQL query to return rows sorted by key plus empty rows for missing keys

I have a table that has, in essence, this structure:
key value
------ ------
2 val1
3 val2
5 val3
The keys are sequential integers from 1 up to (currently) 1 million, increasing by several thousand each day. Gaps in the keys occur when records have been deleted.
I'm looking for an SQL query that returns this:
key value
------ ------
1
2 val1
3 val2
4
5 val3
I can see how to do this with joining to a second table that has a complete list of keys. However I'd prefer a solution that uses standard SQL (no stored procedures or a second table of keys), and that will work no matter what the upper value of the key is.
SQL queries have no looping mechanism. Procedure languages have loops, but queries themselves can only "loop" over data that they find in a table (or a derived table).
What I do to generate a list of numbers on the fly is to do a cross-join on a small table of digits 0 through 9:
CREATE TABLE n (d NUMERIC);
INSERT INTO n VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);
Then to generate 00..99:
SELECT n1.d + n2.d*10 AS d
FROM n AS n1 CROSS JOIN n AS n10;
If you want only 00..57:
SELECT n1.d + n2.d*10 AS d
FROM n AS n1 CROSS JOIN n AS n2
WHERE n1.d + n2.d*10 <= 57;
You can of course join the table for the 100's place, 1000's place, etc. Note that you can't use column aliases in the WHERE clause, so you have to repeat the full expression.
Now you can use this as a derived table in a FROM clause and join it to your data table.
SELECT n0.d, mytable.value
FROM
(SELECT n1.d + n2.d*10 + n2.d*100 + n3.d*1000
+ n4.d*10000 + n5.d*100000 AS d
FROM n AS n1 CROSS JOIN n AS n2 CROSS JOIN n AS n3
CROSS JOIN n AS n4 CROSS JOIN n AS n5) AS n0
LEFT OUTER JOIN mytable ON (n0.d = mytable.key)
WHERE n0.d <= (SELECT MAX(key) FROM mytable);
You do need to add another CROSS JOIN each time your table exceeds an order of magnitude in size. E.g. when it grows past 1 million, add a join for n6.
Note also we can now use the column alias in the WHERE clause of the outer query.
Admittedly, it can be a pretty expensive query to do this solely in SQL. You might find that it's both simpler and speedier to "fill in the gaps" by writing some application code.
Another method would be to create a resultset of the million numbers, and use it as a basis for the join. That might do the job for you. (stolen from ASKTOMs Blog)
select level
from dual
connect by level <= 1000000
yielding something like this
WITH
upper_limit AS
(
select 1000000 limit from dual
),
fake_table AS
(
select level key
from dual
connect by level <= (select limit from upper_limit)
)
select key, value
from table, fake_table
where fake_table.key = table.key(+)
I'm not at work, so I can't test this. Your mileage may vary. I use Oracle at work.
In MySQL you can find the edges of the gaps by performing left joins against itself with positive and negative offsets.
Eg:
create table seq ( i int primary key, v varchar(10) );
insert into seq values( 2, 'val1' ), (3, 'val2' ), (5, 'val3' );
select s.i-1 from seq s left join seq m on m.i = (s.i -1) where m.i is null;
+-------+
| s.i-1 |
+-------+
| 1 |
| 4 |
+-------+
select s.i+1 from seq s left join seq m on m.i = (s.i +1) where m.i is null;
+-------+
| s.i+1 |
+-------+
| 4 |
| 6 |
+-------+
This doesn't give you exactly want you want, but gives enough information to work out what the missing rows are.
WITH range (num) AS (
SELECT 1 -- use your own lowerbound
UNION ALL
SELECT 1 + num FROM range
WHERE num < 10 -- use your own upper bound
)
SELECT r.num, y.* FROM range r left join yourtable y
on r.num = y.id