MySQL - Combine tables and order by max date - php

I have two mysql tables which I've simplified below. I would like to create a query which would pull the data from both tables and order by the most recent date. So if there's an entry (or entries) in the notes table it would look for the most recent notes_date for that cid, and if there's no entry it would use the contact_date for that cid.
contacts
+-----+--------+---------------------+
| cid | name | contact_date |
+-----+------------------------------+
| 1 | george | 2014-03-03 12:24:48 |
| 2 | john | 2014-02-28 15:39:20 |
| 3 | paul | 2014-02-14 10:13:58 |
| 4 | ringo | 2014-02-06 07:13:17 |
+-----+--------+---------------------+
notes
+-----+-----+---------------------+
| nid | cid | notes_date |
+-----+---------------------------+
| 1 | 1 | 2014-03-06 15:43:55 |
| 2 | 1 | 2014-03-14 20:14:12 |
| 3 | 4 | 2014-03-20 22:10:14 |
+-----+-----+---------------------+
This is the result I'd like to get from the query
4 ringo 2014-03-20 22:10:14
1 george 2014-03-14 20:14:12
2 john 2014-02-28 15:39:20
3 paul 2014-02-14 10:13:58
Any help would be much appreciated

This has a few parts to it. One is getting the most recent date for notes. Another is combining this with the contacts data and then choosing the right date.
The following approach uses an aggregation subquery and join to do the calculation:
select c.cid, c.name, coalesce(n.notes_date, c.contact_date) as thedate
from contacts c left outer join
(select n.cid, max(notes_date) as notes_date
from notes
group by n.cid
) n
on c.cid = n.cid

You should use join. You can have query like-
select cont.cid, cont.name, nots.notes_date from contacts cont inner join notes nots on cont.cid=nots.cid order by nots.notes_date

Related

GROUP BY multiple conditions at once

I have a tables like this:
Users
+----+----------+-------------+
| id | name | other_stuff |
+----+----------+-------------+
| 1 | John Doe | x |
| 2 | Jane Doe | y |
| 3 | Burt Olm | z |
+----+----------+-------------+
Places
+----+------------+-------------+
| id | name | other_stuff |
+----+------------+-------------+
| 1 | Building A | x |
| 2 | Building B | y |
+----+------------+-------------+
Subjects
+----+------------+-------------+
| id | name | other_stuff |
+----+------------+-------------+
| 1 | Math | x |
| 2 | English | y |
+----+------------+-------------+
And a joining table:
PastLectures = lectures that took place
+----+-----------+----------+------------+---------+------------+
| id | id_users | id_place | id_subjects| length | date |
+----+-----------+----------+------------+---------+------------+
| 1 | 1 | 1 | 1 | 60 | 2015-10-25 |
| 2 | 1 | 1 | 1 | 120 | 2015-11-06 |
| 3 | 2 | 2 | 2 | 120 | 2015-11-04 |
| 4 | 2 | 2 | 1 | 60 | 2015-11-10 |
| 5 | 1 | 2 | 1 | 60 | 2015-11-10 |
| 6 | 2 | 2 | 1 | 40 | 2015-11-15 |
| 7 | 1 | 2 | 2 | 30 | 2015-11-15 |
+----+-----------+----------+------------+---------+------------+
I would like to display SUM of all lessons for each user for given month. The SUM should by grouped by each Places and Subjects.
The result in final PHP output should look like this:
November 2015
+------------+-------------+---------------+-------------+
| Users.name | Places.name | Subjects.name | sum(length) |
+------------+-------------+---------------+-------------+
| Burt Olm | - | - | - |
| Jane Doe | Building B | Math | 100 |
| = | = | English | 120 |
| John Doe | Building A | Math | 120 |
| = | Building B | Math | 60 |
| = | = | English | 30 |
+------------+-------------+---------------+-------------+
I have tried creating the full output in pure SQL query using multiple GROUP BY (Group by - multiple conditions - MySQL), but when I do GROUP BY User.id,Places.id it shows each user only once (3 results) no matter the other GROUP BY conditions.
SQL:
SELECT PastLectures.id_users,Users.name AS user,Places.name AS places,Subjects.name AS subjects
FROM PastLectures
LEFT JOIN Users ON PastLectures.id_users = Users.id
LEFT JOIN Places ON PastLectures.id_Places = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
WHERE date >= \''.$monthStart->format('Y-m-d H:i:s').'\' AND date <= \''.$monthEnd->format('Y-m-d H:i:s').'\'
GROUP BY Users.id,Places.id
ORDER BY Users.name,Places.name,Subjects.name
But I don't mind if part of the solution is done in PHP, I just don't know what to do next.
EDIT:
I also have a table Timetable, that stores who regularly teaches what and where. It stores only used combinations of the tables (each valid combination once).
Timetable = lectures that regularly take place
+----+-----------+----------+------------+-------------+
| id | id_users | id_place | id_subjects| other_stuff |
+----+-----------+----------+------------+-------------+
| 1 | 1 | 1 | 1 | x |
| 2 | 1 | 2 | 1 | y |
| 3 | 1 | 2 | 2 | z |
| 4 | 2 | 2 | 1 | a |
| 5 | 2 | 2 | 2 | b |
+----+-----------+----------+------------+-------------+
Is it possible to add only users with combinations that have a row in this table?
In this case it would mean omitting Burt Olm (no id=3 in Timetable). But if Burt has a Timetable entry and still no PastLectures entry, he would show here as in sample result (he should have had a lecture that month, because he is in Timetable, but no lectures took place).
Based on #Barmar's solution I updated the final SQL by making Timetable a primary table and adding one more LEFT JOIN to suffice those needs.
Final SQL:
SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(PastLectures.length)
FROM Timetable
LEFT JOIN PastLectures ON PastLectures.id_users = Timetable.id_users AND PastLectures.id_place = Timetable.id_place AND PastLectures.id_subjects = Timetable.id_subjects
AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
LEFT JOIN Places ON Timetable.id_Place = Places.id
LEFT JOIN Subjects ON Timetable.id_Subjects = Subjects.id
LEFT JOIN Users ON Timetable.id_users = Users.id
GROUP BY Timetable.id,Timetable.id_users,Timetable.id_Place,Timetable.id_Subjects
ORDER BY Users.name,Places.name,Subjects.name
You need to include Subjects.id in the GROUP BY, so you get a separate result for each subject.
Also, you shouldn't use columns in tables that are joined with LEFT JOIN in the GROUP BY column. If you do that, all the non-matching rows will be grouped together, because they all have NULL in that column. Use the columns in the main table.
GROUP BY PastLectures.id_users, PastLectures.id_Place, PastLectures.id_Subjects
DEMO
Note that there's no row for Burt Olm in the demo output, because all his rows are filtered out by the WHERE clause. If you want all users to be shown, you should make Users the main table, not PastLectures. And the date criteria needs to be moved into the ON clause when joining with PastLectures.
SELECT Users.name AS user,Places.name AS places,Subjects.name AS subjects, SUM(length)
FROM Users
LEFT JOIN PastLectures ON PastLectures.id_users = Users.id
AND date BETWEEN '2015-11-01 00:00:00' AND '2015-11-30 23:59:59'
LEFT JOIN Places ON PastLectures.id_Place = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
GROUP BY Users.id, PastLectures.id_Place, PastLectures.id_Subjects
ORDER BY Users.name,Places.name,Subjects.name
DEMO
According to standard SQL, you should GROUP BY all the fields you select, except for the aggregated fields (like sum). Althought MySql allows to do otherwise, when it can be done adhering to the standards, it is better to do so (who knows when you need to port your code to another database engine). So write your SQL like this:
SELECT PastLectures.id_users,
Users.name AS user,
Places.name AS places,
Subjects.name AS subjects,
Sum(length)
FROM PastLectures
LEFT JOIN Users ON PastLectures.id_users = Users.id
LEFT JOIN Places ON PastLectures.id_Places = Places.id
LEFT JOIN Subjects ON PastLectures.id_Subjects = Subjects.id
WHERE date BETWEEN \''.$monthStart->format('Y-m-d H:i:s').'\'
AND \''.$monthEnd->format('Y-m-d H:i:s').'\'
GROUP BY PastLectures.id_users,
Users.name,
Places.name,
Subjects.name
ORDER BY Users.name,
Places.name,
Subjects.name

How to get a value from MySQL n days before from CURDATE()

I want to join 4 tables to list all the values from a table those have the duration from last updated to current date is more that the duration in other table, table are given below (my English not good to understand so am explaining with examble)
first table daily_tasks
+---------+---------+
| task_id | type_id |
+---------+---------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+---------+---------+
Second Table daily_task_report
+-----------+---------+------------+
| report_id | task_id | task_date |
+-----------+---------+------------+
| 1 | 1 | 2015-09-10 |
| 2 | 3 | 2015-09-10 |
| 3 | 1 | 2015-09-11 |
| 4 | 3 | 2015-09-16 |
+-----------+---------+------------+
Third Table duration_types
+---------+---------------+------------------------+
| type_id | duration_type | duration_time(in days) |
+---------+---------------+------------------------+
| 1 | Daily Task | 1 |
| 2 | Weekly Task | 6 |
| 3 | Monthly Task | 26 |
| 4 | Yearly Task | 313 |
+---------+---------------+------------------------+
Fourth Table calendar
+--------+------------+---------+
| cal_id | cal_date | holiday |
+--------+------------+---------+
| 1 | 2015-09-10 | 0 |
| 2 | 2015-09-11 | 0 |
| 3 | 2015-09-12 | 0 |
| 4 | 2015-09-13 | 1 |
+--------+------------+---------+
Here daily_tasks.type_id is from duration_types.type_id and daily_task_report.task_id is from daily_tasks.task_id. I want to select all the task_id those task_date and current_date difference will greater than duration_time, also while calculating the duration i have to avoid the dates those have holiday=1 from calendar.
I tried queries but not proper, i got the values without including the calendar table, but that not a good way, query is taking more time to execute.
"SELECT dailyTasks.task_id FROM
(SELECT tab.* FROM (SELECT
tasks.task_type,report.*
FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
WHERE 1 ORDER BY report.task_date DESC) as tab GROUP BY tab.d_task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.task_type=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
Please someone help, I stuck in this section
According to given table You have some unexpected text or unknown columns in your query
Try this query
"SELECT dailyTasks.d_task_id FROM
(SELECT tab.* FROM
(SELECT tasks.type_id,report.* FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
ORDER BY report.task_date DESC)
as tab GROUP BY tab.task_id) AS dailyTasks
LEFT JOIN duration_types AS type ON dailyTasks.type_id=type.type_id
WHERE DATEDIFF(CURDATE(),dailyTasks.task_date)>=type.duration_time"
It can be also works in single query
SELECT tasks.task_id FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
*I tried but not exclude that id which have holiday in calendar
You can create it on your coding side I gave you new query included with calendar
*
SELECT tasks.task_id,report.task_date,calendar.holiday FROM daily_tasks AS tasks
LEFT JOIN daily_task_reports AS report ON tasks.task_id=report.task_id
LEFT JOIN duration_types AS type ON tasks.type_id = type.type_id and DATEDIFF(CURDATE(),report.task_date) >= type.duration_time
LEFT JOIN calendar ON report.task_date=calendar.cal_date
where calendar.holiday = '0'
order By report.task_date desc

Data from three tables duplicates when a JOIN statement is used

when I retrieve data from my tables using JOIN, the rows duplicates. The tables are three in number.
Students
--------
StuID | Name |
1 | Appiah John |
2 | Minister A |
Levels
------
| LevelID | Level | Year | StuID |
| 08 | 100 | 2010 | 2 |
| 83 | 200 | 2011 | 1 |
| 45 | 200 | 2011 | 2 |
Ranks
-----
| RankID | Rank | StuID |
| 101 | 1st | 1 |
| 404 | 4th | 2 |
This is my query statement to select some data from the three tables
SELECT
m.StuID,
n.Level,
n.Year,
o.Rank
FROM
Students m
INNER JOIN
Levels n
ON
m.StuID=n.StuID
INNER JOIN
Ranks o
ON
m.StuID=o.StuID
WHERE
m.StuID=2;
OUTPUT
The query above produces a duplicate answer
| StuID | Level | Year |Rank |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
DESIRED OUTPUT
I therefore wish that the output would be like below
| StuID | Level | Year |Rank |
| 2 | 100 | 2010 | 4th |
| 2 | 200 | 2011 | null |
QUESTIONS
Where am I going wrong?
Is join the best way to select data from three tables like this?
How can I make a query to get the desired output?
Believe it or not I think the comma between Students m and INNER JOIN is doing it. You're selecting from two separate tuples now, joined on any clause rather than joining the first table to the second to the third.
Try doing a left join instead of an inner join:
SELECT m.StuID,
n.Level,
n.Year,
o.Rank
FROM Students m
LEFT JOIN Levels n ON (m.StuID = n.StuID)
LEFT JOIN Ranks o ON (m.StuID = o.StuID)
WHERE m.StuID = 2
How about using select distinct m.StuID?
You can try something like this
SELECT distinct m.StuID, n.Level, n.Year, o.Rank
FROM Students m INNER JOIN Levels n ON m.StuID=n.StuID
INNER JOIN Ranks o
ON m.StuID=o.StuID WHERE m.StuID=2;

MySQL Sum multiple column values with conditions

I have the following schema (two tables):
**APPS**
| ID (bigint) | USERID (Bigint) | USAGE_START_TIME (datetime) |
------------------------------------------------------------------
| 1 | 12 | 2013-05-03 04:42:55 |
| 2 | 12 | 2013-05-12 06:22:45 |
| 3 | 12 | 2013-06-12 08:44:24 |
| 4 | 12 | 2013-06-24 04:20:56 |
| 5 | 13 | 2013-06-26 08:20:26 |
| 6 | 13 | 2013-09-12 05:48:27 |
**USAGE**
| ID (bigint) | APPID (bigint) | DEVICEID (bigint) | HIGH_COUNT (bigint) | MEDIUM_COUNT (bigint) |
--------------------------------------------------------------------------------------------------------
| 1 | 1 | 2 | 400 | 200 |
| 2 | 1 | 3 | 200 | 100 |
| 3 | 2 | 3 | 350 | 40 |
| 4 | 3 | 4 | 2 | 400 |
| 5 | 4 | 2 | 4 | 30 |
| 6 | 5 | 3 | 50 | 300 |
Explanation:
So, there are two tables.
Now I want to find the following:
Given a USERID, Get sum of HIGH_COUNT & MEDIUM_COUNT. While counting
the SUM it should be taken care that: If in USAGE, same device is used
more than once, then the record which has the latest info (based on
APPS.USAGE_START_TIME), should be considered while calculating the
sum.
For ex:
For above schema, result should be (for userid=12) :
| HIGH_COUNT (bigint) | MEDIUM_COUNT (Bigint) |
-----------------------------------------------
| 356 | 470 |
SQL Fiddle: http://sqlfiddle.com/#!2/74ae0f
If a user uses multiple APPS on one device, this query will use the APPS row with the highest usage_start_time:
select a.userid
, sum(u.high_count)
, sum(u.medium_count)
from apps a
join `usage` u
on u.appid = a.id
join (
select u.device_id
, a.userid
, max(a.usage_start_time) as max_start_time
from apps a
join `usage` u
on u.appid = a.id
group by
u.device_id
, a.userid
) filter
on filter.device_id = u.device_id
and filter.userid = a.userid
and filter.max_start_time = a.usage_start_time
group by
a.userid
In your dataset, it will select usage rows 5, 3, 4 for user 12.
See it working at SQL Fiddle.
I can't quite get your numbers, but something like this should work...
SELECT a.userid
, SUM(u.high_count)
, SUM(u.medium_count)
FROM apps a
JOIN `usage` u
ON u.appid = a.id
JOIN
( SELECT userid
, deviceid
, MAX(usage_start_time) max_usage_start_time
FROM apps a
JOIN `usage` u
ON u.appid = a.id
GROUP
BY userid
, deviceid
) x
ON x.userid = a.userid
AND x.deviceid = u.deviceid
AND x.max_usage_start_time = a.usage_start_time
GROUP
BY userid;
Note that usage is a reserved word. Therefore, this is a bad name for a column (or a table). Also, note inconsistencies between your question and your fiddle.
I think not had chance to test it but
SELECT SUM(HIGH_COUNT), SUM(MEDIUM_COUNT) FROM `USAGE` INNER JOIN `APPS` ON USAGE.APPID=APPS.ID WHERE APPS.USERID=$input_user_id_to_lookup
will give you your counts.
For yoru other question (homework?) you didn't give us the full schema so we can't guess what you need doing.
Also whoever designed that db should be shot its horrible

Mysql sql row to column

Now I have 2 tables
STUDENT:
STUDENT_ID | STUDENT_NAME | COURSE_ID
1000 | Anson | 1
1001 | Jnson | 1
1002 | Andy | 2
1003 | Alex | 3
COURSE:
COUSE_ID | COURSE_NAME
1 | P5A
2 | P5B
3 | P5C
Now I would like to produce the result to show the students name in each class
Idea result:
P5A P5B P5C
Anson Andy Alex
Jason
what should I do, I am using php + mysql to build web system
select * from course c left join student s on c.course_id = s.course_id
order by c.course_name, c.course_id
Enumerate the result set. Each time course_id changes, start a new section.

Categories