SQL count and group then pivot - sql

So, I have been having this problem and I guess I am just too overloaded to figure it out. I have a database that I need to count from. That's all good. But where I run into a problem is i need to store it as only 2 rows, one for all the dates and one for the count. Here is an example:
obj_name | date_made
--------------------
1 | 2016-3-04
2 | 2016-5-23
3 | 2016-5-23
4 | 2016-5-23
5 | 2016-6-07
6 | 2016-6-07
7 | 2016-6-07
8 | 2016-6-07
9 | 2016-9-12
10 | 2016-9-12
What I want is to count how many objects are created on a certain date, then return it as 2 rows - one with all the dates then one with all the counts
Row1 | 2016-3-04 | 2016-5-23 | 2016-6-07 | 2016-9-12
Row2 | 1 | 3 | 4 | 2
If anyone can help that would be much appreciated.
here is what I have so far, I can get all the info I need but as 2 columns and I need it as 2 rows
SELECT datem,
SUM(num) AS total_num
FROM (
SELECT date_made AS datem,
obj_name,
COUNT(1) AS num
FROM db.tn
GROUP BY 1,2
) sub
GROUP BY 1
ORDER BY 1 DESC

You can try a dynamic pivot query like below
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(date_made)
FROM tbl
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #query =
'SELECT * FROM '+
'(SELECT COUNT(1) count, date_made FROM tbl ) src '+
' pivot '+
'( max(count) for date_made in ('+#cols+'))p'
EXEC(#query)

If I read this correctly, you are going to end up with an aggregate table that looks like this:
date_made | count
----------|------
2016-3-04 | 1
2016-5-23 | 3
2016-6-07 | 4
2016-9-12 | 2
And then you want to pivot the table on its side to look like the output in your initial question. Therefore, I think this is a repeat of this question:
Simple way to transpose columns and rows in Sql?

Related

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.

Crosstab query in SQL that compares and adds columns

I have a table in sql server that contains three columns: "date", "noon", and "3pm." The first column is self-explanatory, but the latter two contain the names of guest speakers at a venue according to the time they arrived. I want to write a cross-tab query that writes speaker names into the column header and counts the number of times that speaker spoke on that date.
Example
Date | Noon | 3pm
092916 | Tom | <null>
092816 | Dick | Tom
092716 | <null> | Suzy
Desired Output
Date | Dick | Tom | Suzy
092916 | <null> | 1 | <null>
092816 | 1 | 1 | <null>
092716 | <null> | <null> | 1
I can do this pretty easily with a crosstab query if I only select one time and put a count into the value category, but I'm having trouble with merging multiple times so that I can get an accurate count of who spoke on what day.
you can build your query dynamically.
this will create a count(case) statement for each name found in either the noon or 3pm column.. similar to COUNT(CASE WHEN 'Dick' IN ([Noon],[3pm]) THEN 1 END) as [Dick]
DECLARE #speakers NVARCHAR(MAX),
#sql NVARCHAR(MAX)
SET #speakers = STUFF((
SELECT ',COUNT(CASE WHEN ''' + [Name] + ''' IN ([Noon],[3pm]) THEN 1 END) as ' + QUOTENAME([Name])
FROM (SELECT [Noon] AS [Name] FROM Table1
UNION ALL SELECT [3pm] FROM Table1) t
GROUP BY t.Name
FOR XML PATH('')
), 1, 1, '')
SET #sql = N'SELECT Date, ' + #speakers + ' FROM Table1 GROUP BY Date'
--Print #sql to see what's going on
EXEC(#sql)
You could use this query:
select *
from (
select date, noon as speaker, count(*) as times
from events
group by date, noon
union all
select date, [3pm], count(*)
from events
group by date, [3pm]
) as u
pivot (
sum(times)
for speaker in ([Dick], [Tom], [Suzy])
) as piv
order by date desc;
... which gives you a count per cell (null, 1 or 2):
Date | Dick | Tom | Suzy
092916 | <null> | 1 | <null>
092816 | 1 | 1 | <null>
092716 | <null> | <null> | 1

Count number of values across multiple columns

I have a table with 11 columns. The first column includes the category names. The remaining 10 columns have values like white, green, big, damaged etc. and these values can change in time.
I need a SQL query to find how many are there in table (in 10 columns) each value.
Table 1:
+------------+------------+
| ID | decription |
+------------+------------+
| 1 | white |
| 2 | green |
| 3 | big |
| 4 | damaged |
+------------+------------+
Table 2:
+------------+-----------+-----------+-----------+
| CATEGORY | SECTION 1 | SECTION 2 | SECTION 3 |
+------------+-----------+-----------+-----------+
| Category 1 | white | green | big |
| Category 2 | big | damaged | white |
| Category 1 | white | green | big |
| Category 3 | big | damaged | white |
+------------+-----------+-----------+-----------+
Desired result:
+------------+-------+-------+-----+---------+
| CATEGORY | White | Green | Big | Damaged |
+------------+-------+-------+-----+---------+
| Category 1 | 20 | 10 | 9 | 50 |
| Category 2 | 25 | 21 | 15 | 5 |
+------------+-------+-------+-----+---------+
Is it possible doing like this dynamically just as query ?
its on MS sql in visual studio reporting
Thanks
You've got yourself a bit of a mess with the design and the desired result. The problem is that your table is denormalized and then the final result you want is also denormalized. You can get the final result by unpivoting your Section columns, then pivoting the values of those columns. You further add to the mess by needing to do this dynamically.
First, I'd advise you to rethink your table structure because this is far too messy to maintain.
In the meantime, before you even think about writing a dynamic version to get the result you have to get the logic correct via a static or hard-coded query. Now, you didn't state which version of SQL Server you are using but you first need to unpivot the Section columns. You can use either the UNPIVOT function or CROSS APPLY. Your query will start with something similar to the following:
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
See SQL Fiddle with Demo. This gets your data into the format:
| CATEGORY | VALUE |
|------------|---------|
| Category 1 | white |
| Category 1 | green |
| Category 1 | big |
| Category 2 | big |
| Category 2 | damaged |
| Category 2 | white |
Now you have multiple Category rows - one for each value that previously were in the Section columns. Since you want a total count of each word in the Category, you can now apply the pivot function:
select
category,
white, green, big, damaged
from
(
select
category,
value
from yourtable
unpivot
(
value for cols in (Section1,Section2,Section3)
) u
) un
pivot
(
count(value)
for value in (white, green, big, damaged)
) p;
See SQL Fiddle with Demo. This will give you the result that you want but now you need this to be done dynamically. You'll have to use dynamic SQL which will create a SQL string that will be executed giving you the final result.
If the number of columns to UNPIVOT is limited, then you will create a list of the new column values in a string and then execute it similar to:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from yourtable
cross apply
(
select Section1 union all
select Section2 union all
select Section3
) d (SectionValue)
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo
If you have an unknown number of columns to unpivot, then your process will be a bit more complicated. You'll need to generate a string with the columns to unpivot, you can use the sys.columns table to get this list:
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '')
Then you'll need to get a list of the new column values - but since these are dynamic we will need to generate this list with a bit of work. You'll need to unpivot the table to generate the list of values into a temporary table for use. Create a temp table to store the values:
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
Load the temp table with the data that you need to unpivot:
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u'
insert into #Category_Section exec(#unpivotquery);
See SQL Fiddle with Demo. You'll see that your data looks the same as the static version above. Now you need to create a string with the values from the temp table that will be used in the final query:
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Once you have all this you can put it together into a final query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX),
#unpivotquery AS NVARCHAR(MAX);
select #colsUnpivot
= stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('yourtable') and
C.name like 'Section%'
for xml path('')), 1, 1, '');
create table #Category_Section
(
Category varchar(50),
SectionValue varchar(50)
);
set #unpivotquery
= 'select
category,
value
from yourtable
unpivot
(
value for cols in ('+ #colsUnpivot +')
) u';
insert into #Category_Section exec(#unpivotquery);
select #colsPivot
= STUFF((SELECT ',' + quotename(SectionValue)
from #Category_Section
group by SectionValue
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in ('+ #colsunpivot +')
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo. All versions will get you the end result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
If your values are stored in a separate table, then you would generate your list of values from that table:
DECLARE #query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX);
select #colsPivot
= STUFF((SELECT ',' + quotename(decription)
from descriptions
group by decription
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select category, '+#colspivot+'
from
(
select
category,
value
from yourtable
unpivot
(
value
for cols in (Section1, Section2, Section3)
) un
) x
pivot
(
count(value)
for value in ('+ #colspivot +')
) p'
exec sp_executesql #query
See SQL Fiddle with Demo and still get the same result:
| CATEGORY | BIG | DAMAGED | GREEN | WHITE |
|------------|-----|---------|-------|-------|
| Category 1 | 2 | 0 | 2 | 2 |
| Category 2 | 1 | 1 | 0 | 1 |
| Category 3 | 1 | 1 | 0 | 1 |
select category,
SUM(CASE when section1='white' then 1 when section2='white' then 1 when section3='white' then 1 else 0 end) as white,
SUM(CASE when section1='green' then 1 when section2='green' then 1 when section3='green' then 1 else 0 end) as green,
SUM(CASE when section1='damaged' then 1 when section2='damaged' then 1 when section3='damaged' then 1 else 0 end) as damaged,
SUM(CASE when section1='big' then 1 when section2='big' then 1 when section3='big' then 1 else 0 end) as big
from test
group by category
SQLFiddle
You can extend more to n section values as shown above gor section1,section2,section3

PIVOTing variable number of rows to columns

I'm currently attempting to PIVOT some rows to columns. The problem is that I don't always know how many rows will be available. Let's look at an example:
Values_Table Columns_Table
------------ -----------
ID ID
ColumnsTableID GroupID
Value ColumnName
RESULTS"
Columns_Table
---------------
ID | GroupID | ColumnName
---------------------------------
0 1 Cats
1 1 Dogs
2 1 Birds
3 2 Pontiac
4 2 Ford
5 3 Trex
6 3 Raptor
7 3 Triceratops
8 3 Kentrosaurus
SQL FIDDLE EXAMPLE of a STATIC pivot. I am trying to achieve a dynamic pivot - http://sqlfiddle.com/#!3/2be82/1
So, here is my dilemma: I want to be able to pivot an unknown number of columns based on, in this scenario, the GroupID.
I want to be able to PIVOT, for example, all the rows in GroupID 3 into columns. I would need to do this without knowing how many rows are in groupID 3.
The design of the database is set in stone, so I can't do anything about that. All I can do is work with what I have :(
So, that said- does anyone have any suggestions on how to accomplish this task of PIVOTing an unknown number of rows into columns based on, in this example, the groupID?
If you are not going to know the values ahead of time, then you will need to look at using dynamic SQL. This will create a SQL String that will be executed, this is required because the list of columns must be known when the query is run.
The code will be similar to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#groupid as int
set #groupid = 3
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(GroupName)
from Columns_Table
where groupid = #groupid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + '
from
(
SELECT B.GroupName, A.Value
, row_number() over(partition by a.ColumnsTableID
order by a.Value) seq
FROM Values_Table AS A
INNER JOIN Columns_Table AS B
ON A.ColumnsTableID = B.ID
where b.groupid = '+cast(#groupid as varchar(10))+'
) p
pivot
(
min(P.Value)
for P.GroupName in (' + #cols + ')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo. For groupid of 3, then the result will be:
| KENTROSAURUS | RAPTOR | TREX | TRICERATOPS |
| whatisthiseven | Itsaraptor | Jurassic | landbeforetime |
| (null) | zomg | Park | (null) |

SQL - how to create a dynamic matrix showing attribution values per item over time (where number of attributes varies per date)

I have:
items which are described by a set of ids (GroupType, ID, Name)
VALUES table which gets populated with factor values on each date so that an item gets only a certain set of factors with values per date.
FACTORS table containing static descriptions of the factors.
Looking for:
I want to create a temporary table with a matrix showing factor values for each item per date so that one could see in user friendly way which Factors were populated on a given date (with corresponding values).
Values
Date GroupType ID Name FactorId Value
01/01/2013 1 1 A 1 10
01/01/2013 1 1 A 2 8
01/01/2013 1 1 A 3 12
01/01/2013 1 2 B 3 5
01/01/2013 1 2 B 4 6
02/01/2013 1 1 A 1 7
02/01/2013 1 1 A 2 6
02/01/2013 1 2 B 3 9
02/01/2013 1 2 B 4 9
Factors
FactorId FactorName
1 Factor1
2 Factor2
3 Factor3
4 Factor4
. .
. .
. .
temporary table Factor Values Matrix
Date Group ID Name Factor1 Factor2 Factor3 Factor4 Factor...
01/01/2013 1 1 A 10 8 12
01/01/2013 1 2 B 5 6
02/01/2013 1 1 A 7 6
02/01/2013 1 2 B 9 9
Any help is greatly appreciated!
This type of data transformation is known as a PIVOT which takes values from rows and converts it into columns.
In SQL Server 2005+, there is a function that will perform this rotation of data.
Static Pivot:
If your values will be set then you can hard-code the FactorNames into the columns by using a static pivot.
select date, grouptype, id, name, Factor1, Factor2, Factor3, Factor4
from
(
select v.date,
v.grouptype,
v.id,
v.name,
f.factorname,
v.value
from [values] v
left join factors f
on v.factorid = f.factorid
-- where v.date between date1 and date2
) src
pivot
(
max(value)
for factorname in (Factor1, Factor2, Factor3, Factor4)
) piv;
See SQL Fiddle with Demo.
Dynamic Pivot:
In your case, you stated that you are going to have an unknown number of values. If so, then you will need to use dynamic SQL to generate a SQL string that will be executed at run-time:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(FactorName)
from factors
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT date, grouptype, id, name,' + #cols + ' from
(
select v.date,
v.grouptype,
v.id,
v.name,
f.factorname,
v.value
from [values] v
left join factors f
on v.factorid = f.factorid
-- where v.date between date1 and date2
) x
pivot
(
max(value)
for factorname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo.
Both of these versions generate the same result:
| DATE | GROUPTYPE | ID | NAME | FACTOR1 | FACTOR2 | FACTOR3 | FACTOR4 |
------------------------------------------------------------------------------
| 2013-01-01 | 1 | 1 | A | 10 | 8 | 12 | (null) |
| 2013-01-01 | 1 | 2 | B | (null) | (null) | 5 | 6 |
| 2013-02-01 | 1 | 1 | A | 7 | 6 | 11 | (null) |
| 2013-02-01 | 1 | 1 | B | (null) | (null) | 9 | 9 |
If you want to filter the results based on a date range, then you will just need to add a WHERE clause to the above queries.
It looks like you are simply trying to pivot the rows into columns. I think this does what you want:
select Date, Group, ID, Name,
max(case when factorid = 1 then name end) as Factor1,
max(case when factorid = 2 then name end) as Factor2,
max(case when factorid = 3 then name end) as Factor3,
max(case when factorid = 4 then name end) as Factor4
from t
group by Date, Group, ID, Name