How to PIVOT multiple columns using SQL Server - sql

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;

Related

Display Average Billing Amount For Each Customer only between years 2019-2021

QUESTION : Display Average Billing Amount For Each Customer ONLY between YEAR(2019-2021).
If customer doesn't have any billing amount for any of the particular year then consider as 0.
-------: OUTPUT :
Customer_ID | Customer_Name | AVG_Billed_Amount
-------------------------------------------------------------------------
1 | A | 87.00
2 | B | 200.00
3 | C | 183.00
--------: EXPLANATION :
If any customer doesn't have any billing records for these 3 years then we need to consider as one record with billing_amount = 0
Like Customer C doesn't have any record for Year 2020, so for C Average will be
(250+300+0)/3 = 183.33 OR 183.00
TEMP TABLE HAS FOLLOWING DATA
DROP TABLE IF EXISTS #TEMP;
CREATE TABLE #TEMP
(
Customer_ID INT
, Customer_Name NVARCHAR(100)
, Billing_ID NVARCHAR(100)
, Billing_creation_Date DATETIME
, Billed_Amount INT
);
INSERT INTO #TEMP
SELECT 1, 'A', 'ID1', TRY_CAST('10-10-2020' AS DATETIME), 100 UNION ALL
SELECT 1, 'A', 'ID2', TRY_CAST('11-11-2020' AS DATETIME), 150 UNION ALL
SELECT 1, 'A', 'ID3', TRY_CAST('12-11-2021' AS DATETIME), 100 UNION ALL
SELECT 2, 'B', 'ID4', TRY_CAST('10-11-2019' AS DATETIME), 150 UNION ALL
SELECT 2, 'B', 'ID5', TRY_CAST('11-11-2020' AS DATETIME), 200 UNION ALL
SELECT 2, 'B', 'ID6', TRY_CAST('12-11-2021' AS DATETIME), 250 UNION ALL
SELECT 3, 'C', 'ID7', TRY_CAST('01-01-2018' AS DATETIME), 100 UNION ALL
SELECT 3, 'C', 'ID8', TRY_CAST('05-01-2019' AS DATETIME), 250 UNION ALL
SELECT 3, 'C', 'ID9', TRY_CAST('06-01-2021' AS DATETIME), 300
-----------------------------------------------------------------------------------
Here, 'A' has 3 transactions - TWICE in year 2020(100+150) and 1 in year 2021(100), but none in 2019(SO, Billed_Amount= 0).
so the average will be calculated as (100+150+100+0)/4
DECLARE #BILL_dATE DATE = (SELECT Billing_creation_date from #temp group by customer_id, Billing_creation_date) /*-- THIS THROWS ERROR AS #BILL_DATE WON'T ACCEPT MULTIPLE VALUES.*/
OUTPUT should look like this:
Customer_ID
Customer_Name
AVG_Billed_Amount
1
A
87.00
2
B
200.00
3
C
183.00
You just need a formula to count the number of missing years.
That's 3 - COUNT(DISTINCT YEAR(Billing_creation_Date)
Then the average = SUM() / (COUNT() + (3 - COUNT(DISTINCT YEAR)))...
SELECT
Customer_ID,
Customer_Name,
SUM(Billed_Amount) * 1.0
/
(COUNT(*) + 3 - COUNT(DISTINCT YEAR(Billing_creation_Date)))
AS AVG_Billed_amount
FROM
#temp
WHERE
Billing_creation_Date >= '2019-01-01'
AND Billing_creation_Date < '2022-01-01'
GROUP BY
Customer_ID,
Customer_Name
Demo : https://dbfiddle.uk/ILcfiGWL
Note: The WHERE clause in another answer here would cause a scan of the table, due to hiding the filtered column behind a function. The way I've formed the WHERE clause allows a "Range Seek" if the column is in an index.
Here is a query that can do that :
select s.Customer_ID, s.Customer_Name, sum(Billed_amount)/ ( 6 - count(1)) as AVG_Billed_Amount from (
select Customer_ID, Customer_Name, sum(Billed_Amount) as Billed_amount
from TEMP
where year(Billing_creation_Date) between 2019 and 2021
group by Customer_ID, year(Billing_creation_Date)
) as s
group by Customer_ID;
According to your description the customer_name C will be 137.5000 not 183.00 since 2018 is not counted and 2020 is not there.

Creating counts based on date ranges with inner join

Here is an illustration for what I'd like to do
Table A:
user_id | industry | startdate | enddate | generation
1 retail 2000-01-01 2001-01-01 Gen X
1 retail 2002-01-01 2003-02-01 Gen X
2 Tech 2001-01-01 2002-01-01 Gen X
2 Business 2002-03-01 2003-01-01 Gen X
2 Tech 2003-02-01 null Gen X
... ... ... ... ...
35642 Medicine 2020-02-01 2022-03-01 Gen Z
Table B
month
1990-01-01
1990-02-01
...
2022-03-01
Desired Result:
industry | generation| count | month
retail Gen X 200 2002-02-01
retail Gen Y 250 2002-02-01
Tech Gen X 130 2002-02-01
Tech Gen Y 166 2002-02-01
...
For now, I've only got tables A and B. I want to create counts by industry, by month, by generation, but I'm not sure how I can do this using the two tables that I have.
My (incorrect) approach would be something like select count(*), industry, month, generation where A.startdate < B.month and A.enddate > B.month, but this query is obviously not running. Is what I want to do possible with just tables A and B?
Apologies if I'm being unclear, I am admittedly new to SQL queries and am not sure how to approach this problem.
Try this approach:
Create a CTE that generates the distinct list of industry/generation by querying table A
Create a 2nd CTE that cartesian joins the first CTE to table B - giving you a list of all months/industry/generation
Left outer join table A to the 2nd CTE and query for the result you want to achieve
Executing the steps of NickW gives me.
I guess that users whose enddate is null are still in businss
## Create tables
CREATE TABLE table_a (
user_id int,
industry text,
startdate date,
enddate date,
generation text
);
CREATE TABLE table_b (
month date
);
# Insert data
WITH series_months AS (
SELECT date(i)
from generate_series(
date '1999-01-01',
date '2012-09-01',
INTERVAL '1 month'
) i
)
INSERT INTO table_b (month)
SELECT * FROM series_months;
INSERT INTO table_a (user_id, industry, startdate, enddate, generation)
VALUES
(1, 'retail', '2000-01-01', '2001-01-01', 'Gen X'),
(1, 'retail', '2002-01-01', '2003-02-01', 'Gen X'),
(2, 'Tech', '2001-01-01', '2002-01-01', 'Gen X'),
(2, 'Business', '2002-03-01', '2003-01-01', 'Gen X'),
(2, 'Tech', '2003-02-01', NULL, 'Gen X');
# Perform joins
WITH industry_generation as (
SELECT distinct industry, generation from table_a
), months_industry_generation as (
SELECT *
FROM table_b, industry_generation
), combined_table AS (
SELECT mig.month, mig.industry, mig.generation, user_id, startdate, enddate
FROM
(SELECT * FROM months_industry_generation) mig
inner join table_a a
on mig.industry = a.industry AND mig.generation = a.generation
where month >= startdate AND (month <= enddate OR enddate is null)
)
SELECT industry, generation, count(user_id) AS count, month
FROM combined_table
GROUP BY month, industry, generation
ORDER BY 1, 2, 4
;

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 |

unpivot row values into multiple columns

I have the following with the following structure:
http://sqlfiddle.com/#!6/0e72e/8
CREATE TABLE Prices
(
Day date,
ProductName nVARCHAR(10),
Location nVARCHAR(10),
RegPrice1 float,
SalePrice1 float,
SalePrice2 float
)
INSERT INTO Prices VALUES ('6/24/2014', 'xbox', 'NewYork',30,20,10)
INSERT INTO Prices VALUES ('6/24/2014', 'xbox', 'London', 100,80,60)
INSERT INTO Prices VALUES ('6/24/2014', 'xbox', 'Singapore', 70,50,30)
INSERT INTO Prices VALUES ('6/24/2014', 'watch1','NewYork', 500,400,300)
INSERT INTO Prices VALUES ('6/24/2014', 'watch1','London', 1000,800,600)
INSERT INTO Prices VALUES ('6/24/2014', 'watch1','Singapore', 999,888,777)
I want to unpivot this table so it looks like:
Day Pr_Name PriceType NewYork London Singapore
2014-06-24 xbox RegPrice1 30 100 70
2014-06-24 xbox SalePrice1 20 80 50
2014-06-24 xbox SalePrice2 10 60 30
2014-06-24 watch1 RegPrice1 500 1000 999
2014-06-24 watch1 SalePrice1 400 800 888
2014-06-24 watch1 SalePrice1 300 600 777
I was able to unpivot one layer to get the NewYork column but I haven't been able to get the London and Singapore columns in place. I have tinkered with the code below to add London and Singapore but have not been successful. Do I simply keep unpivoting?
select Day, ProductName, PriceType, NewYork
from (select * from Prices ) as t
Unpivot
(
NewYork for PriceType in (RegPrice1, SalePrice1, SalePrice2)
) As unpvt
we can use CROSS APPLY to unpivot the prices and then apply PIVOT
SELECT
*
FROM
( select Day, ProductName, Location, col, value
from Prices
cross apply
(
select 'RegPrice1' , Prices.RegPrice1 union all
select 'SalePrice1', Prices.SalePrice1 union all
select 'SalePrice2', Prices.SalePrice2
) c (col, value)
) PD
PIVOT
( max(value) for Location in ([NewYork],[London],[Singapore])
)pvt

I want a select query result in tabular format like summary report

for examaple
month1 month2 month3 total
district1 5 2 9 16
district2 1 0 11 12
.
.
total 260 150 140 550
here final total is not much important. but at least i need to show count per district per month.
SELECT Districts_mst.district_name,COUNT(Payments.PaymentId)users ,DATEPART(M,payments.saveon)Month
FROM Payments
JOIN Subsciber ON Payments.SubId =Subsciber.SubId
JOIN districts_mst ON districts_mst.district_id = Subsciber.District
where lang_id=1
group by district_name, DATEPART(M,payments.saveon)
which give me list like.....
district_name users Month
dist0 1 1
dist1 1 11
dist2 3 11
dist3 1 11
dist4 3 11
dist5 1 12
dist6 1 12
In SQL Server 2008 you can handle this task pretty easily with a PIVOT query. The following example relies on getting your data into the following format (which it looks like you have done already):
Name Month Value
---------- ------- -----
District 1 Month 1 10
District 1 Month 2 5
District 1 Month 3 6
District 2 Month 1 1
District 2 Month 2 2
District 2 Month 3 3
District 3 Month 1 8
District 3 Month 2 6
District 3 Month 3 11
If you can do that, then your PIVOT query should look something like this:
DECLARE #myTable AS TABLE([Name] VARCHAR(20), [Month] VARCHAR(20), [Value] INT)
INSERT INTO #myTable VALUES ('District 1', 'Month 1', 10)
INSERT INTO #myTable VALUES ('District 1', 'Month 2', 5)
INSERT INTO #myTable VALUES ('District 1', 'Month 3', 6)
INSERT INTO #myTable VALUES ('District 2', 'Month 1', 1)
INSERT INTO #myTable VALUES ('District 2', 'Month 2', 2)
INSERT INTO #myTable VALUES ('District 2', 'Month 3', 3)
INSERT INTO #myTable VALUES ('District 3', 'Month 1', 8)
INSERT INTO #myTable VALUES ('District 3', 'Month 2', 6)
INSERT INTO #myTable VALUES ('District 3', 'Month 3', 11)
SELECT [Name], [Month 1], [Month 2], [Month 3], [NameTotalValue] AS [Total]
FROM
(
SELECT [Name], [Month], [Value],
SUM([Value]) OVER (PARTITION BY [Name]) as [NameTotalValue]
FROM #myTable
UNION
SELECT 'Total', [Month], SUM([Value]), (SELECT SUM([Value]) FROM #myTable)
FROM #myTable
GROUP BY [Month]
) t
PIVOT
(
SUM([Value]) FOR [Month] IN ([Month 1], [Month 2], [Month 3])
) AS pvt
ORDER BY pvt.[Name]
In this example, I used the SUM([Value]) OVER PARTITION to get the sums for each District, and then I did a UNION to add a totals row to the bottom. The results look like this:
Name Month 1 Month 2 Month 3 Total
----------- ------- ------- ------- -----
District 1 10 5 6 21
District 2 1 2 3 6
District 3 8 6 11 25
Total 19 13 20 52
One thing you'll notice about this approach is that you have to know the column names you want at the top of the table ahead of time. That's easy to do if you're setting up the report to run for a full year, but is trickier if the number of columns is going to change. If you're going to allow the users to specify a custom date range (i.e., 07/2011-10/2011 or 06/2011-11/2011), then one way handle that requirement is to build the PIVOT query using dynamic SQL and then execute it with sp_executesql.