Creating sequence in SQL with different length - sql

I have a table with the customer identifier as PK and his time to maturity in months:
Customer | Maturity
---------+-----------
1 80
2 60
3 52
4 105
I want to create a table which will have customer identifier and the maturity will be defined as sequence of number with the increment + 1:
Customer | Maturity
---------+------------
1 1
1 2
1 ....
1 80
2 1
2 2
2 ...
2 60
I don't know whether I should use a sequence or the cross join or how to solve this problem.

one way is to use recursive CTE.
; with cte as
(
select Customer, M = 1, Maturity
from yourtable
union all
select Customer, M = M + 1, Maturity
from yourtable
where M < Maturity
)
select *
from cte
option (MAXRECURSION 0)

You can try joining your current table to a sequence table to generate the maturity ranges you want.
WITH cte AS (
SELECT 1 AS seq
UNION ALL
SELECT seq + 1
FROM cte
WHERE seq < 500
)
SELECT
t1.Customer,
t2.seq AS Maturity
FROM yourTable t1
INNER JOIN cte t2
ON t2.seq <= t1.Maturity
ORDER BY
t1.Customer,
t2.seq
OPTION (MAXRECURSION 0);
Demo here:
Rextester

you can try query like below
create table t (Customer int, Maturity int)
insert into t values
(1,80)
,(2,60)
,(3,52)
,(4,105);
select Customer, r from
t cross join
(select top (select max(maturity) from t)
row_number() over( order by (select NULL)) r
from sys.objects s1 cross join sys.objects s2) k
where r<=Maturity
order by Customer asc,r asc
see live demo

You can try the below.
Created two temporary tables to represent your tables in below example.
You need to replace them with you table names and drop the first three lines.
declare #Customer table (Customer int, Maturity int)
declare #NewTable table (Customer int, Maturity int)
insert #Customer select 1, 80
declare #x int = 0
declare #iterations table (x int)
while #x <= (select max(Maturity) from #Customer)
begin
set #x += 1
insert #iterations select #x
end
insert #NewTable
select c.Customer, i.x from #Customer c left join #iterations i on i.x <= c.Maturity
select * from #NewTable

Late answer, but another option is an ad-hoc tally table in concert with a CROSS APPLY
Example
Select A.customer
,Maturity = B.N
From YourTable A
Cross Apply (
Select Top (A.Maturity) N=Row_Number() Over (Order By (Select NULL))
From master..spt_values n1
) B

Related

Split row into several with SQL statement

I have a row in a databasetable that is on the following form:
ID | Amount | From | To
5 | 5439 | 01.01.2014 | 05.01.2014
I want to split this up to one row pr month using SQL/T-SQL:
Amount | From
5439 | 01.01.2014
5439 | 02.01.2014
5439 | 03.01.2014
5439 | 04.01.2014
5439 | 05.01.2014
I, sadly, cannot change the database source, and I want to preferrably do this in SQL as I am trying to result of this Query with an other table in Powerpivot.
Edit: Upon requests on my code, I have tried the following:
declare #counter int
set #counter = 0
WHILE #counter < 6
begin
set #counter = #counter +1
select amount, DATEADD(month, #counter, [From]) as Dato
FROM [database].[dbo].[table]
end
This however returns several databasesets.
You can use a tally table to generate all dates.
SQL Fiddle
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(DATEDIFF(DAY, [From], [To])) + 1 FROM yourTable)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
yt.Id,
yt.Amount,
[From] = DATEADD(DAY, N-1, yt.[From])
FROM yourTable yt
CROSS JOIN Tally t
WHERE
DATEADD(DAY, N-1, yt.[From]) <= yt.[To]
Simplified explanation on Tally Table
You need a tally table with "running numbers". This may be a function (I posted one shortly here: https://stackoverflow.com/a/32096945/5089204) or a physical table (I posted an example here: https://stackoverflow.com/a/32474751/5089204) or a CTE to do this "on the fly" (the table example does it this way).
If you go with the posted function it could be like this:
declare #startDate DATETIME={d'2015-09-01'};
declare #EndDate DATETIME={d'2015-09-10'};
select DATEADD(DAY, Nmbr,#startDate)
from dbo.GetRunningNumbers(DATEDIFF(DAY,#startDate,#endDate)+1,0);
select * INTO #TEMP1 from
(values
(5 , 5439 , '01.01.2014', '05.01.2014'))t(id,amount,fromd,tod)
WITH CTE
AS
(
SELECT CAST(FROMD AS DATE) AS FROMD,amount,1 AS RN,ID FROM #TEMP1
UNION ALL
SELECT DATEADD(M,1,C.FROMD),C.amount,C.RN+1,C.ID
FROM CTE C
INNER JOIN #TEMP1 T ON T.id = C.ID AND DATEADD(M,1,c.FROMD)<=T.tod
)
SELECT * FROM CTE
create table t (fd date, td date)
insert into t values ('2015-01-01','2015-01-05')
WITH DATES (fd, td, Level)
AS
(
SELECT fd, td, 0 AS Level
FROM t
UNION ALL
-- Recursive member definition
SELECT DATEADD(day,level+1,e.fd),e.td,Level + 1
FROM t AS e
INNER JOIN Dates AS d ON DATEADD(day,-d.level,d.fd) = e.fd AND d.fd < d.td
)
-- Statement that executes the CTE
SELECT fd,td,level
from DATES
variant using recursive cte
--variable table for data sample
DECLARE #tbl AS TABLE
(
ID INT ,
Amount FLOAT ,
[From] DATE ,
[To] DATE
)
INSERT INTO #tbl
( ID, Amount, [From], [To] )
VALUES ( 5, 5439, '2014-01-01', '2014-01-05' )
--final query using recursive cte
;
WITH cte
AS ( SELECT T.ID ,
T.Amount ,
T.[From] ,
T.[To] ,
CONVERT(DATE, NULL) AS Dt ,
n = 0
FROM #tbl AS T
UNION ALL
SELECT cte.ID ,
cte.Amount ,
cte.[From] ,
cte.[To] ,
DATEADD(DAY, n, cte.[From]) ,
cte.n + 1
FROM cte
WHERE n <= DATEDIFF(day, cte.[From], cte.[To])
)
SELECT cte.ID ,
cte.Amount ,
dt AS [From]
FROM cte
WHERE cte.Dt IS NOT NULL
SQL Fiddle

Select minimum number in a range

I have a table with data like.
ItemCode
1000
1002
1003
1020
1060
I'm trying to write a SQL statement to get the minimum number (ItemCode) that is NOT in this table and it should be able to get the next lowest number once the previous minimum order ID has been inserted in the table but also skip the numbers that are already in the DB. I only want to get 1 result each time the query is run.
So, it should get 1001 as the first result based on the table above. Once the ItemCode = 1001 has been inserted into the table, the next result it should get should be 1004 because 1000 to 1003 already exist in the table.
Based on everything I have seen online, I think, I have to use a While loop to do this. Here is my code which I'm still working on.
DECLARE #Count int
SET #Count= 0
WHILE Exists (Select ItemCode
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060')
Begin
SET #COUNT = #COUNT + 1
select MIN(ItemCode) + #Count
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060'
END
I feel like there has to be an easier way to accomplish this. Is there a way for me to say...
select the minimum number between 1000 and 1060 that doesn't exist in table X
EDIT: Creating a new table isn't an option in my case
Final Edit: Thanks guys! I got it. Here is my final query that returns exactly what I want. I knew I was making it too complicated for no reason!
With T0 as ( select convert(int,ItemCode) + row_number() over (order by convert(int,ItemCode)) as ItemCode
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
And convert(int,ItemCode) <= '1060')
Select MIN(convert(varchar,ItemCode)) as ItemCode
from T0
where convert(int,ItemCode) Not in (Select convert(int,ItemCode)
from OITM
where itemCode like '10%'
AND convert(int,ItemCode) >= '1000'
and convert(int,ItemCode) <= '1060');
This should do the thing. Here you are generating sequantial number for rows, then comparing each row with next row(done by joining condition), and filtering those rows only where difference is not 1, ordering by sequence and finally picking the top most.
;with c as(select id, row_number() over(order by id) rn)
select top 1 c1.id + 1 as NewID
from c as c1
join c as c2 on c1.rn + 1 = c2.rn
where c2.id - c1.id <> 1
order by c1.rn
You could use row_number() to produce sequential values for each row, and then look for the first row where the row_number() doesn't match the value stored in the table. My SQL Server installation isn't working at the moment and SQL Fiddle seems to be down too, so I wrote this without being able to test it, but something like this should work:
declare #lowerBound int = 1000;
declare #upperBound int = 1060;
declare #x table ([id] int);
insert #x values (1000), (1002), (1003), (1020), (1060);
with [SequenceCTE] as
(
select
[id],
[seq] = (#lowerBound - 1) + row_number() over (order by [id])
from
#x
)
select top 1
[seq]
from
[SequenceCTE]
where
[seq] != [id] and
[seq] <= #upperBound;
EDIT: Here is a SQL Fiddle that demonstrates this approach. I don't know why the site wasn't working for me before. It doesn't seem to like my declare statements for some reason so I hard-coded the bounds instead, but hopefully it still gets the idea across.
You can do this using a Tally Table. Check this article by Jeff Moden for reference.
Basically, you want to generate numbers from #start to #end. That's where the Tally Table comes in. It will be used in the numbers generation. When you have your numbers, you just have to check for the minimum value that does not exist in your table.
SQL Fiddle
DECLARE #start INT = 1000
DECLARE #end INT = 1060
;WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
,E2(N) AS(SELECT 1 FROM E1 a, E1 b)
,E4(N) AS(SELECT 1 FROM E2 a, E2 b)
,Tally(N) AS(
SELECT TOP(#end - #start + 1) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) - 1 FROM E4
)
SELECT
MIN(#start + t.N)
FROM Tally t
WHERE
#start + t.N <= #end
AND NOT EXISTS(
SELECT 1
FROM OITM
WHERE CAST(ItemCode AS INT) = #start + t.N
)
Here is another query that uses sys.columns to generate the Tally Table:
;WITH Tally(N) AS(
SELECT TOP(#end - #start + 1) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) - 1
FROM sys.columns a
--CROSS JOIN sys.columns b
)
SELECT
MIN(#start + t.N)
FROM Tally t
WHERE
#start + t.N <= #end
AND NOT EXISTS(
SELECT 1
FROM OITM
WHERE CAST(ItemCode AS INT) = #start + t.N
)

missing number or max number from the list

I am looking for missing number in the list, it works perfectly fine but when it start from 2, would it be possible to get 1. in below insertion
it should provide 1 not 4. please help thanks
drop table #temp
create table #temp
(
Number INT
)
insert into #temp
(Number)
select 2 union all
select 3 union all
select 5
SELECT MIN(t1.Number) + 1 AS MissingNumber
FROM #temp t1
LEFT OUTER JOIN #temp t2 ON (t1.Number + 1 = t2.Number)
WHERE t2.Number IS NULL
I will suggest you to create a separate numbers table to do this.
There a many ways to create number table. Check this link for more info
SELECT TOP (1000) n = Row_number()OVER (ORDER BY number)
INTO #numbers
FROM [master]..spt_values
ORDER BY n;
CREATE TABLE #temp
(Number INT)
INSERT INTO #temp(Number)
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 5
SELECT Min(t1.n) AS MissingNumber
FROM #numbers t1
LEFT OUTER JOIN #temp t2
ON ( t1.n = t2.Number )
WHERE t2.Number IS NULL
I think its not possible because join doesn't knows that number starts from 1. it will search for min value.
we can use while loop to solve problem
use this
IF OBJECT_ID('Tempdb..#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp ( Number INT )
INSERT INTO #temp
( Number )
VALUES ( 2 ),
( 3 ),
( 5 );
WITH cte
AS ( SELECT n = 1
UNION ALL
SELECT n + 1
FROM cte
WHERE n <= 100 --can be increased with OPTION ( MAXRECURSION {iteration value} ) at the end of the query
)
SELECT MIN(cte.n) AS MissingNumber
FROM cte
LEFT JOIN #temp t ON ( cte.n = t.Number )
WHERE t.Number IS NULL

Split a row on 2 or more rows depending on a column

I have a question
If I have one row that looks like this
|ordernumber|qty|articlenumber|
| 123125213| 3 |fffff111 |
How can I split this into three rows like this:
|ordernumber|qty|articlenumber|
| 123125213| 1 |fffff111 |
| 123125213| 1 |fffff111 |
| 123125213| 1 |fffff111 |
/J
You can use recursive CTE:
WITH RCTE AS
(
SELECT
ordernumber, qty, articlenumber, qty AS L
FROM Table1
UNION ALL
SELECT
ordernumber, 1, articlenumber, L - 1 AS L
FROM RCTE
WHERE L>0
)
SELECT ordernumber,qty, articlenumber
FROM RCTE WHERE qty = 1
SQLFiddleDEMO
EDIT:
Based on Marek Grzenkowicz's answer and MatBailie's comment, whole new idea:
WITH CTE_Nums AS
(
SELECT MAX(qty) n FROM dbo.Table1
UNION ALL
SELECT n-1 FROM CTE_Nums
WHERE n>1
)
SELECT ordernumber ,
1 AS qty,
articlenumber
FROM dbo.Table1 t1
INNER JOIN CTE_Nums n ON t1.qty >= n.n
Generating number from 1 to max(qty) and join table on it.
SQLFiddle DEMO
Here's a quick hack using an additional table populated with a number of rows suitable for the qty values you are expecting:
-- helper table
CREATE TABLE qty_splitter (qty int)
INSERT INTO qty_splitter VALUES (1)
INSERT INTO qty_splitter VALUES (2)
INSERT INTO qty_splitter VALUES (3)
INSERT INTO qty_splitter VALUES (4)
INSERT INTO qty_splitter VALUES (5)
....
-- query to produce split rows
SELECT t1.ordernumber, 1, t1.articlenumber
FROM table1 t1
INNER JOIN qty_splitter qs on t.qty >= qs.qty
You can do it using CTE
declare #t table (ordername varchar(50), qty int)
insert into #t values ('ord1',5),('ord2',3)
;with cte as
(
select ordername, qty, qty-1 n
from #t
union all
select ordername, qty, n-1
from cte
where n>0
)
select ordername,1
from cte
order by ordername
Also you can use option with master..spt_values system table.
SELECT t.ordernumber, o.qty, t.articlenumber
FROM dbo.SplitTable t CROSS APPLY (
SELECT 1 AS qty
FROM master..spt_values v
WHERE v.TYPE = 'P' AND v.number < t.qty
) o
However, for this purpose is preferable to use its own sequence table
See demo on SQLFiddle

Define ranges to cover gaps in a number sequence (T-SQL)

Simplifying my problem down - I have 6-digit field which assigns numbers to customers starting from 1 and ending to 999999. Most numbers are sequentially assigned, but numbers can be assigned manually by users, and this feature has been used in an unpredicatable pattern throughout the range.
We now need to identify numbers that have not been assigned (easy) - and then convert this into a number of ranges (seems complex).
For example given the following numbers have been assigned
1,2,3,4,5,
1001,1002,1003,1004,1005,
999101,999102,999103,999104,999105
I need a resulting set of ranges like
Start End
6 1000
1006 999100
999106 999999
My thinking so far is this is probably too complex to write in queries - and best achieved by looping from 1 to 999999, and adding ranges to a temp table.
Interested to hear ideas as I can imagine there are a few approaches. I'm using SQL Server 2008 R2. This is a one-off exercise so even a non-SQL solution might be appropriate, if this were for example easily done in Excel.
Try this
declare #t table (num int)
insert #t values (2),(3),(6),(7),(9),(10),(11)
select
MIN(number) as rangestart,
MAX(number) as rangeend
from
(
select *,
ROW_NUMBER() over (order by number) -
ROW_NUMBER() over (order by num,number) grp
from
(
select number from master..spt_values where type='p' and number between 1 and 15
) numbers
left join #t t
on numbers.number = t.num
) v
where num is null
group by grp
Reference : gaps and islands by itzik ben-gan
To create a numbers query upto 999999
select p1.number + p2.number * 2048 as number
from
(select * from master..spt_values where type='p' ) p1,
(select * from master..spt_values where type='p' and number<489) p2
where p1.number + p2.number * 2048 <=999999
declare #t table (num int)
insert #t values
(2),(3),(4),(5),
(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
;with cte as
(
select num,(ROW_NUMBER() OVER(ORDER BY num)) + 1 as idx from #t
union
select 0 [num],1 [idx] --start boundary
union
select 1000000 [num],COUNT(num) + 2 [idx] from #t --end boundary
)
select c1.num + 1 [Start], c2.num - 1 [End]
from cte c1
inner join cte c2 on c2.idx = c1.idx + 1
where c2.num != c1.num + 1
create table #temp (id int)
insert into #temp (id)
values (1),(2),(3),(1000),(1001),(1002),(2000)
--drop table #temp
with cte as
(
select *, ROW_NUMBER() over(order by id) as rn
from #temp a
)
select a.id + 1, b.id - 1
from cte a join cte b on a.rn = b.rn - 1 and a.id <> b.id -1
it wont include tail ranges, like 2001-9999
Here is SQLFiddle demo
select
case when max(n1)=0 then 1 else max(n1)end,
case when max(n2)=0 then 999999 else max(n2)end
from
(
select t.n+1 as n1,0 n2,
row_number() over(order by t.n)
+isnull((select 0 from t where n=1),1)
rn
from t
left join t t2 on t.n+1=t2.n
where t2.n is null
union all
select 0 n1, t.n-1 as n2 ,
row_number() over(order by t.n) rn
from t
left join t t2 on t.n-1=t2.n
where t2.n is null
and t.n>1
) t3
group by rn
declare #t table(id int)
insert #t values
(1),(2),(3),(4),(5),(1001),(1002),(1003),(1004),(1005),
(999101),(999102),(999103),(999104),(999105)
select t1.id+1 [start], coalesce(t3.[end], 999999) [end]
from #t t1
left join #t t2 on t1.id +1 = t2.id
cross apply
(select min(id)-1 [end] from #t where t1.id < id
) t3
where t2.id is null
if you have a table called "kh" for example with a column "myval" which is your list of integers you could try this SELECT.
SELECT MAX(t1.myval+1) AS 'StartRange',t3.myval-1 AS 'EndRange'
FROM kh t1, kh t3
WHERE t1.myval+1 NOT IN (SELECT myval FROM kh t2 ORDER BY myval)
AND t3.myval-1 NOT IN (SELECT myval FROM kh t4 ORDER BY myval)
AND t1.myval < t3.myval
GROUP BY t3.myval
ORDER BY StartRange