I would like to add this expression:
([2013]/[2012]-1) AS [Change%]
As a field to the crosstab query below:
TRANSFORM
Sum(Data.Spending)
SELECT
Data.Category
FROM
Data
WHERE
(((Data.Year)="2012" Or (Data.Year)="2013"))
GROUP BY
Data.Category
PIVOT
Data.Year;
This is solved through using another table in this thread: Access 2007 Crosstab Query Expression
But for my purposes I need to have everything together in one query.
This is because I am writing SQL in another program, http://www.ljzsoft.com/ppt-report.htm, which uses the query to directly access an Access database.
As you have probably discovered, Access won't let us use a Crosstab query as a subquery. If we try to do
SELECT ...
FROM
(
TRANSFORM
we get "Syntax error in FROM clause". One workaround is to do all of the aggregations, create the derived (calculated) values, UNION them together, and then "crosstab" that. In your case:
TRANSFORM Sum(ColValue) AS SumOfColValue
SELECT Category
FROM
(
SELECT Category, [Year] AS ColHead, Sum(Spending) AS ColValue
FROM Data
WHERE [Year] IN ("2012", "2013")
GROUP BY Category, [Year]
UNION ALL
SELECT
curr.Category,
"Change%" AS ColHead,
(curr.SumOfSpending/prev.SumOfSpending-1) AS ColValue
FROM
(
SELECT Category, [Year], Sum(Spending) AS SumOfSpending
FROM Data
WHERE [Year] IN ("2012", "2013")
GROUP BY Category, [Year]
) AS curr
INNER JOIN
(
SELECT Category, [Year], Sum(Spending) AS SumOfSpending
FROM Data
WHERE [Year] IN ("2012", "2013")
GROUP BY Category, [Year]
) AS prev
ON prev.Category = curr.Category
AND CLng(prev.[Year]) = (CLng(curr.[Year]) - 1)
)
GROUP BY Category
PIVOT ColHead
which, for my sample data, returns
Category 2012 2013 Change%
--------- ---- ---- -----------------
Category1 123 345 1.80487804878049
Category2 234 456 0.948717948717949
If that is too nasty for your liking then you might want to investigate whether your reporting tool supports calculated report fields (e.g., in Crystal Reports I believe they are called "Formula Fields").
Related
I have the following two queries that build/feed into the third query. My goal is to have a crosstab query of [MCOs] down the left and possible responses/values for [DrpDown] across the top with the values shown as percentages of the total for each [MCO] (so % of row total).
What I have works, but I want to know if I can do it all in one query.
SELECT tblMCOs.MCOs, tblMCOs.DrpDwn, Count(tblMCOs.ID) AS CountOfID
FROM tblMCOs
GROUP BY tblMCOs.MCOs, tblMCOs.DrpDwn;
SELECT tblMCOs.MCOs, Count(tblMCOs.DrpDwn) AS CountOfDrpDwn
FROM tblMCOs
GROUP BY tblMCOs.MCOs;
TRANSFORM Sum(Round([qryMCODrpDwnCt]![CountOfID]/[qryMCOCtDrpDwn]!
[CountOfDrpDwn],4)*100) AS PCT
SELECT qryMCODrpDwnCt.MCOs
FROM qryMCODrpDwnCt INNER JOIN qryMCOCtDrpDwn ON qryMCODrpDwnCt.MCOs =
qryMCOCtDrpDwn.MCOs
GROUP BY qryMCODrpDwnCt.MCOs
PIVOT qryMCODrpDwnCt.DrpDwn;
Thanks in advance for your help.
What I have works, but I want to know if I can do it all in one query.
Crosstab queries can be a bit fussy, but simply inserting the SQL code as subqueries should work:
TRANSFORM Sum(Round([sqMCODrpDwnCt]![CountOfID]/[sqMCOCtDrpDwn]![CountOfDrpDwn],4)*100) AS PCT
SELECT sqMCODrpDwnCt.MCOs
FROM
(
SELECT tblMCOs.MCOs, tblMCOs.DrpDwn, Count(tblMCOs.ID) AS CountOfID
FROM tblMCOs
GROUP BY tblMCOs.MCOs, tblMCOs.DrpDwn
) AS sqMCODrpDwnCt
INNER JOIN
(
SELECT tblMCOs.MCOs, Count(tblMCOs.DrpDwn) AS CountOfDrpDwn
FROM tblMCOs
GROUP BY tblMCOs.MCOs
) AS sqMCOCtDrpDwn
ON sqMCODrpDwnCt.MCOs = sqMCOCtDrpDwn.MCOs
GROUP BY sqMCODrpDwnCt.MCOs
PIVOT sqMCODrpDwnCt.DrpDwn
I'm working on a T-SQL issue where I needed to Transponse Rows into Columns and using UNPIVOT and PIVOT together per a post at Simple way to transpose columns and rows in Sql?
No problem. It does Exactly what I want with the row/column manipulation. However what I would REALLY like to do is to get the values used for the column headers to become yet another row in the results.
My abbreviated code is:
SELECT *
FROM (SELECT fiscalyear,
Sum(totalrecords) AS TotalRecords
FROM dbo.tbleirstatisticsoverviewsummary
WHERE fiscalquarter = 'ALL'
AND branchcode = 'ALL'
GROUP BY fiscalyear,
fiscalquarter,
branchcode) AS p
UNPIVOT (value
FOR colname IN ( totalrecords )) AS unpvt
PIVOT (Max(value) For FiscalYear IN ([2012],[2013],[ALL])) as p
What it renders is:
colname 2012 2013 ALL
TotalRecords 421 227 648
Where the first line is column headers.
Any thoughts on how I could get the column headers to be data rows?
Adding some sample Raw Data
fiscalyear TotalRecords
2012 421
2013 227
ALL 648
There are a few confusing things that you are doing.
First, typically you will unpivot multiple columns. Right now, you are are unpivoting one column and it seems like you are doing it just to rename the column?
Second, you are aggregating the data twice, the PIVOT should be able to handle the aggregation using SUM().
Third, it is not exactly clear on why you need the column headers as a row, what will you want the column headers to be called?
Based on your sample data you should be able to just apply the PIVOT function:
select 'TotalRecords' TotalRecords,
[2012],
[2013],
[All]
from tbleirstatisticsoverviewsummary
pivot
(
sum(totalrecords)
for FiscalYear IN ([2012],[2013],[ALL])
) p;
See SQL Fiddle with Demo. Then if you want a row with the columns headers, then you can use a UNION ALL:
select 'colname' col1,
2012 col2,
2013 col3,
'All' col4
union all
select 'TotalRecords' TotalRecords,
[2012],
[2013],
[All] = cast([all] as varchar(10))
from tbleirstatisticsoverviewsummary
pivot
(
sum(totalrecords)
for FiscalYear IN ([2012],[2013],[ALL])
) p;
See SQL Fiddle with Demo
My assignment asked me to find out "how many invoices are written for each date?"
I was a little stuck and asked my professor for help. She emailed me a query that would answer the question, "How many stoves of each type and version have been built?
For a challenge but no extra points, include the total number of stoves."
This was the query she sent me:
SELECT STOVE.Type + STOVE.Version AS 'Type+Version'
, COUNT(*) AS 'The Count'
FROM STOVE
GROUP BY STOVE.Type + STOVE.Version WITH ROLLUP;
So, I tweaked that query until it met my needs. This is what I came up with:
SELECT InvoiceDt
, COUNT(InvoiceNbr) AS 'Number of Invoices'
FROM INVOICE
GROUP BY InvoiceDt WITH ROLLUP
ORDER BY InvoiceDt ASC;
And it returned the following results that I wanted.
Anyway, I decided to read up on the ROLLUP clause and started with an article from Microsoft. It said that the ROLLUP clause was similar to the CUBE clause but that it was distinguished from the CUBE clause in the following way:
CUBE generates a result set that shows aggregates for all combinations of values in the selected columns.
ROLLUP generates a result set that shows aggregates for a hierarchy of values in the selected columns.
So, I decided to replace the ROLLUP in my query with CUBE to see what would happen. They produced the same results. I guess that's where I'm getting confused.
It seems like, if you're using the type of query that I am here, that there isn't any practical difference between the two clauses. Is that right? Or, am I not understanding something? I had thought, when I finished reading the Microsoft article, that my results should've been different using the CUBE clause.
You won't see any difference since you're only rolling up a single column. Consider an example where we do
ROLLUP (YEAR, MONTH, DAY)
With a ROLLUP, it will have the following outputs:
YEAR, MONTH, DAY
YEAR, MONTH
YEAR
()
With CUBE, it will have the following:
YEAR, MONTH, DAY
YEAR, MONTH
YEAR, DAY
YEAR
MONTH, DAY
MONTH
DAY
()
CUBE essentially contains every possible rollup scenario for each node whereas ROLLUP will keep the hierarchy in tact (so it won't skip MONTH and show YEAR/DAY, whereas CUBE will)
This is why you didn't see a difference since you only had a single column you were rolling up.
We can understand the difference between ROLLUP and CUBE with a simple example. Consider we have a table which contains the results of quarterly test of students. In certain cases we need to see the total corresponding to the quarter as well as the students. Here is the sample table
SELECT * INTO #TEMP
FROM
(
SELECT 'Quarter 1' PERIOD,'Amar' NAME ,97 MARKS
UNION ALL
SELECT 'Quarter 1','Ram',88
UNION ALL
SELECT 'Quarter 1','Simi',76
UNION ALL
SELECT 'Quarter 2','Amar',94
UNION ALL
SELECT 'Quarter 2','Ram',82
UNION ALL
SELECT 'Quarter 2','Simi',71
UNION ALL
SELECT 'Quarter 3' ,'Amar',95
UNION ALL
SELECT 'Quarter 3','Ram',83
UNION ALL
SELECT 'Quarter 3','Simi',77
UNION ALL
SELECT 'Quarter 4' ,'Amar',91
UNION ALL
SELECT 'Quarter 4','Ram',84
UNION ALL
SELECT 'Quarter 4','Simi',79
)TAB
1. ROLLUP(Can find total for corresponding to one column)
(a) Get total score of each student in all quarters.
SELECT * FROM #TEMP
UNION ALL
SELECT PERIOD,NAME,SUM(MARKS) TOTAL
FROM #TEMP
GROUP BY NAME,PERIOD
WITH ROLLUP
HAVING PERIOD IS NULL AND NAME IS NOT NULL
// Having is used inorder to emit a row that is the total of all totals of each student
Following is the result of (a)
(b) Incase you need to get total score of each quarter
SELECT * FROM #TEMP
UNION ALL
SELECT PERIOD,NAME,SUM(MARKS) TOTAL
FROM #TEMP
GROUP BY PERIOD,NAME
WITH ROLLUP
HAVING PERIOD IS NOT NULL AND NAME IS NULL
Following is the result of (b)
2. CUBE(Find total for Quarter as well as students in a single shot)
SELECT PERIOD,NAME,SUM(MARKS) TOTAL
FROM #TEMP
GROUP BY NAME,PERIOD
WITH CUBE
HAVING PERIOD IS NOT NULL OR NAME IS NOT NULL
Following is the result of CUBE
Now you may be wondering about the real time use of ROLLUP and CUBE. Sometimes we need a report in which we need to see the total of each quarter and total of each student in a single shot. Here is an example
I am changing the above CUBE query slightly as we need total for both totals.
SELECT CASE WHEN PERIOD IS NULL THEN 'TOTAL' ELSE PERIOD END PERIOD,
CASE WHEN NAME IS NULL THEN 'TOTAL' ELSE NAME END NAME,
SUM(MARKS) MARKS
INTO #TEMP2
FROM #TEMP
GROUP BY NAME,PERIOD
WITH CUBE
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + PERIOD + ']',
'[' + PERIOD + ']')
FROM (SELECT DISTINCT PERIOD FROM #TEMP2) PV
ORDER BY PERIOD
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT * FROM
(
SELECT * FROM #TEMP2
) x
PIVOT
(
SUM(MARKS)
FOR [PERIOD] IN (' + #cols + ')
) p;'
EXEC SP_EXECUTESQL #query
Now you will get the following result
This is because you only have one column that you are grouping by.
Add Group by InvoiceDt, InvoiceCountry (or whatever field will give you more data.
With Cube will give you a Sum for each InvoiceDt and you will get a Sum for each InvoiceCountry.
You can find more detail about GROUPING SET, CUBE, ROLL UP. TL;DR they just expand GROUP BY + UNION ALL in some ways to get aggregation :)
https://technet.microsoft.com/en-us/library/bb510427(v=sql.105).aspx
All Voted answers are good.
One Important difference in general is
The N elements of a ROLLUP specification correspond to N+1 GROUPING
SETS.
The N elements of a CUBE specification correspond to 2^N GROUPING
SETS.
Further reading see my article with respect to spark sql
For example :
store_id,product_type
rollup is equivalent to
GROUP BY store_id,product_type
GROUPING SETS (
(store_id,product_type)
,(product_type)
, ())
for 2 (n) group by columns grouping set has (n+1 ) = 3 combinations of columns
Cube is equivalent to
GROUP BY store_id,product_type
GROUPING SETS (
(store_id,product_type)
,(store_id)
,(product_type)
, ())
for 2 (n) group by columns grouping set has (2^n ) = 4 combinations of columns
I am looking for some help on designing a simple pivot so that I can link it into other parts of my queries.
My data is like this
Items Table
Below is my table if I run Select * from items
ITEM Weight
12345 10
12345 11
654321 50
654321 20
654321 100
There are hundreds of Items in this table but each item code will only ever have
maximum of 3 weight records each.
I want the desired output
ITEM Weight_1 Weight_2 Weight_3
12345 10 11 null
654321 50 20 100
Would appreciate any suggestions,
I have played around with pivots but each subsequent item puts the weights into weight 4,5,6,7,etc
instead of starting at weight1 for each item.
Thanks
Update
Below is what I have used so far,
SELECT r.*
FROM (SELECT 'weight' + CAST(Row_number() OVER (ORDER BY regtime ASC)AS
VARCHAR(10))
line,
id,
weight
FROM items it) AS o PIVOT(MIN([weight]) FOR line IN (weight1, weight2,
weight3)) AS r
You were almost there! You were only missing the PARTITION BY clause in OVER:
SELECT r.*
FROM (SELECT 'weight' + CAST(Row_number() OVER (PARTITION BY id ORDER BY
regtime ASC)
AS
VARCHAR(10)) line,
id,
weight
FROM items it) AS o PIVOT(MIN([weight]) FOR line IN (weight1, weight2,
weight3)) AS r
When you PARTITION BY by ID, the row numbers are reset for each different ID.
Update
You do not need dynamic pivot, since you will always have 3 weights. But, if you ever need dynamic number of columns, take a look at some of the examples here:
SQL Server PIVOT perhaps?
Pivot data in T-SQL
How do I build a summary by joining to a single table with SQL Server?
You will need a value to form the columns which I do with row_number. The outcome is what you want. The only negative that I have against PIVOT is that you need to know how many columns in advance. I use a similar method, but build up the select as dynamic SQL and can then insert my columns.
EDIT: updated to show columns as weight1, weight2, etc.
create table #temp (Item int, Weight int)
insert into #temp (Item, Weight)
Values (12345, 10),
(12345, 11),
(654321, 50),
(654321, 20),
(654321, 200)
SELECT *
FROM (SELECT Item,
Weight,
'weight' + cast(Row_number()
OVER (partition by Item order by item) as varchar(10)) as seq
FROM #temp) as Src
PIVOT ( MAX(Weight) FOR Seq IN ([Weight1], [Weight2], [Weight3]) ) as PVT
MySQL
Whenever you need a pivot, use group_concat it will output a CSV list of the values you need.
Once you get used to working with it, it's a great tool.
SELECT item, GROUP_CONCAT(weight) as weights FROM table1
GROUP BY item
See: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
TSQL aka SQL-server
Many many questions on this because T-SQL supports a pivot keyword.
See:
Transact SQL Query-Pivot-SQL
Pivot data in T-SQL
Imagine I have a table showing the sales of Acme Widgets, and where they were sold. It's fairly easy to produce a report grouping sales by country. It's fairly easy to find the top 10. But what I'd like is to show the top 10, and then have a final row saying Other. E.g.,
Ctry | Sales
=============
GB | 100
US | 80
ES | 60
...
IT | 10
Other | 50
I've been searching for ages but can't seem to find any help which takes me beyond the standard top 10.
TIA
I tried some of the other solutions here, however they seem to be either slightly off, or the ordering wasn't quite right.
My attempt at a Microsoft SQL Server solution appears to work correctly:
SELECT Ctry, Sales FROM
(
SELECT TOP 2
Ctry,
SUM(Sales) AS Sales
FROM
Table1
GROUP BY
Ctry
ORDER BY
Sales DESC
) AS Q1
UNION ALL
SELECT
Ctry AS 'Other',
SUM(Sales) AS Sales
FROM
Table1
WHERE
Ctry NOT IN (SELECT TOP 2
Ctry
FROM
Table1
GROUP BY
Ctry
ORDER BY
SUM(Sales) DESC)
Note that in my example, I'm only using TOP 2 rather than TOP 10. This is simply due to my test data being rather more limited. You can easily substitute the 2 for a 10 in your own data.
Here's the SQL Script to create the table:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Table1](
[Ctry] [varchar](50) NOT NULL,
[Sales] [float] NOT NULL
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
And my data looks like this:
GB 10
GB 21.2
GB 34
GB 16.75
US 10
US 11
US 56.43
FR 18.54
FR 98.58
WE 44.33
WE 11.54
WE 89.21
KR 10
PO 10
DE 10
Note that the query result is correctly ordered by the Sales value aggregate and not the alphabetic country code, and that the "Other" category is always last, even if it's Sales value aggregate would ordinarily push it to the top of the list.
I'm not saying this is the best (read: most optimal) solution, however, for the dataset that I provided it seems to work pretty well.
SELECT Ctry, sum(Sales) Sales
FROM (SELECT COALESCE(T2.Ctry, 'OTHER') Ctry, T1.Sales
FROM (SELECT Ctry, sum(Sales) Sales
FROM Table1
GROUP BY Ctry) T1
LEFT JOIN
(SELECT TOP 10 Ctry, sum(sales) Sales
FROM Table1
GROUP BY Ctry) T2
on T1.Ctry = T2.Ctry
) T
GROUP BY Ctry
The pure SQL solutions to this problem make multiple passes through the individual records more than once. The following solution only queries the data once, and uses a SQL ranking function, ROW_NUMBER() to determine if some results belong in the "Other" category. The ROW_NUMBER() function has been available in SQL Server since SQL Server 2008. In my database, this seems to have resulted in a more efficient query. Please note that the "Other" row will appear above some rows if the total of the "Other" sales exceeds the top 10. If this is not desired some adjustments would need to be made to this query:
SELECT CASE WHEN RowNumber > 10 THEN 'Other' ELSE Ctry END AS Ctry,
SUM(Sales) as Sales FROM
(
SELECT Ctry, SUM(Sales) as Sales,
ROW_NUMBER() OVER(ORDER BY SUM(Sales) DESC) AS RowNumber
FROM Table1 GROUP BY Ctry
) as AggregateQuery
GROUP BY CASE WHEN RowNumber > 10 THEN 'Other' ELSE Ctry END
ORDER BY SUM(Sales) DESC
Using a real analytics SQL engine, such as Apache Spark, you can use Common Table Expression with to do:
with t as (
select rank() over (order by sales desc) as r, sales,city
from DB
order by sales desc
)
select sales, city, r
from t where r <= 10
union
select sum(sales) as sales, "Other" as city, 11 as r
from t where r > 10
In pseudo SQL:
select top 10 order by sales
UNION
select 'Other',SUM(sales) where Ctry not in (select top 10 like above)
Union the top ten with an outer Join of the top ten with the table it self to aggregate the rest.
I don't have access to SQL here but I'll hazzard a guess:
select top (10) Ctry, sales from table1
union all
select 'other', sum(sales)
from table1
left outer join (select top (10) Ctry, sales from table1) as table2
on table2.Ctry = table2.Ctry
where table2.ctry = null
group by table1.Ctry
Of course if this is a rapidly changing top(10) then you either lock or maintain a copy of the top(10) for the duration of the query.
Have in mind that depending on your use (and database volume / restrictions) you can achieve the same results using application code (python, node, C#, java etc). Sure it will depend on your use-case but hey, it's possible.
I ended up doing this in C# for instance:
// Mockup Class that has a CATEGORY and it's VOLUME
class YourModel { string category; double volume; }
List<YourModel> groupedList = wholeList.Take (5).ToList ();
groupedList.Add (new YourModel()
{
category = "Others",
volume = tempChartData.Skip (5).Select (t => t.qtd).Sum ()
});
Disclaimer
I understand that this is a "SQL Only" tagged question, but there might be other people like me out there who can make use of the application layer instead of relying only on SQL to make it happen. I am just trying to show people other ways of doing the same thing, that might be helpful. Even if this gets downvoted to oblivion I know that someone will be happy to read this because they were taught to use each tool to it's best, and think "outside the box".