Calculate the period of validity of the price - mysql

I have a table with an item, its cost and the date it was added.
CREATE TABLE item_prices (
item_id INT,
item_name VARCHAR(30),
item_price DECIMAL(12, 2),
created_dttm DATETIME
);
INSERT INTO item_prices(item_id, item_name, item_price, created_dttm) VALUES
(1, 'spoon', 10.20 , '2023-01-01 01:00:00'),
(1, 'spoon', 10.20 , '2023-01-08 01:35:00'),
(1, 'spoon', 10.35 , '2023-01-14 15:00:00'),
(2, 'table', 40.00 , '2023-01-01 01:00:00'),
(2, 'table', 40.00 , '2023-01-03 11:22:00'),
(2, 'table', 41.00 , '2023-01-10 08:28:22'),
(1, 'spoon', 10.35 , '2023-01-28 21:52:00'),
(1, 'spoon', 11.00 , '2023-02-15 16:36:00'),
(2, 'table', 41.00 , '2023-02-16 21:42:11'),
(2, 'table', 45.20 , '2023-02-19 20:25:25'),
(1, 'spoon', 9.00 , '2023-03-02 14:50:00'),
(1, 'spoon', 9.00 , '2023-03-06 16:36:00'),
(1, 'spoon', 8.50 , '2023-03-15 12:00:00'),
(2, 'table', 30 , '2023-03-05 10:10:10'),
(2, 'table', 30 , '2023-03-10 15:45:00');
I need to create a new table with the following fields:
"item_id",
"item_name",
"item_price",
"valid_from_dt": date on which the price was effective (created_dttm price record)
"valid_to_dt": date until which this price was valid (created_dttm of the next record for this product "minus" one day)
I thought it might be possible to start by selecting days on which new entries are added with new prices with such a request:
SELECT item_id, item_name, item_price,
MIN(created_dttm) as dt
FROM table
GROUP BY item_price, item_id, item_name
that provides me this output:
The expected output is the following:
item_id
item_name
item_price
valid_from_dt
valid_to_dt
1
spoon
10.20
2023-01-01
2023-01-13
1
spoon
10.35
2023-01-14
2023-02-14
1
spoon
11.00
2023-02-15
2023-03-01
1
spoon
9.00
2023-03-02
2023-03-01
1
spoon
8.50
2023-03-15
2023-03-14
2
table
40.00
2023-01-01
2022-01-09
2
table
41.00
2023-01-10
2023-02-18
....
....
....
....
....

select distinct
item_id,
item_name,
first_value(item_price) over (partition by item_id order by created_dttm) as item_price,
min(created_dttm) over (partition by item_id ) as valid_from_dt,
max(created_dttm) over (partition by item_id ) as valid_to_dt
from item_prices
;
output:
item_id
item_name
item_price
valid_from_dt
valid_to_dt
1
spoon
10.20
2023-01-01 01:00:00
2023-03-15 12:00:00
2
table
40.00
2023-01-01 01:00:00
2023-03-10 15:45:00
see: DBFIDDLE

Your query is correct. It's only missing the next step:
retrieving the next "valid_from_dt" in the partition <item_id, item_name>, using the LEAD function
subtract 1 day from it
WITH cte AS (
SELECT item_id, item_name, item_price,
MIN(created_dttm) AS valid_from_dt
FROM item_prices
GROUP BY item_id, item_name, item_price
)
SELECT *,
LEAD(valid_from_dt) OVER(PARTITION BY item_id, item_name) - INTERVAL 1 DAY AS valid_to_dt
FROM cte
Check the demo here.

Related

Select the oldest record of a certain group until it changes pattern, in SQL

I am trying to get the oldest record for every status update/change in the following table.
Table (status_updates) :
id
entity_id
status
date
7
2
Approved
2022-02-10
6
2
Approved
2022-02-05
5
2
Approved
2022-02-04
4
2
OnHold
2022-02-04
3
2
OnHold
2022-02-03
2
2
Approved
2022-02-02
1
2
Approved
2022-02-01
Result Needed :
id
entity_id
status
date
5
2
Approved
2022-02-04
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Tried :
select
`status`,
`created_at`
from
`status_updates`
left join
(select
`id`,
row_number() over (partition by status_updates.entity_id, status_updates.status order by status_updates.created_at asc) as sequence
from
`status_updates`)
as `oldest_history`
on
`oldest_history`.`id` = `shipper_credit_histories`.`id`
where `sequence` = 1
Result Achived :
id
entity_id
status
date
3
2
OnHold
2022-02-03
1
2
Approved
2022-02-01
Just using lag:
select s.*
from (
select id, status<>coalesce(lag(status) over (partition by entity_id order by id),'') status_change
from status_updates
) ids
join status_updates s using (id)
where status_change
here are the queries:
create table status_updates
(entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (2, 'Approved', '2022-02-05');
insert into status_updates values (2, 'Approved', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-04');
insert into status_updates values (2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 'Approved', '2022-02-02');
insert into status_updates values (2, 'Approved', '2022-02-01');
select b.*
from status_updates a
right join status_updates b
on a.status=b.status and a.date=(b.date - interval 1 day)
where a.entity_id is null;
or this query(if you prefer left join)
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.date=(b.date + interval 1 day)
where b.entity_id is null;
in both you will see the expected result
the second solution is almost the same, but join by id instead of date
create table status_updates
(id integer,
entity_id integer,
status varchar(32),
date date
);
insert into status_updates values (7, 2, 'Approved', '2022-02-10');
insert into status_updates values (6, 2, 'Approved', '2022-02-05');
insert into status_updates values (5, 2, 'Approved', '2022-02-04');
insert into status_updates values (4, 2, 'On Hold', '2022-02-04');
insert into status_updates values (3, 2, 'On Hold', '2022-02-03');
insert into status_updates values (2, 2, 'Approved', '2022-02-02');
insert into status_updates values (1, 2, 'Approved', '2022-02-01');
select a.*
from status_updates a
left join status_updates b
on a.status=b.status and a.id=b.id + 1
where b.entity_id is null;
result is the same what you expected

SQL divide many records by day

my SQL table's struct is very simple,only contains 3 fields:
createDate(Date): time when record inserted;
title(String): title for record;
count(Integer32): count for record;
There 10w+ records in the table! Represents records inserted in one year:
Any day could inserted any number records(include 0 record)
So,How could I divide records by days???
eg: There 10 records in the table:
1. 2019-01-01 10:20:15 xxx
2. 2019-01-01 12:50:10 xxx
3. 2019-01-01 23:20:19 xxx
4. 2019-01-02 10:20:15 xxx
5. 2019-01-05 08:20:15 xxx
6. 2019-01-05 22:20:15 xxx
7. 2019-02-10 10:20:15 xxx
8. 2019-02-10 11:20:15 xxx
9. 2019-02-10 15:20:15 xxx
10. 2019-02-15 10:20:15 xxx
I want result : divide to 5 "collections"
collection "2019-01-01" (contain 3 records):
- 2019-01-01 10:20:15 xxx
- 2019-01-01 12:50:10 xxx
- 2019-01-01 23:20:19 xxx
collection "2019-01-02" (contain 1 record):
- 2019-01-02 10:20:15 xxx
collection "2019-01-05" (contain 2 records):
- 2019-01-05 08:20:15 xxx
- 2019-01-05 22:20:15 xxx
collection "2019-02-10" (contain 3 records):
- 2019-02-10 10:20:15 xxx
- 2019-02-10 11:20:15 xxx
- 2019-02-10 15:20:15 xxx
collection "2019-02-15" (contain 1 record):
- 2019-02-15 10:20:15 xxx
If my table schema is correct then this would be your possible solution.
GO
CREATE TABLE #tempRequestForMeList
(
createDate datetime,
title nvarchar(50),
[count] int
)
GO
insert into #tempRequestForMeList ( createDate, title, [count] )
values ( '2016-09-20 17:17:04.840', 'dd', 0 )
, ( '2016-09-20 17:17:04.840', 'dd', 1 )
, ( '2016-09-20 07:17:04.840', 'dd', 1 )
, ( '2016-09-20 05:17:04.840', 'dd', 1 )
, ( '2016-09-20 13:17:04.840', 'dd', 1 )
, ( '2016-09-19 12:17:04.840', 'dd', 1 )
, ( '2016-09-19 02:17:04.840', 'dd', 1 )
, ( '2016-09-19 01:17:04.840', 'dd', 1 )
, ( '2016-09-18 02:17:04.840', 'dd', 1 )
, ( '2016-09-18 03:17:04.840', 'dd', 1 )
, ( '2016-09-18 05:17:04.840', 'dd', 1 )
, ( '2016-09-18 07:17:04.840', 'dd', 1 )
GO
; with cte as (
select cast(createdate as date) as Date1, * from #tempRequestForMeList )
update dd set dd.[count] = ct.co from #tempRequestForMeList as dd inner join (select count(date1) as co, date1 from cte group by Date1) as ct on cast(dd.createDate as DATE) = ct.Date1
select * from #tempRequestForMeList --- if require count with each row
go
drop table #tempRequestForMeList
go
If this doesn't work then show your table schema and expected output.
Note: This is for SQL server
Try to use COUNT by PARTITION:
SELECT
t.*
, count( CONVERT(date, t.createDate)) OVER (PARTITION BY CONVERT(date, t.createDate)
ORDER BY CONVERT(date, t.createDate)) CountByDate
FROM
#tempRequestForMeList t
Let me show an example(Thanks to #DarkRob for sample data):
DECLARE #tempRequestForMeList TABLE
(
createDate DATETIME,
title NVARCHAR(50),
[count] INT
);
INSERT INTO #tempRequestForMeList
(
createDate,
title,
count
)
VALUES
('2016-09-20 17:17:04.840', 'dd', 0),
('2016-09-20 17:17:04.840', 'dd', 1),
('2016-09-20 07:17:04.840', 'dd', 1),
('2016-09-20 05:17:04.840', 'dd', 1),
('2016-09-20 13:17:04.840', 'dd', 1),
('2016-09-19 12:17:04.840', 'dd', 1),
('2016-09-19 02:17:04.840', 'dd', 1),
('2016-09-19 01:17:04.840', 'dd', 1),
('2016-09-18 02:17:04.840', 'dd', 1),
('2016-09-18 03:17:04.840', 'dd', 1),
('2016-09-18 05:17:04.840', 'dd', 1),
('2016-09-18 07:17:04.840', 'dd', 1),
('2016-10-20 17:17:04.840', 'dd', 0);
and query:
SELECT
t.*
, count( CONVERT(date, t.createDate)) OVER (PARTITION BY CONVERT(date, t.createDate)
ORDER BY CONVERT(date, t.createDate)) CountByDate
FROM
#tempRequestForMeList t
OUTPUT:
createDate title count CountByDate
2016-09-18 02:17:04.840 dd 1 4
2016-09-18 03:17:04.840 dd 1 4
2016-09-18 05:17:04.840 dd 1 4
2016-09-18 07:17:04.840 dd 1 4
2016-09-19 12:17:04.840 dd 1 3
2016-09-19 02:17:04.840 dd 1 3
2016-09-19 01:17:04.840 dd 1 3
2016-09-20 17:17:04.840 dd 0 5
2016-09-20 17:17:04.840 dd 1 5
2016-09-20 07:17:04.840 dd 1 5
2016-09-20 05:17:04.840 dd 1 5
2016-09-20 13:17:04.840 dd 1 5
2016-10-20 17:17:04.840 dd 0 1

Get Max in a Group based on a condition

ProjID Dno RNo Status DateApproved
100 1 1 Initiated 2014-12-31 09:15:58.000
100 1 1 Approved 2015-01-31 09:15:58.000
100 1 1 Approved 2015-02-01 09:15:58.000
100 1 1 Approved 2015-05-28 09:15:58.000
100 1 1 Approved 2015-06-20 09:15:58.000
101 1 1 Approved 2014-12-31 09:15:58.000
101 1 1 Approved 2015-01-31 09:15:58.000
101 1 1 Approved 2015-02-01 09:15:58.000
101 1 1 Approved 2015-05-28 09:15:58.000
101 1 1 Approved 2015-08-20 09:15:58.000
In the above example i have to get max(Dateapproved) as Dateapproved for each projectid.
if all the revision Status are approved in a particular group for eg :project id=101 has all rows in its group having a status as Approved so i have to get the max date : '2015-08-20 09:15:58.000'.But for Projectid=100 one status is still in Initiated State so we have to show Null as Dateapproved .
Thanks in Advance
My output should be like:
ProjId Dno Rno DateApproved
100 1 1 NUll
101 1 1 2015-08-20 09:15:58.000
Example code:
Create table #temp(
ProjectID varchar(35),
Documentno int,
Revisionno int,
Status varchar(35),
DateApproved Datetime)
insert into #temp values ( '100', 1, 1, 'Initiated','2014-12-31 09:15:58')
insert into #temp values ( '100', 1, 1, 'Approved','2015-01-31 09:15:58 ')
insert into #temp values ( '100', 1, 1, 'Approved','2015-02-01 09:15:58 ')
insert into #temp values ( '100', 1, 1, 'Approved','2015-05-28 09:15:58 ')
insert into #temp values ( '100', 1, 1, 'Approved','2015-06-20 09:15:58 ')
insert into #temp values ( '101', 1, 1, 'Approved','2014-12-31 09:15:58 ')
insert into #temp values ( '101', 1, 1, 'Approved','2015-01-31 09:15:58 ')
insert into #temp values ( '101', 1, 1, 'Approved','2015-02-01 09:15:58 ')
insert into #temp values ( '101', 1, 1, 'Approved','2015-05-28 09:15:58 ')
insert into #temp values ( '101', 1, 1, 'Approved','2015-08-20 09:15:58 ')
select * from #temp
Try this:
SELECT T.ProjectID,
Documentno as Dno,
Revisionno as RNo,
CASE WHEN SUM(CASE WHEN T.Status <> 'Approved' THEN 1 ELSE 0 END) = 0
THEN Max(T.DateApproved) ELSE NULL
END as DateApproved
from #temp T
GROUP BY T.ProjectId, Documentno , Revisionno
This gives the following output when run against your test data:
PROJECT ID DNo TNo DateApproved
100 1 1 NULL
101 1 1 2015-08-20 09:15:58.000
You can do this with a case statement and a conditional aggregate. Get the count of statuses that are not Approved using COUNT(NULLIF(Status, 'Approved')). If this is 0 then get the max date approved:
SELECT ProjectID,
DateApproved = CASE WHEN COUNT(NULLIF(Status, 'Approved')) = 0 THEN MAX(DateApproved) END
FROM #Temp
GROUP BY ProjectID;

mySQL - return 0 as an aggregate result if a field not found

If for example I have:
CREATE TABLE application (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`month` VARCHAR(255) NOT NULL,
`amount` DECIMAL(9,2) NOT NULL)
;
INSERT INTO application
(`id`, `month`, `amount`)
VALUES
(1, 'january', 2000.00),
(2, 'february', 1000.00),
(3, 'january', 3000.00),
(4, 'january', 5000.00)
;
And then I run the query:
SELECT SUM(`amount`) as sum FROM application WHERE month IN ('january', 'february', 'march') GROUP BY `month`;
I get the result:
month sum
___________________
january | 10000.00
february | 1000.00
which is what the query was supposed to do however I'm looking for this result:
month sum
___________________
january | 10000.00
february | 1000.00
march | 0.00
how can I achieve this?
if anyone needs clarity don't vote down just ask and I will be more precise if i can.
cheers
SELECT m.mname, SUM(ISNULL(a.`amount`,0)) as sum
FROM
(
select 'january' as mname union all
select 'february' as mname union all
select 'march' as mname
) m LEFT JOIN application a on a.`month` = m.mname
GROUP BY a.`month`

mysql: count duplicate rows from all the groups

Let's say I have the follwing table:
id | fb_id | date |
---- ---------- ---------
1 1123 2009-1-1
2 1145 2009-1-1
3 1123 2009-1-2
4 1176 2009-1-2
I want to count the total users for each date, the total unique users and the returning users.
My code righte now is this one:
SELECT count(DISTINCT fb_id) as uniqueUsers, count(fb_id) as totalUsers, DATE_FORMAT(date, '%d %b %y') as zoom FROM ".PREFIX."zoom GROUP BY YEAR(date), MONTH(date), DAY(date)
I am expecting the following results:
Group 2009-1-1:
-total users: 2
-unique users: 2
-returning users:0
Group 2009-1-2:
-total users: 2
-unique users: 1
-returning users:1 (total users - unique users)
But instead I am getting:
Group 2009-1-1:
-total users: 2
-unique users: 2
-returning users:0
Group 2009-1-2:
-total users: 2
-unique users: 2
-returning users:0 (total users - unique users)
Any thoughts how I can make this work?
You can do a self join. Something like this
Sample Data
CREATE TABLE zoom
(`id` int, `fb_id` int, `date` datetime);
INSERT INTO zoom
(`id`, `fb_id`, `date`)
VALUES
(1, 1123, '2009-01-01 00:00:00'),
(2, 1145, '2009-01-01 00:00:00'),
(3, 1123, '2009-01-02 00:00:00'),
(4, 1176, '2009-01-02 00:00:00');
Query
SELECT
count(Znew.fb_id) as totalUsers,
count(Zold.fb_id) as returningUsers,
count(Znew.fb_id) - count(Zold.fb_id) as uniqueUsers,
DATE_FORMAT(Znew.date, '%d %b %y') as zoom
FROM zoom Znew
LEFT JOIN zoom Zold
ON Zold.date < Znew.date
AND Zold.fb_id = Znew.fb_id
GROUP BY Znew.date;
SQL Fiddle
Output
totalUsers returningUsers uniqueUsers zoom
2 0 2 01 Jan 09
2 1 1 02 Jan 09
That's because you were doing GROUP BY on YEAR(date),MONTH(date)etc...
Where you should do on 'DATE(date)' only
SELECT count(DISTINCT fb_id) as uniqueUsers,
count(fb_id) as totalUsers,
DATE_FORMAT(date, '%d %b %y') as zoom FROM ".PREFIX."zoom GROUP BY DATE(date)
Hope this helps