Column into rows and make a master table of columns - sql-server

I need insert columns data to a different table and create master table for columns
Like example : I need to convert table tblcatData into two tables tblCat and tblcatDataNew
tblCatData
Primaykey | A | B | C | D | D | F | G | H | I | J | K | L | M |
--------------------------------------------------------------------
1 | 1 | 2 | 3 | 5 | 5 | 5 | 3 | 3 | 3 | 1 | 4 | 1 | 1 |
2 | 1 | 2 | 5 | 5 | 5 | 5 | 3 | 5 | 3 | 1 | 1 | 5 | 1 |
3 | 5 | 2 | 3 | 5 | 5 | 5 | 5 | 3 | 3 | 1 | 1 | 1 | 4 |
tblCat
PrimaryKey | Category
----------------------------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
7 | G
. .
. .
. .
tblCatDataNew
PrimaryKey | FK_CatID | Data |
-----------------------------------
1 | 1 | 1 |
2 | 1 | 1 |
3 | 1 | 5 |
4 | 2 | 2 |
5 | 2 | 2 |
6 | 2 | 2 |
7 | 3 | 3 |
8 | 3 | 5 |
. . .
. . .
. . .

You could try the following scenario:
Create tblCat.
Create tblCatDataNew with the following deviations from the original design:
the FK_CatID column is allowed to accept NULLs temporarily (or maybe permanently, if that was your original intention);
an extra column is added temporarily to receive the category names from the original table.
Unpivot tblCatData and insert the results into tblCatDataNew (the values into Data and the column names, as category names, into the temporary column).
Select all the distinct category names from tblCatDataNew and insert them into tblCat. (That will produce the key values for them.)
Update the foreign keys in tblCatDataNew from tblCat, joining the two tables by category names.
Drop the temporary column from tblCatDataNew.
Set tblCatDataNew.FK_CatID as NOT NULL (that is, if you wanted it to be so).
Here's the entire test script, including creation of the original table (in case someone would like to try it):
BEGIN TRANSACTION
GO
/* prepare the original table, for tests */
WITH data (
Primaykey, A, B, C, D, E, F, G, H, I, J, K, L, M
) AS (
SELECT 1 , 1, 2, 3, 5, 5, 5, 3, 3, 3, 1, 4, 1, 1 UNION ALL
SELECT 2 , 1, 2, 5, 5, 5, 5, 3, 5, 3, 1, 1, 5, 1 UNION ALL
SELECT 3 , 5, 2, 3, 5, 5, 5, 5, 3, 3, 1, 1, 1, 4
)
SELECT * INTO tblCatData FROM data;
GO
/* Step 1 */
CREATE TABLE tblCat (
PrimaryKey int IDENTITY CONSTRAINT PK_tblCat PRIMARY KEY,
Category varchar(50) NOT NULL
);
GO
/* Step 2 */
CREATE TABLE tblCatDataNew (
PrimaryKey int IDENTITY CONSTRAINT PK_tblCatDataNew PRIMARY KEY,
FK_CatID int NULL CONSTRAINT FK_tblCatDataNew_tblCat FOREIGN KEY REFERENCES tblCat (PrimaryKey),
Data int,
Category varchar(50)
);
GO
/* Step 3 */
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data,
Category
FROM tblCatData
UNPIVOT (
Data for Category IN (A, B, C, D, E, F, G, H, I, J, K, L, M)
) u
ORDER BY
Category,
Primaykey;
GO
/* Step 4 */
INSERT INTO tblCat (Category)
SELECT DISTINCT Category
FROM tblCatDataNew
GO
/* Step 5 */
UPDATE tblCatDataNew
SET FK_CatID = c.PrimaryKey
FROM tblCat c
WHERE tblCatDataNew.Category = c.Category
GO
/* Step 6 */
ALTER TABLE tblCatDataNew
DROP COLUMN Category
GO
/* Step 7 */
ALTER TABLE tblCatDataNew
ALTER COLUMN FK_CatID int NOT NULL
GO
/* view the results */
SELECT * FROM tblCat
SELECT * FROM tblCatDataNew
GO
ROLLBACK TRANSACTION
Note that the UNPIVOT clause is supported in SQL Server starting from the 2005 version. In earlier versions you'd have to use a different method to unpivot data (Step 3), e.g. like this:
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data = CASE x.CatNum
WHEN 1 THEN A
WHEN 2 THEN B
WHEN 3 THEN C
WHEN 4 THEN D
WHEN 5 THEN E
WHEN 6 THEN F
WHEN 7 THEN G
WHEN 8 THEN H
WHEN 9 THEN I
WHEN 10 THEN J
WHEN 11 THEN K
WHEN 12 THEN L
WHEN 13 THEN M
END,
Category = CASE x.CatNum
WHEN 1 THEN 'A'
WHEN 2 THEN 'B'
WHEN 3 THEN 'C'
WHEN 4 THEN 'D'
WHEN 5 THEN 'E'
WHEN 6 THEN 'F'
WHEN 7 THEN 'G'
WHEN 8 THEN 'H'
WHEN 9 THEN 'I'
WHEN 10 THEN 'J'
WHEN 11 THEN 'K'
WHEN 12 THEN 'L'
WHEN 13 THEN 'M'
END
FROM tblCatData
CROSS JOIN (
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 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12 UNION ALL
SELECT 13
) x (CatNum)
ORDER BY
Category,
Primaykey;
or even like this:
INSERT INTO tblCatDataNew (
Data,
Category
)
SELECT
Data = CASE x.CatNum
WHEN 1 THEN A
WHEN 2 THEN B
WHEN 3 THEN C
WHEN 4 THEN D
WHEN 5 THEN E
WHEN 6 THEN F
WHEN 7 THEN G
WHEN 8 THEN H
WHEN 9 THEN I
WHEN 10 THEN J
WHEN 11 THEN K
WHEN 12 THEN L
WHEN 13 THEN M
END,
Category = x.CatName
FROM tblCatData
CROSS JOIN (
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' UNION ALL
SELECT 10, 'J' UNION ALL
SELECT 11, 'K' UNION ALL
SELECT 12, 'L' UNION ALL
SELECT 13, 'M'
) x (CatNum, CatName)
ORDER BY
Category,
Primaykey;
Here are the results that the above script produced for me:
tblCat:
PrimaryKey Category
----------- --------------------------------------------------
1 A
2 B
3 C
4 D
5 E
6 F
7 G
8 H
9 I
10 J
11 K
12 L
13 M
tblCatDataNew:
PrimaryKey FK_CatID Data
----------- ----------- -----------
1 1 1
2 1 1
3 1 5
4 2 2
5 2 2
6 2 2
7 3 3
8 3 5
9 3 3
10 4 5
11 4 5
12 4 5
13 5 5
14 5 5
15 5 5
16 6 5
17 6 5
18 6 5
19 7 3
20 7 3
21 7 5
22 8 3
23 8 5
24 8 3
25 9 3
26 9 3
27 9 3
28 10 1
29 10 1
30 10 1
31 11 4
32 11 1
33 11 1
34 12 1
35 12 5
36 12 1
37 13 1
38 13 1
39 13 4

You can use union to unpivot the columns, and select ... into to store the result in a new table:
select PrimaryKey
, FK_CatId
, Category
into tblCatDataNew
from (
select PrimaryKey
, 1
, A
from tblCatData
union all
select PrimaryKey
, 2
, B
from tblCatData
union all
...
)

Related

How can I take out each element of string to a separate column? [duplicate]

This question already has answers here:
How to split a comma-separated value to columns
(38 answers)
Closed 3 years ago.
I have table like:
|----|----------------|--------------------------|----------------------|
| id | tickets | comb1 | comb2 |
|---------------------|--------------------------|----------------------|
| 1 | 3146000011086..| ,13, ,31, ,50,66,77,..| ,22,38,40, , .. |
|---------------------|--------------------------|----------------------|
|2..n| 314600001924...| 5,14,23, , ,50, , ,..| 4,12,21, ,47, ,.. |
|-----------------------------------------------------------------------|
I need to take out each elements of comb1 and comb2 to columns like:
|---------------------|------------------|------------------|---------------|
| val_of_comb1(1) | val_of_comb1(2) | ..val_of_comb2(1)|val_of_comb2(2)|
|---------------------|------------------|------------------|---------------|
| | 13 | | 22 |
|---------------------|------------------|------------------|---------------|
| 5 | 14 | .. 4 | 12 |
|---------------------|------------------|------------------|---------------|
Maybe take out each element with loop? (but if I have a lot of records how it will affect the database) welcome any ideas
A. cross apply, pivot, and string_split
Here is a version if Comb1 splits into 12 strings.
drop table X
create table X
(
id int,
comb1 nvarchar(max)
);
insert into X values (1,',13, ,31, ,50,66,77,..');
insert into X values (2,'5,14,23, , ,50, , ,..');
-- From https://stackoverflow.com/questions/12195504/splitting-a-string-then-pivoting-result by Kannan Kandasamy
select * from (
select * from X x cross apply (select RowN=Row_Number() over (Order by (SELECT NULL)), value from string_split(x.Comb1, ',') ) d) src
pivot (max(value) for src.RowN in([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) as p
id comb1 1 2 3 4 5 6 7 8 9 10 11 12
1 ,13, ,31, ,50,66,77,.. 13 31 50 66 77 .. NULL NULL NULL
2 5,14,23, , ,50, , ,.. 5 14 23 50 .. NULL NULL NULL
B. Just STRING_SPLIT and code
One option is to use STRING_SPLIT which will return rows.
select value from STRING_SPLIT(',13, ,31, ,50,66,77,..',',');
value
13
31
50
66
77
..
You could then collect all the rows in your code and collect them as an array.

How to transpose rows to columns in SQL Server

I have a fact table as below:
Table Types
TypeId | Name
1 x
2 y
3 z
Table Period:
PeriodId | Date
1 2014-01-31
2 2015-01-31
RowNumber | Value | TypeId | Identifier | PeriodId
1 12 1 cc1 1
2 10 2 cc1 2
3 17 3 cc1 1
.. 30 ... ... ..
.. 60 1 cc2 1
23 2 cc2 2
From these tables I am trying to create a single flatten table as below:
Identifier | periodId | x | y | z
cc1 1 12 10 17
cc1 2 .. .. ..
cc2 1 .. .. ..
How can I query to get the data in the above format?
INSERT INTO NewTable
SELECT Identifier, PeriodId,
(SELECT Value FROM [Values] v2 WHERE v2.Identifier = v1.Identifier AND v2.PeriodId = v1.PeriodId AND v2.TypeId = 1) AS 'x',
(SELECT Value FROM [Values] v2 WHERE v2.Identifier = v1.Identifier AND v2.PeriodId = v1.PeriodId AND v2.TypeId = 2) AS 'y',
(SELECT Value FROM [Values] v2 WHERE v2.Identifier = v1.Identifier AND v2.PeriodId = v1.PeriodId AND v2.TypeId = 3) AS 'z'
FROM [Values] v1
GROUP BY Identifier, PeriodId
Is this what you need?

Running "Group By" Ordinal Counter Based on a "Flip" Column

Usually I'm decent at set-based tsql problems. But this one is beating me.
I've been working 3 days on converting a while-loop procedure into a setbased one. I've gotten to the point below.......but can't make the final jump.
I have the following rows. MyOrdinal will be "in order" ... and a second column (MyMarker) will alternate between having a value and being null. Whenever this "flip" occurs on MyMarker, I would like to increment a "group by" ordinal counter by one. Whenever the "flip" values are non-null or null, these are grouped together as a set.
I've tried several things, but it was too ugly to post. That and since moving to ORM, I don't spend as much time in the tsql anymore.
declare #Holder table ( MyOrdinal int not null , MyMarker int , MyGroupNumber int )
INSERT INTO #Holder (MyOrdinal, MyMarker)
Select 1 , 1
union all Select 2, 2
union all Select 3, null
union all Select 4, 3
union all Select 5, 4
union all Select 6, 5
union all Select 7, 6
union all Select 8, 7
union all Select 9, 8
union all Select 10, 9
union all Select 11, 10
union all Select 12, 11
union all Select 13, 12
union all Select 14, 13
union all Select 15, 14
union all Select 16, 15
union all Select 17, null
union all Select 18, null
union all Select 19, null
union all Select 20, 16
union all Select 21, 17
union all Select 22, 18
union all Select 23, null
union all Select 24, null
union all Select 25, 19
union all Select 26, 20
union all Select 27, null
union all Select 28, 21
Select * from #Holder
Desired Output
| MyOrdinal | MyMarker | MyGroupNumber |
|-----------|----------|---------------|
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | null | 2 |
| 4 | 3 | 3 |
| 5 | 4 | 3 |
| 6 | 5 | 3 |
| 7 | 6 | 3 |
| 8 | 7 | 3 |
| 9 | 8 | 3 |
| 10 | 9 | 3 |
| 11 | 10 | 3 |
| 12 | 11 | 3 |
| 13 | 12 | 3 |
| 14 | 13 | 3 |
| 15 | 14 | 3 |
| 16 | 15 | 3 |
| 17 | null | 4 |
| 18 | null | 4 |
| 19 | null | 4 |
| 20 | 16 | 5 |
| 21 | 17 | 5 |
| 22 | 18 | 5 |
| 23 | null | 6 |
| 24 | null | 6 |
| 25 | 19 | 7 |
| 26 | 20 | 7 |
| 27 | null | 8 |
| 28 | 21 | 9 |
Try this one:
First, this assigns a same ROW_NUMBER for continuous Non-NULL MyMarker. ROW_NUMBER is NULL for NULL MyMarkers. After that, you want to add a ROW_NUMBER for NULL MyMarkers such that the value is between the previous NON-NULL and the next NON-NULL. Then use DENSE_RANK to finally assign MyGroupNumber:
SQL Fiddle
;WITH Cte AS(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY MyOrdinal) - MyMarker + 1
FROM #Holder
),
CteApply AS(
SELECT
t.MyOrdinal,
t.MyMarker,
MyGroupNumber =
CASE
WHEN RN IS NULL THEN x.NewRN
ELSE RN
END
FROM Cte t
OUTER APPLY(
SELECT TOP 1 RN * 1.1 AS NewRN
FROM Cte
WHERE
t.MyOrdinal > MyOrdinal
AND MyMarker IS NOT NULL
ORDER BY MyOrdinal DESC
)x
)
SELECT
MyOrdinal,
MyMarker,
MyGroupNumber = DENSE_RANK() OVER(ORDER BY MyGroupNumber)
FROM CteApply
For Sql Server 2012:
select *, sum(b) over(order by myordinal)
from(select *,
case when (lag(mymarker) over(order by myordinal) is not null
and mymarker is null) or
(lag(mymarker) over(order by myordinal) is null
and mymarker is not null)
then 1 else 0 end as b
from #Holder) t
First you mark rows with 1 where there is a change from null to not null or from not null to null. Other columns are marked as 0. Then running sum of all rows till current.
Fiddle http://sqlfiddle.com/#!6/9eecb/5015
For Sql Server 2008:
with cte1 as (select *,
case when (select max(enddate) from t ti
where ti.ruleid = t.ruleid and ti.startdate < t.startdate) = startdate
then 0 else 1 end as b
from t),
cte2 as(select *, sum(b) over(partition by ruleid order by startdate) as s
from cte1)
select RuleID,
Name,
min(startdate),
case when count(*) = count(enddate)
then max(enddate) else null end from cte2
group by s, ruleid, name
Fiddle http://sqlfiddle.com/#!6/4191d/6

Order parent child records by parent group and children

I need sort query results by specific two related columns. My table is:
Row no | Col 1 | Col 2 | Col 3 | Col 4
1 | 1 | X | 1 | 5
2 | 2 | Y | 1 | 6
3 | 5 | Z | 2 | 7
4 | 6 | T | 2 | 0
5 | 7 | T | 3 | 0
6 | 6 | W | 2 | 0
The values in Col 4 represents the child record linked to Col 1.
So for Row no = 1 the next child record is row 3, where Col 1 holds the value of Col 4 from the first row.
The next child row for row 3 is row 5, based on the link between Col 1 and Col 4.
And I'd like to return this results:
Row no | Col 1 | Col 2 | Col 3 | Col 4
1 | 1 | X | 1 | 5
3 | 5 | Z | 2 | 7
5 | 7 | T | 3 | 0
2 | 2 | Y | 1 | 6
4 | 6 | T | 2 | 0
6 | 6 | W | 2 | 0
So I want the ordering to show a Parent row, followed by it's child rows, before moving on to the next top level Parent row.
You can achieve what you're after with a Recursive CTE to find all the parent records and link them to their child records.
Dummy table setup:
CREATE TABLE #Table1
(
[Row no] INT ,
[Col 1] INT ,
[Col 2] VARCHAR(1) ,
[Col 3] INT ,
[Col 4] INT
);
INSERT INTO #Table1
( [Row no], [Col 1], [Col 2], [Col 3], [Col 4] )
VALUES ( 1, 1, 'X', 1, 5 ),
( 2, 2, 'Y', 1, 6 ),
( 3, 5, 'Z', 2, 7 ),
( 4, 6, 'T', 2, 0 ),
( 5, 7, 'T', 3, 0 ),
( 6, 6, 'W', 2, 0 );
Recursive CTE:
;WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( ORDER BY t1.[Col 1] ) GroupNo
FROM #Table1 t1
WHERE t1.[Col 1] NOT IN ( SELECT [Col 4] FROM #Table1 )
UNION ALL
SELECT t.* ,
cte.GroupNo
FROM #Table1 t
INNER JOIN cte ON cte.[Col 4] = t.[Col 1]
)
SELECT *
FROM cte
ORDER BY cte.GroupNo , cte.[Row no]
DROP TABLE #Table1
This combines 2 queries with a UNION ALL. The first query finds the top level items where the value of [Col 1] does not appear in [Col 4]:
WHERE t1.[Col 1] NOT IN ( SELECT [Col 4] FROM #Table1 )
The second query finds the child records on the first query with this JOIN:
INNER JOIN cte ON cte.[Col 4] = t.[Col 1]
For the ordering, I've used the following to give the the results of the first query a GroupNo, which is used later to order the records:
ROW_NUMBER() OVER ( ORDER BY t1.[Col 1] ) GroupNo

Sorting columns to be multi-row

I have data in SQL Server like this:
floor | Apartment
1 1
1 2
1 3
2 4
2 5
2 6
because one floor has 3 apartments, I want to sort or convert the Apartment column to be a row like the flowing
4 | 5 | 6
1 | 2 | 3
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
Floor int,
Apartment int
)
go
insert into YourTable values
( 1, 1),
( 1, 2),
( 1, 3),
( 2, 4),
( 2, 5),
( 2, 6)
Query 1:
select P.Floor,
P.[1] as Room1,
P.[2] as Room2,
P.[3] as Room3
from (
select Floor,
Apartment,
row_number() over(partition by Floor order by Apartment) as rn
from YourTable
) as T
pivot(min(T.Apartment) for T.rn in ([1], [2], [3])) as P
Results:
| FLOOR | ROOM1 | ROOM2 | ROOM3 |
|-------|-------|-------|-------|
| 1 | 1 | 2 | 3 |
| 2 | 4 | 5 | 6 |

Resources