Mysql: Selecting distinct value with 2 conditions - php

I have a table called jobs with fields: id, status. And another table called jobs_history with fields: id, jobid, status, added_time.
Every time a job is edited through php, I insert the updated data into jobs_history table with jobid, user selected status, and added_time.
A job maybe edited many times so there could be many rows in jobs_history table for one job. And the status field may contains different status like, 1st status would be "Under review", 2nd could be "In progress", 3rd could be "E-mail sent", 4th "Completed".
Now, I need to count the total number of jobs at a specific datetime that had status "In progress" on that date. Here is some example data in jobs_history table:
id | jobid | status | added_time
--------------------------------------
1 | 10 | Under review | 2014-05-20 01:00:00
2 | 10 | In progress | 2014-05-21 02:30:00
3 | 10 | E-mail sent | 2014-05-23 10:00:00
4 | 10 | Completed | 2014-05-23 04:00:00
5 | 11 | Under review | 2014-05-19 05:00:00
6 | 11 | In progress | 2014-05-20 06:00:00
7 | 11 | E-mail sent | 2014-05-20 07:00:00
8 | 11 | Completed | 2014-05-22 08:00:00
9 | 12 | Under review | 2014-05-23 09:00:00
10 | 12 | In progress | 2014-05-24 02:00:00
11 | 13 | Under review | 2014-05-20 10:00:00
12 | 13 | In progress | 2014-05-20 12:00:00
I want to count total number of jobs that had status "In progress" on the 2014-05-21 06:00:00. The query should produce result 2, that is jobid 10 and 13, because those are the only jobs that had last status "In progress" on 2014-05-21 06:00:00.
I have about 400,000 data in the jobs_history table so the query should be written in the fastest way possible.
Thanks and I very much appreciate your help on this.

If your date format is different you can use date_format function. Check this link
http://sqlfiddle.com/#!2/4a6e8b/17

Related

MYSQL How to query open time slots between booked events for a set date and time?

I am attempting to create a query to list open time slots for a specific day. I have found many similar questions on here, but none of the answers seem to do the job.
The query will need to only show results between a certain time period. For instance, if I wanted to search open time slots for the a single day I would set a start date of 2017-03-17 08:00:00 (8:00am being opening time) and an end date of 2017-03-17 17:00:00 (5:00pm being the closing time for the day). It would then have to query the day and return open slots and booked appointments from my appointments table. (Note: I do not save open slots in the table or a separate table, these will need to be generated by the query).
A simplified version of my booked appointments table:
Table name: booked_appointments (before output)
+----------------------+---------------------+----------+
| start_time | end_time | duration |
+----------------------+---------------------+----------+
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | 45 |
| 2017-03-22 09:30:00 | 2017-03-22 10:30:00 | 60 |
| 2017-03-24 08:45:00 | 2017-03-24 11:30:00 | 165 |
| 2017-03-22 11:30:00 | 2017-03-22 12:30:00 | 60 |
+----------------------+---------------------+----------+
My current MYSQL query: (I pulled this from another answer and altered for my use)
SET #timeMinimum='2017-03-17 08:00:00';
SET #timeMaximum='2017-03-17 17:00:00';
SELECT IF (d.name = 'Free', #timeMinimum, b.start_time) AS free_from,
IF (d.name = 'Free', b.start_time, #timeMinimum := b.start_time + INTERVAL b.duration MINUTE) AS free_until,
d.name AS Free
FROM (SELECT 1 AS place, 'Free' AS NAME UNION SELECT 2 AS place, 'Booked' AS NAME ) AS d
INNER JOIN booked_appointments b
HAVING free_from < free_until
UNION SELECT #timeMinimum AS free_from, #timeMaximum AS free_until, 'Free' AS Free
FROM (SELECT 1) AS d
WHERE DATE(#timeMinimum) < DATE(#timeMaximum)
ORDER BY free_from, free_until;
The query does almost everything correctly except it ignores the timeMinimum and timeMaximum I have set and it instead returns results for the entire table that ranges from 2017-03-17 through 2017-03-24. This might not be an issue once I get query working for the day but it also is ignoring the last test date that I purposefully put out of order in the table to test a real world example of data entered.
The following is output from above query:
+---------------------+---------------------+--------+
| free_from | free_until | Free |
+---------------------+---------------------+--------+
| 2017-03-17 08:00:00 | 2017-03-17 10:45:00 | Free |
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | Booked |
| 2017-03-17 11:30:00 | 2017-03-22 09:30:00 | Free |
| 2017-03-22 09:30:00 | 2017-03-22 10:30:00 | Booked |
| 2017-03-22 10:30:00 | 2017-03-24 08:45:00 | Free |
| 2017-03-22 11:30:00 | 2017-03-22 12:30:00 | Booked |
| 2017-03-24 08:45:00 | 2017-03-24 11:30:00 | Booked |
+---------------------+---------------------+--------+
Expected output is:
+---------------------+---------------------+--------+
| free_from | free_until | Free |
+---------------------+---------------------+--------+
| 2017-03-17 08:00:00 | 2017-03-17 10:45:00 | Free |
| 2017-03-17 10:45:00 | 2017-03-17 11:30:00 | Booked |
| 2017-03-17 11:30:00 | 2017-03-17 17:00:00 | Free |
+---------------------+---------------------+--------+

Find the total sum of class fees based on the class students enrolled over specific years using mysql or php

I am calculating for the total payments that are made by each class and by the student in a class he or she has ever been enrolled in, depending on the Academic Year.
Now the Student Class Table below has two students, the student with Student ID=0001 has been enrolled in two classes in two different years Class ID = 1, Year=2013 and Class ID=2, Year=2014.
However the student with Student ID=0002 only enrolled in one class and he left the school. Another student was enrolled in class with
Class ID = 1 in Year=2014
Student Class Table
| Student ID | Class ID | Year |
| 0001 | 1 | 2013 |
| 0002 | 1 | 2013 |
| 0003 | 1 | 2014 |
| 0001 | 2 | 2014 |
Below is the amount assigned to each student for each class in a particular period.
Designated Fees
| Class ID | Amount | Year |
| 1 | 100 | 2013 |
| 1 | 120 | 2014 |
| 2 | 210 | 2014 |
Now, I want to find the total amount each 'Class' is suppose to pay in the current year (thus in 2014), with the summation of what each class was suppose to pay in the previous year ( in this case, the 2013 is the previous Year) and the Previous Year (in this case, the 2014 is the current Year).
Using the below query will give you the amount each Student is suppose to pay from the previous year and current year as far is he/she enrolled in there.
This query gives a good results without any problems
SELECT
a.`Student ID`,
a.`Class ID`,
b.`Amount`,
a.`Year
FROM
`Student Class Table` a
JOIN
`Designated Fees` b
ON
a.`Class ID`=b.`Class ID`
and
a.Year=b.Year
QUERY RESULTS
| Student ID | Class ID | Amount | Year |
| 0001 | 1 | 100 | 2013 |
| 0002 | 1 | 100 | 2013 |
| 0003 | 1 | 120 | 2014 |
| 0001 | 2 | 210 | 2014 |
I want find the total class fees for all the years based on the students enrolled, using the current year as the base year, in this case year 2014 is the current year.
For instance
| Class ID | Total Fees | Year |
| 1 | 120 | 2014 |
| 2 | 410 | 2014 |
The Total Fees for the class with Class ID=1 is 120 because only a student was enrolled in that class within the Year 2013 and 2014 and thier allocated fees for the year 2014 is 120, Also the Total Fees for the
class with Class ID=2 is 410 because two students were enrolled in class with Class ID = 1 in Year 2013 and they were expected to pay an amount of 200, and in the Year 2014 Class 2, only one student enrolled and was expected to pay an amount of 210.
Well, my thoughts on solving this has been complicated, however I added a group by to the query afforementioned, this is wrong that I know. I wanted an Idea as to how to resolve this using either mysql or php, I can't seem to find my way around it.
This will be possible if you have batch numbers for the class from the time, the class was registered. This students in such a class will bear the batch No till they complete the school.
For instance, students in class with Class ID= 1 in Year=2013 will bear the same batch No till they complete. You can choose to create another table and reference it in the Student Class Table depending on how you promote students through the classes over the years.
Student Class Table
| Student ID | Class ID | Year | BatchNo |
| 0001 | 1 | 2013 | 1 |
| 0002 | 1 | 2013 | 1 |
| 0003 | 1 | 2014 | 2 |
| 0001 | 2 | 2014 | 1 |
After Adding the Batch No, you can simply group by the batchNo and sum the amount:
SELECT
a.`Student ID`,
a.`Class ID`,
sum(b.`Amount`),
a.`Year
FROM
`Student Class Table` a
JOIN
`Designated Fees` b
ON
a.`Class ID`=b.`Class ID`
and
a.Year=b.Year
GROUP BY batchNo
This will be your Results
| Class ID | Total Fees | Year |
| 1 | 120 | 2014 |
| 2 | 410 | 2014 |
You can go by this idea and I think, it can work well.

PHP/MySQL: Model repeating events in a database but query for date ranges

I'm working on a (what I was intending to be) simple PHP/MySQL app. As part of it I'd like to be able to model repeating events, however I need to be able to query all the events that happened between two dates (including repeated events). The events only have a date, the time of day doesn't matter.
I've been researching this and have looked in to various approaches including Calendar Recurring/Repeating Events - Best Storage Method and Repeating calendar events and some final maths.
However, any example of a database schema supporting this that I find online, only seems to support querying for events that happened on a certain day. There is no support for events that happened between a range of dates.
As an abstract example
Events table (with some sort of repeat representation):
Event | Start Date | Repeats
-------------------------------------
Meeting | 10/Dec/2012 | Every 7 days
Lunch | 10/Dec/2012 | Every 1 days
Target result of abstract query SELECT Events BETWEEN 09/Dec/2012 AND 20/Dec/2012
Event | Date | Repeats
-------------------------------------
Meeting | 10/Dec/2012 | Every 7 days
Meeting | 17/Dec/2012 | Every 7 days
Lunch | 10/Dec/2012 | Every 1 days
Lunch | 11/Dec/2012 | Every 1 days
Lunch | 12/Dec/2012 | Every 1 days
Lunch | 13/Dec/2012 | Every 1 days
etc...
Lunch | 20/Dec/2012 | Every 1 days
Is there a database schema that will support these kind of queries? How would I go around making a query on that schema for any event (including repeating events) that happened between two days?
Or perhaps a design pattern that is used for repeating events?
I would create a tally table with just one col called id and fill that table with numbers from 0 to 500. Now we easily use that to make selections instead of using a while loop.
Id
-------------------------------------
0
1
2
etc...
Then i'd store the events in a table with Name as varchar, startdate as datetime and repeats as int
Name | StartDate | Repeats
-------------------------------------
Meeting | 2012-12-10 00:00:00 | 7
Lunch | 2012-12-10 00:00:00 | 1
Now we can use the tally table to select all dates between two dates by using:
SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
ORDER BY Id ASC
ShowDate
-------------------------------------
2012-12-09 00:00:00
2012-12-10 00:00:00
2012-12-11 00:00:00
2012-12-12 00:00:00
2012-12-13 00:00:00
2012-12-14 00:00:00
2012-12-15 00:00:00
2012-12-16 00:00:00
2012-12-17 00:00:00
2012-12-18 00:00:00
2012-12-19 00:00:00
2012-12-20 00:00:00
Then we join this on the events table to calculate the difference between the startdate and the showdate. We devided the results of this by the repeats column and if the remainder is 0, we have match.
All combined becomes:
SELECT E.Id, E.Name, E.StartDate, E.Repeats, A.ShowDate, DATEDIFF(E.StartDate, A.ShowDate) AS diff
FROM events AS E, (
SELECT DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2012-12-09 00:00:00',INTERVAL Id DAY)<='2012-12-20 00:00:00')
ORDER BY Id ASC
) a
WHERE MOD(DATEDIFF(E.StartDate, A.ShowDate), E.Repeats)=0
AND A.ShowDate>=E.StartDate
Which results in
Id | Name |StartDate | Repeats | ShowDate | diff
---------------------------------------------------------------------------------
1 | Meeting | 2012-12-10 00:00:00 | 7 | 2012-12-10 00:00:00 | 0
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-10 00:00:00 | 0
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-11 00:00:00 | -1
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-12 00:00:00 | -2
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-13 00:00:00 | -3
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-14 00:00:00 | -4
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-15 00:00:00 | -5
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-16 00:00:00 | -6
1 | Meeting | 2012-12-10 00:00:00 | 7 | 2012-12-17 00:00:00 | -7
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-17 00:00:00 | -7
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-18 00:00:00 | -8
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-19 00:00:00 | -9
2 | Lunch | 2012-12-10 00:00:00 | 1 | 2012-12-20 00:00:00 | -10
Now you could (and should!) speed things up. For instance by directly storing dates in a table so you can just select all dates directly instead of using a tally table with dateadd. Every thing you can cache and dont have to calculate again is good.
I didn't quite understand your goal... Maybe you were looking for UNIQUE function in SQL?

Calculating total quantity of equipments for a date range

Project: I am working on a project which is about some rooms and equipments using in the rooms. The software is about scheduling the equipments in the rooms. In other words, it is a reservation software that reserves selected equipments in separate rooms for needed dates and times ranges. I have many tables in MYsSQL database working with Php but I will mention the tables my question is about. The tables I will relate my questions are equipment table (Table A), schedule table (Table B) and equipments using in the related schedule (Table C).
Table A: equipment list table
eqid | eqName | available|
1 | book | 90 |
2 | pen | 82 |
3 | computer | 25 |
In table A; eqid represents unique id of an equipment, eqName represents name of an equipment, available represents total available equipments existing.
Table B: schedule table
scheduleid | startDate | endDate | startTime | endTime | office |
1 | 2012-08-27 | 2012-08-27 | 08:30:00 | 10:00:00 | room1 |
2 | 2012-08-27 | 2012-08-27 | 09:30:00 | 11:00:00 | room3 |
3 | 2012-08-28 | 2012-08-30 | 08:30:00 | 12:00:00 | room2 |
4 | 2012-08-29 | 2012-08-31 | 11:30:00 | 14:00:00 | room1 |
5 | 2012-08-28 | 2012-08-28 | 10:30:00 | 14:00:00 | room3 |
6 | 2012-08-27 | 2012-08-30 | 08:30:00 | 10:00:00 | room4 |
7 | 2012-08-27 | 2012-08-27 | 10:30:00 | 12:00:00 | room4 |
8 | 2012-08-27 | 2012-08-30 | 08:30:00 | 11:00:00 | room6 |
9 | 2012-08-27 | 2012-08-27 | 10:30:00 | 12:00:00 | room5 |
In table B; scheduleid represents unique id for a schedule, startDate and endDate are date range for a schedule, startTime and endTime time range for a schedule, office means that where the schedule will take place. Let me give an example here. Scheduleid 1 means there is a reservation on 27th of august 2012, Monday and it is from 08.30 to 10:00. As it start and end on same day this is just one day reservation in room1. However, Scheduleid 3 means there is a reservation starts on 28th of august 2012, Tuesday and goes on until 30th of august 2012, Thursday at 08:30-12:00... in other words, it lasts for 3 days and everyday from 08:30 to 12:00... So there is a reservation from Tuesday to Thursday at 08:30 to 12:00 in room2... I hope this is clear.
Table C: equipments using in the related schedule
Autoid | scheduleid | eqid | amountInSch|
1 | 1 | 1 | 2 |
2 | 1 | 2 | 3 |
3 | 1 | 3 | 1 |
4 | 2 | 1 | 1 |
5 | 2 | 2 | 1 |
6 | 2 | 3 | 2 |
7 | 3 | 2 | 1 |
8 | 3 | 3 | 3 |
9 | 4 | 2 | 1 |
10 | 4 | 3 | 1 |
11 | 5 | 1 | 1 |
12 | 6 | 1 | 1 |
13 | 6 | 3 | 2 |
14 | 6 | 2 | 4 |
15 | 7 | 1 | 5 |
16 | 7 | 2 | 6 |
17 | 8 | 2 | 1 |
18 | 9 | 1 | 8 |
19 | 9 | 2 | 5 |
20 | 9 | 3 | 6 |
In table C: Autoid represents unique automatic id generated by auto-increment, scheduleid comes from Table B, eqid comes from Table A, amountInSch represents how many (amount) equipment will use in the related schedule. I want to give an example here. Scheduleid 1 in Table C, there are 3 rows. This means that scheduleid 1 related in TAble B will use 2 books (eqid 1), 3 pens (eqid 2) and 1 computer (eqid 3) in room1 specified dates and times in table B . Another example is that scheduleid 3 in Table C is related 2 rows. It means that 1 pen (eqId 2) and 3 computers (eqId 3) will be using in room2 from 27th to 30th of august 2012 everyday from 08:30 to 12:00.
The above is the explanation and give some information about the project. The table rows are not permanent. When you make a reservation, there will be a new row in Table B and if it is selected an equipment, there will be new rows in table C...
The Question:
I want to calculate left amount of a specific equipment when I supply eqId, startDate, endDate, startTime and endTime...
An example:
eqId: 1 (book)
startDate: 2012-08-27
endDate: 2012-08-27
startTime: 08:30:00
endTime: 12:00:00
Result should be: 14 books used in schedule and 76 left available books
Because: if you look scheduleIds and related eqIds, you will only see 1, 2, 6, 7, 9 scheduleIds related to my query(dates and eqId). If you sum the all amount of related in Table C, you will get the wrong result. In other words, related amounts for eqId(1-book) and for 1, 2, 6, 7, 9 scheduleIds are 2, 1, 1, 5, 8 respectively. So if you sum them you will get 17 which is wrong. Because, 1 and 9 schedule don't intersect each other in terms of start and end Times, and 6 and 7 don't intersect each other either. as a result of them 2 stays lonely and can be count separately. We must consider 1 and 9 as summed 8 because 8 is bigger than 2. it is same for 6 and 7, considered as 5 because of 5 is bigger than 1...
So folks! I am not sure how I can sum/ this in programming algorithm. Is there a way to do in SQL or do I have to use PHP and Mysql together? and How?
Cheers!
SQLFiddle Records
I started with the following SQL to gather all date ranges that intersect with the given range:
SELECT MAX(available) - IFNULL(SUM(amountInSch), 0)
FROM Table1
LEFT JOIN Table3 USING (eqid)
LEFT JOIN Table2 USING (scheduleid)
WHERE DATE(startDate) <= '2012-08-27' AND DATE(endDate) >= '2012-08-27'
AND endTime > '08:30' AND startTime < '12:00'
AND eqid = 1
Fiddle
This is only the first part. Next up you have to work out the possible overlaps; this wouldn't be practical to do with SQL, so I would suggest to do this in PHP.
The generic algorithm I would pick is unfortunately O(n**2), it goes like this:
create a timeline (demarcated by each day) with time as the horizontal axis
iterate over every date/time range and mark the time of its left and right edge to create time segments of every possible permutation.
using the segments, you sum vertically for overlaps and you take the daily maximum across.
Hope that helps.

Approach to sending alerts with CakePHP

This is a rather obscure question. It's more about approach than syntax.
I have a MySQL table filled with 'notifications' (id,user_id,date,etc). I have to send an alert (via email, facebook, twitter, whatever... not the issue) when each of those entries pings as 'true'. Here's the thing, how should I go about pinging them as true in the most efficient way possible when the conditions that determine true/false have to be calculated?
Sending an email of a bday is easy. Just search a date field for today's date. Suppose you have to send en email every 20th day starting from a date entered in the field? I have to calculate each row to see if it's true today.
How should I do that? I've considered the following: 1. a complex MySQL query 2. a PHP page cron job run through each row and mark them as done 1 by 1 every x seconds/min 3. pulling my hair out and running out of the room screaming like a little girl. I'm leaning toward 3 at the moment.
My main concern is that I'm on a shared server and I don't want to do anything too intensive. Thanks for spending your brain on this. I appreciate it.
You should have a look at the strtotime() examples and see if it can accomodate the types of alerts you are sending. This could allow you to represent things like annual reminders (birthdays), alerts every 20 days, monthly alerts (first Monday/last Friday of each month) in a table like so:
| id | user_id | status | send_on | next_occurrence |
|------|---------|---------|---------------------|--------------------|
| 1001 | 123 | pending | 2010-03-04 12:00:00 | Next March 4 noon |
| 1002 | 123 | pending | 2010-02-05 00:00:00 | +20 days midnight |
| 1003 | 123 | pending | 2010-02-01 08:00:00 | First Monday 8am |
You then set up a CRON job (or poor man's CRON on a shared host) that fires every ten minutes or so with some fairly simple code:
# get pending alerts
$alerts = $this->Alert->find('all', array(
'conditions' => array(
'send_on <=' => date('Y-m-d H:i:s'),
'status' => 'pending',
),
));
# send alerts
foreach ($alerts as $alert) {
# send alert and update status
$status = $this->Something->send($alert);
$status = ($status) ? 'sent' : 'failed';
$this->Alert->id = $alert['Alert']['id'];
$this->Alert->saveField('status', $status);
# generate and save next pending occurrence
$this->Alert->create();
$this->Alert->save(array('Alert' => array(
'user_id' => $alert['Alert']['user_id'],
'status' => 'pending',
'send_on' => strtotime($alert['Alert']['next_occurrence']),
'next_occurrence' => $alert['Alert']['next_occurrence'],
)));
}
Fast forward to March 5th this year and that same table now looks like this:
| id | user_id | status | send_on | next_occurrence |
|------|---------|---------|---------------------|--------------------|
| 1001 | 123 | sent | 2010-03-04 12:00:00 | Next March 4 noon |
| 1002 | 123 | sent | 2010-02-05 00:00:00 | +20 days midnight |
| 1003 | 123 | sent | 2010-02-01 08:00:00 | First Monday 8am |
| 1004 | 123 | sent | 2010-03-01 08:00:00 | First Monday 8am |
| 1005 | 123 | sent | 2010-02-25 00:00:00 | +20 days midnight |
| 1006 | 123 | pending | 2010-03-17 00:00:00 | +20 days midnight |
| 1007 | 123 | pending | 2010-04-05 08:00:00 | First Monday 8am |
| 1008 | 123 | pending | 2011-03-04 12:00:00 | Next March 4 noon |
Below is somewhat simplified explanation of how I approached a similar situation where I needed to periodically send sms and emails based on varying complex conditions:
I created 2 new tables:
scenarios (id; name; frequency)
processes (id; scenario_id; process_type; execution_type; process)
with:
scenario.frequency: hourly, daily, weekly or monthly
processes.process_type: filter or action
processes.execution_type: sql or function
Then I set up a cron according to these frequencies to go trough the scenarios table and take the scenarios of the appropriate frequency and collect the associated filter (which can be an sql statement or a php function). If the filter returns any results then perform the associated actions with the results. I also extended this system to perform setup, test and teardown so I could safely test my scenarios before activating them.
Hope this helps - Cheers
You might want to look into a queue service ala beanstalk etc.
I know with some of them you can post actions / events into your queue and set them to be periodic, conditional etc.
Queue servers / services is a big topic, but maybe just having thrown this out there will give you some more options and some alternative trains of thought.

Categories