Generating row/columns for individual chars using CTE - sql-server

I have the below input table
Input
ID Row Data
1 1 a2b
1 2 p1d1
2 1 abcd
Expected Output
ID RowCol Chars
1 a1 a
1 b1 X
1 c1 X
1 d1 b
1 a2 p
1 b2 X
1 c2 d
1 d2 X
2 a1 a
2 b1 b
2 c1 c
2 d1 d
Each numbers in the data column will be treated as that many X's. So if the expand the first resord which is 'a2b'
it becomes aXXB. that is the length will be 4. Representing in columns it will be a, b ,c and d.
And since, it is in the first row, therefore, the output will be
ID RowCol Chars
1 a1 a
1 b1 X
1 c1 X
1 d1 b
The ddl is as under
Declare #t table(ID int , Row int, Data varchar(10))
Insert into #t
Select 1, 1,'a2b' Union All Select 1,2,'p1d1' Union All Select 2,1,'abcd'
Looking for a cte based solution.
Thanks in advance

As i promised i would make a better solution today. I know you will like and most likely use it.
DECLARE #t TABLE(ID INT , Row INT, Data VARCHAR(10))
Insert INTO #t
SELECT 1, 1,'a2b' UNION All SELECT 1,2,'p1d1' UNION All SELECT 2,1,'abcd'
;WITH cte(id, row, num, data)
AS (
SELECT id, row, 1 num,CAST(data as VARCHAR(10)) data
FROM #t
UNION ALL
SELECT ch.id, row, CH.num +1, CAST(REPLACE(ch.data, ch.num, REPLICATE('X', ch.num)) as VARCHAR(10))
FROM cte ch
WHERE ch.num < 9 )
, cte2(id, rowcol, row, num, data, chars, LEVEL) as
(SELECT id, CHAR(97) + CAST(row AS CHAR) rowcol, row, num, data, SUBSTRING(data, 1, 1), 1 LEVEL
FROM cte
where num = 9
UNION all
SELECT id, CHAR(97 + LEVEL) + CAST(row AS CHAR) rowcol, row, num, data, SUBSTRING(data, LEVEL + 1, 1), LEVEL + 1
FROM cte2 ch
where LEVEL < LEN(data)
)
SELECT ID, rowcol, chars
FROM CTE2
ORDER BY id, data, rowcol

Related

union the table based on row value

Below is my table structure.
declare #t1 table (id int identity,val int not null,datatype1 int null,datatype2 int null,datatype3 int null,datatype4 int null,type int)
declare #t2 table (id int identity,val int not null,datatype1 int null,datatype2 int null,datatype3 int null,datatype4 int null,type int)
insert into #t1 values (10,1,0,0,0,1),(31,1,0,0,0,1),(20,1,0,0,0,1),(30,1,0,0,0,1)
insert into #t2 values (31,0,1,0,0, 2),(4,0,0,1,0,3),(12,0,0,0,1,4),(31,0,0,0,1,4)
select * from #t1;
select * from #t2;
i am combining 2 table data with below query.
select val,max(datatype1) datatype1,max(datatype2)datatype2,max(datatype3)datatype3,max(datatype4)datatype4 from (
select * from #t1
union all
select * from #t2
) as data group by val
i need to change the logic if val is 31 and type=2 in #t2 ,
for these cases i need get 2 rows for val 31 and other cases only distinct values
Expected Result:
val datatype1 datatype2 datatype3 datatype4
4 0 0 1 0
10 1 0 0 0
12 0 0 0 1
20 1 0 0 0
30 1 0 0 0
31 1 0 0 1
31 0 1 0 0 --- only if in #t2 val =31 and type=2
pls let me knwwat need to be changed for only value 31 and type=2
Based on provided data looks like CASE expression is the way to go:
select val,
max(datatype1) datatype1,
max(datatype2) datatype2,
max(datatype3) datatype3,
max(datatype4) datatype4
from (
select 't1' AS tab_name, * from #t1
union all
select 't2' AS tab_name, * from #t2
) as data
group by val, CASE WHEN tab_name = 't2' and val=31 and type=2 THEN 1 END;
-- creating subgroup for this specific conditions
db<>fiddle demo

Transact-SQL - number rows until condition met

I'm trying to generate the numbers in the "x" column considering the values in field "eq", in a way that it should assign a number for every record until it meets the value "1", and the next row should reset and start counting again. I've tried with row_number, but the problem is that I only have ones and zeros in the column I need to evaluate, and the cases I've seen using row_number were using growing values in a column. Also tried with rank, but I haven't managed to make it work.
nInd Fecha Tipo #Inicio #contador_I #Final #contador_F eq x
1 18/03/2002 I 18/03/2002 1 null null 0 1
2 20/07/2002 F 18/03/2002 1 20/07/2002 1 1 2
3 19/08/2002 I 19/08/2002 2 20/07/2002 1 0 1
4 21/12/2002 F 19/08/2002 2 21/12/2002 2 1 2
5 17/03/2003 I 17/03/2003 3 21/12/2002 2 0 1
6 01/04/2003 I 17/03/2003 4 21/12/2002 2 0 2
7 07/04/2003 I 17/03/2003 5 21/12/2002 2 0 3
8 02/06/2003 F 17/03/2003 5 02/06/2003 3 0 4
9 31/07/2003 F 17/03/2003 5 31/07/2003 4 0 5
10 31/08/2003 F 17/03/2003 5 31/08/2003 5 1 6
11 01/09/2005 I 01/09/2005 6 31/08/2003 5 0 1
12 05/09/2005 I 01/09/2005 7 31/08/2003 5 0 2
13 31/12/2005 F 01/09/2005 7 31/12/2005 6 0 3
14 14/01/2006 F 01/09/2005 7 14/01/2006 7 1 4
There is another solution available:
select
nind, eq, row_number() over (partition by s order by s)
from (
select
nind, eq, coalesce((
select sum(eq) +1 from mytable pre where pre.nInd < mytable.nInd)
,1) s --this is the sum of eq!
from mytable) g
The inner subquery creates groups sequentially for each occurrence of 1 in eq. Then we can use row_number() over partition to get our counter.
Here is an example using Sql Server
I have two answers here. One is based off of the ROW_NUMBER() and the other is based off of what appears to be your index (nInd). I wasn't sure if there would be a gap in your index so I made the ROW_NUMBER() as well.
My table format was as follows -
myIndex int identity(1,1) NOT NULL
number int NOT NULL
First one is ROW_NUMBER()...
WITH rn AS (SELECT *, ROW_NUMBER() OVER (ORDER BY myIndex) AS rn, COUNT(*) AS max
FROM counting c GROUP BY c.myIndex, c.number)
,cte (myIndex, number, level, row) AS (
SELECT r.myIndex, r.number, 1, r.rn + 1 FROM rn r WHERE r.rn = 1
UNION ALL
SELECT r1.myIndex, r1.number,
CASE WHEN r1.number = 0 AND r2.number = 1 THEN 1
ELSE c.level + 1
END,
row + 1
FROM cte c
JOIN rn r1
ON c.row = r1.rn
JOIN rn r2
ON c.row - 1 = r2.rn
)
SELECT c.myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
Now the index...
WITH cte (myIndex, number, level) AS (
SELECT c.myIndex + 1, c.number, 1 FROM counting c WHERE c.myIndex = 1
UNION ALL
SELECT c1.myIndex + 1, c1.number,
CASE WHEN c1.number = 0 AND c2.number = 1 THEN 1
ELSE c.level + 1
END
FROM cte c
JOIN counting c1
ON c.myIndex = c1.myIndex
JOIN counting c2
ON c.myIndex - 1 = c2.myIndex
)
SELECT c.myIndex - 1 AS myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
The answer that I have now is via using
Cursor
I know if there is another solution without cursor it will be better for performance aspects
here is a quick demo of my solution:
-- Create DBTest
use master
Go
Create Database DBTest
Go
use DBTest
GO
-- Create table
Create table Tabletest
(nInd int , eq int)
Go
-- insert dummy data
insert into Tabletest (nInd,eq)
values (1,0),
(2,1),
(3,0),
(4,1),
(5,0),
(6,0),
(7,0),
(8,0),
(9,1),
(8,0),
(9,1)
Create table #Tabletest (nInd int ,eq int ,x int )
go
DECLARE #nInd int , #eq int , #x int
set #x = 1
DECLARE db_cursor CURSOR FOR
SELECT nInd , eq
FROM Tabletest
order by nInd
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #nInd , #eq
WHILE ##FETCH_STATUS = 0
BEGIN
if (#eq = 0)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = #x +1
end
else if (#eq = 1)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = 1
end
FETCH NEXT FROM db_cursor INTO #nInd , #eq
END
CLOSE db_cursor
DEALLOCATE db_cursor
select * from #Tabletest
The end result set will be as following:
Hope it helps.
Looking at this a slightly different way (which might not be true, but eliminates the need for cursors of recursive CTEs), it looks like you building ordered groups within your dataset. So, start by finding those groups, then determining the ordering of each of them.
The real key is to determine the rules to find the correcting grouping. Based on your description and comments, I'm guessing the grouping is from the start (ordered by the nInd column) ending at each row with and eq value of 1, so you can do something like:
;with ends(nInd, ord) as (
--Find the ending row for each set
SELECT nInd, row_number() over(order by nInd)
FROM mytable
WHERE eq=1
), ranges(sInd, eInd) as (
--Find the previous ending row for each ending row, forming a range for the group
SELECT coalesce(s.nInd,0), e.nInd
FROM ends s
right join ends e on s.ord=e.ord-1
)
Then, using these group ranges, you can find the final ordering of each:
select t.nInd, t.Fecha, t.eq
,[x] = row_number() over(partition by sInd order by nInd)
from ranges r
join mytable t on r.sInd < t.nInd
and t.nInd <= r.eInd
order by t.nInd

How do I construct a VARBINARY(MAX) from a byte value with LEN = 1?

Please, observe:
DECLARE #b VARBINARY(MAX) = 5
SELECT #b Value, LEN(#b) Length
Yields
Value Length
0x00000005 4
What I need is a VARBINARY(MAX) instance that equals 5, but has the length of 1.
Motivation:
I would like to generate the following sequence of VARBINARY values:
0x00
0x80
0x8080
0x808080
0x80808080
0x8080808080
0x808080808080
...
With an arbitrary depth given as a parameter. Here is my code:
DECLARE #Depth INT = 50
;WITH number AS (
SELECT TOP (#Depth - 1) ROW_NUMBER() OVER (ORDER BY object_id) - 1 n
FROM sys.objects
), mask AS (
SELECT n, CAST(0 AS VARBINARY(MAX)) mask
FROM number
WHERE n = 0
UNION ALL
SELECT mask.n + 1 n, CAST(mask.mask + CAST(0x80 AS VARBINARY) AS VARBINARY(MAX)) mask
FROM number
JOIN mask ON number.n = mask.n
)
SELECT n,mask FROM mask
And I am almost there, only the result is:
n mask
0 0x00000000
1 0x0000000080
2 0x000000008080
3 0x00000000808080
4 0x0000000080808080
5 0x000000008080808080
6 0x00000000808080808080
7 0x0000000080808080808080
8 0x000000008080808080808080
9 0x00000000808080808080808080
10 0x0000000080808080808080808080
...
So, my problem is the length - it is 4 bytes minimum. I need it to start from 1.
EDIT 1
Found the answer to my primary problem:
DECLARE #Depth INT = 50
;WITH number AS (
SELECT TOP (#Depth - 1) ROW_NUMBER() OVER (ORDER BY object_id) - 1 n
FROM sys.objects
), mask AS (
SELECT n, CAST(0x80 AS VARBINARY(MAX)) mask
FROM number
WHERE n = 1
UNION ALL
SELECT mask.n + 1 n, CAST(mask.mask + CAST(0x80 AS VARBINARY) AS VARBINARY(MAX)) mask
FROM number
JOIN mask ON number.n = mask.n
)
SELECT 0 n,CAST(0 AS VARBINARY(1)) mask
UNION ALL
SELECT n,mask FROM mask
But I do not understand why it does not have the same problem as my original solution. Why do the VARBINARY values now have the minimum length of 1 and not 4 as before?
The literal 5 is of type INT, which is 4 bytes. The literal 0x80(mind the 0x!) is of type BINARY(1), which is 1 byte. If you had written
DECLARE #b VARBINARY(MAX) = 0x5;
You would have achieved the desired result.

Split a string without delimiter in TSQl

I have a result set from SELECT Statement, how can i split one column without any delimiter
this is my result
Size TCount TDevice
2 5 E01
4.5 3 W02E01
I want to have this
Size TCount TDevice
2 5 E
2 5 0
2 5 1
4.5 3 W
4.5 6 0 (we have 2 times of 0)
4.5 3 2
4.5 3 1
thank you
You can join onto an auxiliary numbers table. I am using spt_values for demo purposes but you should create a permanent one.
WITH Nums
AS (SELECT number
FROM master..spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 1000),
Result(Size, TCount, TDevice)
AS (SELECT 2, 5,'E01'
UNION ALL
SELECT 4.5,3,'W02E01')
SELECT Size,
COUNT(*) * TCount AS TCount,
SUBSTRING(TDevice, number, 1) AS TDevice
FROM Result
JOIN Nums
ON Nums.number <= LEN(TDevice)
GROUP BY Size,
TCount,
SUBSTRING(TDevice, number, 1)
;with cte as
(
select Size,TCount,
substring(TDevice, 1, 1) as Chars,
stuff(TDevice, 1, 1, '') as TDevice
from t1
union all
select Size,TCount,
substring(TDevice, 1, 1) as Chars,
stuff(TDevice, 1, 1, '') as TDevice
from cte
where len(TDevice) > 0
)
select distinct Size,sum(TCount),Chars
from cte
group by Size,Chars
SQL Fiddle
Advantage: It doesn't require any User defined function (UDF) to be created.

Transposing columns to rows using UNPIVOT

I have a table that for some reason has hardcoded values like so:
Row ID QtyC1 QtyC2 QtyC3 QtyC4 QtyN1 QtyN2 QtyN3 QtyN4
100 10 5 8 9 11 12 5 6
101 9 11 12 5 6 10 4 9
The table has 35 columns and around 12k records (meaning around 500k values) and is being added to and amended constantly.
I am trying to transpose this in a view into:
Row ID Year Period Val
100 C 1 10
100 C 2 5
100 C 3 8
100 C 4 9
100 N 1 11
100 N 2 12
100 N 3 5
100 N 4 6
So far I have managed to split it out into single values using this query:
SELECT Row ID, YP, Val
FROM (SELECT Row ID
, QtyC1 AS C1
, QtyC2 AS C2
, QtyC3 AS C3
, QtyC4 AS C4
, QtyN1 AS N1
, QtyN2 AS N2
, QtyN3 AS N3
, QtyN4 AS N4
FROM MyTable
) SUB
UNPIVOT (Val FOR YP IN (C1,C2,C3,C4,N1,N2,N3,N4)) AS PVT
This is getting me a single identifying value (eg C1) but how can I split it so I have a numeric period and a single character for the year (1 and C)?
I can see it might be possible just splitting up the string into two parts but I was hoping for a cleaner way if possible.
You can easily split the YP string using LEFT(), RIGHT(), SUBSTRING(), etc. My suggestion would be how you are handling your UNPIVOT. It looks like you have a lot of columns to UNPIVOT so my suggestion might be to implement dynamic SQL to perform this query. You would do it this way:
declare #colsUnpivot varchar(max),
#query AS NVARCHAR(MAX),
#cols varchar(max)
select #colsUnpivot = stuff((select ','
+quotename(replace(C.name, 'Qty', ''))
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Qty%'
for xml path('')), 1, 1, '')
select #cols = stuff((select ','
+quotename(C.name) + ' as ' + replace(C.name, 'Qty', '')
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Qty%'
for xml path('')), 1, 1, '')
set #query
= 'select rowid,
left(YP, 1) YP,
cast(right(YP, len(YP) - 1) as int) period,
Val
from
(
select rowid, ' + #cols + '
from yourtable
) x1
unpivot
(
val for YP IN (' + #colsUnpivot + ')
) u'
exec(#query)
see SQL Fiddle with Demo
Why would this seem unclean?
SELECT Row ID, left(YP, 1) as year, cast(right(yp, 1) as int) as period, Val
FROM (SELECT Row ID
, QtyC1 AS C1
, QtyC2 AS C2
, QtyC3 AS C3
, QtyC4 AS C4
, QtyN1 AS N1
, QtyN2 AS N2
, QtyN3 AS N3
, QtyN4 AS N4
FROM MyTable
) SUB
UNPIVOT (Val FOR YP IN (C1,C2,C3,C4,N1,N2,N3,N4)) AS PVT

Resources