Generate report converting rows into column using multiple tables - sql

I am working on a report with SQL Server database.
My tables are:
Table Name: appointments
id appointment_number applicant_name appointment_date appointment_time
----- ------------------ ------------------------ ---------------- ----------------
1 APP001 Anuj Tyagi 2022-12-28 11:00 AM
2 APP002 Puneet Pathak 2022-12-28 11:30 AM
3 APP003 Rajeev Kumar 2022-12-28 10:00 AM
Table Name: payments
id appointment_id fee_Type_name amount
----- ------------------ --------------- ----------
1 1 Consulatncy 500
2 1 Service Fee 100
3 1 Pharmacy 435
4 2 Consulatncy 800
5 2 Service Fee 160
6 2 Pharmacy 833
7 3 Consulatncy 500
8 3 Service Fee 100
Table Name: tax_details
id payment_id tax_name tax_percentage amount
----- -------------- --------------- -------------- ---------
1 1 CGST 5.00 25.00
2 1 SGST 2.50 12.50
3 2 CGST 8.00 8.00
4 2 SGST 4.00 4.00
5 3 CGST 10.00 43.50
6 3 SGST 8.00 34.80
7 4 CGST 5.00 40.00
8 4 SGST 2.50 20.00
9 5 CGST 8.00 12.80
10 5 SGST 4.00 6.40
11 6 CGST 10.00 83.30
12 6 SGST 8.00 66.64
13 7 CGST 5.00 25.00
14 7 SGST 2.50 12.50
15 8 CGST 8.00 8.00
16 8 SGST 4.00 4.00
I need to generate following report:
appointment_number applicant_name appointment_date appointment_time Consulatncy CGST SGST Service Fee CGST SGST Pharmacy CGST SGST
------------------ ------------------------ ---------------- ---------------- -------------- ------ ------ ------------- ------ ------ ---------- ------ ------
APP001 Anuj Tyagi 2022-12-28 11:00 AM 500 25.00 12.50 100.00 8.00 4.00 435.00 43.50 34.50
APP002 Puneet Pathak 2022-12-28 11:30 AM 800 40.00 20.00 160.00 12.80 6.40 833.00 83.30 66.64
APP003 Rajeev Kumar 2022-12-28 10:00 AM 500 25.00 12.50 100.00 8.00 4.00 0.00 0.00 0.00
I have tried PIVOT to convert the rows to column after creation of a view with all the data together. The problem I am facing there are:
My fee will be dynamic as is taxes
Putting Tax just after the fee.
Any help in this will be appreciated.
My Pivot Effort:
SELECT ##AllSumColumns= COALESCE(#AllSumColumns + ',','') + 'SUM(' + QUOTENAME([fee_name])+'))' + ' AS ' + QUOTENAME([fee_name])
FROM (
SELECT DISTINCT [fee_name] FROM [dbo].vw_fee_list_details fld
) AS PivotExample;
SELECT #AllColumns= COALESCE(#AllColumns + ',','') + QUOTENAME([fee_name])
FROM (
SELECT DISTINCT [fee_type_name] FROM [dbo].vw_fee_list_details fld WHERE fee_type = 1 and
fld.appref_id like concat(#appRefId,'%') AND
fld.e_number like concat(#eNumber,'%') AND
fld.service_center like concat(#vscName,'%') AND
CAST(fld.transaction_date AS DATE) >= #startDate AND
CAST(fld.transaction_date AS DATE) <= #endDate
) AS PivotExample
SET #SQLQuery =
N'SELECT
ROW_NUMBER() over (Order by feeTable.appointment_number, feeTable.applicant_name, feeTable.transaction_date) [S.No],
FORMAT(feeTable.transaction_date,''dd-MMM-yy'') as [Transaction Date],
feeTable.transaction_time as [Transaction Time],
feeTable.appointment_number as [Appointment Reference],
feeTable.applicant_name as [Applicant Name],
+#AllSumColumns+'
FROM(
SELECT * FROM(
SELECT * FROM vw_fee_list_details fld
WHERE
fld.appointment_number like '''+#appRefId+'%'+''' AND '+
'fld.e_number like '''+#eNumber+'%'+'''
) a
PIVOT (
sum(amount)
FOR [fee_name]
IN('+#AllColumns+')) AS PivotTable) AS feeTable
GROUP BY feeTable.appointment_number, feeTable.applicant_name, feeTable.transaction_date,feeTable.transaction_time'
EXEC sp_executesql #SQLQuery;

An advice - if you add the table definitions and the data in your question, it will be easier for people to reproduce your case and help you. Like the following:
DROP TABLE IF EXISTS #appointments;
DROP TABLE IF EXISTS #payments;
DROP TABLE IF EXISTS #tax_details;
CREATE TABLE #appointments
(
[id] INT PRIMARY KEY
,[appointment_number] VARCHAR(12)
,[applicant_name] VARCHAR(32)
,[appointment_date] DATE
,[appointment_time] TIME
);
CREATE TABLE #payments
(
[id] INT PRIMARY KEY
,[appointment_id] INT
,[fee_Type_name] VARCHAR(32)
,[amount] MONEY
);
CREATE TABLE #tax_details
(
[id] INT PRIMARY KEY
,[payment_id] INT
,[tax_name] VARCHAR(8)
,[tax_percentage] DECIMAL(9,2)
,[amount] MONEY
);
INSERT INTO #appointments ([id], [appointment_number], [applicant_name], [appointment_date], [appointment_time])
VALUES (1, 'APP001', 'Anuj Tyagi', '2022-12-28', '11:00 AM')
,(2, 'APP002', 'Puneet Pathak', '2022-12-28', '11:30 AM')
,(3, 'APP003', 'Rajeev Kumar', '2022-12-28', '10:00 AM');
INSERT INTO #payments ([id], [appointment_id], [fee_Type_name], [amount])
VALUES (1, 1, 'Consulatncy' ,500)
,(2, 1, 'Service Fee' ,100)
,(3, 1, 'Pharmacy' ,435)
,(4, 2, 'Consulatncy' ,800)
,(5, 2, 'Service Fee' ,160)
,(6, 2, 'Pharmacy' ,833)
,(7, 3, 'Consulatncy' ,500)
,(8, 3, 'Service Fee' ,100);
INSERT INTO #tax_details ([id], [payment_id], [tax_name], [tax_percentage], [amount])
VALUES (1 , 1, 'CGST', 5.00 , 25.00)
,(2 , 1, 'SGST', 2.50 , 12.50)
,(3 , 2, 'CGST', 8.00 , 8.00)
,(4 , 2, 'SGST', 4.00 , 4.00)
,(5 , 3, 'CGST', 10.00, 43.50)
,(6 , 3, 'SGST', 8.00 , 34.80)
,(7 , 4, 'CGST', 5.00 , 40.00)
,(8 , 4, 'SGST', 2.50 , 20.00)
,(9 , 5, 'CGST', 8.00 , 12.80)
,(10, 5, 'SGST', 4.00 , 6.40)
,(11, 6, 'CGST', 10.00, 83.30)
,(12, 6, 'SGST', 8.00 , 66.64)
,(13, 7, 'CGST', 5.00 , 25.00)
,(14, 7, 'SGST', 2.50 , 12.50)
,(15, 8, 'CGST', 8.00 , 8.00)
,(16, 8, 'SGST', 4.00 , 4.00);
Then, if something is hard to be written as dynamic T-SQL, it's better to first write it as static like this:
SELECT PVT.[id]
,PVT.[appointment_number]
,PVT.[applicant_name]
,PVT.[appointment_date]
,PVT.[appointment_time]
,ISNULL(MAX(PVT.[Consulatncy]), 0) AS [Consulatncy]
,ISNULL(MAX(IIF(PVT.[Consulatncy] IS NOT NULL AND T.[tax_name] = 'CGST', T.[amount], NULL)), 0) AS [CGST]
,ISNULL(MAX(IIF(PVT.[Consulatncy] IS NOT NULL AND T.[tax_name] = 'SGST', T.[amount], NULL)), 0) AS [SGST]
,ISNULL(MAX(PVT.[Service Fee]), 0) AS [Service Fee]
,ISNULL(MAX(IIF(PVT.[Service Fee] IS NOT NULL AND T.[tax_name] = 'CGST', T.[amount], NULL)), 0) AS [CGST]
,ISNULL(MAX(IIF(PVT.[Service Fee] IS NOT NULL AND T.[tax_name] = 'SGST', T.[amount], NULL)), 0) AS [SGST]
,ISNULL(MAX(PVT.[Pharmacy]), 0) AS [Pharmacy]
,ISNULL(MAX(IIF(PVT.[Pharmacy] IS NOT NULL AND T.[tax_name] = 'CGST', T.[amount], NULL)), 0) AS [CGST]
,ISNULL(MAX(IIF(PVT.[Pharmacy] IS NOT NULL AND T.[tax_name] = 'SGST', T.[amount], NULL)), 0) AS [SGST]
FROM
(
SELECT A.[id]
,A.[appointment_number]
,A.[applicant_name]
,A.[appointment_date]
,A.[appointment_time]
,P.[id] AS [payment_id]
,P.[fee_Type_name]
,P.[amount]
FROM #appointments A
INNER JOIN #payments P
ON A.[id] = P.[appointment_id]
) DS
PIVOT
(
MAX([amount]) FOR [fee_Type_name] IN ([Consulatncy], [Service Fee], [Pharmacy])
) PVT
INNER JOIN #tax_details T
ON PVT.[payment_id] = T.[payment_id]
GROUP BY PVT.[id]
,PVT.[appointment_number]
,PVT.[applicant_name]
,PVT.[appointment_date]
,PVT.[appointment_time];
which is one variant of solving the issue and give us:
No, there are two dynamic parts of the above query, which can be calculated as follows:
DECLARE #DynamicTSQLStatement NVARCHAR(MAX)
,#DynamicPVTColumns VARCHAR(MAX)
,#DynamicSELECTColumns VARCHAR(MAX);
SELECT #DynamicPVTColumns = STRING_AGG(QUOTENAME([fee_Type_name]), ', ')
FROM
(
SELECT DISTINCT [fee_Type_name]
FROM #payments
) DS;
SELECT #DynamicSELECTColumns = STRING_AGG(CAST(IIF([tax_name] IS NOT NULL, 'ISNULL(MAX(PVT.[' + [fee_Type_name] + ']), 0) AS [' + [fee_Type_name] + ']', 'ISNULL(MAX(IIF(PVT.[' + [fee_Type_name] + '] IS NOT NULL AND T.[tax_name] = ''' + [tax_name] + ''', T.[amount], NULL)), 0) AS [' + [tax_name] + ']') AS VARCHAR(MAX)), ',') WITHIN GROUP (ORDER BY [ColumnID])
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY [fee_Type_name], [tax_name]) AS [ColumnID]
,[fee_Type_name]
,[tax_name]
FROM
(
SELECT DISTINCT P.[fee_Type_name]
,T.[tax_name]
FROM #payments P
INNER JOIN #tax_details T
ON p.[id] = T.[payment_id]
UNION ALL
SELECT DISTINCT [fee_Type_name]
,NULL
FROM #payments
) DS ([fee_Type_name], [tax_name])
) DS
SET #DynamicTSQLStatement = N'
SELECT PVT.[id]
,PVT.[appointment_number]
,PVT.[applicant_name]
,PVT.[appointment_date]
,PVT.[appointment_time]
,' + #DynamicSELECTColumns + '
FROM
(
SELECT A.[id]
,A.[appointment_number]
,A.[applicant_name]
,A.[appointment_date]
,A.[appointment_time]
,P.[id] AS [payment_id]
,P.[fee_Type_name]
,P.[amount]
FROM #appointments A
INNER JOIN #payments P
ON A.[id] = P.[appointment_id]
) DS
PIVOT
(
MAX([amount]) FOR [fee_Type_name] IN (' + #DynamicPVTColumns + ')
) PVT
INNER JOIN #tax_details T
ON PVT.[payment_id] = T.[payment_id]
GROUP BY PVT.[id]
,PVT.[appointment_number]
,PVT.[applicant_name]
,PVT.[appointment_date]
,PVT.[appointment_time];
';
EXEC sp_executesql #DynamicTSQLStatement;
which give us:
Note, that I am ordering the fee_Type_name and tax_name by name. If you want to order them in a predefined way, you need to specify it in a separate tables (maybe).

Related

Aggregation/Joins in Order and Shipping tables

I am new to SQL and I was facing a problem.
I have 2 tables as shown below,
Order_table
Ord_num
Ord_date
Customer_name
Order_total
1111
2021-03-11
ABC
1000
Shipping_table
Ord_num
Pkg_num
Pkg_weight
shipped_date
shipping_cost
1111
1
30
2021-03-12
10
1111
2
20
2021-03-13
8
I wrote the following query,
select sum(order_total), sum(pkg_weight), sum(shipping_cost)
from order_table O join shipping_table P
on O.Ord_num = P.Ord_num
By this, if I sum my Order total, it shows 2000 but Order was only for 1000.
I basically want my output to be,
Ord_num
Ord_date
Cust_name
Order_total
Pkg_num
shipped_date
pkg_weight
shipping_cost
1111
2021-03-11
ABC
1000
1
2021-03-12
30
10
1111
2021-03-11
ABC
0 or null
2
2021-03-13
20
8
The reason I want Order_total as 0 or null in the second line is because when I aggregate other columns like pkg_weight and shipping_cost, it should show their sum whereas for Order_total, it should not show as 2000 because the order was for 1000 but shipped in two different packages with 2 weights, 2 costs and shipped on 2 different days.
Can anyone help me what I should write my query as?
Thanks in advance.
Start with this:
Declare #order_table Table (Ord_num int, Ord_date date, Customer_name varchar(30), Order_total int);
Insert Into #order_table (Ord_num, Ord_date, Customer_name, Order_total)
Values (1111, '2021-03-11', 'ABC', 1000)
, (2222, '2021-04-11', 'XYZ', 2000);
Declare #shipping_table Table (Ord_Num int, Pkg_num int, Pkg_weight int, Shipped_date date, Shipping_cost int)
Insert Into #shipping_table (Ord_Num, Pkg_num, Pkg_weight, Shipped_date, Shipping_cost)
Values (1111, 1, 30, '2021-03-12', 10)
, (1111, 2, 20, '2021-03-13', 8)
, (2222, 1, 15, '2021-04-12', 5)
, (2222, 2, 10, '2021-04-13', 3);
Select ord.Ord_num
, ord.Ord_date
, ord.Customer_name
, Order_total = iif(shp.Pkg_num = 1, ord.Order_total, 0)
, shp.Pkg_num
, shp.Shipped_date
, shp.Pkg_weight
, shp.Shipping_cost
From #order_table ord
Inner Join #shipping_table shp On shp.Ord_Num = ord.Ord_num;
Which can then be converted to this for totals:
Select ord.Ord_num
, ord.Ord_date
, ord.Customer_name
, Order_total = sum(iif(shp.Pkg_num = 1, ord.Order_total, 0))
, Pkg_weight = sum(shp.Pkg_num)
, Shipping_cost = sum(shp.Shipping_cost)
From #order_table ord
Inner Join #shipping_table shp On shp.Ord_Num = ord.Ord_num
Group By
ord.Ord_num
, ord.Ord_date
, ord.Customer_name;

Multiple rows in to single row in SQL Server if each column has only one value per employee

How to do this please help.
I have the following data. This is a data sample for week. I want single row per EmployeeName / CrewID (unless there are 2 values for a Employee in a single date).
If ORIGINAL DATA is like this :
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL NULL 178 NULL 9 Kanhaiya
NULL 174 173 172 171 NULL NULL 8 Santanu Maulik
NULL NULL NULL NULL NULL 178 NULL 8 Santanu Maulik
The OUTPUT of the above original should be in single row per person as below.
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 178 NULL 9 Kanhaiya
NULL 174 173 172 171 178 NULL 8 Santanu Maulik
but still the data may be spitted in multiple rows per employee for certain condition like if the employee has multiple values for a single date eg. '2018-08-10'
If ORIGINAL DATA is like this :
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- --------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL 163 178 NULL 9 Kanhaiya
The OUTPUT of the above original data should be like below.
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- --------------
NULL 174 173 172 171 178 NULL 9 Kanhaiya
NULL NULL NULL NULL 163 NULL NULL 9 Kanhaiya
I have managed to get the data with a stored procedure.
This is the main logic section of the stored procedure:
ALTER PROCEDURE [dbo].[GetDataForCustomWeekViewReport]
#Week AS INT,
#typeOfData AS VARCHAR(10)
AS
BEGIN
DECLARE #weekAdjustAdd INT
SET NOCOUNT ON;
SET DATEFIRST 7;
SET #weekAdjustAdd = (#Week - 1) * 7
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
-----------------------------
-- Get unique values of pivot column
SELECT
#PivotColumns= COALESCE(#PivotColumns + ',','') + QUOTENAME(WORKDAYS)
FROM
(SELECT DISTINCT WORKDAYS
FROM
(SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, -1) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 0) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 1) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 2) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 3) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 4) AS date)) AS WORKDAYS
UNION
SELECT
DATEADD(DAY, #weekAdjustAdd, CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 5) AS date)) AS WORKDAYS
) i) AS PivotExample
IF (#typeOfData = 'f')
BEGIN
--Get unique values of pivot column
--Create the dynamic query with all the values for
--pivot column at runtime
--',JOBID, JobInfo,CrewID, EmployeeName,Color
-- ,JI.INumber + '' - '' + JI.ITitle AS JobInfo, M.Color,
SET #SQLQuery =
N'
DECLARE #Week AS int
DECLARE #weekAdjustAdd int
SET #Week=1
SET DATEFIRST 7;
SET #weekAdjustAdd = (#Week - 1) * 7
SELECT ' + #PivotColumns + ', CrewID, EmployeeName
FROM (
SELECT J.ID AS JOBID, C.ID AS CrewID ,c.CrewName AS EmployeeName , JI.ID AS JOBINSTANCE_ID,JI.WORKDAYS
FROM [dbo].[Job] J
LEFT JOIN [dbo].[Job_Instances] JI ON J.ID=JI.JOBID
LEFT JOIN [dbo].[Instance_Employee_Relation] IER ON JI.ID=IER.JobInstanceID
LEFT JOIN [dbo].[Crew] C ON C.Id = IER.EMPLOYEEID
INNER JOIN [dbo].[Manager] M ON M.Id = JI.ManagerID
INNER JOIN dbo.JobType JT ON JT.ID = JI.JobTypeID
WHERE ( C.EmployeeTypeID=1 OR C.EmployeeTypeID IS NULL) AND JI.TYPE = ''F'' AND JI.WORKDAYS BETWEEN DATEADD(DAY,'+CONVERT(VARCHAR(20) ,#weekAdjustAdd)+', CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, -1) AS date)) AND DATEADD(DAY,'+ CONVERT(VARCHAR(20) ,#weekAdjustAdd)+', CAST(DATEADD(WEEK, DATEDIFF(DAY, -1, GETDATE()) / 7, 5) AS date))
)i
PIVOT( SUM(JobInstance_ID)
FOR [WORKDAYS] IN (' + #PivotColumns + ')) AS P ORDER BY CASE WHEN EmployeeName IS NULL THEN 1 ELSE 0 END, EmployeeName'
--Execute dynamic query
EXEC sp_executesql #SQLQuery
END
Here is the main schema section for the related tables.
UPDATE
#Ven tried to help me out & did a good job but his answer is working if i have only 2 rows per employee but if i have more than 2 rows per employee his solution is not working.
Original Data
2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11 CrewID EmployeeName
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
NULL 174 173 172 171 NULL NULL 9 Kanhaiya
NULL NULL NULL NULL NULL 178 NULL 9 Kanhaiya
NULL NULL NULL NULL 183 182 NULL 8 Santanu Maulik
NULL NULL NULL NULL NULL 178 NULL 8 Santanu Maulik
NULL 174 173 172 171 NULL NULL 8 Santanu Maulik
Output with #Ven's Solution (did not work For CrewID 8 as he has 3 Rows but worked for CrewID 9 as he has 2 Rows.)
CrewID EmployeeName 2018-08-05 2018-08-06 2018-08-07 2018-08-08 2018-08-09 2018-08-10 2018-08-11
-------- ---------------- ------------ ------------ ------------ ------------ ------------ ------------ ------------
8 Santanu Maulik NULL NULL NULL NULL NULL 178 NULL
8 Santanu Maulik NULL NULL NULL NULL 183 182 NULL
8 Santanu Maulik NULL 174 173 172 171 NULL NULL
9 Kanhaiya NULL 174 173 172 171 178 NULL
Work this out in 3 different steps to satisfy requirements. There is no other dirty way of doing this :)
1) Get previous row and next row in table by self join (left join)
2) Case expression to get value from next row where value is null in first row and non-null
in second row
2) case Expression if both rows are equal then null value in second row
DECLARE #table TABLE (
id INT identity(1, 1)
,[2018-08-05] INT
,[2018-08-06] INT
,[2018-08-07] INT
,[2018-08-08] INT
,[2018-08-09] INT
,[2018-08-10] INT
,[2018-08-11] INT
,CrewID INT
,EmployeeName VARCHAR(20)
)
INSERT #table
------------ ------------ ------------ ------------ ------------ ------------ ------------ -------- ----------------
SELECT NULL
,174
,173
,172
,171
,NULL
,NULL
,9
,'Kanhaiya'
UNION ALL
SELECT NULL
,NULL
,NULL
,NULL
,163
,178
,NULL
,9
,'Kanhaiya'
UNION ALL
SELECT NULL
,174
,173
,172
,171
,NULL
,NULL
,8
,'Santanu Maulik'
UNION ALL
SELECT NULL
,NULL
,NULL
,NULL
,NULL
,178
,NULL
,8
,'Santanu Maulik';
Script:
WITH CTE
AS (
SELECT rownum = ROW_NUMBER() OVER (
PARTITION BY p.crewid ORDER BY p.crewID
)
,p.*
FROM #table p
)
,ct2
AS (
SELECT TOP 100 PERCENT cte.CrewID
,cte.employeename
,CASE WHEN cte.[2018-08-05] IS NULL THEN nex.[2018-08-05] ELSE cte.[2018-08-05] END [2018-08-05]
,CASE WHEN cte.[2018-08-06] IS NULL THEN nex.[2018-08-06] ELSE cte.[2018-08-06] END [2018-08-06]
,CASE WHEN cte.[2018-08-07] IS NULL THEN nex.[2018-08-07] ELSE cte.[2018-08-07] END [2018-08-07]
,CASE WHEN cte.[2018-08-08] IS NULL THEN nex.[2018-08-08] ELSE cte.[2018-08-08] END [2018-08-08]
,CASE WHEN cte.[2018-08-09] IS NULL THEN nex.[2018-08-09] ELSE cte.[2018-08-09] END [2018-08-09]
,CASE WHEN cte.[2018-08-10] IS NULL THEN nex.[2018-08-10] ELSE cte.[2018-08-10] END [2018-08-10]
,CASE WHEN cte.[2018-08-11] IS NULL THEN nex.[2018-08-11] ELSE cte.[2018-08-11] END [2018-08-11]
FROM CTE
LEFT JOIN CTE prev ON prev.rownum = CTE.rownum - 1
LEFT JOIN CTE nex ON nex.rownum = CTE.rownum + 1
ORDER BY cte.CrewID
)
,ct3
AS (
SELECT DISTINCT CrewID
,EmployeeName
,CASE WHEN [2018-08-05] = LEAD([2018-08-05]) OVER (
PARTITION BY crewid ORDER BY [2018-08-05]
) THEN NULL ELSE [2018-08-05] END [2018-08-05]
,CASE WHEN [2018-08-06] = LEAD([2018-08-06]) OVER (
PARTITION BY crewid ORDER BY [2018-08-06]
) THEN NULL ELSE [2018-08-06] END [2018-08-06]
,CASE WHEN [2018-08-07] = LEAD([2018-08-07]) OVER (
PARTITION BY crewid ORDER BY [2018-08-07]
) THEN NULL ELSE [2018-08-07] END [2018-08-07]
,CASE WHEN [2018-08-08] = LEAD([2018-08-08]) OVER (
PARTITION BY crewid ORDER BY [2018-08-08]
) THEN NULL ELSE [2018-08-08] END [2018-08-08]
,CASE WHEN [2018-08-09] = LEAD([2018-08-09]) OVER (
PARTITION BY crewid ORDER BY [2018-08-09]
) THEN NULL ELSE [2018-08-09] END [2018-08-09]
,CASE WHEN [2018-08-10] = LEAD([2018-08-10]) OVER (
PARTITION BY crewid ORDER BY [2018-08-10]
) THEN NULL ELSE [2018-08-10] END [2018-08-10]
,CASE WHEN [2018-08-11] = LEAD([2018-08-11]) OVER (
PARTITION BY crewid ORDER BY [2018-08-11]
) THEN NULL ELSE [2018-08-11] END [2018-08-11]
FROM ct2
)
SELECT *
FROM ct3
WHERE isnull([2018-08-05], 0) + isnull([2018-08-06], 0) + isnull([2018-08-07], 0) + isnull([2018-08-08], 0) + isnull([2018-08-09], 0) + isnull([2018-08-10], 0) + isnull([2018-08-11], 0) > 0

Sql group by latest repeated field

I don't even know what's a good title for this question.
But I'm having a table:
create table trans
(
[transid] INT IDENTITY (1, 1) NOT NULL,
[customerid] int not null,
[points] decimal(10,2) not null,
[date] datetime not null
)
and records:
--cus1
INSERT INTO trans ( customerid , points , date )
VALUES ( 1, 10, '2016-01-01' ) , ( 1, 20, '2017-02-01' ) , ( 1, 22, '2017-03-01' ) ,
( 1, 24, '2018-02-01' ) , ( 1, 50, '2018-02-25' ) , ( 2, 44, '2016-02-01' ) ,
( 2, 20, '2017-02-01' ) , ( 2, 32, '2017-03-01' ) , ( 2, 15, '2018-02-01' ) ,
( 2, 10, '2018-02-25' ) , ( 3, 10, '2018-02-25' ) , ( 4, 44, '2015-02-01' ) ,
( 4, 20, '2015-03-01' ) , ( 4, 32, '2016-04-01' ) , ( 4, 15, '2016-05-01' ) ,
( 4, 10, '2017-02-25' ) , ( 4, 10, '2018-02-27' ) ,( 4, 20, '2018-02-28' ) ,
( 5, 44, '2015-02-01' ) , ( 5, 20, '2015-03-01' ) , ( 5, 32, '2016-04-01' ) ,
( 5, 15, '2016-05-01' ) ,( 5, 10, '2017-02-25' );
-- selecting the data
select * from trans
Produces:
transid customerid points date
----------- ----------- --------------------------------------- -----------------------
1 1 10.00 2016-01-01 00:00:00.000
2 1 20.00 2017-02-01 00:00:00.000
3 1 22.00 2017-03-01 00:00:00.000
4 1 24.00 2018-02-01 00:00:00.000
5 1 50.00 2018-02-25 00:00:00.000
6 2 44.00 2016-02-01 00:00:00.000
7 2 20.00 2017-02-01 00:00:00.000
8 2 32.00 2017-03-01 00:00:00.000
9 2 15.00 2018-02-01 00:00:00.000
10 2 10.00 2018-02-25 00:00:00.000
11 3 10.00 2018-02-25 00:00:00.000
12 4 44.00 2015-02-01 00:00:00.000
13 4 20.00 2015-03-01 00:00:00.000
14 4 32.00 2016-04-01 00:00:00.000
15 4 15.00 2016-05-01 00:00:00.000
16 4 10.00 2017-02-25 00:00:00.000
17 4 10.00 2018-02-27 00:00:00.000
18 4 20.00 2018-02-28 00:00:00.000
19 5 44.00 2015-02-01 00:00:00.000
20 5 20.00 2015-03-01 00:00:00.000
21 5 32.00 2016-04-01 00:00:00.000
22 5 15.00 2016-05-01 00:00:00.000
23 5 10.00 2017-02-25 00:00:00.000
I'm trying to group all the customerid and sum their points. But here's the catch, If the trans is not active for 1 year(the next tran is 1 year and above), the points will be expired.
For this case:
Points for each customers should be:
Customer1 20+22+24+50
Customer2 20+32+15+10
Customer3 10
Customer4 10+20
Customer5 0
Here's what I have so far:
select
t1.transid as transid1,
t1.customerid as customerid1,
t1.date as date1,
t1.points as points1,
t1.rank1 as rank1,
t2.transid as transid2,
t2.customerid as customerid2,
t2.points as points2,
isnull(t2.date,getUTCDate()) as date2,
isnull(t2.rank2,t1.rank1+1) as rank2,
cast(case when(t1.date > dateadd(year,-1,isnull(t2.date,getUTCDate()))) Then 0 ELSE 1 END as bit) as ShouldExpire
from
(
select transid,CustomerID,Date,points,
RANK() OVER(PARTITION BY CustomerID ORDER BY date ASC) AS RANK1
from trans
)t1
left join
(
select transid,CustomerID,Date,points,
RANK() OVER(PARTITION BY CustomerID ORDER BY date ASC) AS RANK2
from trans
)t2 on t1.RANK1=t2.RANK2-1
and t1.customerid=t2.customerid
which gives
from the above table,how do I check for ShouldExpire field having max(rank1) for customer, if it's 1, then totalpoints will be 0, otherwise,sum all the consecutive 0's until there are no more records or a 1 is met?
Or is there a better approach to this problem?
The following query uses LEAD to get the date of the next record withing the same CustomerID slice:
;WITH CTE AS (
SELECT transid, CustomerID, [Date], points,
LEAD([Date]) OVER (PARTITION BY CustomerID
ORDER BY date ASC) AS nextDate,
CASE
WHEN [date] > DATEADD(YEAR,
-1,
-- same LEAD() here as above
ISNULL(LEAD([Date]) OVER (PARTITION BY CustomerID
ORDER BY date ASC),
getUTCDate()))
THEN 0
ELSE 1
END AS ShouldExpire
FROM trans
)
SELECT transid, CustomerID, [Date], points, nextDate, ShouldExpire
FROM CTE
ORDER BY CustomerID, [Date]
Output:
transid CustomerID Date points nextDate ShouldExpire
-------------------------------------------------------------
1 1 2016-01-01 10.00 2017-02-01 1 <-- last exp. for 1
2 1 2017-02-01 20.00 2017-03-01 0
3 1 2017-03-01 22.00 2018-02-01 0
4 1 2018-02-01 24.00 2018-02-25 0
5 1 2018-02-25 50.00 NULL 0
6 2 2016-02-01 44.00 2017-02-01 1 <-- last exp. for 2
7 2 2017-02-01 20.00 2017-03-01 0
8 2 2017-03-01 32.00 2018-02-01 0
9 2 2018-02-01 15.00 2018-02-25 0
10 2 2018-02-25 10.00 NULL 0
11 3 2018-02-25 10.00 NULL 0 <-- no exp. for 3
12 4 2015-02-01 44.00 2015-03-01 0
13 4 2015-03-01 20.00 2016-04-01 1
14 4 2016-04-01 32.00 2016-05-01 0
15 4 2016-05-01 15.00 2017-02-25 0
16 4 2017-02-25 10.00 2018-02-27 1 <-- last exp. for 4
17 4 2018-02-27 10.00 2018-02-28 0
18 4 2018-02-28 20.00 NULL 0
19 5 2015-02-01 44.00 2015-03-01 0
20 5 2015-03-01 20.00 2016-04-01 1
21 5 2016-04-01 32.00 2016-05-01 0
22 5 2016-05-01 15.00 2017-02-25 0
23 5 2017-02-25 10.00 NULL 1 <-- last exp. for 5
Now, you seem to want to calculate the sum of points after the last expiration.
Using the above CTE as a basis you can achieve the required result with:
;WITH CTE AS (
... above query here ...
)
SELECT CustomerID,
SUM(CASE WHEN rnk = 0 THEN points ELSE 0 END) AS sumOfPoints
FROM (
SELECT transid, CustomerID, [Date], points, nextDate, ShouldExpire,
SUM(ShouldExpire) OVER (PARTITION BY CustomerID ORDER BY [Date] DESC) AS rnk
FROM CTE
) AS t
GROUP BY CustomerID
Output:
CustomerID sumOfPoints
-----------------------
1 116.00
2 77.00
3 10.00
4 30.00
5 0.00
Demo here
The tricky part here is to dump all points when they expire, and start accumulating them again. I assumed that if there was only one transaction that we don't expire the points until there's a new transaction, even if that first transaction was over a year ago now?
I also get a different answer for customer #5, as they do appear to have a "transaction chain" that hasn't expired?
Here's my query:
WITH ordered AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY [date]) AS order_id
FROM
trans),
max_transid AS (
SELECT
customerid,
MAX(transid) AS max_transid
FROM
trans
GROUP BY
customerid),
not_expired AS (
SELECT
t1.customerid,
t1.points,
t1.[date] AS t1_date,
CASE
WHEN m.customerid IS NOT NULL THEN GETDATE()
ELSE t2.[date]
END AS t2_date
FROM
ordered t1
LEFT JOIN ordered t2 ON t2.customerid = t1.customerid AND t1.transid != t2.transid AND t2.order_id = t1.order_id + 1 AND t1.[date] > DATEADD(YEAR, -1, t2.[date])
LEFT JOIN max_transid m ON m.customerid = t1.customerid AND m.max_transid = t1.transid
),
max_not_expired AS (
SELECT
customerid,
MAX(t1_date) AS max_expired
FROM
not_expired
WHERE
t2_date IS NULL
GROUP BY
customerid)
SELECT
n.customerid,
SUM(n.points) AS points
FROM
not_expired n
LEFT JOIN max_not_expired m ON m.customerid = n.customerid
WHERE
ISNULL(m.max_expired, '19000101') < n.t1_date
GROUP BY
n.customerid;
It could be refactored to be simpler, but I wanted to show the steps to get to the final answer:
customerid points
1 116.00
2 77.00
3 10.00
4 30.00
5 57.00
can you try this:
SELECT customerid,
Sum(t1.points)
FROM trans t1
WHERE NOT EXISTS (SELECT 1
FROM trans t2
WHERE Datediff(year, t1.date, t2.date) >= 1)
GROUP BY t1.customerid
Hope it helps!
try this:
select customerid,Sum(points)
from trans where Datediff(year, date, GETDATE()) < 1
group by customerid
output:
customerid Points
1 - 74.00
2 - 25.00
3 - 10.00
4 - 30.00

SQL query to calculate interval discount

I have trouble understanding how I can solve this problem with a T-SQL query.
I have a price column and a volume column. In another table a have discounts at different levels of volume. So my discount table could have values as
(StartLevel, DiscountFactor)
(0, 1);
(25, 0.95);
(50, 0.90);
(100, 0.75)
What I want is to calculate a total price. If Volume is 35, I want it to multiply
Price x ((35-25) x 0.95 + (25-0) x 1)
If the volume is 200, it should be
Price x ((200-100) x 0.75 + (100-50) x .9+(50-25) x .95+(25) x 1)
Can anybody help me with a query that solves this?
This can help:
DECLARE #products TABLE
(
id INT ,
price MONEY ,
volume INT
)
DECLARE #discounts TABLE
(
id INT ,
Level INT ,
Factor MONEY
)
INSERT INTO #products
VALUES ( 1, 10, 35 ),
( 2, 15, 200 )
INSERT INTO #discounts
VALUES ( 1, 0, 1 ),
( 2, 25, 0.95 ),
( 3, 50, 0.90 ),
( 4, 100, 0.75 )
SELECT p.id, p.price * SUM(ca.m)
FROM #products p
CROSS APPLY ( SELECT * ,
Factor * ( -Level + LEAD(Level) OVER ( PARTITION BY p.id ORDER BY Level, d ) ) AS m
FROM ( SELECT 1 AS d ,
Level ,
Factor
FROM #discounts
WHERE Level < p.volume
UNION ALL
SELECT 2 AS d ,
p.volume ,
0
) t
) ca
GROUP BY p.id, p.price
Without grouping it returns:
id price volume d Level Factor m
1 10.00 35 1 0 1.00 25.00
1 10.00 35 1 25 0.95 9.50
1 10.00 35 2 35 0.00 NULL
2 15.00 200 1 0 1.00 25.00
2 15.00 200 1 25 0.95 23.75
2 15.00 200 1 50 0.90 45.00
2 15.00 200 1 100 0.75 75.00
2 15.00 200 2 200 0.00 NULL
Then just group by product and sum of m results in:
id Total
1 345.00
2 2531.25
For a given Volume and Price you can get the discount based on interval using LEAD which is available in SQL Server 2012+ onwards.
Sample Data
DECLARE #PriceTable TABLE(Volume INT,Price DECIMAL(9,2) )
DECLARE #Discount TABLE(StartLevel int, DiscountFactor DECIMAL(9,2))
INSERT INTO #PriceTable
VALUES(75, 20.5),
(150, 20),
(250, 20.5),
(0, 15);
INSERT INTO #Discount
VALUES(0, 1),
(25, 0.95),
(50, 0.90),
(100, 0.75);
Query
SELECT Volume,Price,FinalPrice
FROM #PriceTable P
CROSS APPLY(
SELECT SUM(CASE WHEN (MaxLevel >=StartLevel) THEN (MaxLevel-StartLevel) ELSE 0 END *DiscountFactor)*P.Price as FinalPrice
FROM
(
SELECT CASE WHEN LEAD(StartLevel)OVER(ORDER BY StartLevel) < P.Volume THEN LEAD(StartLevel)OVER(ORDER BY StartLevel) ELSE P.Volume END MaxLevel,StartLevel, DiscountFactor
FROM #Discount
) IQ
)T
Output
Volume Price FinalPrice
75 20.50 1460.6250
150 20.00 2625.0000
250 20.50 4228.1250
0 15.00 0.0000

SQL Server 2012 Pivot to Count Types of Data?

I have a result set that looks like the below. Can anyone show me how to "pivot" this , so
datadate , timestart , timeend , datatype , datacount , datasum
2013-06-03 , 20:00:00.0000000 , 21:00:00.0000000 , 10 , 3 , 30
2013-06-03 , 20:00:00.0000000 , 21:00:00.0000000 , 20 , 3 , 30
2013-06-03 , 20:00:00.0000000 , 21:00:00.0000000 , 30 , 3 , 30
2013-06-03 , 19:00:00.0000000 , 20:00:00.0000000 , 10 , 2 , 20
can turn into this
date , timestart timeend , type10count , type10sum , type20count , type20sum , type30count , type30sum
2013-06-03 , 20:00:00.0000000 , 21:00:00.0000000 , 3 , 30 , 3 , 30 , 3 , 30
2013-06-03 , 19:00:00.0000000 , 20:00:00.0000000 , 2 , 20 , 0 , 0 , 0 , 0
I was trying to make a PIVOT to avoid a CASE statement and redefining a new table but am coming up short.
How should this be done?
declare #starttable table
(
datadate date , timestart time , timeend time , datatype tinyint , datacount int , datasum int
)
insert into #starttable
select '2013-06-03' , '20:00:00' , '21:00:00' , 10 , 3 , 30
union all
select '2013-06-03' , '20:00:00' , '21:00:00' , 20 , 3 , 30
union all
select '2013-06-03' , '20:00:00' , '21:00:00' , 30 , 3 , 30
union all
select '2013-06-03' , '19:00:00' , '20:00:00' , 10 , 2 , 20
select datadate , timestart , timeend
from ( select datadate , timestart , timeend , datacount ,datasum from #starttable ) as t1
pivot ( sum(datasum) for datatype in (datacount,datasum) ) as t2
-- yes i know sql server gongs this
Here is pivot syntax that works:
select datadate, timestart, timeend, [10], [20], [30]
from ( select datadate , timestart, timeend, datatype, datacount, datasum
from #starttable
) t1
pivot (sum(datasum) for [datatype] in ([10], [20], [30])
) t2;
The first thing you need is datatype in the inner subquery. The second thing is the list of values in the pivot statement.
To get both the count and the sum in the same query, you need to "align" the data:
select datadate, timestart, timeend, [10sum], [10count], [20count], [20sum], [30count], [30sum]
from (select datadate, timestart, timeend, cast(datatype as varchar(255))+'count' as datatype, datacount as thedata
from starttable
union all
select datadate, timestart, timeend, cast(datatype as varchar(255))+'sum' as datatype, datasum as thedata
from starttable
) t
pivot (sum(thedata) for [datatype] in ([10sum], [10count], [20count], [20sum], [30count], [30sum])
) t2
In order to get the result that you want you will have to look at unpivoting the datacount and datasum columns first, then apply the pivot function.
The unpivot of the data will take the multiple columns and return multiple rows which will allow for the data rotation with the datatype values much easier. Since you are using SQL Server 2012 you can easily unpivot the data using the UNPIVOT function or you can use CROSS APPLY with a VALUES clause.
select t.datadate, t.timestart, t.timeend,
'type'+cast(t.datatype as varchar(2))+ replace(c.col, 'data', '') as col, c.value
from starttable t
cross apply
(
values ('datacount', datacount), ('datasum', datasum)
) c (col, value);
See Demo. This will give a result that include multiple columns and it also has a new computed column that contains the values that will be pivoted on:
| DATADATE | TIMESTART | TIMEEND | COL | VALUE |
--------------------------------------------------------------------------
| 2013-06-03 | 20:00:00.0000000 | 21:00:00.0000000 | type10count | 3 |
| 2013-06-03 | 20:00:00.0000000 | 21:00:00.0000000 | type10sum | 30 |
| 2013-06-03 | 20:00:00.0000000 | 21:00:00.0000000 | type20count | 3 |
| 2013-06-03 | 20:00:00.0000000 | 21:00:00.0000000 | type20sum | 30 |
Then you apply a PIVOT to this result:
select datadate, timestart, timeend,
type10count, type10sum, type20count, type20sum, type30count, type30sum
from
(
select t.datadate, t.timestart, t.timeend,
'type'+cast(t.datatype as varchar(2))+ replace(c.col, 'data', '') as col, c.value
from starttable t
cross apply
(
values ('datacount', datacount), ('datasum', datasum)
) c (col, value)
) d
pivot
(
sum(value)
for col in (type10count, type10sum, type20count, type20sum, type30count, type30sum)
) piv;
See SQL Fiddle with Demo
Now if you have an unknown number of datatype values, then you could use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME('type'+cast(t.datatype as varchar(2))+c.col)
from starttable t
cross apply
(
values ('count', 1), ('sum', 2)
) c (col, so)
group by t.datatype, c.col, c.so
order by t.datatype, c.so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT ',isNull(' + QUOTENAME('type'+cast(t.datatype as varchar(2))+c.col)
+', 0) as '+QUOTENAME('type'+cast(t.datatype as varchar(2))+c.col)
from starttable t
cross apply
(
values ('count', 1), ('sum', 2)
) c (col, so)
group by t.datatype, c.col, c.so
order by t.datatype, c.so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT datadate, timestart, timeend,' + #colsNull + '
from
(
select t.datadate, t.timestart, t.timeend,
''type''+cast(t.datatype as varchar(2))+ replace(c.col, ''data'', '''') as col, c.value
from starttable t
cross apply
(
values (''datacount'', datacount), (''datasum'', datasum)
) c (col, value)
) d
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both versions give a result:
| DATADATE | TIMESTART | TIMEEND | TYPE10COUNT | TYPE10SUM | TYPE20COUNT | TYPE20SUM | TYPE30COUNT | TYPE30SUM |
----------------------------------------------------------------------------------------------------------------------------------
| 2013-06-03 | 19:00:00.0000000 | 20:00:00.0000000 | 2 | 20 | 0 | 0 | 0 | 0 |
| 2013-06-03 | 20:00:00.0000000 | 21:00:00.0000000 | 3 | 30 | 3 | 30 | 3 | 30 |