Given two date ranged discount tables and product price, calculate date ranged final price - sql

I have two tables with seasonal discounts. In each of these two tables are non overlapping date ranges, product id and discount that applies in that date range. Date ranges from one table however may overlap with date ranges in the other table. Given a third table with product id and its default price, the goal is to efficiently calculate seasonal - date ranged prices for product id after discounts from both tables have been applied.
Discounts multiply only in their overlapping period, e.g. if a first discount is 0.9 (10%) from 2019-07-01 to 2019-07-30, and a second discount is 0.8 from 2019-07-16 to 2019-08-15, this translates to: 0.9 discount from 2019-07-01 to 2019-07-15, 0.72 discount from 2019-07-16 to 2019-07-30, and 0.8 discount from 2019-07-31 to 2019-08-15.
I have managed to come to a solution, by first generating a table that holds ordered all of start and end dates in both discount tables, then generating a resulting table of all smallest disjoint intervals, and then for each interval, generating all prices, default, price with only the discount from first table applied (if any applies), price with only the discount from second table applied (if any applies), price with both discounts applied (if so possible) and then taking a min of these four prices. See sample code bellow.
declare #pricesDefault table (product_id int, price decimal)
insert into #pricesDefault
values
(1, 100),
(2, 120),
(3, 200),
(4, 50)
declare #discountTypeA table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
insert into #discountTypeA
values
(1, 0.75, '2019-06-06', '2019-07-06'),
(1, 0.95, '2019-08-06', '2019-08-20'),
(1, 0.92, '2019-05-06', '2019-06-05'),
(2, 0.75, '2019-06-08', '2019-07-19'),
(2, 0.95, '2019-07-20', '2019-09-20'),
(3, 0.92, '2019-05-06', '2019-06-05')
declare #discountTypeB table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
insert into #discountTypeB
values
(1, 0.85, '2019-06-20', '2019-07-03'),
(1, 0.65, '2019-08-10', '2019-08-29'),
(1, 0.65, '2019-09-10', '2019-09-27'),
(3, 0.75, '2019-05-08', '2019-05-19'),
(2, 0.95, '2019-05-20', '2019-05-21'),
(3, 0.92, '2019-09-06', '2019-09-09')
declare #pricingPeriod table(product_id int, discountedPrice decimal, startdate datetime, enddate datetime);
with allDates(product_id, dt) as
(select distinct product_id, dta.startdate from #discountTypeA dta
union all
select distinct product_id, dta.enddate from #discountTypeA dta
union all
select distinct product_id, dtb.startdate from #discountTypeB dtb
union all
select distinct product_id, dtb.enddate from #discountTypeB dtb
),
allproductDatesWithId as
(select product_id, dt, row_number() over (partition by product_id order by dt asc) 'Id'
from allDates),
sched as
(select pd.product_id, apw1.dt startdate, apw2.dt enddate
from #pricesDefault pd
join allproductDatesWithId apw1 on apw1.product_id = pd.product_id
join allproductDatesWithId apw2 on apw2.product_id = pd.product_id and apw2.Id= apw1.Id+1
),
discountAppliedTypeA as(
select sc.product_id, sc.startdate, sc.enddate,
min(case when sc.startdate >= dta.startdate and dta.enddate >= sc.enddate then pd.price * dta.modifier else pd.price end ) 'price'
from sched sc
join #pricesDefault pd on pd.product_id = sc.product_id
left join #discountTypeA dta on sc.product_id = dta.product_id
group by sc.product_id, sc.startdate , sc.enddate ),
discountAppliedTypeB as(
select daat.product_id, daat.startdate, daat.enddate,
min(case when daat.startdate >= dta.startdate and dta.enddate >= daat.enddate then daat.price * dta.modifier else daat.price end ) 'price'
from discountAppliedTypeA daat
left join #discountTypeB dta on daat.product_id = dta.product_id
group by daat.product_id, daat.startdate , daat.enddate )
select * from discountAppliedTypeB
order by product_id, startdate
Calculating a min of all possible prices is unnecessary overhead. I'd like to generate, just one resulting price and have it as a final price.
Here is the resulting set:
product_id start_date end_date final_price
1 2019-05-06 00:00:00.000 2019-06-05 00:00:00.000 92.0000
1 2019-06-05 00:00:00.000 2019-06-06 00:00:00.000 100.0000
1 2019-06-06 00:00:00.000 2019-06-20 00:00:00.000 75.0000
1 2019-06-20 00:00:00.000 2019-07-03 00:00:00.000 63.7500
1 2019-07-03 00:00:00.000 2019-07-06 00:00:00.000 75.0000
1 2019-07-06 00:00:00.000 2019-08-06 00:00:00.000 100.0000
1 2019-08-06 00:00:00.000 2019-08-10 00:00:00.000 95.0000
1 2019-08-10 00:00:00.000 2019-08-20 00:00:00.000 61.7500
1 2019-08-20 00:00:00.000 2019-08-29 00:00:00.000 65.0000
1 2019-08-29 00:00:00.000 2019-09-10 00:00:00.000 100.0000
1 2019-09-10 00:00:00.000 2019-09-27 00:00:00.000 65.0000
2 2019-05-20 00:00:00.000 2019-05-21 00:00:00.000 114.0000
2 2019-05-21 00:00:00.000 2019-06-08 00:00:00.000 120.0000
2 2019-06-08 00:00:00.000 2019-07-19 00:00:00.000 90.0000
2 2019-07-19 00:00:00.000 2019-07-20 00:00:00.000 120.0000
2 2019-07-20 00:00:00.000 2019-09-20 00:00:00.000 114.0000
3 2019-05-06 00:00:00.000 2019-05-08 00:00:00.000 184.0000
3 2019-05-08 00:00:00.000 2019-05-19 00:00:00.000 138.0000
3 2019-05-19 00:00:00.000 2019-06-05 00:00:00.000 184.0000
3 2019-06-05 00:00:00.000 2019-09-06 00:00:00.000 200.0000
3 2019-09-06 00:00:00.000 2019-09-09 00:00:00.000 184.0000
Is there a more efficient to this solution that I am not seeing?
I have a large data set of ~20K rows in real product prices table, and 100K- 200K rows in both discount tables.
Indexing structure of the actual tables is following: product id is clustered index in product prices table, whilst discount tables have an Id surrogate column as clustered index (as well as primary key), and (product_id, start_date, end_date) as a non clustered index.

You can generate the dates using union. Then bring in all discounts that are valid on that date, and calculate the total.
This looks like:
with prices as (
select a.product_id, v.dte
from #discountTypeA a cross apply
(values (a.startdate), (a.enddate)) v(dte)
union -- on purpose to remove duplicates
select b.product_id, v.dte
from #discountTypeB b cross apply
(values (b.startdate), (b.enddate)) v(dte)
),
p as (
select p.*, 1-a.modifier as a_discount, 1-b.modifier as b_discount, pd.price
from prices p left join
#pricesDefault pd
on pd.product_id = p.product_id left join
#discountTypeA a
on p.product_id = a.product_id and
p.dte >= a.startdate and p.dte < a.enddate left join
#discountTypeb b
on p.product_id = b.product_id and
p.dte >= b.startdate and p.dte < b.enddate
)
select p.product_id, price * (1 - coalesce(a_discount, 0)) * (1 - coalesce(b_discount, 0)) as price, a_discount, b_discount,
dte as startdate, lead(dte) over (partition by product_id order by dte) as enddate
from p
order by product_id, dte;
Here is a db<>fiddle.

Here is a version that works out the price for every date. You can then either use this directly, or use one of the many solutions on SO for working out date ranges.
In this example I have hard coded the date limits, but you could easily read them from your tables if you prefer.
I haven't done any performance testing on this, but give it a go. Its quite a bit simpler do if you have the right indexes it might be quicker.
;with dates as (
select convert(datetime,'2019-05-06') as d
union all
select d+1 from dates where d<'2019-09-27'
)
select pricesDefault.product_id, d, pricesDefault.price as baseprice,
discountA.modifier as dA,
discountB.modifier as dB,
pricesDefault.price*isnull(discountA.modifier,1)*isnull(discountB.modifier,1) as finalprice
from #pricesDefault pricesDefault
cross join dates
left join #discountTypeA discountA on discountA.product_id=pricesDefault.product_id and d between discountA.startdate and discountA.enddate
left join #discountTypeB discountB on discountB.product_id=pricesDefault.product_id and d between discountB.startdate and discountB.enddate
order by pricesDefault.product_id, d
Option (MaxRecursion 1000)

Related

SQL query joining on existing date records and max date for missing records

I have an items table with dates and values. As soon as the value gets to 1, there are no more records for that Itemid.
Item Table
Itemid ItemDate Value
1 2020-04-30 0.5
1 2020-05-31 0.75
1 2020-06-30 1.0
2 2020-05-31 0.6
2 2020-06-30 1.0
I want to join this with a simple date table
dateId EOMDate
1 2020-04-30
2 2020-05-31
3 2020-06-30
4 2020-07-31
5 2020-08-31
The result should produce one record for each date in the date table and for each item where the date is >= the Item date. Where there is an exact date match with the Item table, it will use that record from the item table. Where there is no matching record in the item table, then it uses the record with the Max(ItemDate) value, that exists in the item table.
So it should produce this:
Result EOMDate ItemDate Value
1 2020-04-30 2020-04-30 0.5
1 2020-05-31 2020-05-31 0.75
1 2020-06-30 2020-06-30 1.0
1 2020-07-31 2020-06-30 1.0
1 2020-08-31 2020-06-30 1.0
2 2020-05-31 2020-05-31 0.6
2 2020-06-30 2020-06-30 1.0
2 2020-07-31 2020-06-30 1.0
2 2020-08-31 2020-06-30 1.0
The item table has several hundred millions of rows, and the date table has 120 records (each month end for 10 years), so I need a good performing solution. This has completely stumped me for some reason!
EDIT
my initial and non-working solution uses an outer apply
select p.ItemId, p.ItemDate, d.EOMDate, p.Value
from (select ItemId, ItemDate, Value from Items) p
OUTER APPLY
(
SELECT EOMDate from dates
) d
order by p.ItemDate,d.EOMDate
However it returns a table that has one record for each combination of Item date and EOM date. So in the above example, 20 records for ItemId 1 and 16 records for ItemId2
Here is to sql to create the above example tables:
CREATE TABLE #Items (ItemId int, ItemDate date, [Value] float)
Insert into #Items (ItemId,ItemDate,[Value])
Values (1,'2020-04-30',0.5),(1,'2020-05-31',0.75),(1,'2020-06-30',1),(2,'2020-05-31',0.6),(2,'2020-06-30',1)
Create Table #dates (dateId int, EOMDate date)
Insert into #dates (dateId,EOMDate) Values (1,'2020-04-30'),(2,'2020-05-31'),(3,'2020-06-30'),(4,'2020-07-31'),(5,'2020-08-31')
One method uses apply:
select i.*, d.*
from (select item_id, max(date) as max_date
from items
group by item_id
) i outer apply
(select top (1) d.*
from dates d
where d.date >= max_date
order by d.date asc
) d
You can use cross join and analytical function as follows:
Select * from
(Select a.item_id, d.eomdate, i.itemdate, i.value,
Row_number() over (partition by a.item_id, d.eomdate order by i.itemdate) as rn
From
(Select distinct item_id from items) a
Cross join Dates d
join items i on i.item_id = a.item_id and d.eomdate >= i.item_date) t
Where rn = 1

Group By first day of month and join with a separate table

I have 2 tables in SQL
one with monthly sales targets:
Date Target
1/7/17 50000
1/8/17 65000
1/9/17 50000
1/10/17 48000
etc...
the other with sales orders:
TxnDate JobNum Value
3/7/17 100001 20000
3/7/17 100002 11000
8/7/17 100003 10000
10/8/17 100004 15000
15/9/17 100005 20000
etc...
what I want is a table with following:
Date Target Sales
1/7/17 50000 41000
1/8/17 65000 15000
1/9/17 50000 20000
please help me I'm a newbie to coding and this is doing my head in.. :)
Assuming your 1st table is targetSales and your 2nd table is Sales and your database is SQL Server:
select
t.date
, t.target
, isnull(sum(s.value), 0) as Sales
from targetSales t
left join Sales s
on (month(t.date) = month(s.date)
and year(t.date) = year(s.date))
group by t.date
, t.target
You can follow a similar approach if you use a different database, just find the equivalents of month() and year() functions for your RDBMS.
try this
select tb1.date,tb1.target,tb2.value from table1 as tb1
INNER JOIN (select sum(value) as sales, date from table2 group by date) as tb2
on tb1.date = tb2.date,
you can use this script for daily targets
An another way around, looks like in target table the date is always the first day of the month. So in the sales table, just round the TxnDate column value to first day of the month.
Query
select t1.[date],
max(t1.[target]) as [target],
coalesce(sum(t2.[value]), 0) as [value]
from [targets] t1
left join [sales] t2
on t1.[Date] = dateadd(day, - datepart(day, t2.[txnDate]) + 1, t2.[txnDate])
group by t1.[Date];
demo
If you take any datetime value in SQL Server, calculate the number of months from that date to zero datediff(month,0,TxnDate) then add that number of moths to zero dateadd(month, ... , 0) you get the first day of the month for the original datetime value. This works in all versions of SQL Server. With this we can sum the values of the orders by the first day of the month, then join to targets using that date.
CREATE TABLE Orders
([TxnDate] datetime, [JobNum] int, [Value] int)
;
INSERT INTO Orders
([TxnDate], [JobNum], [Value])
VALUES
('2017-07-03 00:00:00', 100001, 20000),
('2017-07-03 00:00:00', 100002, 11000),
('2017-07-08 00:00:00', 100003, 10000),
('2017-08-10 00:00:00', 100004, 15000),
('2017-09-15 00:00:00', 100005, 20000)
;
CREATE TABLE Targets
([Date] datetime, [Target] int)
;
INSERT INTO Targets
([Date], [Target])
VALUES
('2017-07-01 00:00:00', 50000),
('2017-08-01 00:00:00', 65000),
('2017-09-01 00:00:00', 50000),
('2017-10-10 00:00:00', 48000)
;
GO
9 rows affected
select dateadd(month,datediff(month,0,TxnDate), 0) month_start, sum(Value) SumValue
from Orders
group by dateadd(month, datediff(month,0,TxnDate), 0)
GO
month_start | SumValue
:------------------ | -------:
01/07/2017 00:00:00 | 41000
01/08/2017 00:00:00 | 15000
01/09/2017 00:00:00 | 20000
select
t.[Date], t.Target, coalesce(o.SumValue,0)
from targets t
left join (
select dateadd(month,datediff(month,0,TxnDate), 0) month_start, sum(Value) SumValue
from Orders
group by dateadd(month, datediff(month,0,TxnDate), 0)
) o on t.[Date] = o.month_start
GO
Date | Target | (No column name)
:------------------ | -----: | ---------------:
01/07/2017 00:00:00 | 50000 | 41000
01/08/2017 00:00:00 | 65000 | 15000
01/09/2017 00:00:00 | 50000 | 20000
10/10/2017 00:00:00 | 48000 | 0
dbfiddle here
This is not the best solution but this will give you a correct result.
select date,target,(
select sum(value)
from sales_orders s
where datepart(m,s.TxnDate) = datepart(m,targets.Date)
and datepart(year,s.TxnDate) = datepart(year,targets.Date)
) as sales
from targets

How to join tally dates with list of periods to retrieve wanted results?

I have a list of tally dates that I want to combine with prices, but I want for results to have all the dates from tally and dates and price values from prices (and null prices when no periods correspond to tally date)
Dates
Date
2017-12-22
2017-12-23
2017-12-24
2017-12-25
2017-12-26
2017-12-27
2017-12-28
2017-12-29
2017-12-30
2017-12-31
Prices
periodstart periodend price productID
2017-12-23 2017-12-25 50 1
2017-12-26 2017-12-29 10 1
Sql query result
date price productid
2017-12-22 null 1
2017-12-23 50 1
2017-12-24 50 1
2017-12-25 50 1
2017-12-26 10 1
2017-12-27 10 1
2017-12-28 10 1
2017-12-29 10 1
2017-12-30 null 1
2017-12-31 null 1
UPDATE
I added productID column in prices
rextester: http://rextester.com/ADJZSW20744
create table dbo.calendar (
[date] date primary key clustered
);
insert into dbo.calendar values
('2017-12-22'),('2017-12-23'),('2017-12-24')
,('2017-12-25'),('2017-12-26'),('2017-12-27')
,('2017-12-28'),('2017-12-29'),('2017-12-30')
,('2017-12-31');
create table prices (
periodstart date
, periodend date
, price int
, productid int
);
insert into prices values
('2017-12-23','2017-12-25',50,1)
,('2017-12-26','2017-12-29',10,1)
,('2017-12-22','2017-12-23',50,2)
,('2017-12-26','2017-12-27',10,2);
query: This will work with multiple products:
select
c.Date
, p.Price
, x.ProductId
from dbo.Calendar c
outer apply (
select distinct
ProductId
from prices
) x
left join dbo.Prices p on
c.Date >= p.PeriodStart
and c.Date <= p.PeriodEnd
and x.ProductId = p.ProductId
order by x.ProductId, c.Date;
A simple left join should do the trick
Select A.Date
,B.Price
From Dates A
Left Join Prices B on A.Date Between B.periodstart and B.periodend
Try this:
SELECT Date
, price
FROM Dates d
LEFT JOIN Prices p
ON d.Date BETWEEN p.periodstart AND ISNULL(p.periodend, d.Date)
To avoid conflicts in case your periods are intersecting or don't have an ending date, take the latest start period using an apply:
SELECT Date
, price
FROM Dates d
OUTER APPLY
(
SELECT TOP 1 price
FROM Prices p
WHERE d.Date BETWEEN p.periodstart AND ISNULL(p.periodend, d.Date)
ORDER BY p.periodstart DESC
) oa

Contiguous Dates

Here is the table that I am working with:
MemberID MembershipStartDate MembershipEndDate
=================================================================
123 2010-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2011-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2013-05-01 00:00:00.000 2013-12-31 00:00:00.000
123 2014-01-01 00:00:00.000 2014-12-31 00:00:00.000
123 2015-01-01 00:00:00.000 2015-03-31 00:00:00.000
What I want is to create one row that shows continuous membership,
and a second row if the membership breaks by more than 2 days, with a new start and end date..
So the output I am looking for is like:
MemberID MembershipStartDate MembershipEndDate
=================================================================
123 2010-01-01 00:00:00.000 2012-12-31 00:00:00.000
123 2013-05-01 00:00:00.000 2015-03-31 00:00:00.000
There is a memberID field attached to these dates which is how they are grouped.
I've had to deal with this kind of thing before
I use something like this
USE tempdb
--Create test Data
DECLARE #Membership TABLE (MemberID int ,MembershipStartDate date,MembershipEndDate date)
INSERT #Membership
(MemberID,MembershipStartDate,MembershipEndDate)
VALUES (123,'2010-01-01','2012-12-31'),
(123,'2011-01-01','2012-12-31'),
(123,'2013-05-01','2013-12-31'),
(123,'2014-01-01','2014-12-31'),
(123,'2015-01-01','2015-03-31')
--Create a table to hold all the dates that might be turning points
DECLARE #SignificantDates Table(MemberID int, SignificantDate date, IsMember bit DEFAULT 0)
--Populate table with the start and end dates as well as the days just before and just after each period
INSERT #SignificantDates (MemberID ,SignificantDate)
SELECT MemberID, MembershipStartDate FROM #Membership
UNION
SELECT MemberID,DATEADD(day,-1,MembershipStartDate ) FROM #Membership
UNION
SELECT MemberID,MembershipEndDate FROM #Membership
UNION
SELECT MemberID,DATEADD(day,1,MembershipEndDate) FROM #Membership
--Set the is member flag for each date that is covered by a membership
UPDATE sd SET IsMember = 1
FROM #SignificantDates sd
JOIN #Membership m ON MembershipStartDate<= SignificantDate AND SignificantDate <= MembershipEndDate
--To demonstrate what we're about to do, Select all the dates and show the IsMember Flag and the previous value
SELECT sd.MemberID, sd.SignificantDate,sd.IsMember, prv.prevIsMember
FROM
#SignificantDates sd
JOIN (SELECT
MemberId,
SignificantDate,
IsMember,
Lag(IsMember,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate desc) AS prevIsMember FROM #SignificantDates
) as prv
ON sd.MemberID = prv.MemberID
AND sd.SignificantDate = prv.SignificantDate
ORDER BY sd.MemberID, sd.SignificantDate
--Delete the ones where the flag is the same as the previous value
delete sd
FROM
#SignificantDates sd
JOIN (SELECT MemberId, SignificantDate,IsMember, Lag(IsMember,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate) AS prevIsMember FROM #SignificantDates ) as prv
ON sd.MemberID = prv.MemberID
AND sd.SignificantDate = prv.SignificantDate
AND prv.IsMember = prv.prevIsMember
--SELECT the Start date for each period of membership and the day before the following period of non membership
SELECT
nxt.MemberId,
nxt.SignificantDate AS MembershipStartDate,
DATEADD(day,-1,nxt.NextSignificantDate) AS MembershipEndDate
FROM
(
SELECT
MemberID,
SignificantDate,
LEAd(SignificantDate,1) OVER (PARTITION BY MemberId ORDER BY SignificantDate) AS NextSignificantDate,
IsMember
FROM #SignificantDates
) nxt
WHERE nxt.IsMember = 1

how to replace "Nothing" cell by last month value

I am using SSRS develop a report via Matrix.
I sum the product sales qty every month but if some month sale qty is 0, the cell will be Nothing.
I'm trying to using Previous function to solve problem but this function seems not work for Matrix object.
Is there any way to do this?
1st table is currently report result.
2nd table is that i want.
Assuming that you have a table with the structure and content like this:
CREATE TABLE a (date Date, product_name NVarchar(20), sale_qty Decimal(10,2))
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130510','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130601','product A',0)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20130501','product B',5)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140205','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140215','product A',1)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140202','product B',2)
INSERT INTO a (date, product_name, sale_qty) VALUES ('20140301','product A',0)
Then the below sql-statement will work for you (as far as I understood the requirement):
SELECT
y.Date2 AS 'yyyy/mm'
, y.product_name
, ISNULL(z.Month_sale_QTY,0) AS Month_sale_QTY
FROM
(SELECT
DATEADD(MONTH, x.number, y.StartDate) AS Date2
, (SELECT TOP 1 DATEADD(d,1-DATEPART(d,a.date),a.date) FROM a WHERE DATEADD(d,1-DATEPART(d,a.date),a.date) <= DATEADD(MONTH, x.number, y.StartDate) AND a.product_name = p.product_name AND ISNULL(a.sale_qty,0)<>0 ORDER BY 1 DESC) AS Date3
, p.product_name
FROM
master.dbo.spt_values x
INNER JOIN
(SELECT Dateadd(d,1-DATEPART(d, MIN(Date)), MIN(Date)) AS StartDate, MAX(Date) AS EndDate FROM a) AS y
ON x.number <= DATEDIFF(MONTH, y.StartDate, y.EndDate)
,(SELECT DISTINCT product_name FROM a) AS p WHERE x.type = 'P') y
LEFT JOIN
(SELECT
DATEADD(d,1-DATEPART(d,a.date),a.date) AS Date2
, a.product_name
, SUM(a.sale_qty) as Month_sale_QTY
FROM a
GROUP BY
DATEADD(d,1-DATEPART(d,a.date),a.date)
, a.product_name
) z ON y.Date3 = z.Date2 AND y.product_name = z.product_name
It returns the following:
yyyy/mm product_name Month_sale_QTY
---------- -------------------- ---------------------------------------
2013-05-01 product A 1.00
2013-06-01 product A 1.00
2013-07-01 product A 1.00
2013-08-01 product A 1.00
2013-09-01 product A 1.00
2013-10-01 product A 1.00
2013-11-01 product A 1.00
2013-12-01 product A 1.00
2014-01-01 product A 1.00
2014-02-01 product A 2.00
2014-03-01 product A 2.00
2013-05-01 product B 5.00
2013-06-01 product B 5.00
2013-07-01 product B 5.00
2013-08-01 product B 5.00
2013-09-01 product B 5.00
2013-10-01 product B 5.00
2013-11-01 product B 5.00
2013-12-01 product B 5.00
2014-01-01 product B 5.00
2014-02-01 product B 2.00
2014-03-01 product B 2.00
I'm sure it can be optimized using windowing functions and/or CTE, but as a quick solution this one will work too.