Turn Columns into Rows partitioned - sql

I have a table like this:
CFL52_ID CFL52_PRICE CFL51_MILEAGE
------------ --------------- -----------------
1 100000.00 10000
1 200000.00 20000
2 800000.00 10000
2 900000.00 20000
I want to pivot the columns into rows. Mileage was the column title and Price the value. Turning into something like this:
CFL52_ID [10000] [20000]
------------ --------------- -----------------
1 100000.00 200000.00
2 800000.00 900000.00
Notice that id was grouping, prices are pivoted and mileage turning into columns title, Mileage are dynamic -could increment- .
I've tried with no success with this:
SELECT [10000],[20000]
FROM ( SELECT
CFL52_PRICE as indicatorvalue,
CFL51_MILEAGE as indicatorname
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000' AND CFL52_DATEEN= '2018-07-02 00:00:00.000') as a
pivot
(
max(indicatorvalue) for indicatorname in ([10000],[20000])
) p

just use conditional aggregation instead. You also should use aliases to indicate which table a column belongs to.
select CFL52_ID
, [10000] = MAX(case when CFL51_MILEAGE = 10000 then CFL52_PRICE end)
, [20000] = MAX(case when CFL51_MILEAGE = 20000 then CFL52_PRICE end)
FROM [TFL52_PRICES_M] p
INNER JOIN [TFL51_PARAM_MILEAGE] k ON CFL52_CFL51_CODIGO = CFL51_CODIGO
WHERE CFL52_DATES = '2018-07-01 00:00:00.000'
AND CFL52_DATEEN= '2018-07-02 00:00:00.000'
group by CFL52_ID

To manage dynamic titles you need dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(CFL52_ID varchar(50), CFL52_PRICE decimal(18,2), CFL51_MILEAGE int)
--populate test table
insert into dbo.test values
(1, 100000.00, 10000),
(1, 200000.00, 20000),
(2, 800000.00, 10000),
(2, 900000.00, 20000)
declare #columns nvarchar(max)='' --holds all the numbers that will become column names
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
--generate dynamic column names
select #columns = #columns + ', [' + cast(CFL51_MILEAGE as varchar(max))+ ']'
from dbo.test
group by CFL51_MILEAGE
--remove first (unnecessary) comma
set #columns = RIGHT(#columns, len(#columns)-2)
--build dynamic TSQL query
set #sql = #sql + ' select piv.[CFL52_ID], ' + #columns
set #sql = #sql + ' from ( '
set #sql = #sql + ' select [CFL52_ID], [CFL52_PRICE], [CFL51_MILEAGE] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( '
set #sql = #sql + ' max([CFL52_PRICE]) '
set #sql = #sql + ' for [CFL51_MILEAGE] in ('+#columns+') '
set #sql = #sql + ' ) piv '
--execute dynamic TSQL query
exec(#sql)
output:
If you add more rows to your input table with CFL51_MILEAGE values of 10000 and 20000, then input and output tables will be:
If you add more rows to your input table introducing new CFL51_MILEAGE values (other than 10000 or 20000), then input and output tables will be:

Related

Transposing a SQL query result

I have a query that produces a table that looks like this:
+----------------+-----------+------------+----------+
| CustomerNumber | FirstName | MiddleName | LastName |
+----------------+-----------+------------+----------+
| 123456 | Test | Test1 | Test2 |
+----------------+-----------+------------+----------+
What I am needing is for the table to look like this:
+----------------+--------+
| CustomerNumber | 123456 |
+----------------+--------+
| FirstName | Test |
| MiddleName | Test1 |
| LastName | Test2 |
+----------------+--------+
This is my current sql query:
SELECT
CustomerNumber,
FirstName,
MiddleName,
LastName
FROM Customers
WHERE CustermerNumber = 123456
Is there a way to complete this? I have been looking at transposing it via unpivot but have not been able to understand how.
You can use apply :
select tt.*
from table t cross apply (
values ('CustomerNumber', CustomerNumber), ('FirstName', FirstName),
('MiddleName', MiddleName), ('LastName', LastName)
) tt (name, v);
With dynamic TSQL pivoting you can also manage multiple rows input:
if OBJECT_ID('Test') is not null
drop table [dbo].[Test]
CREATE TABLE [dbo].[Test](CustomerNumber varchar(10), FirstName varchar(10),
MiddleName varchar(10), LastName varchar(10))
--populate test table
insert into [dbo].[Test] values
(123456, 'Test','Test1','Test2')
, (234567, 'Test_2','Test_21','Test_22')
, (345678, 'Test_3','Test_31','Test_32')
--this variable holds all the customer numbers that will become column names
declare #columns nvarchar(max)=''
--this variable contains the dinamically generated TSQL code
declare #sql nvarchar(max)=''
select #columns = #columns + ', [' + [CustomerNumber] + ']' from [dbo].[Test]
set #columns = RIGHT(#columns, len(#columns)-2)
set #sql = #sql + 'select piv.COL as CustomerNumber, ' + #columns
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [CustomerNumber], col, val, ord '
set #sql = #sql + ' from [dbo].[Test] '
set #sql = #sql + ' CROSS APPLY ('
set #sql = #sql + ' VALUES (''FirstName'' ,FirstName , 1), '
set #sql = #sql + ' (''MiddleName'',MiddleName, 2), '
set #sql = #sql + ' (''LastName'' ,LastName, 3) '
set #sql = #sql + ' )CS (COL,VAL,ORD) '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot ( max(val) for [CustomerNumber] in ('+#columns+') ) piv'
set #sql = #sql + ' order by ord'
exec(#sql)
Sample input and output with one row:
Sample input and output with three rows:

Dynamic pivot a 3 column table

I'm trying to use dynamic pivot to have a column containing dates to be the column names.
I want this table:
App Date Count
Excel 2018-05-01 1
Excel 2018-05-02 1
Excel 2018-05-03 2
Word 2018-05-02 3
Word 2018-05-07 5
Word 2018-05-12 2
Paint 2018-05-07 6
to look like this:
2018-05-01 2018-05-02 2018-05-03 2018-05-07 2018-05-12
Excel 1 1 2 0 0
Word 0 3 0 5 2
Paint 0 0 0 6 0
I can't use a normal pivot as I don't know how many or what the dates will actually be. Each app can have a different number of rows. This table isn't just a SELECT * FROM TABLE either, it's made up of subqueries and CTEs so is a little complicated to work with.
Any help is appreciated. Let me know if you need more information.
Using dynamic TSQL:
if OBJECT_ID('dbo.test') is null
create table dbo.test(App varchar(50), [Date] varchar(50), [Count] int)
truncate table dbo.test
insert into dbo.test values
('Excel', '2018-05-01', 1),
('Excel', '2018-05-02', 1),
('Excel', '2018-05-03', 2),
('Word ', '2018-05-02', 3),
('Word ', '2018-05-07', 5),
('Word ', '2018-05-12', 2),
('Paint', '2018-05-07', 6)
declare #dates nvarchar(max)='' --holds all the dates that will become column names
declare #dates_aliases nvarchar(max)='' --holds the headers without NULL values
declare #sql nvarchar(max)='' --contains the TSQL dinamically generated
select #dates = #dates + ', [' + CONVERT(char(10), [date],126)+ ']' from dbo.test
group by [date]
select #dates_aliases = #dates_aliases + ', isnull(['
+ CONVERT(char(10), [date],126)+ '], 0) as ['
+ CONVERT(char(10), [date],126)+ ']'
from dbo.test group by [date]
set #dates = RIGHT(#dates, len(#dates)-2)
set #dates_aliases = RIGHT(#dates_aliases, len(#dates_aliases)-2)
set #sql = #sql + ' select piv.[App], ' + #dates_aliases
set #sql = #sql + ' from '
set #sql = #sql + ' ( '
set #sql = #sql + ' select [App], [Date], [Count] '
set #sql = #sql + ' from dbo.test '
set #sql = #sql + ' ) src '
set #sql = #sql + ' pivot '
set #sql = #sql + ' ( '
set #sql = #sql + ' max([Count]) '
set #sql = #sql + ' for [Date] in ('+#dates+') '
set #sql = #sql + ' ) piv '
exec(#sql)
Results:
Try this:
SELECT A.*
INTO #TEMP
FROM
(
SELECT 'Excel' as app,'2018-05-01' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-02' as 'Date',1 as 'Count'
UNION ALL
SELECT 'Excel' as app,'2018-05-03' as 'Date',2 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-02' as 'Date', 3 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-07' as 'Date', 5 as 'Count'
UNION ALL
SELECT 'Word' as app,'2018-05-12' as 'Date', 2 as 'Count'
UNION ALL
SELECT 'Paint' as app,'2018-05-07' as 'Date', 6 as 'Count'
) as A
ANSWER:
DECLARE #SQL VARCHAR(MAX)
DECLARE #Columns VARCHAR(MAX) = ''
DECLARE #Columns2 VARCHAR(MAX) = ''
SELECT #Columns = #Columns + '[' + a.[Column] + '], '
FROM
(SELECT DISTINCT [date] as [Column]
FROM #TEMP) as a
SELECT #Columns2 = #Columns2 + 'ISNULL([' + a.[Column] + '],0) as [' + a.[column] +'], '
FROM
(
SELECT DISTINCT [date] as [Column]
FROM #TEMP
) as a
SET #Columns2 = Left(#Columns2, Len(#Columns2) - 1)
SET #Columns = Left(#Columns, Len(#Columns) - 1)
SET #SQL = 'SELECT app, ' + #Columns2
+ ' FROM #TEMP PIVOT (Avg (Count) FOR Date IN ('
+ #Columns
+ ')) AS pt '
--PRINT #Columns
EXEC( #SQL )

How do I use loop to generate column names dynamically?

I have table sdata and it has 35 columns (id, name, TRx1, TRx2, TRx3, TRx4,..., TRx30, city, score, total)
I want to fetch data from the TRx1,...TRx30 columns.
Can I use loop here?
I did following code:
DECLARE #flag INT
DECLARE #sel varchar(255)
DECLARE #frm varchar(255)
SET #flag = 1;
SET #sel = 'select TRx';
SET #frm = ' from sdata';
exec(#sel +
(WHILE #flag <=5
#flag
SET #flag = #flag + 1)
+ #frm)
What wrong am I doing? And how can I resolve this?
If your table name is sdata, this code should work for you:
-- Grab the names of all the remaining columns
DECLARE #sql nvarchar(MAX);
DECLARE #columns nvarchar(MAX);
SELECT #columns = STUFF ( ( SELECT N'], [' + name
FROM sys.columns
WHERE object_id = (select top 1 object_id FROM sys.objects where name = 'sdata')
AND name LIKE 'TRx%' -- To limit which columns
ORDER BY column_id
FOR XML PATH('')), 1, 2, '') + ']';
PRINT #columns
SELECT #sql = 'SELECT ' + #columns + ' FROM sdata';
PRINT #sql;
EXEC (#sql);
Note I included PRINT statements so you could see what's going on. You might want to comment out the EXEC while testing.
This would be much easier to do by just copy/pasting the column names and changing them to be the correct one. However if you must do it this way, I do not advise using a loop at all. This method uses a tally table to generate the columns you want to select (in this example, columns 1 through 30, but that can be changed), then generates a dynamic SQL statement to execute against the SData table:
Declare #From Int = 1,
#To Int = 30,
#Sql NVarchar (Max)
Declare #Columns Table (Col Varchar (255))
;With Nums As
(
Select *
From (Values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) As V(N)
), Tally As
(
Select Row_Number() Over (Order By (Select Null)) As N
From Nums A --10
Cross Join Nums B --100
Cross Join Nums C --1000
)
Insert #Columns
Select 'TRx' + Cast(N As Varchar)
From Tally
Where N Between #From And #To
;With Cols As
(
Select (
Select QuoteName(Col) + ',' As [text()]
From #Columns
For Xml Path ('')
) As Cols
)
Select #Sql = 'Select ' + Left(Cols, Len(Cols) - 1) + ' From SData'
From Cols
--Select #Sql
Execute (#Sql)
Note: The --Select #Sql section is there to preview the generated query before executing it.
You can select the column names like this:
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'my name here'

Multiple Dynamic Selects queries in one return

I need to run multiple select count queries to count how many people are available at certain times through the day to plot into a table from ms sql server.
I have the below sql which works, but is returning each count as a new table, I would like them to all in one table on different columns.
DECLARE #Day varchar(max)
SET #Day = 'Sunday'
DECLARE #Provider varchar(max)
SET #Provider = '58611'
DECLARE #sqlText varchar(max);
SET #sqlText = N'SELECT COUNT(*) AS Available0700
FROM tblCarersRota INNER JOIN tblCarersProviders ON tblCarersProviders.CarerID = tblCarersRota.CarerID
WHERE Rotation = 2 AND tblCarersProviders.ProviderID = '''+ #Provider + ''' AND ''07:00'' between ' + #Day + 'StartTime AND ' + #Day + 'EndTime '
Exec (#sqlText)
SET #sqlText = N'SELECT COUNT(*) AS Available0800
FROM tblCarersRota INNER JOIN tblCarersProviders ON tblCarersProviders.CarerID = tblCarersRota.CarerID
WHERE Rotation = 2 AND tblCarersProviders.ProviderID = '''+ #Provider + ''' AND ''08:00'' between ' + #Day + 'StartTime AND ' + #Day + 'EndTime '
Exec (#sqlText)
Actual current result:
Available0700
21
Available0800
22
Desired result:
Available0700 || Available0800
21 || 22
I have looked at where you select (select query 1) (select query 2) but I can't get that to work with the dynamic sqltext.
How can I modify my selects to get them to all return as 1 table?
Thanks
Option 1, put it in 2 rows with UNION ALL:
SELECT COUNT(*) as Counts, 'Available0700' AvailableTime
FROM ...
UNION all
SELECT COUNT(*) as Counts, 'Available0800' AvailableTime
FROM ...
Option 2, in 2 columns with subqueries:
SELECT (SELECT COUNT(*) FROM ...) as Available0700,
(SELECT COUNT(*) FROM ...) as Available0800
I think you can store these values in variables and finally select these variables in your select query.
Below is an example code in which i have used a table variable to execute the dynamic queries and store the records and finally used a PIVOT query to fetch the require output.
DECLARE #tablevar TABLE
(
Query VARCHAR(100),
Cnt INT
)
DECLARE #sqlText varchar(max)
SET #sqlText = N'SELECT ''Available Value 1'', 100 '
INSERT INTO #tablevar
Exec (#sqlText)
SET #sqlText = N'SELECT ''Available Value 2'', 200 '
INSERT INTO #tablevar
Exec (#sqlText)
SET #sqlText = N'SELECT ''Available Value 3'', 300 '
INSERT INTO #tablevar
Exec (#sqlText)
SELECT * FROM #tablevar
PIVOT(SUM(Cnt) FOR Query IN([Available Value 1], [Available Value 2], [Available Value 3])) AS PIV
Above code is an example, please replace it with your actual code accordingly.
Thanks
Why did you use dynamic query? You can use case when inside operator COUNT in such way: COUNT(CASE WHEN <yours filter with time 7.00 > then 1 else null end) AS Available0700 , COUNT(CASE WHEN <yours filter with time 8.00 > then 1 else null end) AS Available0800.

Get top three most common values from every column in a table

I'm trying to write a query that will produce a very small sample of data from each column of a table, in which the sample is made up of the top 3 most common values. This particular problem is part of a bigger task, which is to write scripts that can characterize a database and its tables, its data integrity, and also quickly survey common values in the table on a per-column basis. Think of this as an automated "analysis" of a table.
On a single column basis, I do this already by simply calculating the frequency of values and then sorting by frequency. If I had a column called "color" and all colors were in it, and it just so happened that the color "blue" was in most rows, then the top 1 most frequently occurring value would be "blue". In SQL that is easy to calculate.
However, I'm not sure how I would do this over multiple columns.
Currently, when I do a calculation over all columns of a table, I perform the following type of query:
USE database;
DECLARE #t nvarchar(max)
SET #t = N'SELECT '
SELECT #t = #t + 'count(DISTINCT CAST(' + c.name + ' as varchar(max))) "' + c.name + '",'
FROM sys.columns c
WHERE c.object_id = object_id('table');
SET #t = SUBSTRING(#t, 1, LEN(#t) - 1) + ' FROM table;'
EXEC sp_executesql #t
However, its not entirely clear to me how I would do that here.
(Sidenote:columns that are of type text, ntext, and image, since those would cause errors while counting distinct values, but i'm less concerned about solving that)
But the problem of getting top three most frequent values per column has got me absolutely stumped.
Ideally, I'd like to end up with something like this:
Col1 Col2 Col3 Col4 Col5
---------------------------------------------------------------------
1,2,3 red,blue,green 29,17,0 c,d,j nevada,california,utah
I hacked this together, but it seems to work:
I cant help but think I should be using RANK().
USE <DB>;
DECLARE #query nvarchar(max)
DECLARE #column nvarchar(max)
DECLARE #table nvarchar(max)
DECLARE #i INT = 1
DECLARE #maxi INT = 10
DECLARE #target NVARCHAR(MAX) = <table>
declare #stage TABLE (i int IDENTITY(1,1), col nvarchar(max), tbl nvarchar(max))
declare #results table (ColumnName nvarchar(max), ColumnValue nvarchar(max), ColumnCount int, TableName NVARCHAR(MAX))
insert into #stage
select c.name, o.name
from sys.columns c
join sys.objects o on o.object_id=c.object_id and o.type = 'u'
and c.system_type_id IN (select system_type_id from sys.types where [name] not in ('text','ntext','image'))
and o.name like #target
SET #maxi = (select max(i) from #stage)
while #i <= #maxi
BEGIN
set #column = (select col from #stage where i = #i)
set #table = (select tbl from #stage where i = #i)
SET #query = N'SELECT ' +''''+#column+''''+' , '+ #column
SELECT #query = #query + ', COUNT( ' + #column + ' ) as count' + #column + ' , ''' + #table + ''' as tablename'
select #query = #query + ' from ' + #table + ' group by ' + #column
--Select #query
insert into #results
EXEC sp_executesql #query
SET #i = #i + 1
END
select * from #results
; with cte as (
select *, ROW_NUMBER() over (partition by Columnname order by ColumnCount desc) as rn from #results
)
select * from cte where rn <=3
Start with this SQL Statement builder, and modify it to suit your liking:
EDIT Added Order by Desc
With ColumnSet As
(
Select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
From INFORMATION_SCHEMA.COLUMNS
Where 1=1
And TABLE_NAME IN ('Table1')
And COLUMN_NAME IN ('Column1', 'Column2')
)
Select 'Select Top 3 ' + COLUMN_NAME + ', Count (*) NumInstances From ' + TABLE_SCHEMA + '.'+ TABLE_NAME + ' Group By ' + COLUMN_NAME + ' Order by Count (*) Desc'
From ColumnSet