MySQL Select rows having sum(criteria1) <> sum(criteria2) - mysql

I'm sure the answer is out there somewhere, but can't find it...
One table stores logged hours for employees, classified by type (type = Project or Task).
For each day, I can have hours logged on multiple tasks.
By doing a simple sum and grouping I can get the total hours per type and per day, but I'd like to go one step further:
display only the "error cases" where sum(hours) per Project is different than sum(hours) for all Tasks for a given date.
mysql> select sum(logged), type, date from `hoursLog` group by idEmployee, date, type;
+-------------+----------+----------+
| sum(logged) | type | date |
+-------------+----------+----------+
| 0.8 | Project | 20160525 |
| 1.0 | Task | 20160525 |
| 0.3 | Project | 20160526 |
| 0.3 | Task | 20160526 |
| 0.3 | Project | 20160527 |
| 0.5 | Task | 20160527 |
+-------------+----------+----------+
From the above table, I want only the dates 20160525 and 20160527, for which the sum is different.
Appreciate any help!

SELECT
SUM(IF(`type`='Task',logged,0)) task_hours,
SUM(IF(`type`='Project',logged,0)) project_hours,
date
FROM `hoursLog`
GROUP by idEmployee, date
HAVING task_hours <> project_hours
But I am not sure if you really need GROUP BY idEmployee. I guess this works now just because you have few records in test DB with only 1 employee involved. Do you need time summarized per person? or just per date?

Related

Need an aggregate MySQL select that iterates virtually across date ranges and returns bills

I have a MySQL table named rbsess with columns RBSessID (key), ClientID (int), RBUnitID (int), RentAmt (fixed-point int), RBSessStart (DateTime), and PrevID (int, references to RBSessID).
It's not transactional or linked. What it does track when a client was moved into a room and what the rent at the time of move in was. The query to find what the rent was for a particular client on a particular date is:
SET #DT='Desired date/time'
SET #ClientID=Desired client id
SELECT a.RBSessID
, a.ClientID
, a.RBUnitID
, a.RentAmt
, a.RBSessStart
, b.RBSessStart AS RBSessEnd
, a.PrevID
FROM rbsess a
LEFT
JOIN rbsess b
ON b.PrevID=a.RBSessID
WHERE a.ClientID=#ClientID
AND (a.RBSessStart<=#DT OR a.RBSessStart IS NULL)
AND (b.RBSessStart>#DT OR b.RBSessStart IS NULL);
This will output something like:
+----------+----------+----------+---------+---------------------+-----------+--------+
| RBSessID | ClientID | RBUnitID | RentAmt | RBSessStart | RBSessEnd | PrevID |
+----------+----------+----------+---------+---------------------+-----------+--------+
| 2 | 4 | 1 | 57500 | 2020-11-22 00:00:00 | NULL | 1 |
+----------+----------+----------+---------+---------------------+-----------+--------+
I also have
SELECT * FROM rbsess WHERE rbsess.ClientID=#ClientID AND rbsess.PrevID IS NULL; //for finding the first move in date
SELECT TIMESTAMPDIFF(DAY,#DT,LAST_DAY(#DT)) AS CountDays; //for finding the number of days until the end of the month
SELECT DAY(LAST_DAY(#DT)) AS MaxDays; //for finding the number of days in the month
SELECT (TIMESTAMPDIFF(DAY,#DT,LAST_DAY(#DT))+1)/DAY(LAST_DAY(#DT)) AS ProRateRatio; //for finding the ratio to calculate the pro-rated rent for the move-in month
SELECT ROUND(40000*(SELECT (TIMESTAMPDIFF(DAY,#DT,LAST_DAY(#DT))+1)/DAY(LAST_DAY(#DT)) AS ProRateRatio)) AS ProRatedRent; //for finding a pro-rated rent amount based on a rent amount.
I'm having trouble putting all of these together to form a single query that can output pro-rated and full rent amounts based on a start date and an optional end date all rent owed amounts in a single statement for each month in the period. I can add a payments table received and integrate it afterwards, just having a hard time with this seemingly simple real-world concept in a MySQL query. I'm using php with a MySQL back end. Temporary tables as intermediary queries are more than acceptable.
Even a nudge would be helpful. I'm not super-experienced with MySQL queries, just your basic CREATE, SELECT, INSERT, DROP, and UPDATE.
Examples as requested by GMB:
//Example data in rbsess table:
+----------+----------+----------+---------+---------------------+--------+
| RBSessID | ClientID | RBUnitID | RentAmt | RBSessStart | PrevID |
+----------+----------+----------+---------+---------------------+--------+
| 1 | 4 | 1 | 40000 | 2020-10-22 00:00:00 | NULL |
| 2 | 4 | 1 | 57500 | 2020-11-22 00:00:00 | 1 |
| 3 | 2 | 5 | 40000 | 2020-11-29 00:00:00 | NULL |
+----------+----------+----------+---------+---------------------+--------+
Expected results would be a list of the rent amounts owed for every month, including pro-rated amounts for partial occupancy in a month, from a date range of months. For example for the example data above for a date range spanning all of the year of 2020 from client with ClientID=4 the query would produce an amount for each month within the range similar to:
Month | Amt
2020-10-1 | 12903
2020-11-1 | 45834
2020-12-1 | 57500

Best way to weekly update a MYSQL field?

I want to weekly update a field in a MySQL table "Persons", with the avg of two fields of the "Tasks" table, end_date and start_date:
PERSON:
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| average_speed | int(11) | NO | | 0 | |
+----------------+-------------+------+-----+---------+-------+
TASKS:
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| person_id | int(11) | NO | | NULL | |
| start_date | date | NO | | NULL | |
| end_date | date | NO | | NULL | |
+----------------+-------------+------+-----+---------+-------+
(tables are not complete).
average_speed = AVG(task.end_date - task.start_date)
Now, the Tasks table is really big, and ** I don't want to compute the average on every task for every person every week**. (That's a solution, but I'm trying to avoid it).
What's the best way to update the average_speed?
I thought about adding two columns in the person's table:
"last_count": count of computed tasks since now for each person
"last_sum": last sum of (end_date - start_date) for each person
So that on a new update i could do something like average_speed = (last_sum+new_sum) / (last_count + new_count) where new_count is the sum of the tasks in the last week.
Is there a better solution/architecture?
EDIT:
to answer a comment, the query I would do is something like this:
SELECT
count(t.id) as last_count,
sum(TIMESTAMPDIFF(MINUTE, t.start_date, t.end_date)) as last_sum
avg(TIMESTAMPDIFF(MINUTE, t.start_date, t.end_date))
from tasks as t
where t.end_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 1 WEEK) AND CURDATE()
And i can rely on a php script to get result and do some calculations
Having a periodic update to the table is a bad way to go for all the reasons you've listed above, and others.
If you have access to the code that writes to the Tasks table, that’s the best place to put the update. Add an Average field and calculate and set the value when you write the task end time.
If you don’t have access to the code, you can add a calculated field to the table that shows the average and let SQL figure it out during the execution of a query. This can slow queries down a little, but the data is always valid and SQL is smart enough to only calculate that value when it is needed.
A third (ugly) option is a trigger on the table that updates the value when appropriate. I’m not a fan of triggers because they hide business logic in unexpected places, but sometimes you just have to get the job done.

Sum distinct values in SSRS

I need some help in achieving a functionality. The idea is to calculate the sum of duration for all distinct activities which the query is bringing. A couple of considerations
The query is not SQL, its FetchXML (Microsoft Dynamics 365).
Grouping is not done on Activity IDs instead Contact ID, and it has to be that way only.
Here is how the data looks like
| Contact ID | a.activityid | b.activityid | b.duration |
|------------|--------------|--------------|------------|
| A | a1 | act_1 | 15 |
| A | b1 | act_1 | 15 |
| A | c1 | act_1 | 15 |
Now, I'm trying to Sum(b.duration) and it gives me result as 45, but I want the sum as 15 because b.activityid is getting duplicated in the resultset.
In short Sum of duration for distinct b.activityid.
Can anyone help me out a little here?

How to write a script for count working time employee?

help me, please!
At the table - date, time, person, source. Updated with new values ​​when employee passing through the checkpoint, he can leave / came several times per day.
+---------------+----------+--------+-------------+
| date | time |person |source |
+---------------+----------+--------+-------------+
| 01.08.2014 | 08:42:08 | Name1 | enter1 |
+---------------+----------+--------+-------------+
| 01.08.2014 | 09:42:12 | Name1 | exit1 |
+---------------+----------+--------+-------------+
| 01.08.2014 | 10:22:45 | Name1 | enter2 |
+---------------+----------+--------+-------------+
| 01.08.2014 | 18:09:11 | Name1 | exit2 |
+---------------+----------+--------+-------------+
I need to count for each employee the actual time he spent at work each day. Table will always be not editable. It is formed from a csv file. The script runs once.
I think need to do something like this:
TIMESTAMPDIFF(MINUTE, enterTime, exitTime)
for each employee for 1 day. But I have a very poor knowledge in sql.
The date/time formats should be stored in a datetime/timestamp column. It is possible to convert them, although ugly (there's probably a better way...):
> SELECT CONCAT(STR_TO_DATE('01.08.2014', '%m.%d.%Y'), ' ', '08:42:08');
2014-01-08 08:42:08
Now Suppose the times are unix timestamps. An employ arrives at t0 and leaves at t1. The time he was at work is (t1-t0) seconds. Now suppose he he arrives at t0, leaves for a break at t1, returns at t2, and leaves for the day at t3. His total time at work is (t1-t0) + (t3-t2) = (t1+t3)-(t0+t2). In general: his time at work for a given day is the sum of the arrival times subtracted from the sum of the departure times.
Using your times:
1389188528 enter1
1389192132 exit1
1389194565 enter2
1389222551 exit2
We see that total time at work is: 1389222551 + 1389192132 - (1389188528 + 1389194565) = 31590, or about 8 hours and 47 minutes. Now what remains is converting to unix timestamps (UNIX_TIMESTAMP()) and applying this reasoning via SQL. In the following example, I have added your data to a table named work_log and assumed that when source begins with exit or enter, we are respectively referring to a departure or arrival.
SELECT person, DATE(dt) AS day,
SUM(IF(`source` like 'enter%', -1, 1)*UNIX_TIMESTAMP(dt))/3600 AS hours
FROM (SELECT CONCAT(STR_TO_DATE(`date`, '%m.%d.%Y'), ' ', `time`) AS `dt`,
`person`,`source` FROM work_log) AS wl
GROUP BY person,day;
+--------+------------+--------------+
| person | day | hours |
+--------+------------+--------------+
| Name1 | 2014-01-08 | 8.7750000000 |
+--------+------------+--------------+
1 row in set (0.00 sec)
There are probably cleaner ways of doing that.

Grouping MySQL results buy times

I'm having trouble getting my SQL results to do what I need them to. I've been reading for awhile and I'm still having a little snag. I have a database with times in it. What I'm trying to do is return the amount of calls that fall within each hour. I'm trying to avoid doing 24 different queries for each hour and was hoping the group function might be something I can work with. Is it possible to do one query on this table and be able to group them into one hour increments, or do I need to do individual queries for each hour. My end result will have the number of calls that happened in each of the 24 hours.
+----------+---------+
| calltime | time |
+----------+---------+
| 160523 | 4:05pm |
| 150259 | 3:02pm |
| 025942 | 2:59am |
| 024729 | 2:47am |
| 072419 | 7:24am |
| 142450 | 2:24pm |
| 201937 | 8:19pm |
| 190428 | 7:04pm |
+----------+---------+
Is this possible?
Answer:
SELECT COUNT(*) FROM table GROUP BY HOUR(calltime)
And then looping through results.
You can try this:
SELECT HOUR(STR_TO_DATE( time, '%l:%i:%S %p' )), COUNT(calltime) FROM table GROUP BY HOUR(STR_TO_DATE( time, '%l:%i:%S %p' ));
You can read more here.