SQL Pivot Percent Row Distribution Columns - sql

I am looking for help with writing a query to display percent distribution to display across the values of a row in a pivot in SQL Server.
My data and current query for the pivot is as follows: https://dbfiddle.uk/CgwcAtRU
Existing Query:
SELECT StoreID, Quarter, MonthofYear, WeekOfYear, [0], [1], [2], [3], [4], [5], [6], [7]
FROM (SELECT Sales.StoreID, TimeGrouping, Sales.Quantity, MonthOfYear, Quarter, WeekOfYear
FROM Date INNER JOIN Sales ON Date.DateID = Sales.DateID) AS t
PIVOT (Sum(Quantity) FOR TimeGrouping IN ([0], [1], [2], [3], [4], [5], [6], [7])) p
GROUP BY StoreID, Quarter, MonthofYear, WeekOfYear,[0], [1], [2], [3], [4], [5], [6], [7]]
I am open to changing the existing query, but it provides a starting point of where my thinking was. In this example the results would return the following:
StoreID
Quarter
MonthofYear
WeekOfYear
0
1
2
3
4
5
6
7
1
1
1
2
null
null
null
71.43%
28.57%
null
null
null
2
1
1
2
null
null
100%
null
null
null
null
null
3
1
1
1
null
100%
null
null
null
null
null
null
Query to re-create data:
CREATE TABLE stores
(StoreID int, Location varchar(13), StoreName varchar(8));
INSERT INTO stores
(StoreID, Location, StoreName)
VALUES
(1, 'Miami', 'Joes''s'),
(2, 'Orlando', 'Franks''s'),
(3, 'Florida', 'Joes''s');
CREATE TABLE products
(ProductID int, Name varchar(10), Category varchar(5));
INSERT INTO products
(ProductID, Name, Category)
VALUES
(1, 'Milk', 'Food'),
(2, 'Eggs', 'Food'),
(3, 'Reese''s', 'Candy'),
(4, 'Hershey''s', 'Candy');
CREATE TABLE date
(DateID varchar(10), Year int, MonthofYear int, Quarter int, WeekofYear int, DayOfYear int);
INSERT INTO date
(DateID, Year, MonthofYear, Quarter, WeekOfYear, DayofYear)
VALUES
('1/1/2022',2022,'1',1,1,1),
('1/2/2022',2022,'1',1,2,2),
('1/3/2022',2022,'1',1,2,3),
('1/4/2022',2022,'1',1,2,4);
CREATE TABLE sales
(SaleID int, DateID varchar(10), ProductID int, StoreID int, Quantity int, TimeGrouping int);
INSERT INTO sales
(SaleID, DateID, ProductID, StoreID, Quantity, TimeGrouping)
VALUES
(1,'1/1/2022',4,3,6,1),
(2,'1/2/2022',2,2,6,2),
(3,'1/3/2022',3,1,5,3),
(4,'1/4/2022',2,1,2,4);

Related

How to PIVOT multiple columns using SQL Server

I just wrote a query (for SQL Server) that is returning this output:
VendorId
Category
FirstSaleDate
StoreId
1
Car
1/1/2021
12
1
Clothes
1/2/2021
13
1
Toys
1/3/2021
14
1
Food
1/4/2021
15
1
Others
1/5/2021
15
But I actually need the following output
VendorId
Car
StoreId_car
Clothes
StoreId_clothes
Toys
StoreId_toys
Food
StoreId_food
Others
StoreId_others
1
1/1/2021
12
1/2/2021
1/2/2021
1/3/2021
14
1/4/2021
15
1/5/2021
15
I am new to SQL Server, but I saw that this might be possible by using two PIVOTs. I really need your help to find the right syntax.
scenario and output
You just need to pivot twice and combine the results, e.g.:
-- Setup example data...
drop table if exists #Example;
create table #Example (
VendorId int,
Category varchar(10),
FirstSaleDate date,
StoreId int
);
insert #Example (VendorId, [Category], FirstSaleDate, StoreId)
values
(1, 'Car', '2021-01-01', 12),
(1, 'Clothes', '2021-01-02', 13),
(1, 'Toys', '2021-01-03', 14),
(1, 'Food', '2021-01-04', 15),
(1, 'Others', '2021-01-05', 15);
-- Pivot data...
with FirstSales as (
select VendorId, Category, FirstSaleDate from #Example
), Stores as (
select VendorId, 'StoreId_' + Category as Category, StoreId from #Example
)
select
FirstSales.VendorId,
Car, StoreId_Car,
Clothes, StoreId_Clothes,
Toys, StoreId_Toys,
Food, StoreId_Food,
Others, StoreId_Others
from (
select VendorId, Car, Clothes, Toys, Food, Others
from FirstSales
pivot (min(FirstSaleDate) for Category in ([Car], [Clothes], [Toys], [Food], [Others])) as pvt
) as FirstSales
join (
select VendorId, StoreId_Car, StoreId_Clothes, StoreId_Toys, StoreId_Food, StoreId_Others
from Stores
pivot (min(StoreId) for Category in ([StoreId_Car], [StoreId_Clothes], [StoreId_Toys], [StoreId_Food], [StoreId_Others])) as pvt
) as Stores on Stores.VendorId=FirstSales.VendorId;

SQL Server : creating a table from procedure with selected data

I'm pretty new to SQL Server and been trying to brush up on my skills. I came across this problem today and its stumped me. I can return the products I need but I'm unsure how to create a table using the date/month/name of product from the procedure. If anyone could help or steer me in the right direction that be greatly appreciated.
Data:
CREATE TABLE products
(
id INTEGER NOT NULL PRIMARY KEY,
fruit VARCHAR(30) NOT NULL,
dateBought DATE NOT NULL
);
INSERT INTO products (id, fruit, dateBought) VALUES (0, 'Banana', '2021-07-01');
INSERT INTO products (id, fruit, dateBought) VALUES (1, 'Apple', '2021-06-23');
INSERT INTO products (id, fruit, dateBought) VALUES (2, 'Pear', '2021-01-11');
INSERT INTO products (id, fruit, dateBought) VALUES (3, 'Peach', '2021-08-01');
INSERT INTO products (id, fruit, dateBought) VALUES (4, 'Grape', '2021-08-02');
Executing procedure:
EXEC ProductsBought '2021-07-01'
Expected output:
day month name
----------------------
1 7 Banana
1 8 Peach
My stored procedure:
CREATE PROCEDURE ProductsBought
(#date DATE)
AS
BEGIN
SELECT *
FROM products
WHERE dateBought >= #date
AND dateBought <= DATEADD(MONTH, 1, #date);
END;
I assume you are looking for a resultset to be returned, not actually create a table--two very different things.
To get the date parts of a date, use DATEPART in SQL Server.
Run the following in SSMS:
-- Data mock-up.
DECLARE #products table (
id int NOT NULL PRIMARY KEY,
fruit varchar(30) NOT NULL,
dateBought date NOT NULL
);
INSERT INTO #products VALUES
( 0, 'Banana', '2021-07-01' ),
( 1, 'Apple', '2021-06-23' ),
( 2, 'Pear', '2021-01-11' ),
( 3, 'Peach', '2021-08-01' ),
( 4, 'Grape', '2021-08-02' );
-- Date var.
DECLARE #date date = '07/01/2021';
-- Return resultset.
SELECT
DATEPART ( day, dateBought ) AS [day],
DATEPART ( month, dateBought ) AS [month],
fruit
FROM #products
WHERE
dateBought BETWEEN #date AND DATEADD( month, 1, #date );
Returns
+-----+-------+--------+
| day | month | fruit |
+-----+-------+--------+
| 1 | 7 | Banana |
| 1 | 8 | Peach |
+-----+-------+--------+
You can use below procedure to get what you are looking for:
Create PROCEDURE ProductsBought (#date DATE) AS
BEGIN
SELECT day(datebought)day,month(datebought)Month,fruit name FROM products WHERE dateBought >= #date AND dateBought <= DATEADD(month,1, #date);
END;
Output:
| day | month | name |
----- ------- --------
| 1 | 7 | Banana |
| 1 | 8 | Peach |

Count records by month & type

I have a table in SQL Server 2014 with this schema:
OccuredDate (date) TypeID (int)
2014-1-1 1
2014-1-2 1
2014-2-5 4
2015-5-23 2
2015-6-3 3
…it has thousands of rows comprised of dates & typeIDs, spanning years.
So that I can plot this to a charting component, I’m trying to build a query that for a given year 1) returns one row per-month that 2) counts the total number of TypeID instances for the given TypeIDs. The charting component prefers columns for the type counts.
So for "2014" it would look like this:
MonthDate TypeOne TypeTwo TypeThree TypeFour
2014-1-1 2 0 0 0
2014-2-1 0 0 0 1
or:
Year Month TypeOne TypeTwo TypeThree TypeFour
2014 Jan 2 0 0 0
2014 Feb 0 0 0 1
Spent most of the night on it but no luck. Is there some dark SQL magic that will do this?
thanks!
You can do it using pivot, with something like this:
SELECT OccuredDate, [1], [2], [3], [4]
FROM
(
SELECT OccuredDate, TypeID FROM Table1) AS SourceTable
PIVOT
(
count(TypeID) FOR TypeID IN ([1], [2], [3], [4])
) AS PivotTable
And per month version:
SELECT
DATEADD(month, DATEDIFF(month, 0, OccuredDate), 0) as Month,
sum([1]) as [1],
sum([2]) as [2],
sum([3]) as [3],
sum([4]) as [4]
FROM
(
SELECT OccuredDate, TypeID FROM Table1) AS SourceTable
PIVOT
(
count(TypeID) FOR TypeID IN ([1], [2], [3], [4])
) AS PivotTable
group by
DATEADD(month, DATEDIFF(month, 0, OccuredDate), 0)
You can test this in SQL Fiddle: daily and monthly
Edit: Rewrote the monthly SQL

How would I write a SQL query to include monthly counts for all categories with all months and categories showing even if no records exist

I'm working with SSRS and I'm trying to populate a report to show monthly counts grouped by categories. The report should have the categories listed as row headers and all months of the year shown as column headers. The requirement is that I need to display all months and categories regardless if there is data that falls within those columns/rows.
My problem is constructing the SQL query to do just that.
Here are examples of the tables I'm working with:
Transaction table:
create table [Transaction] (
ContactID int Primary Key
, CategoryID int
, DateKey int
)
Calendar table:
Note that this table was originally created as a Date Dimension to be used with SSAS but I decided not to use SSAS as cube development was getting overwhelming for me. There are many other fields in this table but these are the fields of importance regarding this issue.
create table [Calendar] (
DateID int Primary Key
, Date datetime
, Year nchar(4)
, Month nvarchar(2)
)
Category table:
create table [Category] (
CategoryID int Primary Key
, CategoryName nvarchar
)
The query needs to return a dataset to be used in SSRS to populate a report similar to the following:
Category | Jan | Feb | Mar | Apr | May | June | etc...
--------------------------------------------------------------------
Category A | - | - | - | - | - | - | etc...
--------------------------------------------------------------------
Category B | - | - | - | - | - | - | etc...
--------------------------------------------------------------------
Category C | - | - | - | - | - | - | etc...
I know that it involved some combination of OUTER JOINS, GROUP BY, and subqueries. I just can't wrap my head around how to accomplish this.
I'd be very happy if someone could assist me in this issue.
Thanks
The following query gets all the categories in the transactions. It is pivoting on the month, by extracting the month from the date and counting the number of transactions. This will return a row for all categories in the data
select c.categoryName,
sum(case when extract(month from date) = 1 then 1 else 0 end) as Jan,
sum(case when extract(month from date) = 2 then 1 else 0 end) as Feb,
sum(case when extract(month from date) = 3 then 1 else 0 end) as Mar,
. . .
from transaction t join
category c
on t.categoryID = c.categoryID
group by categoryName
order by categoryName
If you actually need all categories, even when there are no transactions at all, then use a right outer join:
select c.categoryName,
sum(case when extract(month from date) = 1 then 1 else 0 end) as Jan,
sum(case when extract(month from date) = 2 then 1 else 0 end) as Feb,
sum(case when extract(month from date) = 3 then 1 else 0 end) as Mar,
. . .
from transaction t right outer join
category c
on t.categoryID = c.categoryID
group by categoryName
order by categoryName
You could use the PIVOT function:
WITH Data AS
( SELECT CategoryName,
Calendar.[Month],
ContactID
FROM Category
LEFT JOIN [Transaction]
ON [Transaction].CategoryID = Category.CategoryID
LEFT JOIN Calendar
AND [Transaction].DateKey = Calendar.DateID
)
SELECT CategoryName,
[1] AS [Jan],
[2] AS [Feb],
[3] AS [Mar],
[4] AS [Apr],
[5] AS [May],
[6] AS [Jun],
[7] AS [Jul],
[8] AS [Aug],
[9] AS [Sep],
[10] AS [Oct],
[11] AS [Nov],
[12] AS [Dec]
FROM Data
PIVOT
( COUNT(ContactID)
FOR [Month] IN ([1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12])
) pvt

Table Design for User Selectable Schedule and Select Using PIVOT

I am creating a scheduling application that allows employees to input their desired schedule which is stored in a table. The current design I'm looking at is below using SQL 2008 R2.
CREATE TABLE [dbo].[Schedule] (
[EmpNum] [varchar](10) NOT NULL,
[Start] [datetime] NOT NULL,
[Length] [decimal](18, 2) NOT NULL,
[Reason] [varchar](1) NOT NULL,
CONSTRAINT [PK_Schedule] PRIMARY KEY CLUSTERED
(
[EmpNum] ASC,
[Start] ASC
)
)
A few things to note
A user may have entered no schedule for a specific date range and still want to see their name listed but with no schedule
A user may select nothing for a day in a week, but select something for other days
Start is the beginning of the shift
Length is the number of hours a shift is, may vary WILDLY from day to day and person to person
The Reason column is for what has been selected by the user (W - Work, P - PTO, etc)
Here is sample data
EmpNum Start Length Reason
---------- ----------------------- --------------------------------------- ------
000001 2012-08-02 09:00:00.000 12.00 W
000001 2012-08-04 08:00:00.000 9.50 P
000002 2012-08-02 08:30:00.000 10.00 W
000002 2012-08-03 19:00:00.000 12.00 W
000003 2012-08-03 08:00:00.000 8.00 P
The output I desire is something like this
EmpNum [0] [1] [2] [3] [4] [5] [6]
---------- ------ ------ ------ ------ ------ ------ ------
000001 NULL NULL NULL NULL W NULL P
000002 NULL NULL NULL NULL W W NULL
000003 NULL NULL NULL NULL NULL P NULL
I've never used a PIVOT query before since we just upgraded from SQL 2000, so bear with me. I've constructed the below query which fails with the below error and I'm stuck.
Msg 102, Level 15, State 1, Line 14
Incorrect syntax near '('.
Query
declare #FirstDayOfWeek date
set #FirstDayOfWeek = '7/29/2012'
select EmpNum,
#FirstDayOfWeek [0],
dateadd(day, 1, #FirstDayOfWeek) [1],
dateadd(day, 2, #FirstDayOfWeek) [2],
dateadd(day, 3, #FirstDayOfWeek) [3],
dateadd(day, 4, #FirstDayOfWeek) [4],
dateadd(day, 5, #FirstDayOfWeek) [5],
dateadd(day, 6, #FirstDayOfWeek) [6]
from Schedule
pivot (
max(Reason)
for Start in ([0], [1], [2], [3], [4], [5], [6])
) as Pvt
Any thoughts on how to best implement this or how badly wrong I am here?
It looks like you are trying to PIVOT your data based on the Day of the Week 1-7. I suggest a slight change to this to get it to work:
SELECT *
FROM
(
select EmpNum,
reason,
datepart(dw, start) as DyOfWk
from #Schedule
) s
pivot (
max(Reason)
for dyofwk in ([1], [2], [3], [4], [5], [6], [7])
) as Pvt
See SQL Fiddle with Demo
Results: