Get periods of each segment for each client - mysql

There are monthly periods of customer segments, I need to get the periods of each segment for each client.
Dataset:
CREATE TABLE segm (
dt DATE,
client_id VARCHAR(6),
segment_id INT
)
INSERT INTO segm VALUES
('2018-01-31' ,'A11111', 2),
('2018-02-28' ,'A11111', 2),
('2018-03-31' ,'A11111', 1),
('2018-04-30' ,'A11111', 1),
('2017-11-30' ,'B22222', 1),
('2017-10-31' ,'B22222', 1),
('2017-09-30' ,'B22222', 3),
('2017-09-30' ,'C33333', 1),
('2017-10-31' ,'C33333', 1)
I do a SQL query:
SELECT
client_id
, segment_id
, FIRST_VALUE(dt) OVER(PARTITION BY dt ORDER BY dt) AS "first_value"
, LAST_VALUE(dt) OVER(PARTITION BY dt ORDER BY dt) AS "last_value"
FROM segm
ORDER BY client_id, segment_id, "first_value", "last_value";
I get the result:
client_id
segment_id
"first_value"
"last_value"
A11111
1
2018-03-31
2018-03-31
A11111
1
2018-04-30
2018-04-30
A11111
2
2018-01-31
2018-01-31
A11111
2
2018-02-28
2018-02-28
B22222
1
2017-10-31
2017-10-31
B22222
1
2017-11-30
2017-11-30
B22222
3
2017-09-30
2017-09-30
C33333
1
2017-09-30
2017-09-30
C33333
1
2017-10-31
2017-10-31
And I need:
client_id
segment_id
"first_value"
"last_value"
A11111
1
2018-03-31
2018-04-30
A11111
2
2018-01-31
2018-02-28
B22222
1
2017-10-31
2017-11-30
B22222
3
2017-09-30
2017-09-30
C33333
1
2017-09-30
2017-10-31
How can I fix the request to get the desired result?
Thanks.

I don't think you need analytic functions, simple aggregation will give you what you need:
select Client_Id, Segment_Id, Min(dt) firstValue, Max(dt) LastValue
from segm
group by client_id, segment_id
order by Client_Id, Segment_Id;

You can simplify your solution by using a MAX and MIN aggregation functions with a GROUP BY clause:
SELECT
client_id,
segment_id,
MIN(dt) AS first_value,
MAX(dt) AS last_value
FROM
segm
GROUP BY
client_id,
segment_id
ORDER BY
client_id,
segment_id,
first_value,
last_value

Related

Running total in two tables

Consider two tables: invoices and payments. The invoices table contains records of invoices raised, and the payments table contains records of payments received.
invoices
id
date
cname
amount
1
2021-12-12
cname1
10000
2
2021-12-13
cname2
5000
3
2022-01-15
cname1
7000
4
2022-01-16
cname2
1000
payments
id
date
cname
amount
1
2022-01-05
cname1
5000
2
2022-01-07
cname2
5000
3
2022-02-05
cname1
10000
4
2022-02-06
cname2
1000
CALCULATE RUNNING BALANCE
Q) Extend the SQL query to do invoice / payment matching as follows (as of 28/2/2022)
matching
date
document_id
cname
amount
due
2021-12-12 00:00:00
1
cname1
10000
10000
2022-01-05 00:00:00
1
cname1
-5000
5000
2022-01-15 00:00:00
3
cname1
7000
12000
2022-02-05 00:00:00
3
cname1
-10000
2000
2021-12-13 00:00:00
2
cname2
5000
5000
2022-01-07 00:00:00
2
cname2
-5000
0
2022-01-16 00:00:00
4
cname2
1000
1000
2022-02-06 00:00:00
4
cname2
-1000
0
You can union both tables considering the second one with negative amount, and then a simple running total will produce the result you want. For example:
select
date,
id as document_id,
cname,
amount,
sum(amount) over(partition by id order by date) as due
from (
select * from invoices
union all select id, date cname, -amount from payments
) x
order by cname, date
SELECT `date`,
documentId,
cname,amount,
due FROM (SELECT `date`,
documentId,
cname,
amount,
(CASE WHEN #running_customer='' THEN #running_balance:=amount
WHEN #running_customer=cname THEN #running_balance:=#running_balance+amount ELSE #running_balance:=amount END) due,
#running_customer:=cname
FROM (SELECT `date`, id AS documentId,cname, amount FROM `invoices`i
UNION ALL
SELECT `date`, id AS documentId,cname, amount*-1 AS actionType FROM `payments` p) final
JOIN (SELECT #running_customer:='') rc
JOIN (SELECT #running_balance:=0) rb
ORDER BY cname, `date`) finalResult
You need to be using assignment operator for these kind of problems.

how to check if value from another column is present in every date -mysql?

I have a table:
date id count(sub_id)
2016-03-01 2 1
2016-03-02 650 1
2016-03-03 2 1
2016-03-04 2697 2
2016-03-05 2 4
2016-03-06 2697 3
2016-03-07 1000 2
2016-03-08 2 3
2016-03-09 2697 3
the dates go from 1st march 2016 to 15 march 2016.
i need to check what id is present in every date - from 1 to 15 march.
i.e what user submitted data on every day
so far I have this
select submission_date, id, count(submission_id)
from submissions
group by submission_date, id
having count(submission_id) >= 1
but this only shows the dates and users and how many submissions they made on every day - for example 2.
You must group by id and check in the HAVING clause if the distinct number of dates of each id is equal to the distinct number of dates in the table:
select id
from submissions
group by id
having count(distinct submission_date) = (select count(distinct submission_date) from submissions)
If there are no duplicate id, submission_date combinations you can change to:
having count(submission_date) = (select count(distinct submission_date) from submissions)
and if also there are no null dates:
having count(*) = (select count(distinct submission_date) from submissions)
You should GROUP BY date
CREATE TABLE table1 (
`date` DATE,
`id` INTEGER
);
INSERT INTO table1
(`date`, `id`)
VALUES
('2016-03-01', '2'),
('2016-03-02', '650'),
('2016-03-03', '2'),
('2016-03-04', '2697'),
('2016-03-03', '2697'),
('2016-03-05', '2'),
('2016-03-06', '2697'),
('2016-03-07', '1000'),
('2016-03-08', '2'),
('2016-03-09', '2697');
SELECT `date`, GROUP_CONCAT(DISTINCT `id` ORDER BY `id`)
FROM table1
GROUP BY `date`
ORDER BY `date`
date | GROUP_CONCAT(DISTINCT `id` ORDER BY `id`)
:--------- | :----------------------------------------
2016-03-01 | 2
2016-03-02 | 650
2016-03-03 | 2,2697
2016-03-04 | 2697
2016-03-05 | 2
2016-03-06 | 2697
2016-03-07 | 1000
2016-03-08 | 2
2016-03-09 | 2697
db<>fiddle here

How to Fetch records based on the Dates in MYSQL

I have sample data
ID Name Amount cal_amt Run_amt Dates
1 Test 15000 0.00 15000 2020-06-01
1 Test 15000 0.00 30000 2020-04-01
1 Test 15000 12000 30000 2020-05-01
2 Test_1 18000 0.00 25000 2020-06-01
2 Test_1 18000 0.00 35000 2020-04-01
2 Test_1 18000 16000 35000 2020-05-01
I need to get MAX(month) of Run_Amount i.e : 2020-06-01 --> 15000
Need to fetch cal_amt of current month i.e : 2020-05-01 --> 12000 and 0.00 also relates to this month 2020-04-01
I need to get output like this :
ID Name Amount cal_amt Run_amt
1 Test 15000 12000 15000
2 Test_1 18000 16000 25000
It is sample data but have another few more columns are there I have tried with MAX() conditions and
ROW_NUMBER()over (PARTITION BY run_amt order by Date )
Can anyone suggest me the best way
Use ROW_NUMBER() window function to get the row with the Run_amt of the max month and then with conditional aggregation:
SELECT t.ID, t.Name, t.Amount,
MAX(CASE WHEN LAST_DAY(Date) = LAST_DAY(CURRENT_DATE) THEN cal_amt END) cal_amt,
MAX(CASE WHEN t.rn = 1 THEN Run_amt END) Run_amt
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, Name, Amount ORDER BY Date DESC) rn
FROM tablename
) t
GROUP BY t.ID, t.Name, t.Amount
Or:
SELECT t.ID, t.Name, t.Amount,
MAX(t.cal_amt) cal_amt,
MAX(t.Run_amt) Run_amt
FROM (
SELECT ID, Name, Amount,
MAX(CASE WHEN LAST_DAY(Date) = LAST_DAY(CURRENT_DATE) THEN cal_amt END)
OVER (PARTITION BY ID, Name, Amount ORDER BY Date DESC) cal_amt,
FIRST_VALUE(Run_amt) OVER (PARTITION BY ID, Name, Amount ORDER BY Date DESC) Run_amt
FROM tablename
) t
GROUP BY t.ID, t.Name, t.Amount
See the demo.
Results:
> ID | Name | Amount | cal_amt | Run_amt
> -: | :----- | -----: | ------: | ------:
> 1 | Test | 15000 | 12000 | 15000
> 2 | Test_1 | 18000 | 16000 | 25000

How do I Rank column in SQL based on row day-difference and partition?

I am trying to get RANK() on a column based on a row difference < 3.
select hotel.*,
IFNULL(datediff(visit_date, lag(visit_date)
OVER (partition by hotel_id)), 0) as diff
from hotel;
I get the following output,
hotel_id customer_id visit_date diff
1 1 2020-01-01 0
1 2 2020-01-03 2
2 1 2020-01-01 0
2 2 2020-01-10 9
2 3 2020-01-14 4
3 1 2020-01-04 0
3 1 2020-01-11 7
I am stuck with the RANK() part.
Expected Output:
If Day Difference is less than 3 then 1 else 2. And if the next one is greater than 3 days the 3, and so on
hotel_id customer_id visit_date rank
1 1 2020-01-01 1
1 2 2020-01-03 1
2 1 2020-01-01 1
2 2 2020-01-10 2
2 3 2020-01-14 3
3 1 2020-01-04 1
3 1 2020-01-11 2
You can use this query to generate your rank values. It uses a couple of CTEs, the first to generate row numbers for each visit (on a per-hotel basis), and the second (recursive) CTE to generate the rank values, iterating through the rows from the first CTE and only incrementing the rank when the difference in dates is more than 2 days:
WITH RECURSIVE hotel_rows AS (
SELECT hotel_id, customer_id, visit_date,
ROW_NUMBER() OVER (PARTITION BY hotel_id ORDER BY visit_date) AS rn
FROM hotel
ORDER BY hotel_id, visit_date
),
ranks AS (
SELECT hotel_id, customer_id, visit_date, rn, 1 AS `rank`
FROM hotel_rows
WHERE rn = 1
UNION ALL
SELECT h.hotel_id, h.customer_id, h.visit_date, h.rn,
r.rank + (h.visit_date > r.visit_date + INTERVAL 2 DAY)
FROM hotel_rows h
JOIN ranks r ON h.hotel_id = r.hotel_id
AND h.rn = r.rn + 1
)
SELECT SELECT hotel_id, customer_id, visit_date, `rank`
FROM ranks
ORDER BY hotel_id, visit_date
Output (for my slightly extended demo):
hotel_id customer_id visit_date rank
1 1 2020-01-01 1
1 2 2020-01-03 1
2 1 2020-01-01 1
2 2 2020-01-10 2
2 3 2020-01-14 3
2 1 2020-01-15 3
2 2 2020-01-20 4
3 1 2020-01-04 1
3 1 2020-01-11 2
Demo on dbfiddle
If you want the result as per your given condition then you can try below in SQL Server. here is the Demo
select
hotel_id,
customer_id,
visit_date,
case
when days < 3 then 1
else 2
end as rnk
from
(
select
*,
datediff(day, n_date, visit_date) as days
from
(
select
*,
coalesce(lag(visit_date) over (partition by hotel_id order by visit_date), visit_date) as n_date
from hotel
) val
)days
I would express this as:
select h.*,
(case when lag(visit_date) over (partition by hotel_id order by visit_date) < visit_date - interval 3 day
then 2 else 1
end)
from hotel h;
Edit;
Based on your revised point, you want to assign groups based on the date difference and then use row_number():
select h.*,
1 + sum( coalesce(visit_date > prev_vd + interval 3 day, 0) ) over (partition by hotel_id order by visit_date) as grp
from (select h.*,
lag(visit_date) over (partition by hotel_id order by visit_date) as prev_vd
from hotel h
) h;
Here is a db<>fiddle.

Getting last values for week in mysql

I would like know how to get the last value for each week.
Let's say I have the next values
-- Table 1 --
day value
2018-03-12 32
2018-02-14 42
2018-03-16 62
2018-03-19 82
2018-03-20 92
2018-03-21 102
2018-03-27 112
2018-03-28 122
2018-03-29 132
How can I get the next values which are the last values for each week. Assuming the week start on Monday.
Day Value
2018-03-16 62
2018-03-21 102
2018-03-29 132
I have everything settled here SQL Fiddle
You can get the week number of day then get the max value per week number.
select t1.*
from table1 t1
join (
select week(day) as wknum,
max(day) as day
from table1
group by week(day)
) t2
on t1.day=t2.day
Result:
day value
2018-03-16 62
2018-03-21 102
2018-03-29 132
You can group by YEARWEEK()
create table tbl (day date, value int);
✓
insert into tbl values
('2018-03-12', 32),
('2018-02-14', 42),
('2018-03-16', 62),
('2018-03-19', 82),
('2018-03-20', 92),
('2018-03-21', 102),
('2018-03-27', 112),
('2018-03-28', 122),
('2018-03-29', 132);
✓
select day, yearweek(day) from tbl;
day | yearweek(day)
:--------- | ------------:
2018-03-12 | 201810
2018-02-14 | 201806
2018-03-16 | 201810
2018-03-19 | 201811
2018-03-20 | 201811
2018-03-21 | 201811
2018-03-27 | 201812
2018-03-28 | 201812
2018-03-29 | 201812
select day, value
from tbl
join (select max(day) mday
from tbl
group by yearweek(day)) t
on day = mday
day | value
:--------- | ----:
2018-02-14 | 42
2018-03-16 | 62
2018-03-21 | 102
2018-03-29 | 132
dbfiddle here
This solution uses window functions and picks the latest date within the week.
https://dev.mysql.com/doc/refman/8.0/en/window-functions-usage.html
I use SQL Server, but I believe this is the MySQL equivalent:
with cte AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY WEEKOFYEAR([day]) ORDER BY DAYOFWEEK([day]) DESC) AS counter_
from #table1
)
SELECT [day], [value]
FROM cte
WHERE counter_ = 1
Here's how you would do it in SQL Server - Use mysql equivalent
select b.day, b.value from (
select datepart(ww,day) a, max(day) b
from yourtable
group by datepart(ww,day))a
join yourtable b on a.a=datepart(ww,b.day) and a.b=b.day
Try this FIDDLE:
= Order by the closest to the end of every week
= Group by week
SELECT day, value
FROM (SELECT * FROM table1 ORDER BY DATEDIFF(day + INTERVAL 6 - weekday(day) DAY, day) ASC) t
GROUP BY week(day);