Convert columns to rows and a column name in SQL Server - sql

I want to convert columns to rows in SQL Server:
Id Value Jan1 Jan2
----------------------
1 2 25 35
2 5 45 45
result should be
Id Value Month 1 2
----------------------
1 2 Jan 25 35
2 5 Jan 45 45
How can I get this result? Anyone please help

What you are asking seems a little strange. If I extend your example to include columns for Feb1 and Feb2, then I see two options for transposing your columns from this:
+----+-------+------+------+------+------+
| Id | Value | Jan1 | Jan2 | Feb1 | feb2 |
+----+-------+------+------+------+------+
| 1 | 2 | 25 | 35 | 15 | 28 |
| 2 | 5 | 45 | 45 | 60 | 60 |
+----+-------+------+------+------+------+
Transpose just the month part:
select Id, Value, MonthName, MonthValue1, MonthValue2
from t
cross apply (values ('Jan',Jan1,Jan2),('Feb',Feb1,Feb2)
) v (MonthName,MonthValue1,MonthValue2)
returns:
+----+-------+-----------+-------------+-------------+
| Id | Value | MonthName | MonthValue1 | MonthValue2 |
+----+-------+-----------+-------------+-------------+
| 1 | 2 | Jan | 25 | 35 |
| 1 | 2 | Feb | 15 | 28 |
| 2 | 5 | Jan | 45 | 45 |
| 2 | 5 | Feb | 60 | 60 |
+----+-------+-----------+-------------+-------------+
Or completely transpose the month columns like so:
select Id, Value, MonthName, MonthValue
from t
cross apply (values ('Jan1',Jan1),('Jan2',Jan2),('Feb1',Feb1),('Feb2',Feb2)
) v (MonthName,MonthValue)
returns:
+----+-------+-----------+------------+
| Id | Value | MonthName | MonthValue |
+----+-------+-----------+------------+
| 1 | 2 | Jan1 | 25 |
| 1 | 2 | Jan2 | 35 |
| 1 | 2 | Feb1 | 15 |
| 1 | 2 | Feb2 | 28 |
| 2 | 5 | Jan1 | 45 |
| 2 | 5 | Jan2 | 45 |
| 2 | 5 | Feb1 | 60 |
| 2 | 5 | Feb2 | 60 |
+----+-------+-----------+------------+
rextester demo: http://rextester.com/KZV45690

This would appear to be:
select Id, Value, 'Jan' as [month], Jan1 as [1], Jan2 as [2]
from t;
You are basically just adding another column to the output.
I don't recommend using numbers as column names, nor SQL Server keywords such as month.

Here is an option that you won't have to specify up to 365 fields
Declare #YourTable table (Id int,Value int,Jan1 int,Jan2 int,Feb1 int, Feb2 int)
Insert Into #YourTable values
(1, 2, 25, 35, 100, 101),
(2, 5, 45, 45, 200, 201)
Select [Id],[Value],[Month],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31]
From (
Select A.Id
,A.Value
,[Month] = Left(C.Item,3)
,[Col] = substring(C.Item,4,5)
,[Measure] = C.Value
From #YourTable A
Cross Apply (Select XMLData = cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','int')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','Value')
) C
) A
Pivot (sum(Measure) For [Col] in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],[31]) ) p
Returns

Related

Compare results of two selects statements

How can i compare the results of two select statements in TSQL (2014)?
my both queries:
SELECT CallDisposition, count(CallDisposition) as Count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
where (DateTime between dateadd(minute,#timespan,convert(datetime2,(GETDATE())+ #count)) AND convert(datetime2,(GETDATE())+ #count))
Group by CallDisposition
SELECT CallDisposition, count(CallDisposition) as Count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
where DateTime >= dateadd(minute,#timespan,convert(datetime2,(GETDATE())))
Group by CallDisposition
Results of Query 1:
+-----------------+-------+
| CallDisposition | Count |
+-----------------+-------+
| 2 | 2 |
| 3 | 8 |
| 4 | 8 |
| 7 | 21 |
| 10 | 16 |
| 13 | 738 |
| 14 | 45 |
| 15 | 14 |
| 19 | 8 |
| 28 | 41 |
| 29 | 12 |
| 52 | 76 |
| 55 | 1 |
+-----------------+-------+
Results of Query 2:
+-----------------+-------+
| CallDisposition | Count |
+-----------------+-------+
| 2 | 4 |
| 3 | 7 |
| 4 | 6 |
| 6 | 2 |
| 7 | 22 |
| 10 | 6 |
| 13 | 703 |
| 14 | 67 |
| 15 | 11 |
| 19 | 4 |
| 26 | 1 |
| 28 | 62 |
| 29 | 10 |
| 52 | 79 |
+-----------------+-------+
The main problem behind, the results of both queries can be different.
Based on example above: CallDisposition 6 and 26 are missing in Query 1.
CallDispoition 55 is missing in Query 2
Note: CallDispoition values 0-100 can be expected, maybe that helps?!
The expected result should look like that:
+-----------------+-------+
| CallDisposition | Count |
+-----------------+-------+
| 2 | 2 |
| 3 | 1 |
| 4 | 2 |
| 6 | 2 |
| 7 | 1 |
| 10 | 10 |
| .. | |
| .. | |
| .. | |
| 52 | 3 |
| 55 | 1 |
+-----------------+-------+
WITH S1 AS (SELECT CallDisposition, count(CallDisposition) as Count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
where (DateTime between dateadd(minute,#timespan,convert(datetime2,(GETDATE())+ #count)) AND convert(datetime2,(GETDATE())+ #count))
Group by CallDisposition
)
,S2 AS (
SELECT CallDisposition, count(CallDisposition) as Count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
where DateTime >= dateadd(minute,#timespan,convert(datetime2,(GETDATE())))
Group by CallDisposition
)
Select ISNULL(S1.CallDisposition,S2.CallDisposition) AS CallDisposition
,ABS(ISNULL(S1.COUNT,0)-ISNULL(S2.Count,0)) Count
FROM S1 FULL JOIN S2
ON S1.CallDisposition=s2.CallDisposition
Honestly I'm puzzled by your expected result and that compares the results. So my solution won't produce such a result. But maybe it helps by pointing you tte right direction.
You can to full join the results on calldisposition. That way coexisting calldisposition will be put next to each other. If for a calldisposition in one result no one exists in the other the columns of the other will be all NULL.
SELECT *
FROM (SELECT calldisposition,
count(CallDisposition) count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
WHERE (datetime BETWEEN dateadd(minute, #timespan, convert(datetime2, (getdate()) + #count))
AND convert(datetime2, (getdate()) + #count))
GROUP BY calldisposition) x
FULL JOIN (SELECT calldisposition,
count(calldisposition) count
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
WHERE datetime >= dateadd(minute, #timespan, convert(datetime2, (getdate())))
GROUP BY calldisposition) y
ON y.calldisposition = x.calldisposition;
I'd probably insert the results into a temporary table, then query that. Use a table variable:
DECLARE #Results TABLE ([Source] VARCHAR(10), CallDisposition INT, [CallCount] INT)
(Use CallCount as that's not a reserved word)
INSERT INTO #Results(CallDisposition , CallCount, [Source])
SELECT CallDisposition, count(CallDisposition) as Count, [Source] = 'Query1',
FROM [bs_Reporting].[dbo].[Termination_Call_Detail]
where (DateTime between dateadd(minute,#timespan,convert(datetime2,(GETDATE())+ #count)) AND convert(datetime2,(GETDATE())+ #count))
Group by CallDisposition
Repeat with your 2nd query, Setting Source = 'Query2' (or union with it in the above insert).
Now you can examine your #Results:
SELECT r1.* FROM #Results r1
WHERE [Source] = 'Query1'
AND NOT EXISTS (SELECT 'X' FROM #Results r2 WHERE [Source] = 'Query2' AND r2.CallDisposition = r1.CallDisposition)
(example only; possibly not the most insightful)
Please forgive any slight syntax errors; I don't have SQL Server in front of me.

SQL Dynamic Pivoting a "Week" Table

Table design:
| PeriodStart | Person | Day1 | Day2 | Day3 | Day4 | Day5 | Day6 | Day7 |
-------------------------------------------------------------------------
| 01/01/2018 | 123 | 2 | 4 | 6 | 8 | 10 | 12 | 14 |
| 01/15/2018 | 246 | 1 | 3 | 5 | 7 | 9 | 11 | 13 |
I am trying to create a pivot statement that can dynamically transpose both rows.
Desired output:
| Date | Person | Values |
--------------------------------
| 01/01/2018 | 123 | 2 |
| 01/02/2018 | 123 | 4 |
| 01/03/2018 | 123 | 6 |
| 01/04/2018 | 123 | 8 |
| 01/05/2018 | 123 | 10 |
| 01/06/2018 | 123 | 12 |
| 01/15/2018 | 246 | 1 |
| 01/16/2018 | 246 | 3 |
| 01/17/2018 | 246 | 5 |
... and so on. Date order not important
The following query will help initialize things:
DECLARE #WeekTable TABLE (
[PeriodStart] datetime
, [Person] int
, [Day1] int
, [Day2] int
, [Day3] int
, [Day4] int
, [Day5] int
, [Day6] int
, [Day7] int
)
INSERT INTO #WeekTable(
[PeriodStart],[Person],[Day1],[Day2],[Day3],[Day4],[Day5],[Day6],[Day7]
)
VALUES ('01/01/2018','123','2','4','6','8','10','12','14')
,('01/15/2018','246','1','3','5','7','9','11','13')
Other option is to do that with APPLY operator
SELECT DATEADD(DAY, Days-1, Dates) Date, a.Person, Value
FROM #WeekTable t CROSS APPLY (
VALUES (PeriodStart, Person, 1, Day1), (PeriodStart, Person, 2, Day2),
..., (PeriodStart, Person, 7, Day7)
)a(Dates, Person, Days, Value)
you can use unpivot to turn the day columns back into rows and then parse the number out of the column name:
with periods as (
select * from #WeekTable
unpivot ([Values] for [Day] in (Day1, Day2, Day3, Day4, Day5, Day6, Day7))x
)
select dateadd(day, convert(int, substring(Day, 4, 1)), PeriodStart), Person, [Values]
from periods
fiddle

SQL server rank rows by group and condition

I need help with ranking of rows in one table.
+-----+-------+-------------+------------+-------+------+
| ID | group | typeInGroup | rankOfType | score | Rank |
+-----+-------+-------------+------------+-------+------+
| 1 | a | type1 | 1 | 40 | |
| 2 | a | type2 | 2 | 55 | |
| 3 | a | type1 | 1 | 20 | |
| 4 | b | type3 | 3 | 80 | |
| 5 | b | type2 | 2 | 60 | |
| 6 | b | type1 | 1 | 70 | |
| 7 | b | type1 | 1 | 70 | |
+-----+-------+-------------+------------+-------+------+
I am basically looking for solution which would give me order for last column "Rank".
Each "group" has up to 9 "typeInGroup" which are ranked by 1-9 in column "rankOfTypes". Each "typeInGroup" has "score". When i am calculating last column "Rank" i look at the "score" and "rankOfType" column.
The row with the higgest score should be ranked first unless there is row with "rankOfType" column that has lower value and score that is <= 15 than the score we have been looking at. Order of rows with same "score" and "rankOfType" is not important.
I should do this check for every single row in group and in the end end with something like this:
+-----+-------+-------------+------------+-------+------+
| ID | group | typeInGroup | rankOfType | score | Rank |
+-----+-------+-------------+------------+-------+------+
| 1 | a | type1 | 1 | 40 | 1 |
| 2 | a | type2 | 2 | 55 | 2 |
| 3 | a | type1 | 1 | 20 | 3 |
| 4 | b | type3 | 3 | 80 | 3 |
| 5 | b | type2 | 2 | 60 | 4 |
| 6 | b | type1 | 1 | 70 | 1 |
| 7 | b | type1 | 1 | 70 | 2 |
+-----+-------+-------------+------------+-------+------+
Any idea how to do this?
the CROSS APPLY query, checks for any rows that meet your special requirement, if exists, than that row will have higher priority
try it out with larger data set and verify the result
declare #tbl table
(
ID int,
Grp char,
typeInGrp varchar(5),
rankOfType int,
score int
)
insert into #tbl select 1, 'a', 'type1', 1, 40
insert into #tbl select 2, 'a', 'type2', 2, 55
insert into #tbl select 3, 'a', 'type1', 1, 20
insert into #tbl select 4, 'b', 'type3', 3, 80
insert into #tbl select 5, 'b', 'type2', 2, 60
insert into #tbl select 6, 'b', 'type1', 1, 70
insert into #tbl select 7, 'b', 'type1', 1, 70
select *,
[rank] = row_number() over (partition by Grp
order by case when cnt > 0 then 1 else 2 end,
score desc)
from #tbl t
cross apply
(
select cnt = count(*)
from #tbl x
where x.Grp = t.Grp
and x.ID <> t.ID
and x.rankOfType > t.rankOfType
and x.score - t.score <= 15
) s
order by ID

How to get values of rows and columns

I have two tables.
Student Table
Property Table
Result Table
How can I get the value of Student Table and the property ID of the column fron the Property table and merge that into the Result table?
Any advice would be helpful.
Update #1:
I tried using Christian Moen 's suggestion, this is what i get.
You need to UNPIVOT the Student's columns first, to get the columns (properties names) in one column as rows. Then join with the Property table based on the property name like this:
WITH UnPivoted
AS
(
SELECT ID, value,col
FROM
(
SELECT ID,
CAST(Name AS NVARCHAR(50)) AS Name,
CAST(Class AS NVARCHAR(50)) AS Class,
CAST(ENG AS NVARCHAR(50)) AS ENG,
CAST(TAM AS NVARCHAR(50)) AS TAM,
CAST(HIN AS NVARCHAR(50)) AS HIN,
CAST(MAT AS NVARCHAR(50)) AS MAT,
CAST(PHY AS NVARCHAR(50)) AS PHY
FROM Student
) AS s
UNPIVOT
(value FOR col IN
([Name], [class], [ENG], [TAM], [HIN], [MAT], [PHY])
)AS unpvt
)
SELECT
ROW_NUMBER() OVER(ORDER BY u.ID,PropertyID) AS ID,
p.PropertyID,
u.Value,
u.ID AS StudID
FROM Property AS p
INNER JOIN UnPivoted AS u ON p.PropertyName = u.col;
For the first ID, I used the ranking function ROW_NUMBER() to generate this sequence id.
This will give the exact results that you are looking for.
Results:
| ID | PropertyID | Value | StudID |
|----|------------|--------|--------|
| 1 | 1 | Jack | 1 |
| 2 | 2 | 10 | 1 |
| 3 | 3 | 89 | 1 |
| 4 | 4 | 88 | 1 |
| 5 | 5 | 45 | 1 |
| 6 | 6 | 100 | 1 |
| 7 | 7 | 98 | 1 |
| 8 | 1 | Jill | 2 |
| 9 | 2 | 10 | 2 |
| 10 | 3 | 89 | 2 |
| 11 | 4 | 99 | 2 |
| 12 | 5 | 100 | 2 |
| 13 | 6 | 78 | 2 |
| 14 | 7 | 91 | 2 |
| 15 | 1 | Trevor | 3 |
| 16 | 2 | 12 | 3 |
| 17 | 3 | 100 | 3 |
| 18 | 4 | 50 | 3 |
| 19 | 5 | 49 | 3 |
| 20 | 6 | 94 | 3 |
| 21 | 7 | 100 | 3 |
| 22 | 1 | Jim | 4 |
| 23 | 2 | 8 | 4 |
| 24 | 3 | 100 | 4 |
| 25 | 4 | 91 | 4 |
| 26 | 5 | 92 | 4 |
| 27 | 6 | 100 | 4 |
| 28 | 7 | 100 | 4 |
Other option is to use of apply if you don't want to go unpivot way
select row_number() over (order by (select 1)) ID, p.PropertyID [PropID], a.Value, a.StuID
from Student s
cross apply
(
values (s.ID, 'Name', s.Name),
(s.ID, 'Class', cast(s.Class as varchar)),
(s.ID, 'ENG', cast(s.ENG as varchar)),
(s.ID, 'TAM', cast(s.TAM as varchar)),
(s.ID, 'HIN', cast(s.HIN as varchar)),
(s.ID, 'MAT', cast(s.MAT as varchar)),
(s.ID, 'PHY', cast(s.PHY as varchar))
) as a(StuID, Property, Value)
join Property p on p.PropertyName = a.Property

Get Multiple rows from multiple values (columns) in SQL Server 2008 R2

I have this table
+-----+------+--------+--------+
| ID | Name | Start | End |
+-----+------+--------+--------+
| 20 | Mike | 1 | 3 |
| 21 | Luke | 4 | 7 |
+-----+------+--------+--------+
And I want to generate all rows based on the range (start / end) of each person.
The outcome should be this
+-----+------+-----------------+
| ID | Name | Start_End |
+-----+------+-----------------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+-----+------+--------+--------+
To get unique values based on Start and End column, I have this function
CREATE FUNCTION [dbo].[ufn_SplitRange] (#Start INT, #End INT)
RETURNS TABLE
AS
RETURN
(
SELECT TOP (#End - #Start+1) ROW_NUMBER() OVER (ORDER BY S.[object_id])+(#Start - 1) [Start_End]
FROM sys.all_objects S WITH (NOLOCK)
);
The above function returns the output of (based on Mike range of 1-3):
1
2
3
I have been trying several approaches and, I can't find the right solution, it seems a very common task, but a tricky one.
Any input is highly appreciated
using cross apply():
select t.Id, t.Name, x.Start_End
from t
cross apply dbo.ufn_SplitRange(t.Start,t.[End]) as x
rextester demo: http://rextester.com/FVA48693
returns:
+----+------+-----------+
| Id | Name | Start_End |
+----+------+-----------+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+-----------+
You can use tally table as below:
Select Id, Name, Start_end from #Values
cross apply (
Select top ([end] - [start] +1) Start_end = [start] + Row_number() over (order by (Select NULL))-1
from master..spt_values s1, master..spt_values s2
) a
Output :
+----+------+----+
| Id | Name | RN |
+----+------+----+
| 20 | Mike | 1 |
| 20 | Mike | 2 |
| 20 | Mike | 3 |
| 21 | Luke | 4 |
| 21 | Luke | 5 |
| 21 | Luke | 6 |
| 21 | Luke | 7 |
+----+------+----+
You could use recursive cte like this
DECLARE #SampleData AS TABLE
(
Id int,
Name varchar(10),
Start int,
[End] int
)
INSERT INTO #SampleData
(
Id,
Name,
Start,
[End]
)
VALUES
(1,'Mike',1,3),
(2,'Luke',4,7)
;WITH temp AS
(
SELECT Id, sd.Name, sd.Start , sd.[End]
FROM #SampleData sd
UNION ALL
SELECT t.Id, t.Name, t.Start + 1, t.[End]
FROM temp t
WHERE t.Start < t.[End]
)
SELECT t.Id, t.Name, t.Start AS [Start_End]
FROM temp t
ORDER BY t.Id
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/AFNYFW81782