Sum and subtract from another relation table in one query - mysql

I have two tables, one is called Attendance and the other is called Timeslices, I am trying to get the total seconds of Attendances subtracted from Timeslices for the current week and also with Doctrine.
I've got to get the rows but I have to sum and subtract each of them out of the query, but I need to learn to do it in one query.
This is the structure of the Attendance Table:
SELECT * FROM attendance;
+----+---------+---------------------+--------+---------------------+---------------------+
| id | user_id | day | status | check_in | check_out |
+----+---------+---------------------+--------+---------------------+---------------------+
| 1 | 1 | 2019-12-18 00:00:00 | end | 2019-12-18 09:52:00 | 2019-12-18 23:37:02 |
| 2 | 1 | 2019-12-19 00:00:00 | end | 2019-12-19 12:12:00 | 2019-12-19 21:05:00 |
+----+---------+---------------------+--------+---------------------+---------------------+
Timeslice table:
SELECT * FROM timeslice;
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
| id | attendance_id | title | day | start_at | stopped_at |
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
| 20 | 1 | Sacar al perro, ducharme y vestirme | 2019-12-18 00:00:00 | 2019-12-18 15:57:50 | 2019-12-18 12:15:36 |
| 21 | 1 | Dormir | 2019-12-18 00:00:00 | 2019-12-18 18:44:30 | 2019-12-18 16:16:44 |
| 22 | 1 | Descansar | 2019-12-18 00:00:00 | 2019-12-18 23:04:53 | 2019-12-18 20:56:29 |
| 23 | 2 | Comer | 2019-12-19 00:00:00 | 2019-12-19 16:03:00 | 2019-12-19 15:37:00 |
| 24 | 2 | Comer | 2019-12-19 00:00:00 | 2019-12-19 16:55:00 | 2019-12-19 16:17:00 |
| 25 | 2 | ducharme | 2019-12-19 00:00:00 | 2019-12-19 19:58:00 | 2019-12-19 17:20:00 |
+----+---------------+-------------------------------------+---------------------+---------------------+---------------------+
This is my current query in which I get the results, but then I have to calculate out of the query to get the desired result and SQLFiddle:
http://sqlfiddle.com/#!9/646be/3
SELECT SUM(TIME_TO_SEC(TIMEDIFF(a.check_out, a.check_in))) AS secondsAttendance
, ( SELECT SUM(TIME_TO_SEC(TIMEDIFF(t.start_at, t.stopped_at)))
FROM timeslice t
WHERE t.attendance_id = a.id
) secondsPauses
FROM attendance a
GROUP
BY a.id
What I need as I said before is to be able to do it in the same query without having to use PHP and with Doctrine

I've changed my answer after your comments. If you only need the value it seams that all you need to do is to use you initial query (with a few twicks) as a
subquery in the FROM clause (Derived Table) and then, do your calculations over it. In this case simply need to SUM the result of subtracting the secondsPauses to the secondsAttendance, like this:
-- make the calculation you need over the results
SELECT SUM(Results.secondsAttendance - Results.secondsPauses) as ActualValue
FROM (
-- use you initial results as a subquery and name it as Results
SELECT
SUM(TIME_TO_SEC(TIMEDIFF(a.check_out, a.check_in))) AS secondsAttendance,
(SELECT SUM(TIME_TO_SEC(TIMEDIFF(t.start_at, t.stopped_at)))
FROM timeslice t WHERE t.attendance_id = a.id) AS secondsPauses
FROM attendance a
-- filter date for the current week
where yearweek(DATE(a.check_in), 1) = yearweek(curdate(), 1)
GROUP BY a.id
) Results;
The result is:
+-------------+
| ActualValue |
+-------------+
| 38258 |
+-------------+
SqlFiddle in here

Related

MySQL - select average of column A for first N entries from column B

I have a ratings table, where each user can add one rating a day. But each user might miss several days between ratings.
I'd like to get the average rating for each user_id's first 7 entries of created_at.
My table:
mysql> desc entries;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| rating | tinyint(4) | NO | | NULL | |
| user_id | int(10) unsigned | NO | MUL | NULL | |
| created_at | timestamp | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
Ideally I'd just get something like:
+------------+------------------+
| day | average_rating |
+------------+------------------+
| 1 | 2.53 |
+------------+------------------+
| 2 | 4.30 |
+------------+------------------+
| 3 | 3.67 |
+------------+------------------+
| 4 | 5.50 |
+------------+------------------+
| 5 | 7.23 |
+------------+------------------+
| 6 | 6.98 |
+------------+------------------+
| 7 | 7.22 |
+------------+------------------+
The closest I've been able to get is:
SELECT rating, user_id, created_at FROM entries ORDER BY user_id asc, created at desc
Which isn't very close at all...
Is it even possible? Will the performance be terrible? It's something that would need to run every time a web page is loaded, so would it be better to just run this once a day and save the results? (to another table!?)
edit - second attempt
Working towards a solution, I think this would get the rating for each user's first day:
select rating from entries where user_id in
(select user_id from entries order by created_at limit 1);
But I get:
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
So now I'm going to play around with JOIN to see if that helps.
edit - third attempt, getting closer
I found this stackoverflow post, which is closer to what I want.
select e1.* from entries e1 left join entries e2
on (e1.user_id = e2.user_id and e1.created_at > e2.created_at)
where e2.id is null;
It gets the rating for the first day for each user.
Next step is to work out how to get days 2 to 7. I can't use 1.created_at > e2.created_at for that, so I'm really confused now.
edit - fourth attempt
Okay, I think it's not possible. Once I worked out how to turn off 'full group by' mode, I realised I'll probably need to use a subquery with limit <user_id>, <day_num>, for which I get:
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
My current method is to just get the entire table, and use PHP to calculate the average for each day.
If I understand correctly you want to take the last 7 ratings the user gave, ordered by the date they gave the rating. The last 7 ratings of one user may fall on different days to another user, however they will be averaged together regardless of date.
First we need to order the data by user and date and give each user their own incrementing row count. I do this by adding two variables, one for the last user id and one for the row number:
select e.created_at,
e.rating,
if(#lastUser=user_id,#row := #row+1, #row:=1) as row,
#lastUser:= e.user_id as user_id
from entries e,
( select #row := 0, #lastUser := 0 ) vars
order by e.user_id asc,
e.created_at desc;
If the previous user_id is different we reset the row counter to 1. The result from this is:
+---------------------+--------+------+---------+
| created_at | rating | row | user_id |
+---------------------+--------+------+---------+
| 2017-01-10 00:00:00 | 1 | 1 | 1 |
| 2017-01-09 00:00:00 | 1 | 2 | 1 |
| 2017-01-08 00:00:00 | 1 | 3 | 1 |
| 2017-01-07 00:00:00 | 1 | 4 | 1 |
| 2017-01-06 00:00:00 | 1 | 5 | 1 |
| 2017-01-05 00:00:00 | 1 | 6 | 1 |
| 2017-01-04 00:00:00 | 1 | 7 | 1 |
| 2017-01-03 00:00:00 | 1 | 8 | 1 |
| 2017-01-02 00:00:00 | 1 | 9 | 1 |
| 2017-01-01 00:00:00 | 1 | 10 | 1 |
| 2017-01-13 00:00:00 | 1 | 1 | 2 |
| 2017-01-11 00:00:00 | 1 | 2 | 2 |
| 2017-01-09 00:00:00 | 1 | 3 | 2 |
| 2017-01-07 00:00:00 | 1 | 4 | 2 |
| 2017-01-05 00:00:00 | 1 | 5 | 2 |
| 2017-01-03 00:00:00 | 1 | 6 | 2 |
| 2017-01-01 00:00:00 | 1 | 7 | 2 |
| 2017-01-13 00:00:00 | 1 | 1 | 3 |
| 2017-01-01 00:00:00 | 1 | 2 | 3 |
| 2017-01-03 00:00:00 | 1 | 1 | 4 |
| 2017-01-01 00:00:00 | 1 | 2 | 4 |
| 2017-01-02 00:00:00 | 1 | 1 | 5 |
+---------------------+--------+------+---------+
We now simply wrap this in another statement to select the avg where the row number is less than or equal to seven.
select e1.row day, avg(e1.rating) avg
from (
select e.created_at,
e.rating,
if(#lastUser=user_id,#row := #row+1, #row:=1) as row,
#lastUser:= e.user_id as user_id
from entries e,
( select #row := 0, #lastUser := 0 ) vars
order by e.user_id asc,
e.created_at desc) e1
where e1.row <=7
group by e1.row;
This outputs:
+------+--------+
| day | avg |
+------+--------+
| 1 | 1.0000 |
| 2 | 1.0000 |
| 3 | 1.0000 |
| 4 | 1.0000 |
| 5 | 1.0000 |
| 6 | 1.0000 |
| 7 | 1.0000 |
+------+--------+

MySQL select a row from a daterange excluding the year

I'm trying to create a MySQL query to select the daily price from a table that is between a date range from another. I only want to use 'starting-ending' months and days from the table "seasons" and I want to pass the year dynamically to the query.
This is my query: (I'm giving it the Year to exclude the one on the table)
SELECT a.season, b.base_price
FROM seasons a
JOIN pricebyseason b ON a.id=b.season_id
WHERE b.prop_id='6' AND '2015-11-29' BETWEEN DATE_FORMAT(a.starting,'2015-%m-%d') AND DATE_FORMAT(a.ending,'2016-%m-%d')
ORDER BY b.base_price DESC
It works but not with all dates.
These are the tables:
seasons (these are static date values)
+----+--------------+------------+------------+
| id | season | starting | ending |
+----+--------------+------------+------------+
| 1 | Peak Season | 2015-12-11 | 2016-01-09 |
| 2 | High Season | 2015-11-27 | 2016-04-15 |
| 3 | Mid Season | 2015-04-16 | 2015-09-01 |
| 4 | Low Season | 2015-09-02 | 2015-11-26 |
| 5 | Spring Break | 2015-03-05 | 2015-03-21 |
+----+--------------+------------+------------+
pricebyseason
+----+---------+-----------+------------+
| id | prop_id | season_id | base_price |
+----+---------+-----------+------------+
| 1 | 6 | 1 | 950 |
| 2 | 6 | 2 | 750 |
| 3 | 6 | 3 | 450 |
| 4 | 6 | 4 | 400 |
| 5 | 6 | 5 | 760 |
+----+---------+-----------+------------+
What I want to achive is query the dialy price between checkin, checkout selection
I create this sqlfiddle: http://sqlfiddle.com/#!9/4a6f4
This is a previuos query that is not working either:
SELECT a.base_price,b.season,b.starting,b.ending
FROM pricebyseason a JOIN seasons b ON a.season_id=b.id
WHERE a.prop_id='6' AND
(DATE_FORMAT(b.starting,'%m-%d') <= '12-27' OR DATE_FORMAT(b.starting,'2016-%m-%d') >= '2015-12-27')
AND
(DATE_FORMAT(b.ending,'%m-%d') >= '12-27' OR DATE_FORMAT(b.ending,'2016-%m-%d') <= '2015-12-27')
ORDER BY base_price DESC
And here are some sample dates for each season: '2016-01-08','2015-12-27','2016-04-14','2015-11-29','2016-04-15','2015-09-01','2016-09-02','2015-11-26','2016-10-10','2016-03-18','2016-06-22','2015-06-15'
Thank a lot

SQL: Searching X table and returning rows from Y based on X data

Search goals for rows where userid = 1.
Using returned rows, search checkin where biometricid's match, along with the userid, and filter out rows that are older than the goal date.
Note: Both the userid and biometricid are foreign tables.
How may I do this with one query?
checkin
id | userid | date | biometricid | value
1 | 1 | 2015-01-10 00:00:00 | 1 | 9
2 | 1 | 2000-05-11 00:00:00 | 1 | 7
3 | 2 | 2015-01-10 00:00:00 | 1 | 9
4 | 1 | 2015-01-10 00:00:00 | 2 | 1
5 | 1 | 2017-01-11 00:00:00 | 1 | 4
goals
id | userid | date | biometricid | value
1 | 1 | 2000-01-05 00:00:00 | 1 | 3
2 | 1 | 2015-01-01 00:00:00 | 2 | 2
3 | 2 | 2015-01-01 00:00:00 | 1 | 2
desired result
id | date | biometricid | value
1 | 2015-01-10 00:00:00 | 1 | 9
2 | 2017-01-11 00:00:00 | 1 | 4
Due to the way you worded the question, and the fact that it seems many other questions I've answer recently used them, I was tempted to show a subselect in a WHERE; but I realize your question can be answered much more simply:
SELECT c.*
FROM goals AS g
INNER JOIN checkin AS c ON g.userid = c.userid
AND g.biometricid = c.biometricid
AND c.`date` >= g.`date`
WHERE g.user_id = 1
;

Getting the missing period through mmysql query or procedure..?

I have a table customer_order as follows
mysql> select * from customer_order;
+---------+---------+-----------+------------------+----------------+
| cust_id | orderno | region_cd | order_start_date |order_del_date |
+---------+---------+-----------+------------------+----------------+
| CU_082 | ONO_001 | reg1 | 2012-04-25 | 2012-08-25 |
| CU_082 | ONO_002 | reg1 | 2012-04-28 | 2012-11-28 |
| CU_083 | ONO_002 | reg2 | 2012-04-28 | 2012-11-28 |
| CU_082 | ONO_003 | reg1 | 2012-04-25 | 2012-08-25 |
| CU_084 | ONO_004 | reg4 | 2012-04-25 | 2012-10-25 |
I need a table like this...which i get....as follows
mysql> select order_start_date,order_del_date,orderno,cust_id from customer_order wh
ere order_start_date >= '2012-04-25' AND order_del_date <='2012-12-28' and cust_i
d IN ('36082','36088') order by cust_id ;
+------------------+----------------+---------+---------+
| order_start_date | order_del_date | pid | emp_id |
+------------------+----------------+---------+---------+
| 2012-04-25 | 2012-05-25 | ONO_001 | CU_082 |
| 2012-08-22 | 2012-12-28 | ONO_004 | CU_082 |
| 2012-06-22 | 2012-08-28 | ONO_003 | CU_082 |
| 2012-05-27 | 2012-06-25 | ONO_002 | CU_082 |
| 2012-04-25 | 2012-05-25 | ONO_001 | CU_082 |
| 2012-05-27 | 2012-06-25 | ONO_001 | CU_082 |
| 2012-04-30 | 2012-06-25 | ONO_001 | CU_088 |
| 2012-06-28 | 2012-07-15 | ONO_002 | CU_088 |
| 2012-07-28 | 2012-08-25 | ONO_003 | CU_088 |
| 2012-07-16 | 2012-09-25 | ONO_004 | CU_088 |
+------------------+----------------+---------+---------+
now i need to query on this table ...
to get
for each customer here we get data for the period from wat date to wat date his order processing details in the above table..
now for each customer i shld find the period for which thr is no order processing...
eg cust_id =CU_088
he has his order processed from 30 apr to 25 june
den from 28th june to 15 july
(here thr is a diff that is thr is no order taken or any processing done from 26th to 27th june..this is wat is the required result)
**one more imp consideration is...
in the next entry we find thr is a order process from 28th july to 25th aug
w.r.t previous entry i.e, 28th june to 15july we find that for this customer thr is no order taken or processed from 16th to 27th july..
but with the last entry tat is 16th july to 25th sept he has an order with different order_no thrfore the gap 16th july to 27th july is filled here so this kind of a condition also needs to be checked...
I need to get the output as something like this..
+------------------+----------------+---------+---------+
| order_start_date | order_del_date | pid | emp_id |
+------------------+----------------+---------+---------+
| 2012-06-26 | 2012-06-27 | ONO_001 | CU_088 |
+------------------+----------------+---------+---------+
that is either the query or procedure which is more efficient should give me the period wer in thr was no action done for customer...
help me write the query which does the all the above things.
I m new to db queries..so please help me out..
To fetch every 'gap' between orders, you can use a self-join:
SELECT o1.cust_id,
o1.order_del_date + INTERVAL 1 DAY AS gap_begin,
MIN(o2.order_start_date) - INTERVAL 1 DAY AS gap_end
FROM customer_order o1
JOIN customer_order o2 ON o1.cust_id = o2.cust_id
AND o1.order_del_date <= o2.order_start_date
GROUP BY o1.cust_id, o1.order_del_date
HAVING gap_begin < gap_end

help in forming mysql query to find free(available) venues/resources for a give date range

I have tables & data like this:
venues table contains : id
+----+---------+
| id | name |
+----+---------+
| 1 | venue 1 |
| 2 | venue 2 |
---------------
event_dates : id, event_id, event_from_datetime, event_to_datetime, venue_id
+----+----------+---------------------+---------------------+----------+
| id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+----------+---------------------+---------------------+----------+
| 1 | 1 | 2009-12-05 00:00:00 | 2009-12-07 00:00:00 | 1 |
| 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+----------+---------------------+---------------------+----------+
This is my requirement: I want venues that will be free on 2009-12-06 00:00:00
i.e.
I should get
|venue_id|
|2 |
Currently I'm having the following query,
select ven.id , evtdt.event_from_datetime, evtdt.event_to_datetime
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where evtdt.venue_id is null
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime);
+----+---------------------+---------------------+
| id | event_from_datetime | event_to_datetime |
+----+---------------------+---------------------+
| 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 |
| 2 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 |
| 3 | NULL | NULL |
| 5 | NULL | NULL |
+----+---------------------+---------------------+
If you note the results, its not including venue id 1 where date is in between 2009-12-06 00:00:00 but showing other bookings.
Please help me correct this query.
Thanks in advance.
SELECT *
FROM venue v
WHERE NOT EXISTS
(
SELECT NULL
FROM event_dates ed
WHERE ed.venue_id = v.id
AND '2009-12-06 00:00:00' BETWEEN ed.event_from_datetime AND ed.event_to_datetime
)
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime);
12/6/2009 is between 12/5/09 and 12/7/09... that's why venue_id 1 is being excluded... what is it you're trying to extract from the data exactly?
The join query you've constructed says, take the venues table and for each row of it that has a matching venue_id make a copy of the venue table row and append the matching row. So if you just did:
select *
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id);
It would yield:
+----+---------+------+----------+---------------------+---------------------+----------+
| id | name | id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+---------+------+----------+---------------------+---------------------+----------+
| 1 | venue 1 | 1 | 1 | 2009-12-05 00:00:00 | 2009-12-07 00:00:00 | 1 |
| 1 | venue 1 | 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 2 | venue 2 | 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+---------+------+----------+---------------------+---------------------+----------+
If you then added your condition, which states the date of interest is not between the from and to date of the event, the query looks like:
select *
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where not ('2009-12-06' between evtdt.event_from_datetime and evtdt.event_to_datetime)
Which yields a result of:
+----+---------+------+----------+---------------------+---------------------+----------+
| id | name | id | event_id | event_from_datetime | event_to_datetime | venue_id |
+----+---------+------+----------+---------------------+---------------------+----------+
| 1 | venue 1 | 2 | 1 | 2009-12-09 00:00:00 | 2009-12-12 00:00:00 | 1 |
| 2 | venue 2 | 3 | 1 | 2009-12-15 00:00:00 | 2009-12-20 00:00:00 | 2 |
+----+---------+------+----------+---------------------+---------------------+----------+
These are my actual experimental results with your data in MySQL.
If you want to get the venue_ids that are free on the proposed date then you would write something like:
select ven.id, SUM('2009-12-06' between evtdt.event_from_datetime and evtdt.event_to_datetime) as num_intersects
from venues ven left join event_dates evtdt on (ven.id=evtdt.venue_id)
group by ven.id
having num_intersects = 0;
which yields:
+----+----------------+
| id | num_intersects |
+----+----------------+
| 2 | 0 |
+----+----------------+
this also comes up with the right answer (without modification) in the case where you have a venue with no events in the event_date table.
At a guess, if you remove not from
or not ('2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime)
this will then return row 1 from event dates but not the other event date rows.
I say "at a guess" because your where clause is a bit hard to understand. Maybe you mean
select ven.id , evtdt.event_from_datetime, evtdt.event_to_datetime
from venues ven
left join event_dates evtdt
on (ven.id=evtdt.venue_id)
where '2009-12-06 00:00:00' between evtdt.event_from_datetime
and evtdt.event_to_datetime;