MySQL SUM in different ways - mysql

I have two tables
user_raters:
| id(int) | to_id(int) | value(int) | created_at(datetime)
|1 | 2 | 1 | 2009-03-01 00:00:00
EDIT: I changed the user_rater_id. history_user_raters.user_rater_id is related to user_raters.id
history_user_raters:
| id(int) | user_rater_id(int) | value(int) | created_at(datetime)
| 1 | 1 | 1 | 2009-03-02 00:00:00
| 2 | 1 | 1 | 2009-03-02 00:00:00
| 3 | 1 | -1 | 2009-03-02 00:00:00
| 4 | 1 | 1 | 2009-03-03 00:00:00
| 5 | 1 | -1 | 2009-03-03 00:00:00
| 6 | 1 | -1 | 2009-03-03 00:00:00
| 7 | 1 | -1 | 2009-03-03 00:00:00
I want to count the sum of the values from history_user_raters as it relates to the to_id from user_raters. The result from the query should be:
| year | month | day | total | down | up
| 2009 | 3 | 2 | 1 | 1 | 2
| 2009 | 3 | 3 | -2 | 3 | 1
I have a query that is close, but it is not counting the up and down correctly. The total is right. Can some one help me write the query or new query that calculates correct up and down?
My current query:
SELECT
YEAR(history.created_at) AS `year`,
MONTH(history.created_at) AS `month`,
DAY(history.created_at) AS `day`,
SUM(history.value) as `total`,
(SELECT
abs(SUM(historydown.value))
FROM `user_raters` as raterdown
INNER JOIN `history_user_raters` AS historydown
WHERE raterdown.to_id = 2
AND historydown.value = -1
AND date(historydown.created_at)
GROUP BY history.created_at) as down,
(SELECT SUM(historyup.value)
FROM `user_raters` as raterup
INNER JOIN `history_user_raters` AS historyup
WHERE raterup.to_id = 2
AND historyup.value = 1
AND date(history.created_at)
GROUP BY raterup.to_id) as up
FROM `user_raters`
INNER JOIN history_user_raters AS history ON user_raters.id = history.user_rater_id
WHERE (user_raters.to_id = 2)
GROUP BY DATE(history.created_at)

I might see it too simply (and sorry I can't test with data at the moment), but I'm guessing the following trick with two CASE statements would do just what is needed
SELECT
YEAR(history.created_at) AS year,
MONTH(history.created_at) AS month,
DAY(history.created_at) AS day,
SUM(history.value) as total,
SUM(CASE WHEN history.value < 0 THEN history.value ELSE 0 END) as down,
SUM(CASE WHEN history.value > 0 THEN history.value ELSE 0 END) as up
FROM `user_raters`
INNER JOIN `history_user_raters` AS history
ON user_raters.id = history.user_rater_id
WHERE (user_raters.to_id = 1) -- or some other condition...
GROUP BY DATE(history.created_at)

EDIT: #OMG Ponies deleted his answer. This response make no sense now, but I am not going to delete my answer, because I think it is silly.
#OMG ponies
Your query runs, but it returns no results. I had to adjust it a bit to add the to_id in the main queries where clause
SELECT
YEAR( t.created_at ) AS `year` ,
MONTH( t.created_at ) AS `month` ,
DAY( t.created_at ) AS `day` ,
SUM( t.value ) AS `total` ,
MAX( COALESCE( x.sum_down, 0 ) ) AS down,
MAX( COALESCE( y.sum_up, 0 ) ) AS up
FROM history_user_raters AS t
JOIN user_raters AS ur ON ur.to_id = t.user_rater_id
LEFT JOIN (
SELECT hur.user_rater_id,
SUM( hur.value ) AS sum_down
FROM history_user_raters AS hur
WHERE hur.value = -1
GROUP BY hur.user_rater_id
) AS x ON x.user_rater_id = t.user_rater_id
LEFT JOIN (
SELECT hur.user_rater_id,
SUM( hur.value ) AS sum_up
FROM history_user_raters AS hur
WHERE hur.value =1
GROUP BY hur.user_rater_id
) AS y ON y.user_rater_id = t.user_rater_id
WHERE ur.to_id =1
GROUP BY YEAR( t.created_at ) , MONTH( t.created_at ) , DAY( t.created_at )

Related

How to convert rows to columns?

I've the following query:
SELECT first_period, period, sum(num) trans_num
FROM (SELECT (DATEDIFF(created_at, '2022-12-10') DIV 6) period,
user_id,
count(1) num,
MIN(MIN(DATEDIFF(created_at, '2022-12-10') DIV 6)) OVER (PARTITION BY user_id) as first_period
FROM pos_transactions
WHERE DATE(created_at) >= '2022-12-10'
GROUP BY user_id, DATEDIFF(created_at, '2022-12-10') DIV 6
) u
GROUP BY first_period, period
ORDER BY first_period, period
It returns the following result:
But now I need to make it visualize like a Cohort diagram. So I need to restructure the same result as follows:
+--------------+------+------+------+------+
| first_period | 0 | 1 | 2 | 3 |
+--------------+------+------+------+------+
| 0 | 6230 | 2469 | 2846 | 1713 |
| 1 | | 2589 | 742 | 375 |
| 2 | | | 3034 | 397 |
| 3 | | | | 1207 |
+--------------+------+------+------+------+
Any idea how can I do that?
WITH YOUR_TABLE_DATA(FIRST_PERIOD,PERIOD,TRANS_NUM)AS
(
SELECT 0,0,6230 UNION ALL
SELECT 0,1,2469 UNION ALL
SELECT 0,2,2846 UNION ALL
SELECT 0,3,1713 UNION ALL
SELECT 1,1,2589 UNION ALL
SELECT 1,2,742 UNION ALL
SELECT 1,3,375 UNION ALL
SELECT 2,2,3034 UNION ALL
SELECT 2,3,397 UNION ALL
SELECT 3,3,1207
)
SELECT C.FIRST_PERIOD,
MAX(
CASE
WHEN C.PERIOD=0
THEN C.TRANS_NUM
ELSE 0
END)AS ZERO,
MAX(
CASE
WHEN C.PERIOD=1
THEN C.TRANS_NUM
ELSE 0
END)AS ONE,
MAX(
CASE
WHEN C.PERIOD=2
THEN C.TRANS_NUM
ELSE 0
END)AS TWO,
MAX(
CASE
WHEN C.PERIOD=3
THEN C.TRANS_NUM
ELSE 0
END)AS THREE
FROM YOUR_TABLE_DATA AS C
GROUP BY C.FIRST_PERIOD

Merge two queries that use different timeframes for min/max/avg

I am using 10.1.39-MariaDB - mariadb.org binary and I have the following table:
| id | date | ticker | close |
|-------|---------------------|--------|-----------|
| 39869 | 2019-09-18 00:00:00 | AAPL | 221.96160 |
| 39870 | 2019-09-17 00:00:00 | AAPL | 220.70000 |
| 39871 | 2019-09-16 00:00:00 | AAPL | 219.90000 |
| 39872 | 2019-09-13 00:00:00 | AAPL | 218.75000 |
| 39873 | 2019-09-12 00:00:00 | AAPL | 223.09000 |
| 39874 | 2019-09-11 00:00:00 | AAPL | 223.59000 |
| 39875 | 2019-09-10 00:00:00 | AAPL | 216.70000 |
I have two queries where I am calculating metrics for 52-weeks and the second query calculates aggregation metrics for 20-days:
52-weeks:
SELECT
Y.*
FROM
(
SELECT
MAX(CLOSE) AS week_52_High,
DATE AS week_52_High_date,
MIN(CLOSE) AS week_52_Low,
DATE AS week_52_Low_date,
AVG(CLOSE) AS week_52_Avg
FROM
`prices`
WHERE
DATE >= DATE(NOW()) - INTERVAL 52 WEEK AND ticker = "AAPL") Y
LEFT JOIN prices tmax52 ON
tmax52.date = Y.week_52_High_date AND tmax52.close = week_52_High
LEFT JOIN prices tmin52 ON
tmin52.date = Y.week_52_Low_date AND tmin52.close = week_52_Low
LEFT JOIN prices tavg52 ON
tavg52.close = week_52_Avg
20-days
SELECT
Y.*
FROM
(
SELECT
MAX(CLOSE) AS day_20_High,
DATE AS day_20_High_date,
MIN(CLOSE) AS day_20_Low,
DATE AS day_20_Low_date,
AVG(CLOSE) AS day_20_Avg
FROM
`prices`
WHERE
DATE >= DATE(NOW()) - INTERVAL 20 DAY AND ticker = "AAPL") Y
LEFT JOIN prices tmax20 ON
tmax20.date = Y.day_20_High_date AND tmax20.close = day_20_High
LEFT JOIN prices tmin20 ON
tmin20.date = Y.day_20_Low_date AND tmin20.close = day_20_Low
LEFT JOIN prices tavg20 ON
tavg20.close = day_20_Avg
Both queries calculate the min/max/avg for each close price and attach the days, when this happened.
Any suggestions how to merge these two queries to get everything in 1 output?
I appreciate your replies!
Your first problem is that your query won't actually work. The correct way to get the dates for the high and low values is as below. Note that it is meaningless to try and get a date for the average close as it's highly unlikely that the stock will have closed at that price.
SELECT
Y.day_20_High,
tmax20.DATE AS day_20_High_date,
Y.day_20_Low,
tmin20.DATE AS day_20_Low_date,
Y.day_20_Avg
FROM
(
SELECT
MAX(CLOSE) AS day_20_High,
MIN(CLOSE) AS day_20_Low,
ROUND(AVG(CLOSE),2) AS day_20_Avg
FROM
`prices`
WHERE
DATE >= CURDATE() - INTERVAL 20 DAY AND ticker = "AAPL") Y
LEFT JOIN prices tmax20 ON tmax20.close = Y.day_20_High
LEFT JOIN prices tmin20 ON tmin20.close = Y.day_20_Low
Output (for my sample data)
day_20_High day_20_High_date day_20_Low day_20_Low_date day_20_Avg
107.50 2019-09-20 101.10 2019-09-10 104.05
Demo on dbfiddle
Having corrected the query, you can now just JOIN to the same query for 52-week data:
SELECT
Y20.day_20_High,
tmax20.DATE AS day_20_High_date,
Y20.day_20_Low,
tmin20.DATE AS day_20_Low_date,
Y20.day_20_Avg,
Y52.week_52_High,
tmax52.DATE AS week_52_High_date,
Y52.week_52_Low,
tmin52.DATE AS week_52_Low_date,
Y52.week_52_Avg
FROM ((
SELECT
MAX(CLOSE) AS day_20_High,
MIN(CLOSE) AS day_20_Low,
ROUND(AVG(CLOSE),2) AS day_20_Avg
FROM
`prices`
WHERE
DATE >= CURDATE() - INTERVAL 20 DAY AND ticker = "AAPL") Y20
LEFT JOIN prices tmax20 ON tmax20.close = Y20.day_20_High
LEFT JOIN prices tmin20 ON tmin20.close = Y20.day_20_Low)
JOIN ((
SELECT
MAX(CLOSE) AS week_52_High,
MIN(CLOSE) AS week_52_Low,
ROUND(AVG(CLOSE),2) AS week_52_Avg
FROM
`prices`
WHERE
DATE >= DATE(NOW()) - INTERVAL 52 WEEK AND ticker = "AAPL") Y52
LEFT JOIN prices tmax52 ON tmax52.close = Y52.week_52_High
LEFT JOIN prices tmin52 ON tmin52.close = Y52.week_52_Low)
Output (for my sample data)
day_20_High day_20_High_date day_20_Low day_20_Low_date day_20_Avg week_52_High week_52_High_date week_52_Low week_52_Low_date week_52_Avg
107.50 2019-09-20 101.10 2019-09-10 104.05 109.70 2019-08-24 100.00 2019-08-21 104.19
Demo on dbfiddle
If you want to merge your queries one after another:
with t as (
select 0 as id, 1 as v0, 2 as v1
union all select 1 as id, 1 as v0, 2 as v1
)
select *
from (
select t.*, 'weekly_report' as typ from t
union all
select t.*, 'monthly_report' as typ from t
) as t1
id | v0 | v1 | typ
-: | -: | -: | :-------------
0 | 1 | 2 | weekly_report
1 | 1 | 2 | weekly_report
0 | 1 | 2 | monthly_report
1 | 1 | 2 | monthly_report
db<>fiddle here
And to have your results column by column, do a join:
with t as (
select 0 as id, 1 as v0, 2 as v1
union all select 1 as id, 1 as v0, 2 as v1
)
select t0.id, t0.v0, t0.v1, t1.v0, t1.v1
from (
select * from t
) as t0
full join (
select * from t
) as t1 on t0.id = t1.id
id | v0 | v1 | v0 | v1
-: | -: | -: | -: | -:
0 | 1 | 2 | 1 | 2
1 | 1 | 2 | 1 | 2
db<>fiddle here

Group by multiple timestamps; join months but keep separate values

I need to grab the # of settled and unsettled orders by month. Here is what I have so far:
SELECT
CONCAT(MONTH(time_settle), "/", YEAR(time_settle)) as month_settled, count(*) as settled
FROM payment_order
WHERE time_settle IS NOT NULL
AND datediff(time_settle,NOW())>-90
AND datediff(time_settle,NOW())<+90
AND account_id=28 AND current_state !="deleted"
GROUP BY MONTH(time_settle);
The result is:
month_settled | settled
-------------------------
6/2014 | 1
9/2014 | 10
12/2014 | 1
And unsettled I use time_due:
SELECT
CONCAT(MONTH(time_due), "/", YEAR(time_due)) as month_due, count(*) as unsettled
FROM payment_order
WHERE time_settle IS NULL
AND datediff(time_due,NOW())>-90
AND datediff(time_due,NOW())<+90
AND account_id=28
AND current_state !="deleted"
GROUP BY MONTH(time_due);
The result is:
month_due | unsettled
-------------------------
8/2014 | 1
9/2014 | 8
10/2014 | 2
I need a result that looks something like this:
month | settled | unsettled | total
----------------------------------------------
6/2014 | 1 | 0 | 1
8/2014 | 0 | 1 | 1
9/2014 | 10 | 8 | 18
10/2014 | 0 | 2 | 2
12/2014 | 1 | 0 | 1
Any ideas? How can I do a group by months if my sum criteria are two different months? I need the settled transactions to be grouped by time_settle and the unsettled transactions to be grouped by time_due.
Ok try this
SELECT t.month,
coalesce(t1.settled, 0) as settled,
coalesce(t2.unsettled, 0) as unsettled,
coalesce(t1.settled, 0) + coalesce(t2.unsettled, 0) as total
FROM
( SELECT CONCAT(MONTH(a.t_month), "/", YEAR(a.t_month)) as month
FROM
( SELECT COALESCE(time_settle, time_due) as t_month
FROM payment_order
)a
WHERE datediff(a.t_month,NOW())>-90 AND datediff(a.t_month,NOW())<+90
GROUP BY month
)t
LEFT JOIN(SELECT CONCAT(MONTH(time_settle), "/", YEAR(time_settle)) as month_settled, count(*) as settled FROM payment_order WHERE time_settle IS NOT NULL AND datediff(time_settle,NOW())>-90 AND datediff(time_settle,NOW())<+90 AND account_id=28 AND current_state !="deleted" GROUP BY EXTRACT(YEAR_MONTH FROM time_settle)) t1
on t1.month_settled = t.month
LEFT JOIN(SELECT CONCAT(MONTH(time_due), "/", YEAR(time_due)) as month_due, count(*) as unsettled FROM payment_order WHERE time_settle IS NULL AND account_id=28 AND current_state !="deleted" GROUP BY EXTRACT(YEAR_MONTH FROM time_due)) t2
on t2.month_due = t.month;
DEMO

MySQL, Get number of last records in second table for each element of first, divided by specific time intervals

I have a two tables:
case_map
case_id | creation_date
________|___________________
49 | 2013-04-30
51 | 2013-05-15
82 | 2014-05-23
109 | 2013-06-01
123 | 2013-07-23
case_legend
id | case_id | operation_number | operation_date | failure
___|_________|__________________|________________|________
1 | 49 | 105 | 2013-05-03 | 0
2 | 51 | 105 | 2013-05-28 | 0
3 | 51 | 110 | 2013-05-29 | 0
4 | 51 | 115 | 2013-06-02 | 1
5 | 51 | 110 | 2013-06-05 | 0
6 | 82 | 105 | 2013-05-28 | 0
7 | 82 | 110 | 2013-05-30 | 0
8 | 82 | 115 | 2013-06-01 | 0
9 | 82 | 120 | 2013-06-01 | 0
10 | 82 | 125 | 2013-06-02 | 0
11 | 109 | 105 | 2013-06-27 | 0
12 | 123 | 105 | 2013-07-27 | 0
13 | 123 | 110 | 2013-08-10 | 0
And I want to know how many cases was recieved and how many of these cases are on operation 105 and 125 ('on operation' = value of operation_number for row with max operation_date for this case_id) in a certain time interval, for example 'from May, 2013 to Jule, 2013', splited by mounth.
For these purposes, I made ​​the following query:
SELECT
`recieved_cases`.`abbreviated_date`,
`recieved_cases`.`recieved_count`,
`operation_105`.`105_count`,
`operation_125`.`125_count`
FROM (
SELECT DATE_FORMAT( `creation_date`, '%Y-%m' ) AS `abbreviated_date`, COUNT( `case_id` ) AS `recieved_count`
FROM `case_map`
WHERE `creation_date` BETWEEN '2013-05-01' AND '2013-07-31'
GROUP BY `abbreviated_date`
) AS `recieved_cases`
LEFT JOIN (
SELECT DATE_FORMAT( `t1`.`operation_date`, '%Y-%m' ) AS `abbreviated_date`, COUNT( `t1`.`operation_number` ) AS `105_count`
FROM `case_legend` AS `t1`
WHERE `t1`.`id` = (
SELECT `t2`.`id`
FROM `case_legend` AS `t2`
WHERE `t2`.`case_id` = `t1`.`case_id`
ORDER BY `t2`.`operation_date` DESC, `t2`.`id` DESC
LIMIT 1
)
AND `operation_number` = 105
GROUP BY `abbreviated_date`
) AS `operation_105`
ON `recieved_cases`.`abbreviated_date` = `operation_105`.`abbreviated_date`
LEFT JOIN (
SELECT DATE_FORMAT( `t1`.`operation_date`, '%Y-%m' ) AS `abbreviated_date`, COUNT( `t1`.`operation_number` ) AS `125_count`
FROM `case_legend` AS `t1`
WHERE `t1`.`id` = (
SELECT `t2`.`id`
FROM `case_legend` AS `t2`
WHERE `t2`.`case_id` = `t1`.`case_id`
ORDER BY `t2`.`operation_date` DESC, `t2`.`id` DESC
LIMIT 1
)
AND `operation_number` = 125
GROUP BY `abbreviated_date`
) AS `operation_125`
ON `recieved_cases`.`abbreviated_date` = `operation_125`.`abbreviated_date`
ORDER BY `recieved_cases`.`abbreviated_date`
My problem is that such a request takes into account also cases with creation_date not in a specified time interval. Ie case, which has the last operation in the specified interval, but was created earlier - is taken into account, and it should not be. How can I fix this query?
Desired result for provided example is:
abbreviated_date | recieved_count | 105_count | 125_count
_________________|________________|___________|__________
2013-05 | 2 | 0 | 0
2013-06 | 1 | 1 | 1
2013-07 | 1 | 0 | 0
Can a temporary table helps me here? I mean if I first create table like case_legend with creation_date field. I think on this one for the last hour, but not sure how to do it.
P.S. Also, if my query is bad and you can give me an advise how to optimize it - I would be grateful.
This may be what you want:
select year(cm.creation_date), month(cm.creation_date),
count(distinct case when cl.operation_number = 105 then case_id end) as op105,
count(distinct case when cl.operation_number = 125 then case_id end) as op125
from case_map cm join
case_legend cl
on cm.case_id = cl.case_id
group by year(cm.creation_date), month(cm.creation_date)
order by 1, 2;
If not, you can start with the most recent date for each of the operations:
select cm.*,
max(case when cl.operation_number = 105 then operation_date end) as op105,
max(case when cl.operation_number = 125 then operation_date end) as op125
from case_map cm join
case_legend cl
on cm.case_id = cl.case_id
group by cm.case_id;
And then work from there.
EDIT:
Now that you supplied the desired results, the query is not too difficult:
select date_format(cm.creation_date, '%Y-%m' ) as yyyymm,
sum(cl.opeartion_number = 105) as op105,
sum(cl.opeartion_number = 125) as op125
from case_map cm join
case_legend cl
on cm.case_id = cl.case_id
where not exists (select 1
from case_legend cl2
where cl2.case_id = cl.case_id and
cl2.operation_date > cl.operation_date
)
group by date_format(cm.creation_date, '%Y-%m')
order by 1;
So the answer is: You don't need temporary tables.
Here's this fiddle:
http://sqlfiddle.com/#!2/8673a3/9
It gives you the same results but in a different format perhaps, than what you prefer.
select
DATE_FORMAT( operation_date, '%Y-%m' ) as `Date`,
count(cl.case_id), cl.operation_number,
t.count
from
case_legend cl
left join case_map cm
on cl.case_id = cm.case_id
left join (
select
DATE_FORMAT( cl.operation_date, '%Y-%m' ) as `Date`, count(cl.case_id) as `count`, 'All' as operation_number
from
case_legend cl
left join case_map cm
on cl.case_id = cm.case_id
where operation_date BETWEEN '2013-05-01' AND '2013-07-31'
GROUP BY DATE_FORMAT( operation_date, '%Y-%m' )
) t
on t.Date = DATE_FORMAT( operation_date, '%Y-%m' )
where cl.operation_number in (105, 125)
and operation_date BETWEEN '2013-05-01' AND '2013-07-31'
GROUP BY DATE_FORMAT( operation_date, '%Y-%m' ), cl.operation_number
;

Mysql count group by several criteria

An example of a table that I use looks:
id_user | process_number | date
---------------------------------------
01 | 1 | 2012-05-04
01 | 1 | 2012-05-04
02 | 0 | 2012-05-04
01 | 1 | 2012-05-05
01 | 2 | 2012-05-05
01 | 0 | 2012-05-05
02 | 0 | 2012-05-05
03 | 1 | 2012-05-05
04 | 2 | 2012-05-05
05 | 1 | 2012-05-05
05 | 1 | 2012-05-05
06 | 0 | 2012-05-05
07 | 3 | 2012-05-05
The result that I want to get is the number of unique processes (group by id_user), number of total process (group by date) and count distinct user when process_number is not equal to "0" (grouped by date and by user). I've tried to achieve this with query:
SELECT COUNT( DISTINCT id_user ) AS user_process, COUNT( * ) AS total_process, DATE( date) AS date_process, SUM( IF( process_number = '0', 0, 1 ) ) notification FROM a_proces GROUP BY DATE( date ) ORDER BY DATE( date ) DESC LIMIT 0 , 10
This query adds all users in a given day, who had more than one process_number that is greater than zero in notification column. The correct result in this example should look like:
date_process | user_process | total_process | notification
------------------------------------------------------------------------------
2012-05-04 | 2 | 3 | 1
2012-05-05 | 7 | 10 | 6
Thanks for any help.
You should be able to use the following:
SELECT DATE_FORMAT( date, '%d/%m/%Y' ) AS date_process,
COUNT(DISTINCT id_user) AS user_process,
COUNT( * ) AS total_process,
count(distinct case when process_number <> 0 then id_user end) notification
FROM a_proces
GROUP BY DATE( date )
ORDER BY DATE( date ) DESC
LIMIT 0 , 10;
See SQL Fiddle with Demo
If the notification result that you want is the total count of each distinct process_number for each id_user, then you can use:
SELECT DATE_FORMAT( p.date, '%d/%m/%Y' ) AS date_process,
COUNT(DISTINCT p.id_user) AS user_process,
COUNT( * ) AS total_process,
d.notification
FROM a_proces p
INNER JOIN
(
select count(process_number) notification, date
from
(
select distinct id_user, process_number,
DATE_FORMAT( date, '%d/%m/%Y' ) date
from a_proces
where process_number <> 0
) d
group by date
) d
on DATE_FORMAT( p.date, '%d/%m/%Y' ) = d.date
GROUP BY DATE( p.date ), d.notification
ORDER BY DATE( p.date ) DESC
LIMIT 0 , 10;
See SQL Fiddle with Demo