Split row into several with SQL statement - sql

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

Related

Creating sequence in SQL with different length

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

Can I delete multiple of nth rows in a single query from a table in SQL?

I want to delete multiple of 4 from my table which have thousands of record. How can I do it?
Ex:-
Table1
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
I want to delete every 4th row.
I don't want to use a loop or cursor.
Delete A from
(
Select *,Row_Number() Over(Order By Id) as RN from TableA
) A
where RN%4=0
SQL Fiddle Link
Try this...
delete from table_name where (col1 % 4) = 0
Use a CTE.
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (ORDER BY t.rowfield) AS rank
FROM Table1 t)
SELECT rowfield, fielda
FROM cte
WHERE rank%4 != 0
Output
rowfield fielda
1 a
2 b
3 c
5 e
6 f
7 g
9 i
SQL Fiddle: http://sqlfiddle.com/#!6/c9540b/13/0
Once you are happy with the output use DELETE FROM.
This can use an index if one exists and uses numbers table
;with cte
as
(select n from numbers
where n%4=0
)
delete t
from table1 t
join
cte c
on c.n=t.id
Try this
DECLARE #nvalToDelete varchar(100)='4,7,3,8,9'-- just give values to delete from table
DECLARE #temp TABLE
(
valtodelete VARCHAR(100)
)
DECLARE #Deletetemp TABLE
(
valtodelete INT
)
INSERT INTO #temp
SELECT #nvalToDelete
INSERT INTO #Deletetemp
SELECT split.a.value('.', 'VARCHAR(1000)') AS valToDelete
FROM (SELECT Cast('<S>' + Replace(valtodelete, ',', '</S><S>')
+ '</S>' AS XML) AS valToDelete
FROM #temp) AS A
CROSS apply valtodelete.nodes('/S') AS Split(a)
DECLARE #Table1 TABLE
(
ID INT,
val varchar(10)
)
INSERT INTO #Table1
SELECT 1,'a' UNION ALL
SELECT 2,'b' UNION ALL
SELECT 3,'c' UNION ALL
SELECT 4,'d' UNION ALL
SELECT 5,'e' UNION ALL
SELECT 6,'f' UNION ALL
SELECT 7,'g' UNION ALL
SELECT 8,'h' UNION ALL
SELECT 9,'i'
SELECT *
FROM #Table1;
WITH cte
AS (SELECT *,
RN = Row_number()
OVER (
ORDER BY id )
FROM #Table1)
DELETE FROM #Table1
WHERE id IN(SELECT id FROM cte
WHERE rn IN (SELECT CASt(valToDelete AS INT) FROM #Deletetemp)
)
SELECT *
FROM #Table1

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
)

SQL create table with integers

I want to have in a database a table with single column with integers (1,2,...) since it can be helpful for certain joins.
I came up with a solution using a loop. Is there a more efficient way of creating such a table?
My solution
CREATE TABLE #NUM
(
NUM int
)
DECLARE #i int=1
WHILE #i<10000
BEGIN
INSERT INTO #Temp
SELECT #i
SET #i = #i + 1
END
I will do this using a tally table
;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
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b) -- 100*100
INSERT INTO #Temp
SELECT ROW_NUMBER() OVER (ORDER BY n) FROM e3 ORDER BY n;
check here for more info
SELECT
row_number() over (order by message_id) num
into #Table
from sys.messages
This is the same principle as the answer by NoDisplayName, but I think it is a little cleaner (my opinion):
;WITH TBL(ROW_NUM) AS (
SELECT 1 AS ROW_NUM
UNION ALL
SELECT ROW_NUM+1
FROM TBL
WHERE ROW_NUM < 100
)
SELECT ROW_NUMBER() OVER (ORDER BY T1.ROW_NUM) AS ROW_NUM
FROM TBL T1
JOIN TBL T2 ON 1=1
JOIN TBL T3 ON 1=1

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