How to calculate the minimum amount during the period? - mysql

I have a table named wallet, which is like this:
// wallet
+----+----------+----------+------------+
| id | user_id | amount | created_at |
+----+----------+----------+------------+
| 1 | 5 | 1000 | 2022-05-20 | -- 1000
| 2 | 5 | 500 | 2022-05-20 | -- 1500
| 3 | 5 | -1000 | 2022-05-21 | -- 500 <-- this
| 4 | 5 | 4000 | 2022-05-23 | -- 4500
| 5 | 5 | -2000 | 2022-05-23 | -- 2500
| 6 | 5 | 1000 | 2022-05-24 | -- 3500
+----+----------+----------+------------+
As you can see, (after all deposits and withdrawals), sum(amount) is 500 at the lower point (minimum calculated amount) in the period which is happened at 2022-05-21. So, selecting this row is the expected result:
| 3 | 5 | -1000 | 2022-05-21 |
Any idea how can I get that?

select t0.id, t0.user_id, t0.sum_amt, t0.rank_amt
from
(
select t.id, t.user_id, sum_amt, rank() over(partition by t.user_id order by t.sum_amt) rank_amt
from
(
select t1.id, t1.user_id, SUM(t2.amount) as sum_amt
from wallet t1
inner join wallet t2 on t1.id >= t2.id and t1.user_id = t2.user_id
group by t1.id, t1.user_id
) t
) t0
where t0.rank_amt = 1;
Fiddle

WITH
cte1 AS ( SELECT *,
SUM(amount) OVER (PARTITION BY user_id
ORDER BY created_at ASC) cumulative_sum
FROM wallet
),
cte2 AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY user_id
ORDER BY cumulative_sum ASC,
created_at DESC) rn
FROM cte1
)
SELECT *
FROM cte2
WHERE rn = 1;

Related

Set limit for IN condition element that evaluate true

table: t
+--------------+-----------+-----------+
| Id | price | Date |
+--------------+-----------+-----------+
| 1 | 30 | 2021-05-09|
| 1 | 24 | 2021-04-26|
| 1 | 33 | 2021-04-13|
| 2 | 36 | 2021-04-18|
| 3 | 15 | 2021-04-04|
| 3 | 33 | 2021-05-06|
| 4 | 46 | 2021-02-16|
+--------------+-----------+-----------+
I want to select rows where id is 1,2,4 and get maximum 2 row for each id by date descending order.
+--------------+-----------+-----------+
| Id | price | Date |
+--------------+-----------+-----------+
| 1 | 30 | 2021-05-09|
| 1 | 24 | 2021-04-26|
| 2 | 36 | 2021-04-18|
| 4 | 46 | 2021-02-16|
+--------------+-----------+-----------+
Something like:
Select * from t where Id IN ('1','2','4') limit 2 order by Date desc;
this will limit the overall result fetched.
Use row_number():
select id, price, date
from (select t.*,
row_number() over (partition by id order by date desc) as seqnum
from t
where id in (1, 2, 4)
) t
where seqnum <= 2;
Probably the most efficient method is a correlated subquery:
select t.*
from t
where t.id in (1, 2, 4) and
t.date >= coalesce( (select t2.date
from t t2
where t2.id = t.id
order by t2.date desc
limit 1,1
), t.date
);
For performance, you want an index on (id, date). Also, this can return duplicates if there are multiple rows for a given id on the same date.
Here is a db<>fiddle.

How query which employee has been full-time employeed

I have the following MySQL table:
+----+---------+----------------+------------+
| id | user_id | employment_type| date |
+----+---------+----------------+------------+
| 1 | 9 | full-time | 2013-01-01 |
| 2 | 9 | half-time | 2013-05-10 |
| 3 | 9 | full-time | 2013-12-01 |
| 4 | 248 | intern | 2015-01-01 |
| 5 | 248 | full-time | 2018-10-10 |
| 6 | 58 | half-time | 2020-10-10 |
| 7 | 248 | NULL | 2021-01-01 |
+----+---------+----------------+------------+
I want to query, for example, which employees were full-time employed on 2014-01-01.
Which SQL query I need to pass to get the correct result?
In this case, the result will be an employee with user_id=9;
Is this table properly structured to be possible to get such a result?
If your version of MySql is 8.0+ you can do it with FIRST_VALUE() window function:
SELECT DISTINCT user_id
FROM (
SELECT user_id,
FIRST_VALUE(employment_type) OVER (PARTITION BY user_id ORDER BY date DESC) last_type
FROM tablename
WHERE date <= '2014-01-01'
) t
WHERE last_type = 'full-time'
For previous versions of MySql you can do it with NOT EXISTS:
SELECT t1.user_id
FROM tablename t1
WHERE t1.date <= '2014-01-01' AND t1.employment_type = 'full-time'
AND NOT EXISTS (
SELECT 1
FROM tablename t2
WHERE t2.user_id = t1.user_id AND t2.date BETWEEN t1.date AND '2014-01-01'
AND COALESCE(t2.employment_type, '') <> t1.employment_type
)
See the demo.
Results:
| user_id |
| ------- |
| 9 |
You want the most recent record on or before that date. I would use row_number():
select t.*
from (select t.*,
row_number() over (partition by user_id order by date desc) as seqnum
from t
where date <= '2014-01-01'
) t
where seqnum = 1 and employment_type = 'full_time';
A fun method that just uses group by is:
select t.user_id
from t
where t.date <= '2014-01-01'
group by t.user_id
having max(date) = max(case when employment_type = 'full_time' then date end);
This checks that the maximum date -- before the cutoff -- is the same as the maximum date for 'full-time'.

How to sum and count Id's of different tables from a Union

I have 3 tables councils, station_levy, market_levy, and the station table is joined to get councils in station_levy.
I need to get data by council sum the number of station_levy + market_levy and get the total amount tendered.
Tables are as follows
councils
---------------+----------+
| council_id | Name |
+--------------+----------+
| 1 | LSK |
---------------+----------+
| 2 | KBW |
---------------+----------+
station_levy
------------------+-------------+-----------------+
| station_levy_id | station_id | amount_tendered |
+-----------------+-------------+-----------------+
| 1 | 3 | 10.00 |
+-----------------+-------------+-----------------+
| 2 | 3 | 10.00 |
+-----------------+-------------+-----------------+
| 3 | 1 | 5.00 |
+-----------------+-------------+-----------------+
(station_id = 1 is found in the LSK council_id=1 And station_id = 3 is found in the KBW council_id=2)
market_levy
------------------+-------------+-----------------+
| market_levy_id | council_id | amount_tendered |
+-----------------+-------------+-----------------+
| 1 | 1 | 5.00 |
+-----------------+-------------+-----------------+
| 2 | 2 | 5.00 |
+-----------------+-------------+-----------------+
| 3 | 1 | 5.00 |
+-----------------+-------------+-----------------+
mysql
SELECT c.council_name, (COUNT(market_levy.market_levy_id)+ COUNT(st.station_levy_id )) count, SUM(amount_tendered) revenue
FROM councils c
JOIN (
(SELECT council_id, amount_tendered,market_levy_id FROM market_levy WHERE transaction_date >= CURDATE() )
UNION ALL
(SELECT station_levy_id , councils.council_id, amount_tendered
FROM station_levy st
JOIN stations ON stations.station_id = st.station_id
JOIN councils ON councils .council_id= stations .council_id
WHERE transaction_datetime >= CURDATE()
)) totalCouncilRevenue USING (council_id)
group by council_id, c.council_name ORDER BY SUM(amount_tendered) DESC
Expected result
------------------+-------------+-----------------+
| council_name | count | revenue |
+-----------------+-------------+-----------------+
| LSK | 3 | 15.00 |
+-----------------+-------------+-----------------+
| KBW | 3 | 25.00 |
+-----------------+-------------+-----------------+
You are confusing columns in your UNION ALL matching council_id with station_levy_id, amount_tendered with council_id, and market_levy_id with amount_tendered.
Then, in your main query you try to access market_levy.market_levy_id and st.station_levy_id, but these columns are not accessible, as you select from a subquery called totalCouncilRevenue, not from tables labelled market_levy and st there.
Your query fixed:
SELECT
c.council_name,
COUNT(*) AS transaction_count,
SUM(amount_tendered) AS revenue
FROM councils c
JOIN
(
SELECT council_id, amount_tendered
FROM market_levy
WHERE transaction_date >= CURDATE()
UNION ALL
SELECT s.council_id, st.amount_tendered
FROM station_levy st
JOIN stations s ON s.station_id = st.station_id
WHERE st.transaction_datetime >= CURDATE()
) totalCouncilRevenue USING (council_id)
GROUP BY council_id, c.council_name
ORDER BY SUM(amount_tendered) DESC;
I prefer aggregating before joining, though:
SELECT
c.council_name,
COALESCE(t1.cnt, 0) + COALESCE(t2.cnt, 0) AS transaction_count,
COALESCE(t1.total, 0) + COALESCE(t2.total, 0) AS revenue
FROM councils c
LEFT JOIN
(
SELECT council_id, SUM(amount_tendered) as total, COUNT(*) as cnt
FROM market_levy
WHERE transaction_date >= CURDATE()
GROUP BY council_id
) t1 USING (council_id)
LEFT JOIN
(
SELECT s.council_id, SUM(st.amount_tendered) as total, COUNT(*) as cnt
FROM station_levy st
JOIN stations s ON s.station_id = st.station_id
WHERE st.transaction_datetime >= CURDATE()
GROUP BY s.council_id
) t2 USING (council_id)
ORDER BY revenue DESC;
Such queries are usually less prone to errors and are sometimes faster, because they may be able to use indexes more efficiently.

Mysql query the most visited url per user

I need to count the urls with the most visits per user.
Table name:visit_actions: mysql version:5.7
+----+--------+---------+---------------------+
| id | url_id | user_id | server_time |
+----+--------+---------+---------------------+
| 1 | 265338 | 4 | 2019-11-07 08:54:47 |
| 2 | 265405 | 1 | 2019-11-07 08:55:21 |
| 3 | 265391 | 4 | 2019-11-07 08:56:03 |
| 4 | 265338 | 1 | 2019-11-07 08:57:36 |
| 5 | 265338 | 1 | 2019-11-07 10:02:46 |
| 21 | 265207 | 5 | 2019-11-08 02:17:30 |
| 22 | 265207 | 5 | 2019-11-08 02:17:30 |
+----+--------+---------+---------------------+
I have tried this sql:
SELECT
url_id,
user_id,
count( * ) AS visit_times
FROM
visit_actions
GROUP BY
user_id,
url_id
ORDER BY
visit_times DESC
I expect the output :
+--------+---------+-------------+
| url_id | user_id | visit_times |
+--------+---------+-------------+
| 265338 | 4 | 1 |
| 265207 | 5 | 2 |
| 265338 | 1 | 2 |
+--------+---------+-------------+
Each user only finds the one with the most url_id.
Click Here Online Demo . Thanks folks!
On MySQL 8+ a fairly clean solution uses ROW_NUMBER with aggregation:
WITH cte AS (
SELECT url_id, user_id, COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY COUNT(*) DESC) rn
FROM visit_actions
GROUP BY url_id, user_id
)
SELECT
url_id,
user_id,
cnt AS visit_times
FROM cte
WHERE rn = 1;
If you had to do this on MySQL 5.7, here is one way:
SELECT
t1.url_id,
t1.user_id,
t1.cnt AS visit_times
FROM
(
SELECT url_id, user_id, COUNT(*) AS cnt
FROM visit_actions
GROUP BY url_id, user_id
) t1
INNER JOIN
(
SELECT user_id, MAX(cnt) AS max_cnt
FROM
(
SELECT url_id, user_id, COUNT(*) AS cnt
FROM visit_actions
GROUP BY url_id, user_id
) t
GROUP BY user_id
) t2
ON t1.user_id = t2.user_id AND
t1.cnt = t2.max_cnt;
Here's a MySQL 5.7 solution. Basically you have to find the maximum number of visits per user and then join the count of visits per user to that table to give the user and urls they have visited most. Note in your sample that yields 6 rows for user 1 as they have visited 6 sites twice.
SELECT c.url_id, c.user_id, c.visit_times
FROM (SELECT url_id, user_id, count( * ) AS visit_times
FROM visit_actions
GROUP BY user_id, url_id) c
JOIN (SELECT user_id, MAX(visit_times) AS max_visits
FROM (SELECT url_id, user_id, count( * ) AS visit_times
FROM visit_actions
GROUP BY user_id, url_id ) c
GROUP BY user_id) m ON m.user_id = c.user_id AND m.max_visits = c.visit_times
Output:
url_id user_id visit_times
265207 0 2
265338 1 2
265391 1 2
265394 1 2
265396 1 2
265410 1 2
265431 1 2
265338 4 1
Demo on SQLFiddle

mySQL Ranking (and draws)

Next weekend we're having a competition with 3 qualifications a semifinal and a final. Only the best 15 participants could compete in the semifinal. Only the best 6 compete in the Finals.
in the qualifications you get a score from 0 to 100 for each qualification
I'm looking to find a way to select the contesters for the semi-final. This should be based on (rank of qualification1) * (rank of qualification2) * (rank of qualification3)
so i need something like:
select id, name, ((.... as RANK_OF_SCORE_1) * (.. as RANK_OF_SCORE_2) * (... as RANK_OF_SCORE_3)) as qualification_score from participants order by qualification_score desc limit 15
but of course this is not valid mySQL.
Besides this problem if tho contesters have the same score, they should be both included in the semi-finals even if this exceeds the maximum of 15.
For the finals, we would like to select the best 6 of the semi-final scores. If 2 scores are the same we would like to select on the qualifications..
option 1 : use postgres, which support windowing functions (namely RANK() and DENSE_RANK())
SELECT user_id, score, rank() over (order by score desc) from scores;
Time : 0.0014 s
option 2 : use a self- join : the rank of a user with score X is (1 +the count(*) of users with score less than X) ; this is likely to be pretty slow
CREATE TABLE scores( user_id INT PRIMARY KEY, score INT, KEY(score) );
INSERT INTO scores SELECT id, rand()*100 FROM serie LIMIT 1000;
SELECT a.user_id, a.score, 1+count(b.user_id) AS rank
FROM scores a
LEFT JOIN scores b ON (b.score>a.score)
GROUP BY user_id ORDER BY rank;
+---------+-------+------+
| user_id | score | rank |
+---------+-------+------+
| 381 | 100 | 1 |
| 777 | 100 | 1 |
| 586 | 100 | 1 |
| 907 | 100 | 1 |
| 790 | 100 | 1 |
| 253 | 99 | 6 |
| 393 | 99 | 6 |
| 429 | 99 | 6 |
| 376 | 99 | 6 |
| 857 | 99 | 6 |
| 293 | 99 | 6 |
| 156 | 99 | 6 |
| 167 | 98 | 13 |
| 594 | 98 | 13 |
| 690 | 98 | 13 |
| 510 | 98 | 13 |
| 436 | 98 | 13 |
| 671 | 98 | 13 |
time 0.7s
option 3 :
SET #rownum = 0;
SELECT a.user_id, a.score, b.r FROM
scores a
JOIN (
SELECT score, min(r) AS r FROM (
SELECT user_id, score, #rownum:=#rownum+1 AS r
FROM scores ORDER BY score DESC
) foo GROUP BY score
) b USING (score)
ORDER BY r;
time : 0.0014 s
EDIT
SET #rownum1 = 0;
SET #rownum2 = 0;
SET #rownum3 = 0;
SELECT s.*, s1.r, s2.r, s3.r FROM
scores s
JOIN
(
SELECT score_1, min(r) AS r FROM (
SELECT score_1, #rownum1:=#rownum1+1 AS r
FROM scores ORDER BY score_1 DESC
) foo GROUP BY score_1
) s1 USING (score_1) JOIN (
SELECT score_2, min(r) AS r FROM (
SELECT score_2, #rownum2:=#rownum2+1 AS r
FROM scores ORDER BY score_2 DESC
) foo GROUP BY score_2
) s2 USING (score_2) JOIN (
SELECT score_3, min(r) AS r FROM (
SELECT score_3, #rownum3:=#rownum3+1 AS r
FROM scores ORDER BY score_3 DESC
) foo GROUP BY score_3
) s3 USING (score_3)
ORDER BY s1.r * s2.r * s3.r;