SQL Server - Calculating target vs actual values per week for sales overview - sql

I am new to SQL. I am trying to create an over view using these 2 tables
Actual sale
Week_Year
Unit 34_2020 35_2020 36_2020 37_2020
Unit 1 10 12 15 19
Unit 2 10 12 15 19
Unit 3 10 12 15 19
Target sale
Unit Total to be sold Start Date End Date
Unit 1 50 24-08-20 24-09-20
Unit 2 1000 18-01-20 01-01-21
Unit 3 1000 05-02-20 01-10-20
To combine into this resulting view with Targets and Actuals:
Unit 1 Unit 2 Unit 3
Week Target Actual Week Target Actual Week Target Actual
34_2020 11 10 3_2020 20 10 6_2020 20 10
35_2020 24 12 4_2020 40 12 7_2020 40 12
36_2020 36 15 5_2020 60 15 8_2020 60 15
37_2020 50 19 6_2020 80 19 9_2020 80 19
. 100 . . 100 .
36_2020 120 95 36_2020 700 650
37_2020 140 100 37_2020 800 700
. . 38_2020 .
. . 39_2020 .
. . 40_2020 1000
1_2021 1000
where column Target is 'Total to be sold' spread linearly between the available weeks.
How can I achieve this using SQL Server? Any inputs much appreciated. Thanks.

In order to make the week numbers (34, 35, 36, 37) correspond to system weeks the variable #start_wk_no sets the starting point. The actual sales needs to be unpivoted to join with projected sales. The query uses a tally (or numbers) function to generate the rows.
Data
drop table if exists dbo.test_actuals;
go
create table dbo.test_actuals(
Unit varchar(100) not null,
[34_2020] int not null,
[35_2020] int not null,
[36_2020] int not null,
[37_2020] int not null);
--select * from dbo.test_actuals
insert dbo.test_actuals values
('Unit 1', 10, 12, 15, 19),
('Unit 2', 10, 12, 15, 19),
('Unit 3', 10, 12, 15, 19);
drop table if exists dbo.test_target;
go
create table dbo.test_target(
Unit varchar(100) not null,
TotalToSell int not null,
StartDate date not null,
EndDate date not null)
insert dbo.test_target values
('Unit 1', 50, '08-24-2020', '09-24-2020'),
('Unit 2', 1000, '01-18-2020', '01-01-2021'),
('Unit 3', 1000, '02-05-2020', '10-01-20');
Query
/* based on system weeks, what is the start point */
declare
#start_wk_no int=6250;
;with unpvt_actuals_cte as (
select a.Unit, v.*
from
dbo.test_actuals a
cross apply
(values (34, [34_2020]), (35, [35_2020]), (36, [36_2020]), (36, [36_2020]), (37, [37_2020])) v([Week], act_sales))
select
t.Unit,
wd.wk_proj [Week],
isnull(act.act_sales, 0) [Actual],
TotalToSell/(wk_diff.wk_diff*1.0) [Target],
sum(TotalToSell/(wk_diff.wk_diff*1.0)) over (partition by t.Unit order by wd.wk_proj) Cum_Target
from
dbo.test_target t
cross apply
(select datediff(wk, t.StartDate, t.EndDate) wk_diff) wk_diff
cross apply
dbo.fnTally(0, wk_diff.wk_diff-1) f
cross apply
(select dateadd(wk, f.n, t.StartDate) wk_dt) wk
cross apply
(select datediff(week, 0, wk.wk_dt)-#start_wk_no wk_proj) wd
left join
unpvt_actuals_cte act on t.Unit=act.Unit
and wd.wk_proj=act.[Week];

Related

Calculate Date difference between multiple rows SQL

I need to calculate the date difference between multiple rows. The scenario is I have a vehicle that can do inspections throughout the month as well as when the vehicle is assigned to a different project. I want to calculate that how many days that a vehicle is assigned to the project per month or previous month. I have tried multiple ways and I can't get even closer. I am relatively new to stack overflow. Apologies if anything is missing. Please let me know if this can be done. Thank you.
All the columns are in one single table if that helps. Please let me know the query on how to achieve this
I am using SQL server 2017.
Original Data
Expected Output
I am not proud of this solution, but I think it works for you. My approach was to create a table of days and then look at which project the vehicle was assigned to each day. Finally, aggregate by month and year to get the results. I had to do this as a script since you can use aggregate functions in the definitions of recursive CTEs, but you may find a way to do this without needing a recursive CTE.
I created a table variable to import your data so I could write this. Note, I added an extra assignment to test assignments that spanned months.
DECLARE #Vehicles AS TABLE
(
[VehicleID] INT NOT NULL,
[Project] CHAR(2) NOT NULL,
[InspectionDate] DATE NOT NULL
);
INSERT INTO #Vehicles
(
[VehicleID],
[Project],
[InspectionDate]
)
VALUES
(1, 'P1', '2021-08-20'),
(1, 'P1', '2021-09-05'),
(1, 'P2', '2021-09-15'),
(1, 'P3', '2021-09-20'),
(1, 'P2', '2021-10-10'),
(1, 'P1', '2021-10-20'),
(1, 'P3', '2021-10-21'),
(1, 'P2', '2021-10-22'),
(1, 'P4', '2021-11-15'),
(1, 'P4', '2021-11-25'),
(1, 'P4', '2021-11-30'),
(1, 'P1', '2022-02-05');
DECLARE #StartDate AS DATE, #EndDate AS DATE;
SELECT #StartDate = MIN([InspectionDate]), #EndDate = MAX([InspectionDate])
FROM #Vehicles;
;WITH [seq]([n])
AS (SELECT 0 AS [n]
UNION ALL
SELECT [n] + 1
FROM [seq]
WHERE [n] < DATEDIFF(DAY, #StartDate, #EndDate)),
[days]
AS (SELECT DATEADD(DAY, [n], #StartDate) AS [d]
FROM [seq]),
[inspections]
AS (SELECT [VehicleID],
[Project],
[InspectionDate],
LEAD([InspectionDate], 1) OVER (PARTITION BY [VehicleID]
ORDER BY [InspectionDate]
) AS [NextInspectionDate]
FROM #Vehicles),
[assignmentsByDay]
AS (SELECT [d].[d], [i].[VehicleID], [i].[Project]
FROM [days] AS [d]
INNER JOIN [inspections] AS [i]
ON [d].[d] >= [i].[InspectionDate]
AND [d] < [i].[NextInspectionDate])
SELECT [assignmentsByDay].[VehicleID],
[assignmentsByDay].[Project],
MONTH([assignmentsByDay].[d]) AS [month],
YEAR([assignmentsByDay].[d]) AS [year],
COUNT(*) AS [daysAssigned]
FROM [assignmentsByDay]
GROUP BY [assignmentsByDay].[VehicleID],
[assignmentsByDay].[Project],
MONTH([assignmentsByDay].[d]),
YEAR([assignmentsByDay].[d])
ORDER BY [year], [month], [assignmentsByDay].[VehicleID], [assignmentsByDay].[Project]
OPTION(MAXRECURSION 0);
And the output is:
VehicleID
Project
month
year
daysAssigned
1
P1
8
2021
12
1
P1
9
2021
14
1
P2
9
2021
5
1
P3
9
2021
11
1
P1
10
2021
1
1
P2
10
2021
20
1
P3
10
2021
10
1
P2
11
2021
14
1
P4
11
2021
16
1
P4
12
2021
31
1
P4
1
2022
31
1
P4
2
2022
4
I think you are looking for this:
select vehicleId
, Project
, month(inspectiondate) month
, year(inspectiondate) year
, datediff(day , min(inspectiondate), case when max(inspectiondate) = min(inspectiondate) then eomonth(min(inspectiondate)) else max(inspectiondate) end) days
from Vehicles
group by vehicleId, Project , month(inspectiondate), year(inspectiondate)
This query in for each month/year for each specific vehicle in a project in that month/year , you get the max and min inspection date and calculate the difference.
db<>fiddle here

Gap analysis on recurrent value which is added once every month

I need little help on a challenge that I have to determine on which month I have a missing recurrent value record. In short, the story is the following: Let say that I have a table e.g.
customer_charge (id, charge_id, from_date, to_date , recurrent_value)
( 1, 10 , 01.01.2020, 31.01.2020, 15 )
( 2, 23 , 01.01.2020, 31.01.2020, 0 )
( 3, 10 , 01.02.2020, 29.02.2020, 0 )
( 4, 23 , 01.02.2020, 29.02.2020, 15 )
( 5, 10 , 01.03.2020, 31.03.2020, 0 )
( 6, 10 , 01.04.2020, 30.04.2020, 15 )
I have also another table details as
details (id, cust_id, charge_id, charge_date, recurrent_value, period )
( 1, 100 , 10 , 01.01.2020 03:12:10, 15 , 01.01.2020-31.01.2020)
( 2, 100 , 10 , 01.04.2020 02:54:12, 15 , 01.04.2020-30.04.2020)
( 3, 200 , 23 , 01.02.2020 02:22:25, 15 , 01.02.2020-29.02.2020)
Those recurrent values in customer_charge table are added once a month (following calendar length as period). There is trigger logic which checks the value if it's positive (value > 0) -> insert a new record in details table. The expected result is that, for every customer to see the missing recurrent values per month. So the result be something like:
cust_id, month, recurrent_value
100 , 'FEB', 0
100 , 'MAR', 0
200 , 'JAN', 0
There is a 3rd table which link the customer_charge.charge_id with details.cust_id, so the system knows for which customer is the respective recurrent_value

How to divide results into separate rows based on year?

I have a query that looks at profits and operations costs of different stores based on the fiscal year, and currently the fiscal years and variables are sorted into single, respective columns such as:
FiscalYear Metric Store Amount
2017 Profit A 220
2017 Cost A 180
2018 Profit B 200
2018 Cost B 300
...
I need to cross tab the rows so that for each store, I can compare the 2017 profit against the 2018 profit, and 2017 cost against the 2018 cost.
I broke out profits and costs by creating CASE WHEN statements for the ProfitLossTable, but I don't know how to make it create a "2017 Profit" and "2018 Profit" column, respectively, for each Store.
WITH [Profits, Cost] AS
(
SELECT ID, StoreID, Number, FYYearID,
CASE WHEN ID = 333 then Number END AS Profit
CASE WHEN ID = 555 then Number END AS Cost
FROM ProfitLossTable
),
Location AS
(
Select StoreID, StoreName
FROM StoreTable
),
FiscalMonth AS
(
SELECT FYYearID, FYYear
FROM FiscalMonthTable
)
SELECT A.Profit, A.Cost
FROM [Profits, Cost] A
JOIN Location B
ON A.StoreID = B.StoreID
JOIN FiscalMonth C
ON A.FYYearID = C.FYYearID
The code above shows this, and I feel like I am close to creating columns based on year, but I don't know what to do next.
FiscalYear Store Profit Cost
2017 A 220 100
2017 A 180 100
2018 B 200 100
2018 B 300 100
As a working (on my machine anyway ;-p) example using your data:
create table #temp(
FiscalYear int not null,
Metric nvarchar(50) not null,
Store nvarchar(10) not null,
Amount int not null
)
insert into #temp
values
(2017, N'Profit', N'A', 220),
(2017, N'Cost', N'A', 180),
(2018, N'Profit', N'B', 200),
(2018, N'Cost', N'B', 300)
select * from #temp
select Metric,
[2017] as [2017],
[2018] as [2018]
from (select FiscalYear, Amount, Metric from #temp) base_data
PIVOT
(SUM(Amount) FOR FiscalYear in ([2017], [2018])
) as pvt
order by pvt.Metric
drop table #temp

SQL First In First Out Loyalty Point

fellow developers and analysts. I have some experience in SQL and have resorted to similar posts. However, this is slightly more niche. Thank you in advance for helping.
I have the below dataset (edited. Apology)
Setup
CREATE TABLE CustomerPoints
(
CustomerID INT,
[Date] Date,
Points INT
)
INSERT INTO CustomerPoints
VALUES
(1, '20150101', 500),
(1, '20150201', -400),
(1, '20151101', 300),
(1, '20151201', -400)
and need to turn it into (edited. The figures in previous table were incorrect)
Any positive amount of points are points earned whereas negative are redeemed. Because of the FIFO (1st in 1st out concept), of the second batch of points spent (-400), 100 of those were taken from points earned on 20150101 (UK format) and 300 from 20151101.
The goal is to calculate, for each customer, the number of points spent within x and y months of earning. Again, thank you for your help.
I have already answered a similar question here and here
You need to explode points earned and redeemed by single units and then couple them, so each point earned will be matched by a redeemed point.
For each of these matching rows calculate the months elapsed from the earning to the redeeming and then aggregate it all.
For FN_NUMBERS(n) it is a tally table, look at other answers I have linked above.
;with
p as (select * from CustomerPoints),
e as (select * from p where points>0),
r as (select * from p where points<0),
ex as (
select *, ROW_NUMBER() over (partition by CustomerID order by [date] ) rn
from e
join FN_NUMBERS(1000) on N<= e.points
),
rx as (
select *, ROW_NUMBER() over (partition by CustomerID order by [date] ) rn
from r
join FN_NUMBERS(1000) on N<= -r.points
),
j as (
select ex.CustomerID, DATEDIFF(month,ex.date, rx.date) mm
from ex
join rx on ex.CustomerID = rx.CustomerID and ex.rn = rx.rn and rx.date>ex.date
)
-- use this select to see points redeemed in current and past semester
select * from j join (select 0 s union all select 1 s ) p on j.mm >= (p.s*6)+(p.s) and j.mm < p.s*6+6 pivot (count(mm) for s in ([0],[2])) p order by 1, 2
-- use this select to see points redeemed with months detail
--select * from j pivot (count(mm) for mm in ([0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])) p order by 1
-- use this select to see points redeemed in rows per month
--select CustomerID, mm, COUNT(mm) PointsRedeemed from j group by CustomerID, mm order by 1
output of default query, 0 is 0-6 months, 1 is 7-12 (age of redemption in months)
CustomerID 0 1
1 700 100
output of 2nd query, 0..12 is the age of redemption in months
CustomerID 0 1 2 3 4 5 6 7 8 9 10 11 12
1 0 700 0 0 0 0 0 0 0 0 0 100 0
output from 3rd query, is the age of redemption in months
CustomerID mm PointsRedeemed
1 1 700
1 11 100
bye

Calculate selling price to achieve at least 10% profit

I need to run a loop in an SQL query. My query looks at [PROFIT_MARGIN] to ensure the %PROFIT is above 10% (in this case).
But some figures are below 10 or produce a minus figure, so I need to increase the price until the [PROFIT_MARGIN] reaches a more favorable point.
SELECT [RRP], [COST], [PROFIT], ([PROFIT/RRP]*100) AS [PROFIT_MARGIN],
CASE WHEN ([PROFIT/RRP]*100) > 10
THEN RRP
****** OH NO THE PROFIT IS LESS THAN 10, WE NEED TO INCREASE THE RRP UNTIL SUCH A POINT THAT THE PROFIT_MARGIN is EQUAL to 10 THEN SET THE PRICE TO THAT NEW FIGURE ******
END AS [PRICE]
FROM SOME_TABLE
Some Sample Data
RRP Cost Profit Profit Margin
25 8 17 68
5.95 7.08 -1.13 -18.9915966387
17 13.02 3.98 23.4117647059
1.85 4.57 -2.72 -147.027027027
2.3 4.74 -2.44 -106.0869565217
2.65 5.02 -2.37 -89.4339622642
My guess is that I need to artificially increase the [RRP] until such a point that the end [PROFIT_MARGIN] column hits 10%.
My approach, might be wrong in the requirement for the loop, I'm no SQL Pro.
Please, read my comment to the question and see the solution provided by Tanner.
So, if you want to update RRP, use UPDATE statement:
UPDATE t1 SET [RRP] = t2.[COST] *1.1
FROM tableName t1 INNER JOIN tableName t2 ON t1.PKey = t2.PKey
WHERE (t1.[RRP]/t1.[COST]) < 1.1
Thanks Tanner, for your valuable comment ;)
You don't need to use a loop for something of this nature.
Your basic check requires that profit should be at least 10%, so you simply have to check if the profit margin is < 10%, and increase the selling price otherwise.
Here's a sample with the above data that you can execute as is:
CREATE TABLE #SOME_TABLE
(
[RRP] NUMERIC ,
[Cost] NUMERIC ,
[Profit] NUMERIC ,
[Profit_Margin] NUMERIC
);
INSERT INTO #SOME_TABLE
( [RRP], [Cost], [Profit], [Profit_Margin] )
VALUES ( 25, 8, 17, 68 ),
( 5.95, 7.08, -1.13, -18.9915966387 ),
( 17, 13.02, 3.98, 23.4117647059 ),
( 1.85, 4.57, -2.72, -147.027027027 ),
( 2.3, 4.74, -2.44, -106.0869565217 ),
( 2.65, 5.02, -2.37, -89.4339622642 );
DECLARE #MinProfitMargin INT = 10
SELECT RRP ,
Cost ,
Profit ,
Profit_Margin ,
CASE WHEN Profit_Margin < #MinProfitMargin
THEN Cost * 1.1
ELSE RRP
END AS RRPor10PercentProfit
FROM #SOME_TABLE
DROP TABLE #SOME_TABLE
The Output:
RRP Cost Profit Profit_Margin RRPor10PercentProfit
25 8 17 68 25.0
6 7 -1 -19 7.7
17 13 4 23 17.0
2 5 -3 -147 5.5
2 5 -2 -106 5.5
3 5 -2 -89 5.5
The final column RRPor10PercentProfit shows you the current RRP if it achieves at least 10% profit, otherwise it shows you the value required to achieve 10% profit. This is calculated in this section of the code:
-- is the Profit_Margin less than my variable (10%)
CASE WHEN Profit_Margin < #MinProfitMargin
-- if so add 10% to cost price
THEN Cost * 1.1
-- otherwise give me the RRP
ELSE RRP
END AS RRPor10PercentProfit