T-SQL Override special rates and generate final date range - sql

I have transaction table which has date range and basic rate for the range. I have another table for special rate which has date range for special rate and its rate. I would like to split my original transaction in multiple records if special rates falls in transaction date range.
Just for simplicity I have created two tables with limited columns
DECLARE #ClientTrx AS TABLE (ClientId int, StartDate Date, EndDate Date, Rate decimal(10,2))
DECLARE #SpecialRate AS TABLE (ClientId int, StartDate Date, EndDate Date, Rate decimal(10,2))
insert into #ClientTrx select 1, '1/1/2020', '1/15/2020', 10
insert into #ClientTrx select 1, '1/16/2020', '1/31/2020', 10
insert into #ClientTrx select 2, '1/1/2020', '1/15/2020', 20
insert into #ClientTrx select 2, '1/16/2020', '1/31/2020', 20
insert into #ClientTrx select 2, '2/1/2020', '2/13/2020', 20
insert into #SpecialRate select 1, '12/25/2019', '1/3/2020', 13
insert into #SpecialRate select 1, '1/4/2020', '1/6/2020', 15
insert into #SpecialRate select 1, '1/11/2020', '1/18/2020', 12
insert into #SpecialRate select 2, '1/25/2020', '1/31/2020', 23
insert into #SpecialRate select 2, '2/4/2020', '2/8/2020', 25
insert into #SpecialRate select 2, '2/11/2020', '2/29/2020', 22
I need help write a query which produce following results:
ClientId StartDate EndDate Rate
1 2020-01-01 2020-01-03 13.00 special rate
1 2020-01-04 2020-01-06 15.00 special rate
1 2020-01-07 2020-01-10 10.00 regular rate
1 2020-01-11 2020-01-15 12.00 special rate
1 2020-01-16 2020-01-18 12.00 special rate splitting pay period
1 2020-01-19 2020-01-31 10.00 regular rate
2 2020-01-01 2020-01-15 20.00 regular rate
2 2020-01-16 2020-01-24 20.00 regular rate
2 2020-01-25 2020-01-31 23.00 special rate
2 2020-02-01 2020-02-03 20.00 regular rate
2 2020-02-04 2020-02-08 25.00 special rate
2 2020-02-09 2020-02-10 20.00 regular rate
2 2020-02-11 2020-02-13 22.00 special rate
I think using CTE its possible but I can't figure it out. can anyone please help?
Note: I have made some changes in my input and expected output, i think I need one more group level, can you please help?

This is an approach which uses and ad-hoc tally table to expand the datasets and then applies a Gaps-and-Islands for the final summary
Example
;with cte as (
Select A.ClientId
,D
,Rate = coalesce(NewRate,A.Rate)
,Grp = datediff(day,'1900-01-01',D) - row_number() over (partition by ClientID,coalesce(NewRate,A.Rate) Order by D)
From #ClientTrx A
Cross Apply (
Select Top (DateDiff(DAY,StartDate,EndDate)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),StartDate)
From master..spt_values n1,master..spt_values n2
) B
Outer Apply (
Select NewRate=Rate
From #SpecialRate
Where D between StartDate and EndDate
and ClientId=A.ClientID
) C
)
Select ClientID
,StartDate= min(D)
,EndDate = max(D)
,Rate = Rate
From cte
Group By ClientID,Grp,Rate
Order by ClientID,min(D)
Returns
ClientID StartDate EndDate Rate
1 2020-01-01 2020-01-03 13.00
1 2020-01-04 2020-01-06 15.00
1 2020-01-07 2020-01-10 10.00
1 2020-01-11 2020-01-18 12.00
1 2020-01-19 2020-01-31 10.00
2 2020-01-01 2020-01-24 20.00
2 2020-01-25 2020-01-31 23.00
2 2020-02-01 2020-02-03 20.00
2 2020-02-04 2020-02-08 25.00
2 2020-02-09 2020-02-10 20.00
2 2020-02-11 2020-02-15 22.00
Notes:
Cross Apply B generates a record for each date between startDate and endDate in #ClientTrx.
Outer Apply C attempts to find the Exception or NewRate
the CTE generates one record per date and toggles the default or exception rate. It looks like this
Notice how GRP changes. This is a simple technique to "feed" the Gaps-and-Islands
Then is becomes a small matter to group the results from cte by ClientID and Grp

Related

T-SQL - adding more date values to a table all rows

I have a table that contains many IDs as random numbers and I would like to create another table that contains all the IDs with another column having dates from first day of the year until a specific date.
For example: My actual table looks like:
ID
101
431
566
And I would like to have a table if my specific end date is 2020-01-03 that looks like:
ID Date
101 2020-01-01
101 2020-01-02
101 2020-01-03
431 2020-01-01
431 2020-01-02
431 2020-01-03
566 2020-01-01
566 2020-01-02
566 2020-01-03
Could you help me to solve my issue? Thanks in advance!
You can use a recursive CTE to define the dates and then cross join:
with dates as (
select convert(date, '2020-01-01') as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < #enddate
)
select t.id, dates.date
from t cross join
dates
option (maxrecursion 0);

Add remaining value to next rows in sql server

I have table, as below and its contains customer electricity volume for the period as.Available data like
OwnerID StartDate EndDate Volume
1 2019-01-01 2019-01-15 10.40
1 2019-01-16 2019-01-31 5.80
1 2019-02-01 2019-02-10 7.90
1 2019-02-11 2019-02-28 8.50
2 2019-03-01 2019-03-04 10.50
And another table having their existing remaining volume. Both table are connected with Column OwnerID
OwnerID ExistingVolume
1 0.90
2 0.60
Now add (apply) the ExistingVolume with current Volume (first table) as
Calculate the new volume as whole numer and remaining decimal value add to next period to the customer.
So expected result set should like,
OwnerId StartDate EndDate CalulatedVolume RemainingExistingVolume
1 2019-01-01 2019-01-15 11 0.30
1 2019-01-16 2019-01-31 6 0.10
1 2019-02-01 2019-02-10 8 0.00
1 2019-02-11 2019-02-28 8 0.50
2 2019-03-01 2019-03-04 11 0.10
Don't round off the CalulatedVolume. Just get the whole when add the table1.Volume + table2.ExistingVolume.
And Remaining decimal value (from 1st row) should be applied the next row value table1.Volume
Could you someone suggest how to achieve this is in SQL query?
If I understand correctly, you want to accumulative the "error" from rounding and apply that against the value in the second table.
You can use a cumulative sum for this purpose -- along with some arithmetic:
select t1.ownerid, t1.startdate, t1.enddate,
round(t1.volume, 0) as calculatedvolume,
( sum( t1.volume - round(t1.volume, 0) ) over (partition by t1.ownerid order by t1.startdate) +
t2.existingvolume
) as remainingexisting
from table1 t1 left join
table2 t2
on t1.ownerid = t2.ownerid;
You have a non-standard definition of rounding. This can be implemented as ceil(x - 0.5). With this definition, the code is:
select t1.ownerid, t1.startdate, t1.enddate,
ceiling(t1.volume - 0.5) as calculatedvolume,
( sum( t1.volume - ceiling(t1.volume - 0.5) ) over (partition by t1.ownerid order by t1.startdate) +
t2.existingvolume
) as remainingexisting
from table1 t1 left join
table2 t2
on t1.ownerid = t2.ownerid;
Here is a db<>fiddle.

How duplicate a rows in SQL base on difference between date columns and divided aggregated column per duplicate row?

I have a table with some records about fuel consumption. The important columns in the table are: CONSUME_DATE_FROM and CONSUM_DATE_TO.
I want to calculate average fuel consumption per cars on a monthly basis but some rows are not in the same month. For example some have a three month difference between them and the total of gas per litre is aggregated in a single row.
Now I should find records that have difference more than a month between CONSUME_DATE_FROM and CONSUM_DATE_TO, and duplicate them in current or second table per count of month and divide the total gas per litre between related rows.
I've this table with the following data:
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER
1 100 2018-10-25 2018-12-01 600
2 101 2018-07-19 2018-07-24 100
3 102 2018-12-31 2019-01-01 400
4 103 2018-03-29 2018-05-29 200
5 104 2018-02-05 2018-02-09 50
The expected output table should be as below
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER
1 100 2018-10-25 2018-12-01 200
1 100 2018-10-25 2018-12-01 200
1 100 2018-10-25 2018-12-01 200
2 101 2018-07-19 2018-07-24 100
3 102 2018-12-31 2019-01-01 200
3 102 2018-12-31 2019-01-01 200
4 103 2018-03-29 2018-05-29 66.66
4 103 2018-03-29 2018-05-29 66.66
4 103 2018-03-29 2018-05-29 66.66
5 104 2018-02-05 2018-02-09 50
Or as below
ID VehicleId CONSUME_DATE_FROM CONSUM_DATE_TO GAS_PER_LITER DATE_RELOAD_GAS
1 100 2018-10-25 2018-12-01 200 2018-10-01
1 100 2018-10-25 2018-12-01 200 2018-11-01
1 100 2018-10-25 2018-12-01 200 2018-12-01
2 101 2018-07-19 2018-07-24 100 2018-07-01
3 102 2018-12-31 2019-01-01 200 2018-12-01
3 102 2018-12-31 2019-01-01 200 2019-01-01
4 103 2018-03-29 2018-05-29 66.66 2018-03-01
4 103 2018-03-29 2018-05-29 66.66 2018-04-01
4 103 2018-03-29 2018-05-29 66.66 2018-05-01
5 104 2018-02-05 2018-02-09 50 2018-02-01
Can someone please help me out with this query?
I'm using oracle database
Your business rule treats the difference between CONSUME_DATE_FROM and CONSUM_DATE_TO as absolute months. So you expect the difference between 2018-10-25 and 2018-12-01 to be three months whereas the difference in days actually equates to about 1.1 months. So we can't use simple date arithmetic to get your desired output, we need to do some additional massaging of the dates.
The query below implements your desired logic by deriving the first day of the month for CONSUME_DATE_FROM and the last day of the month for CONSUME_DATE_TO, then using ceil() to round the difference up to the nearest whole number of months.
This is calculated in a subquery which is used in the main query with the old connect by level trick to multiply a record by level number of times:
with cte as (
select f.*
, ceil(months_between(last_day(CONSUM_DATE_TO)
, trunc(CONSUME_DATE_FROM,'mm'))) as diff
from fuel_consumption f
)
select cte.id
, cte.VehicleId
, cte.CONSUME_DATE_FROM
, cte.CONSUM_DATE_TO
, cte.GAS_PER_LITER/cte.diff as GAS_PER_LITER
, add_months(trunc(cte.CONSUME_DATE_FROM, 'mm'), level-1) as DATE_RELOAD_GAS
from cte
connect by level <= cte.diff
and prior cte.id = cte.id
and prior sys_guid() is not null
;
"what about if add a additional column "DATE_RELOAD_GAS" that display difference date for similar rows"
From your posted sample it seems like DATE_RELOAD_GAS is the first day of the month for each month bounded by CONSUME_DATE_FROM and CONSUM_DATE_TO. I have amended my solution to implement this rule.
By using connect by level structure with considering to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') as month I was able to resolve as below :
select ID, VehicleId, myMonth, CONSUME_DATE_FROM, CONSUM_DATE_TO,
trunc(GAS_PER_LITER/max(rn) over (partition by ID order by ID),2) as GAS_PER_LITER,
'01.'||substr(myMonth,5,2)||'.'||substr(myMonth,1,4) as DATE_RELOAD_GAS
from
(
with consumption( ID, VehicleId, CONSUME_DATE_FROM, CONSUM_DATE_TO, GAS_PER_LITER ) as
(
select 1,100,date'2018-10-25',date'2018-12-01',600 from dual union all
select 2,101,date'2018-07-19',date'2018-07-24',100 from dual union all
select 3,102,date'2018-12-31',date'2019-01-01',400 from dual union all
select 4,103,date'2018-03-29',date'2018-05-29',200 from dual union all
select 5,104,date'2018-02-05',date'2018-02-09', 50 from dual
)
select ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') myMonth,
VehicleId, c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, GAS_PER_LITER,
row_number() over (partition by ID order by ID) as rn
from dual join consumption c
on c.ID >= 2
group by ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm'), VehicleId,
c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, c.GAS_PER_LITER
connect by level <= c.CONSUM_DATE_TO - c.CONSUME_DATE_FROM + 1
union all
select ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm') myMonth,
VehicleId, c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, GAS_PER_LITER,
row_number() over (partition by ID order by ID) as rn
from dual join consumption c
on c.ID = 1
group by ID, to_char(c.CONSUME_DATE_FROM + level - 1,'yyyymm'), VehicleId,
c.CONSUME_DATE_FROM, c.CONSUM_DATE_TO, c.GAS_PER_LITER
connect by level <= c.CONSUM_DATE_TO - c.CONSUME_DATE_FROM + 1
) q
group by ID, VehicleId, myMonth, CONSUME_DATE_FROM, CONSUM_DATE_TO, GAS_PER_LITER, rn
order by ID, myMonth;
I met an interesting issue that if I consider the join condition in the subquery as c.ID >= 1 query hangs on for huge period of time, so splitted into two parts by union all
as c.ID >= 2 and c.ID = 1
Rextester Demo

Using Over(Partition By) when calculating median moving average unit cost

Good morning,
I am trying to calculate a 12 month moving average cost (MAUC) for each item in a particular warehouse. I am using the 2012_B – paging trick to calculate the median price (http://sqlperformance.com/2012/08/t-sql-queries/median) instead of using AVG in order to remove the potential for outliers to skew the result.
The following code works, however it only calculates the MAUC for one item or all items - depending on whether I remove or retain "AND t_item = 'xxxxx'
WITH Emily AS
(SELECT
t_item AS [Item Code]
,t_mauc_1 AS [MAUC]
FROM twhina113100
WHERE t_cwar = '11'
AND t_item = ' TNC-C2050NP-G'
AND t_trdt > GETDATE()-365)
(SELECT
AVG(1.0 * [Valuation Table].[MAUC])
FROM (
SELECT [MAUC] FROM Emily
ORDER BY [Emily].[MAUC]
OFFSET ((SELECT COUNT(*) FROM Emily) - 1) / 2 ROWS
FETCH NEXT 1 + (1 - (SELECT COUNT(*) FROM Emily) % 2) ROWS ONLY
) AS [Valuation Table] )
I believe that using Over(Partition By) may help me to partition by t_item however I am at a loss as to where to insert it into the code. I am quite new to SQL and my lack of formal training is starting to show.
If you have any other suggestions please share.
Any help would be much appreciated!
This one caught my attention, so I'm posting two options:
The first is a straight cte approach, and the second uses temp tables. The cte approach is fine for smaller data sets, but performance suffers as the series expands.
Both options will calculate the RUNNING Min, Max, Mean, Median, and Mode for a data series
Just a couple of items before we get into it. The normalized structure is ID and Measure.
- The ID could be a date or identity.
- The Measure is any numeric value
- Median is the mid-value of the sorted series. If an even number of observations we return the average of the two middle records
- Mode is represented as ModeR1 and ModeR2. If no repeated values, we show the min/max range
OK, let's take a look at the cte Approach
Declare #Table table (ID Int,Measure decimal(9,2))
Insert into #Table (ID,Measure) values
(1,25),
(2,75),
(3,50),
(4,25),
(5,12),
(6,66),
(7,45)
;with cteBase as (Select *,RowNr = Row_Number() over (Order By ID) From #Table),
cteExpd as (Select A.*,Measure2 = B.Measure,ExtRowNr = Row_Number() over (Partition By A.ID Order By B.Measure) From cteBase A Join cteBase B on (B.RowNr<=A.RowNr)),
cteMean as (Select ID,Mean=Avg(Measure2),Rows=Count(*) From cteExpd Group By ID),
cteMedn as (Select ID,MedRow1=ceiling(Rows/2.0),MedRow2=ceiling((Rows+1)/2.0) From cteMean),
cteMode as (Select ID,Mode=Measure2,ModeHits=count(*),ModeRowNr=Row_Number() over (Partition By ID Order By Count(*) Desc) From cteExpd Group By ID,Measure2)
Select A.ID
,A.Measure
,MinVal = min(Measure2)
,MaxVal = max(Measure2)
,Mean = max(B.Mean)
,Median = isnull(Avg(IIF(ExtRowNr between MedRow1 and MedRow2,Measure2,null)),A.Measure)
,ModeR1 = isnull(max(IIf(ModeHits>1,D.Mode,null)),min(Measure2))
,ModeR2 = isnull(max(IIf(ModeHits>1,D.Mode,null)),max(Measure2))
From cteExpd A
Join cteMean B on (A.ID=B.ID)
Join cteMedn C on (A.ID=C.ID)
Join cteMode D on (A.ID=D.ID and ModeRowNr=1)
Group By A.ID
,A.Measure
Order By A.ID
Returns
ID Measure MinVal MaxVal Mean Median ModeR1 ModeR2
1 25.00 25.00 25.00 25.000000 25.000000 25.00 25.00
2 75.00 25.00 75.00 50.000000 50.000000 25.00 75.00
3 50.00 25.00 75.00 50.000000 50.000000 25.00 75.00
4 25.00 25.00 75.00 43.750000 37.500000 25.00 25.00
5 12.00 12.00 75.00 37.400000 25.000000 25.00 25.00
6 66.00 12.00 75.00 42.166666 37.500000 25.00 25.00
7 45.00 12.00 75.00 42.571428 45.000000 25.00 25.00
This cte approach is very light and fast for smaller data series
Now the Temp Table Approach
-- Generate Base Data -- Key ID and Key Measure
Select ID =TR_Date
,Measure=TR_Y10,RowNr = Row_Number() over (Order By TR_Date)
Into #Base
From [Chinrus-Series].[dbo].[DS_Treasury_Rates]
Where Year(TR_Date)>=2013
-- Extend Base Data one-to-many
Select A.*,Measure2 = B.Measure,ExtRowNr = Row_Number() over (Partition By A.ID Order By B.Measure) into #Expd From #Base A Join #Base B on (B.RowNr<=A.RowNr)
Create Index idx on #Expd (ID)
-- Generate Mean for Series
Select ID,Mean=Avg(Measure2),Rows=Count(*) into #Mean From #Expd Group By ID
Create Index idx on #Mean (ID)
-- Calculate Median Row Number(s) -- If even(avg of middle two rows)
Select ID,MednRow1=ceiling(Rows/2.0),MednRow2=ceiling((Rows+1)/2.0) into #Medn From #Mean
Create Index idx on #Medn (ID)
-- Calculate Mode
Select * into #Mode from (Select ID,Mode=Measure2,ModeHits=count(*),ModeRowNr=Row_Number() over (Partition By ID Order By Count(*) Desc,Measure2 Desc) From #Expd Group By ID,Measure2) A where ModeRowNr=1
Create Index idx on #Mode (ID)
-- Generate Final Results
Select A.ID
,A.Measure
,MinVal = min(Measure2)
,MaxVal = max(Measure2)
,Mean = max(B.Mean)
,Median = isnull(Avg(IIF(ExtRowNr between MednRow1 and MednRow2,Measure2,null)),A.Measure)
,ModeR1 = isnull(max(IIf(ModeHits>1,D.Mode,null)),min(Measure2))
,ModeR2 = isnull(max(IIf(ModeHits>1,D.Mode,null)),max(Measure2))
From #Expd A
Join #Mean B on (A.ID=B.ID)
Join #Medn C on (A.ID=C.ID)
Join #Mode D on (A.ID=D.ID and ModeRowNr=1)
Group By A.ID
,A.Measure
Order By A.ID
Returns
ID Measure MinVal MaxVal Mean Median ModeR1 ModeR2
2013-01-02 1.86 1.86 1.86 1.86 1.86 1.86 1.86
2013-01-03 1.92 1.86 1.92 1.89 1.89 1.86 1.92
2013-01-04 1.93 1.86 1.93 1.9033 1.92 1.86 1.93
2013-01-07 1.92 1.86 1.93 1.9075 1.92 1.92 1.92
2013-01-08 1.89 1.86 1.93 1.904 1.92 1.92 1.92
...
2016-07-20 1.59 1.37 3.04 2.2578 2.24 2.20 2.20
2016-07-21 1.57 1.37 3.04 2.257 2.235 2.61 2.61
2016-07-22 1.57 1.37 3.04 2.2562 2.23 2.20 2.20
Both approaches where validated in Excel
I should add that in the final query, you could certainly add/remove items like STD, Total

TVF UDF does not return the same data as SELECT

Calling the UDF like so:
SELECT
product_name,
SUM(quantity) AS SumQty,
SUM(face_value) AS SumFaceValue,
SUM(net_cost)AS SumNetCost,
SUM(face_value - net_cost) AS SumScripRebate,
organization_name
FROM getSalesSummary(#GLSCOrgId, #BeginDate, #EndDate) getSalesSummary
GROUP BY product_name, organization_name
ORDER BY product_name
yields:
"Chili's 1 25.00 22.75 2.25 Sample Organization 1
CVS/pharmacy 1 25.00 23.50 1.50 Sample Organization 1
Macy's 1 100.00 90.00 10.00 Sample Organization 1"
Using the UDF logic and testing the results with SELECT:
SELECT
product_name,
SUM(quantity) AS SumQty,
SUM(face_value) AS SumFaceValue,
SUM(net_cost) AS SumNetCost,
SUM(face_value - net_cost) AS SumScripRebate,
organization_name
FROM #ReturnTable
GROUP BY product_name, organization_name
ORDER BY product_name
yields:
"Chili's 4 100.00 91.00 9.00 Sample Organization 1
CVS/pharmacy 1 25.00 23.50 1.50 Sample Organization 1
Macy's 1 100.00 90.00 10.00 Sample Organization 1"
#ReturnTable is the table returned by the UDF and is created like so:
INSERT INTO #ReturnTable(product_name,
unit_price,
quantity,
face_value,
net_cost,
organization_name)
(select * from #TablePartial UNION select * from #TableClosed)
The test with the SELECT and variables is returning the correct data, but calling the UDF is not getting those other 3 Chili's records. I am using the same data for parameters. I'm quite new to UDFs and I'm not sure why it would return different data than what the SELECT does. Any suggestions and/or answers?
You probably need UNION ALL not UNION
Looking at the two result sets it adds up as though the 4 Chilli's rows are all the same.
Chili's 1 25.00 22.75 2.25 Sample Organization 1
Chili's 1 25.00 22.75 2.25 Sample Organization 1
Chili's 1 25.00 22.75 2.25 Sample Organization 1
Chili's 1 25.00 22.75 2.25 Sample Organization 1
-------------------------------------------------------------
Chili's 4 100.00 91.00 9.00 Sample Organization 1
Using UNION will remove the duplicates leaving you with one row.
The only thing I can think of is the UNION change it to UNION ALL UNION will eliminate dups
Run these queries to see the difference
select 1 as a
union
select 1
union
select 1
select 1 as a
union all
select 1
union all
select 1