How to convert SQL Server columns into rows? - sql

I need help to switch columns with rows in SQL. Need to turn this:
+------------+------------+-------------+------+
| Date | Production | Consumption | .... |
+------------+------------+-------------+------+
| 2017-01-01 | 100 | 1925 | |
| 2017-01-02 | 200 | 2005 | |
| 2017-01-03 | 150 | 1998 | |
| 2017-01-04 | 250 | 2200 | |
| 2017-01-05 | 30 | 130 | |
|... | | | |
+------------+------------+-------------+------+
into this:
+------------+------------+------------+------------+------------+-----+
| 01-01-2017 | 02-01-2017 | 03-01-2017 | 04-01-2017 | 05-01-2017 | ... |
+------------+------------+------------+------------+------------+-----+
| 100 | 200 | 150 | 250 | 30 | |
| 1925 | 2005 | 1998 | 2200 | 130 | |
+------------+------------+------------+------------+------------+-----+
Can someone help me? Should I use PIVOT?
EDIT: I've tried using some suggestions like PIVOT and UNPIVOT, but I could not achieve the expected result.
I've tried:
SELECT *
FROM (
SELECT date, Consumption
FROM Energy
where date < '2017-02-01'
) r
pivot (sum(Consumption) for date in ([2017-01-01],[2017-01-02],[2017-01-03]....)) c
order by 1
However with the above query I only managed to get some of what I need,
+------------+------------+------------+------------+------------+-----+
| 01-01-2017 | 02-01-2017 | 03-01-2017 | 04-01-2017 | 05-01-2017 | ... |
+------------+------------+------------+------------+------------+-----+
| 100 | 200 | 150 | 250 | 30 | |
+------------+------------+------------+------------+------------+-----+
I need to have production and consumption, all in the same query, but I can only get one of them.
Is it possible to put more than one column in PIVOT? I've tried, but unsuccessfully.

You can achieve the desired output with dynamic sql, but be aware of performance and security problems (i.e. SQL injection) of this approach.
--create test table
CREATE TABLE dbo.Test (
[Date] date
, Production int
, Consumption int
)
--populate test table with values
insert into dbo.Test
values
('2017-01-01', 100, 1925)
,('2017-01-02', 200, 2005)
,('2017-01-03', 150, 1998)
,('2017-01-04', 250, 2200)
,('2017-01-05', 30, 130)
--table variable that will hold the names of all columns to pivot
declare #columNames table (ColumnId int identity (1,1), ColumnName varchar(255))
--variable that will hold the total number of columns to pivot
declare #columnCount int
--variable that will be used to run through the columns to pivot
declare #counter int = 1
--this variable holds all column names
declare #headers nvarchar(max) = ''
--this variable contains the TSQL dinamically generated
declare #sql nvarchar(max) = ''
--populate list of columns to pivot
insert into #columNames
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where
TABLE_NAME = 'test'
and TABLE_SCHEMA = 'dbo'
and COLUMN_NAME <>'date'
--populate column total counter
select #columnCount = count(*) from #columNames
--populate list of headers of the result table
select #headers = #headers + ', ' + quotename([Date])
from dbo.Test
set #headers = right(#headers, len(#headers) - 2)
--run through the table containing the columns names and generate the dynamic sql query
while #counter <= #columnCount
begin
select #sql = #sql + ' select piv.* from (select [Date], '
+ quotename(ColumnName) + ' from dbo.Test) p pivot (max('
+ quotename(ColumnName) + ') for [Date] in ('
+ #headers + ') ) piv '
from #columNames where ColumnId = #counter
--add union all except when we are concatenating the last pivot statement
if #counter < #columnCount
set #sql = #sql + ' union all'
--increment counter
set #counter = #counter + 1
end
--execute the dynamic query
exec (#sql)
Result:
Now if you add a column and some more rows:
--create test table
CREATE TABLE [dbo].[Test] (
[Date] date
, Production int
, Consumption int
, NewColumn int
)
--populate test table with values
insert into [dbo].[Test]
values
('2017-01-01', 100, 1925 , 10)
,('2017-01-02', 200, 2005, 20)
,('2017-01-03', 150, 1998, 30)
,('2017-01-04', 250, 2200, 40)
,('2017-01-05', 30, 130 , 50)
,('2017-01-06', 30, 130 , 60)
,('2017-01-07', 30, 130 , 70)
this is the result:

Related

Current day's counts vs Yesterday's counts difference into a table

The main "issue": I am inserting the current day's row counts from every table into a table. The main goal is to have a table which has the current day's vs yesterday's counts difference for each day.
What is essential that to know that the current days row total is or not than the yesterday's total.
Example :
+----------+-------------+--------+
| Table | Date | Count |
+----------+-------------+--------+
| Table 1 | 2020-01-01 | 50 |
| Table 1 | 2020-01-02 | 150 |
| Table 1 | 2020-01-03 | 565 |
+----------+-------------+--------+
and this is what I would like to have as a final product:
+----------+-------------+--------------------+
| Table | Date | Count (difference) |
+----------+-------------+--------------------+
| Table 1 | 2020-01-01 | 50 |
| Table 1 | 2020-01-02 | 100 |
| Table 1 | 2020-01-03 | 415 |
+----------+-------------+--------------------+
I was thinking in a kind of solution which is similar to the Rolling Forecast, but after a while I stuck and still thinking that what can be the best approach for this.
This is the current state of my "code":
DECLARE #SQL_RecordCounting nvarchar(MAX) =
(
SELECT
STRING_AGG(CAST( N'SELECT N' AS nvarchar(MAX)) +QUOTENAME(name,'''') +N' AS [DatabaseName], -- Database
T.NAME AS [TableName], -- Table
0-P.ROWS AS [RowCount], -- Counting the rows
GETDATE() as [Datetime] -- To have a history
FROM ' +
QUOTENAME(name) +
N'.sys.tables T
LEFT JOIN ' +
QUOTENAME(name) +
N'.sys.partitions P ON T.object_id = P.OBJECT_ID
WHERE T.is_ms_shipped = 0 -- excluding the MS provided tables
AND P.rows <> 0 -- only the zeros
GROUP BY T.Name,
P.Rows'
, NCHAR(10) + N' UNION ALL ' + NCHAR(10)
) + NCHAR(10) + N'ORDER BY Name;'
FROM sys.databases
WHERE database_id > 4 -- excluding the sys dbs
AND name not in ('DELTA','work','stage_dev') -- dbs which are out of scope
)
;
--INSERT INTO [ABC].[dbo].[TablesRecords]
exec sys.sp_executesql #SQL_RecordCounting
Use :
COALESCE([Count] - LAG([Count]) OVER(ORDER BY [Date]), [Count])
In the select clause of your query.
Just keep the totals in your table TablesRecords and query it using:
select tr.*,
(tr.count - lag(tr.count, 1, 0) over (partition by tr.table order by tr.date)) as count_difference
from [ABC].[dbo].[TablesRecords] tr;

SQL Find dynamic column and update value [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I have 2 tables
##tblReports - temporary table
Books| GroupId | Category | 01-01-2014 | 02-01-2014 | ..etc
----------+------------+--------------------------
100 | 1 | Limit | 700 | 0
100 | 1 | Exp | 70 | 0
100 | 1 | Balance | 630 | 0
200 | 1 | Limit | 0 | 900
200 | 1 | Exp | 0 | 100
200 | 1 | Balance | 0 | 800
tblLimits -user table
GroupId | 100BooksLimit | 200BooksLimit
----------+------------+---------------
1 | 700 | 900
2 | 7 | 10
Desired output
Books| GroupId | Category | 01-01-2014 | 02-01-2014
----------+------------+--------------------------
100 | 1 | Limit | 700 | 700
100 | 1 | Exp | 70 | 0
100 | 1 | Balance | 630 | 700
200 | 1 | Limit | 900 | 900
200 | 1 | Exp | 0 | 100
200 | 1 | Balance | 900 | 800
the 3rd column onwards from ##tblReports are dynamic. Can you help me how to update it?
Basically:
find all the columns with 0 values
search for its limit in tblLimits table using GroupId and Books.
get the limit and update 'Limit' and 'Balance' row
I tried to use dynamic queries but I cant make it work. Please help me
*I know the design of the tables are not ideal and follow the best practices as this is a client requirement that I need to follow. This is a temporary table and a lot of things happened before this table (multiple joins, pivot and un-pivot)
tables shown are simplified and does not exactly replicate the actual table. Thanks!
-- Create temp tables and sample data
CREATE TABLE ##tblReports (books INT, groupid INT, category VARCHAR(25), [01-01-2014] INT, [02-01-2014] INT)
INSERT INTO ##tblReports VALUES (100, 1, 'Limit', 700, 0), (100, 1, 'Exp', 70, 0), (100, 1, 'Balance', 630, 0),
(200, 1, 'Limit', 0, 900), (200, 1, 'Exp', 0, 100), (200, 1, 'Balance', 0, 800)
CREATE TABLE ##tblLimits (groupid INT, [100bookslimit] INT, [200bookslimit] INT)
INSERT INTO ##tblLimits VALUES (1, 700, 900), (2, 7, 10)
-- Unpivot ##tblLimits in a CTE (see footnote for what this outputs)
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = '
;WITH cte_unpivot AS
(
SELECT groupid, val, CAST(REPLACE(col, ''bookslimit'', '''') AS INT) AS books
FROM ##tblLimits
UNPIVOT (val FOR col IN ('
-- Are the columns in ##tblLimits dynamic (other than groupid)? If so, get their
-- names from tempdb.sys.columns metadata.
SELECT #sql += QUOTENAME(name) + ',' -- [Column],
FROM tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..##tblLimits') AND name <> 'groupid'
-- Delete trailing comma
SELECT #sql = SUBSTRING(#sql, 1, LEN(#sql) - 1)
SELECT #sql += '))AS u
)
SELECT t.books, t.groupid, category,
'
-- Get ##tblReports column names from tempdb.sys.columns metadata.
SELECT #sql += '
CASE WHEN ' + QUOTENAME(name) + ' = 0 AND t.category IN (''Limit'', ''Balance'')
THEN c.val ELSE t.' + QUOTENAME(name) + ' END AS ' + QUOTENAME(name) + ','
FROM tempdb.sys.columns
WHERE [object_id] = OBJECT_ID(N'tempdb..##tblReports') AND name NOT IN ('books', 'groupid', 'category')
-- Delete trailing comma again
SELECT #sql = SUBSTRING(#sql, 1, LEN(#sql) - 1)
SELECT #sql += '
FROM ##tblReports t
LEFT JOIN cte_unpivot c ON t.books = c.books AND t.groupid = c.groupid
'
EXEC sp_executesql #sql
Returns:
books groupid category 01-01-2014 02-01-2014
100 1 Limit 700 700
100 1 Exp 70 0
100 1 Balance 630 700
200 1 Limit 900 900
200 1 Exp 0 100
200 1 Balance 900 800
The key is unpivoting ##tblLimits into this format so you can easily join it to ##tblReports:
groupid val books
1 700 100
1 900 200
2 7 100
2 10 200
Here's the SQL it generates (but formatted):
;WITH cte_unpivot
AS (SELECT groupid,
val,
Cast(Replace(col, 'bookslimit', '') AS INT) AS books
FROM ##tbllimits
UNPIVOT (val
FOR col IN ([100bookslimit],
[200bookslimit]))AS u)
SELECT t.books,
t.groupid,
category,
CASE
WHEN [01-01-2014] = 0
AND t.category IN ( 'Limit', 'Balance' ) THEN c.val
ELSE t.[01-01-2014]
END AS [01-01-2014],
CASE
WHEN [02-01-2014] = 0
AND t.category IN ( 'Limit', 'Balance' ) THEN c.val
ELSE t.[02-01-2014]
END AS [02-01-2014]
FROM ##tblreports t
LEFT JOIN cte_unpivot c
ON t.books = c.books
AND t.groupid = c.groupid

How to get SQL query result column name as first row

I have a dynamic SQL query that gets me result sets after execution. However, the UI model that I am rendering results back from SQL server engine doesn't provide a way to render query column names.
Due to the dynamic nature of the query, I can't hard code the column names at design time. So my question is how do I get column names along with the data set returned by the query?
This Query:
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
convert(date, DATEADDED) DATEADDED
,COUNT(1) as NUMBEROFRECORDS
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
Gives me this table (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
But I want this (Original Image):
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
+ ---------- + --------------- +
| DATEADDED | NUMBEROFRECORDS |
| 2017-03-14 | 1 |
| 2017-03-10 | 1 |
| 2016-07-07 | 5 |
| 2016-06-29 | 3 |
| 2016-06-15 | 1 |
| 2014-11-11 | 465 |
| 2005-06-09 | 11 |
| 2005-04-13 | 1 |
| 2005-02-28 | 2 |
+ ---------- + --------------- +
Thanks
It's doable, but not very pretty. A Stored Procedure where you pass the dynamic SQL would be much cleaner
We're essentially doing Dynamic SQL within Dynamic SQL
One caveat: I reserved the field RN
Example (Using my FRED Series Data)
-- This is Your Base/Initial Query, or the only portion you need to supply
Declare #SQL varchar(max) = 'Select Updated as Updated,Count(*) as NumberOfRecords From [dbo].[FRED-Series] Group By Updated'
Select #SQL = '
;with cte0 as ('+#SQL+')
, cte1 as (Select *,RN = Row_Number() over (Order By (Select null)) From cte0 )
, cte2 as (
Select A.RN,C.*
From cte1 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(''.'',''varchar(max)'')
,ColNr = Row_Number() over (Order By (Select Null))
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 (''RN'')
) C
)
Select Distinct RN=0,Item,Value=Item,ColNr Into #Temp From cte2 Union All Select * from cte2
Declare #SQL varchar(max) = Stuff((Select '','' + QuoteName(Item) From #Temp Where RN=0 Order by ColNr For XML Path('''')),1,1,'''')
Select #SQL = ''Select '' + #SQL + '' From (Select RN,Item,Value From #Temp ) A Pivot (max(Value) For [Item] in ('' + #SQL + '') ) p''
Exec(#SQL);
'
Exec(#SQL)
Returns
Updated NumberOfRecords
Updated NumberOfRecords
2017-03-22 597
2017-03-23 40
2017-03-20 228
2017-03-21 1404
Just some Commentary
cte0 is your primary query
cte1 will take the results of your initial query and add a Row Number
cte2 will dynamically unpivot your data
The results of cte2 are dropped into a #temp table for convenience (assuming this is allowed)
Then we perform a dynamic pivot
Union a static query with the column names. You have to cast the results of the second query to varchar or nvarchar so they are the same data type as your column names.
DECLARE #SQLSTATMENT nvarchar(1000)
SELECT #SQLSTATEMENT = '
SELECT
''DATEADDED'' AS [DATEADDED]
,''NUMBEROFRECORDS'' AS [NUMBEROFRECORDS]
SELECT
CAST(convert(date, DATEADDED) AS NVARCHAR(MAX)
,CAST(COUNT(1) AS NVARCHAR(MAX))
FROM
dbo.CONSTITUENT
GROUP BY
convert(date, DATEADDED)
ORDER BY
convert(date, DATEADDED) DESC
'
Exec (#SQLSTATEMENT);
With this said, you should be able to reference the column names via code and not have to add them to the query. This way you could keep the data types of the result set.

SQL group rows to columns [duplicate]

This question already has answers here:
Simple way to transpose columns and rows in SQL?
(9 answers)
Closed 6 years ago.
I have a table
ID | Customer | Type | Value |
---+----------+---------+-------+
1 | John | Income | 50 |
2 | John | Income | 20 |
3 | Mike | Outcome | 150 |
4 | Robert | Income | 100 |
5 | John | Outcome | 300 |
Want a table like that;
| John | Mike | Robert |
--------+------+------+--------+
Income | 70 | 0 | 100 |
Outcome| 300 | 150 | 0 |
What should be the SQL Query? Thanks
The problem is Customers and Type are not static they are dynamic.
What I tried:
SELECT 'TotalIncome' AS TotalSalaryByDept,
[John], [Mike]
FROM
(SELECT Customer, Income
FROM table001) AS a
PIVOT
(
SUM(Income)
FOR ID IN ([John], [Mike])
) AS b;
Here is a quick dynamic pivot. We use a CROSS APPLY to unpivot the desired measures.
Declare #SQL varchar(max)
Select #SQL = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (
Select Item=A.Customer,B.*
From YourTable A
Cross Apply (
Select Type=''Income'' ,Value=A.Income Union All
Select Type=''Outcome'',Value=A.Outcome
) B
) A
Pivot (sum(value) For Item in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
EDIT - For the Revised Question
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(Customer) From YourTable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Type],' + #SQL + '
From (Select Customer,Type,Value from YourTable ) A
Pivot (Sum(Value) For [Customer] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
The Table as you have it is how it should be in your SQL database. Columns are reserved for classifying your data, and rows are where you add new instances.
What you need to do is set up your ASP, Excel Pivot Table, or whatever you are using to display the data to format it into a horizontal table. I would need to know what you are using to interface with your database to give you an example.

Convert Access TRANSFORM/PIVOT query to SQL Server

TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |