I've got the following tables
menu_supp
menu_id supp_id supp_weight supp_load_follow
1 29 10.00 1
1 31 20.00 2
supps
supp_id user_id supp_name
29 1 Test supp 1
31 1 Test supp 2
supps_prop
supp_id supp_dry_w supp_price supp_date
29 95.00 125.00 2015-10-25
29 94.00 124.00 2015-11-06
29 94.00 128.00 2015-11-12
31 25.00 200.00 2015-06-25
Now I've got this query:
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price, sp.supp_dry_weight
FROM menu_supp ms
LEFT JOIN supps s ON ms.supp_id = s.supp_id
LEFT JOIN supps_prop sp ON ms.supp_id = sp.supp_id
WHERE menu_id = 1
GROUP BY s.supp_id
ORDER BY ms.supp_load_follow ASC
Which gives me this result:
supp_id supp_name supp_weight supp_price supp_dry_weight
29 Test supp 1 10.00 125.00 95.00
31 Test supp 2 20.00 200.00 25.00
From supp 29 it gets the oldest value. Where it should take the value based on the current date. How can I achieve that?
If the supp_date is unique for a supp_id then you can use the following to get the value for the latest date:-
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price, sp.supp_dry_weight
FROM menu_supp ms
LEFT JOIN supps s
ON ms.supp_id = s.supp_id
LEFT JOIN
(
SELECT supp_id, MAX(supp_date) AS max_supp_date
FROM supps_prop
GROUP BY supp_id
) sub0
ON ms.supp_id = sub0.supp_id
LEFT OUTER JOIN supps_prop sp
ON sub0.supp_id = sp.supp_id
AND sub0.max_supp_date = sp.supp_date
WHERE menu_id = 1
ORDER BY ms.supp_load_follow ASC
This gets the max supp_date for each supp_id and joins that back to the supps_prop table to get the other fields from it.
EDIT - Coping with either the highest date, or the lowest date after today is a bit more complicated.
I would suggest having 2 sub queries. One to get the highest date for each supp_id and one to get the lowest date on or after today for each supp_id. If the 2nd is found then use that, if not use the first. Not tested but:-
SELECT s.supp_id, s.supp_name, ms.supp_weight, COALESCE(sp1.supp_price, sp0.supp_price), COALESCE(sp1.supp_dry_weight, sp0.supp_dry_weight)
FROM menu_supp ms
LEFT JOIN supps s
ON ms.supp_id = s.supp_id
LEFT JOIN
(
SELECT supp_id, MAX(supp_date) AS max_supp_date
FROM supps_prop
GROUP BY supp_id
) sub0
ON ms.supp_id = sub0.supp_id
LEFT OUTER JOIN supps_prop sp0
ON sub0.supp_id = sp0.supp_id
AND sub0.max_supp_date = sp0.supp_date
LEFT JOIN
(
SELECT supp_id, MIN(supp_date) AS max_supp_date
FROM supps_prop
WHERE supp_date >= CURDATE()
GROUP BY supp_id
) sub1
ON ms.supp_id = sub1.supp_id
LEFT OUTER JOIN supps_prop sp1
ON sub1.supp_id = sp1.supp_id
AND sub1.max_supp_date = sp1.supp_date
WHERE menu_id = 1
ORDER BY ms.supp_load_follow ASC
EDIT - An explanation of GROUP BY, etc:-
GROUP BY is used for aggregate functions; these are functions that give a value over a range of rows which share common field values. For example, SUM would be used to add up the values of the fields over multiple rows often for a shared value (ie, maybe the SUM of order values for a customer id). The shared value field is used given in the GROUP BY field.
In normal standard SQL all the returned non aggregate fields returned by the SELECT statement must be mentioned in the GROUP BY statement. This makes logical sense as if they are not mentioned then the values for a group of rows could be different and then there is the problem of which one to choose.
However there are times when this can be a bit too restrictive. For example if you are grouping by a customer id then the customer name is directly related to this customer id. MySQL does allow you to return non aggregate fields in the SELECT statement that are not specified in the GROUP BY clause, but if the values vary over the rows that are grouped together then which value is chosen is not specified; it could be from any of the rows, and indeed there is no reason that it might not change in the future or when using a different storage engine.
Sometimes GROUP BY is abused to return unique rows, in the way that DISTINCT is meant to be used.
In your original query
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_weight FROM menu_supp ms LEFT JOIN supps s ON ms.supp_id =
s.supp_id LEFT JOIN supps_prop sp ON ms.supp_id = sp.supp_id WHERE
menu_id = 1 GROUP BY s.supp_id ORDER BY ms.supp_load_follow ASC
you are using GROUP BY s.supp_id. While s.supp_name is dependent on this, ms.supp_weight and sp.supp_price are not. There could be numerous values of each of these for any s.supp_id. MySQL has just used the value from one of the grouped rows for these and doesn't really care which row it chose to use.
Here is your query without the group by and using inner joins. It appears to me that no supp_id would be inserted into menu_supp that is not already defined in supps. I suppose it would be possible to have no entry in supps_prop but that looks doubtful also. If I am wrong, simply change it back.
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_w, sp.supp_date
FROM menu_supp ms
JOIN supps s
ON s.supp_id = ms.supp_id
JOIN supps_prop sp
ON sp.supp_id = ms.supp_id
WHERE menu_id = 1
ORDER BY ms.supp_load_follow;
I've also added the date to make it easier to follow. The results are all four possible rows:
supp_id supp_name supp_weight supp_price supp_dry_w supp_date
------- --------- ----------- ---------- ---------- ---------
29 Test supp 1 10.00 125.00 95.00 2015-10-25
29 Test supp 1 10.00 124.00 94.00 2015-11-06
29 Test supp 1 10.00 128.00 94.00 2015-11-12
31 Test supp 2 20.00 200.00 25.00 2015-06-25
Obviously, you only want to join with the prop information contained in the row with the current or most recent date. That date is the largest value still in the past. Which can be found like this:
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_w, sp.supp_date
FROM menu_supp ms
JOIN supps s
ON s.supp_id = ms.supp_id
JOIN supps_prop sp
ON sp.supp_id = ms.supp_id
and sp.supp_date =(
select Max( supp_date )
from supps_prop
where supp_id = ms.supp_id
and supp_date <= NOW() )
WHERE menu_id = 1
ORDER BY ms.supp_load_follow;
Don't let the subquery concern you. Since the combination of supp_id and supp_date are the most obvious PK for the prop table, those fields should already be indexed, making this an impressively fast query.
See it in action at sqlfiddle.
Related
I would like assistance with adding a subquery into the below query as I understand this is the method I need to use to get the result from the last record for scan_type column, not the first record in the group by due to mysql server running 5.7.
I have tried doing this but I am not understanding how I can put the subquery into the current query. I have tried unsuccessfully which causes the query to error.
Currently I am able to get the date/time stamp by using MAX which gives me the last record for the person's attendance, but I am having trouble getting the related "scan_type". Apart from this, the remainder of the query returns all of the expected results.
Below is the current query:
SELECT A.attendance_sessions_id, A.person_id, A.scan_type, A.absence_type, MAX(A.date_time), B.name, B.student_level
FROM `attendance_record` A
LEFT JOIN `person` B ON A.person_id = B.student_no
WHERE A.scan_type IS NULL
OR A.scan_type <> 'evac_scan'
OR A.scan_type NOT LIKE 'evac_%'
GROUP BY A.attendance_sessions_id, A.person_id
Below is the current output of the above query:
attendance_sessions_id
person_id
scan_type
absence_type
MAX(A.date_time)
name
student_level
1
65
scan_in
NULL
2022-02-06 12:59:48
Chris
Year 1
Expecting scan_type = "scan_out"
attendance_record table:
attendance_record_id
attendance_sessions_id
person_id
scan_type
absence_type
date_time
4
1
65
scan_in
NULL
2022-02-05 20:13:17
5
1
65
scan_out
NULL
2022-02-05 20:14:39
6
1
65
scan_in
NULL
2022-02-06 12:06:45
7
1
65
evac_scan
NULL
2022-02-06 12:53:01
8
1
65
scan_out
NULL
2022-02-06 12:59:48
person table:
person_id
student_no
name
student_level
9
65
Chris
Year 1
attendance_sessions table:
attenance_sessions_id
session_name
session_date_time
1
February Weekend 1
2022-02-05 00:01:00
Since some time only_full_group_by is the default, (at least for MySQL 8+ ). It would be great to change this query in such a way that it's handled correctly, als in the furture.
SELECT
x.attendance_sessions_id,
x.person_id,
A.scan_type,
A.absence_type,
x.max_date_time,
B.name,
B.student_level
FROM (
SELECT
A.attendance_sessions_id,
A.person_id,
-- A.scan_type,
-- A.absence_type,
MAX(A.date_time) as max_date_time,
-- B.name,
-- B.student_level
FROM `attendance_record` A
-- LEFT JOIN `person` B ON A.person_id = B.student_no
WHERE A.scan_type IS NULL
OR A.scan_type <> 'evac_scan'
OR A.scan_type NOT LIKE 'evac_%'
GROUP BY
A.attendance_sessions_id,
A.person_id
) x
INNER JOIN `attendance_record` A ON A.attendance_sessions_id = x.attendance_sessions_id
AND A.person_id = x.person_id
AND A.date_time = x.max_date_time
LEFT JOIN `person` B ON B.student_no = A.person_id
Removed some columns (--) because of the only_full_group_by setting, and removed the LEFT JOIN because in the current sub-query the table person is no longer used.
Changed query to sub-query, and added all (remove)fields to the outer query which also includes a JOIN to get the MAX record from attendance_record
NOTE: When there are multiple records with the same date_time for one attendance_sessions_id,person_id, this query will not produce correct results.
I have two tables in the datbase to store client basic info (name, location, phone number) and another table to store client related transactions (date_sub, profile_sub,isPaid,date_exp,client_id) and i have an html table to view the client basic info and transaction if are available, my problem that i can't get a query to select the client info from table internetClient and from internetclientDetails at the same time, because query is only resulting when client have trans in the detail table. the two table fields are as follow:
internetClient
--------------------------------------------------------
id full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 03556133
and
internetclientdetails
--------------------------------------------------------------------------
incdid icid date_sub date_exp isPaid sub_price
----------------------------------------------------------------------------
6 4 2018-01-01 2018-01-30 0 2000
7 5 2017-01-01 2017-01-30 0 1000
8 4 2018-03-01 2018-03-30 1 50000
9 5 2018-05-01 2019-05-30 1 90000
// incdid > internetClientDetailsId
// icid> internetClientId
if client have trans in orderdetails, the query should return value like that:
client_id full_name date_sub date_exp isPaid sub_price
-------------------------------------------------------------------------------------
4 Joe Amine 2018-03-01 2018-03-30 1 50000
5 Mariam zoue 2018-05-01 2019-05-30 1 90000
else if the client has no id in internetOrederDetails
--------------------------------------------------------
icid full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 0355613
Thanks in advance
try with left join. It will display all records from internetClient and related record from internetclientdetails
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.incdid, internetclientdetails.icid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid group by internetclientdetails.icid order by internetclientdetails.incdid desc
if you want to get records of, only paid clients then you can try the following
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.icid, internetclientdetails.incdid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid
and internetclientdetails.isPaid=1 group by internetclientdetails.icid
order by internetclientdetails.incdid desc
SUMMARY
We generate a dataset containing just the ICID and max(date_sub) (alias:ICDi) We join this to the InternetClientDetails (ICD) to obtain just the max date record per client. Then left join this to the IC record; ensuring we keep all InternetClient(IC) records; and only show the related max Detail Record.
The below approach should work in most mySQL versions. It does not use an analytic which we could use to get the max date instead of the derived table provided the MySQL version you use supported it.
FINAL ANSWER:
SELECT IC.id
, IC.full_name
, IC.location
, IC.phone_number
, ICD.icid
, ICD.incdid
, ICD.date_sub
, ICD.date_exp
, ICD.isPaid
, ICD.sub_price
FROM internetClient IC
LEFT JOIN (SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
on IC.id=ICD.icid
ORDER BY ICD.incdid desc
BREAKDOWN / EXPLANATION
The below gives us a subset of max(date_Sub) for each ICID in clientDetails. We need to so we can filter out all the records which are not the max date per clientID.
(SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
Using that set we join to the details on the Client_ID's and the max date to eliminate all but the most recent detail for each client. We do this because we need the other detail attributes. This could be done using a join or exists. I prefer the join approach as it seems more explicit to me.
(SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
Finally the full query joins the client to the detail keeping client even if there is no detail using a left join.
COMPONENTS:
You wanted all records from InternetClient (FROM internetClient IC)
You wanted related records from InternetClientDetail (LEFT Join InternetClientDetail ICD) while retaining teh records from InternetClient.
You ONLY wanted the most current record from InternetClientDetail (INNER JOIN InternetClientDetail mICD as a derived table getting ICID and max(date))
Total record count should = total record count in InternetClient which means all relationships must be a 1:1o on the table joins -- one-to-one Optional.
Struggling with an SQL query to select the 5 most recent, unique, entries in a MySQL 5.7.22 table. For example, here's the 'activity' table:
uaid nid created
9222 29722 2018-05-17 03:19:33
9221 31412 2018-05-17 03:19:19
9220 31160 2018-05-16 23:47:34
9219 31160 2018-05-16 23:47:30
9218 31020 2018-05-16 22:35:59
9217 31020 2018-05-16 22:35:54
9216 28942 2018-05-16 22:35:20
...
The desired query should return the 5 most recent, unique entries by the 'nid' attribute, in this order (but only need the nid attribute):
uaid nid created
9222 29722 2018-05-17 03:19:33
9221 31412 2018-05-17 03:19:19
9220 31160 2018-05-16 23:47:34
9218 31020 2018-05-16 22:35:59
9216 28942 2018-05-16 22:35:20
I have tried a variety of combinations of DISTINCT but none work, ie:
select distinct nid from activity order by created desc limit 5
What is the proper query to return the 5 most recent, uniq entries by nid?
Your problem is the simplest form of the top-N-per-group problem. In general, this problem is a real headache to handle in MySQL, which doesn't support analytic functions (at least not in most versions folks are using in production these days). However, since you only want the first record per group, we can do a join to subquery which finds the max created value for each nid group.
SELECT a1.*
FROM activity a1
INNER JOIN
(
SELECT nid, MAX(created) AS max_created
FROM activity
GROUP BY nid
) a2
ON a1.nid = a2.nid AND a1.created = a2.max_created;
Demo
You can use a subquery and join
select * from activity m
inner join (
select nid, min(created) min_date
from activity
group by nid
limit 5
) t on t.nid = m.nin and t.min_date = m.created
I have a spendings table and a dates table, that are joined by date_id and id...
What I'm trying to do, is get from 1 query all the info from spendings, plus the sum of all the spendings but with a limit and/or offset
This is the query right now
SELECT spendings.id, spendings.price, spendings.title,
dates.date, users.username, currencies.value,
( SELECT SUM(sum_table.price)
FROM (
SELECT s.price
FROM spendings s, dates d
WHERE s.date_id = d.id
AND day(d.date) = 25
LIMIT 2 OFFSET 0
) as sum_table
) AS sum_price
FROM spendings, dates, users, currencies
WHERE spendings.date_id = dates.id
AND day(dates.date) = 25
AND spendings.user_id = users.id
AND spendings.curr_id = currencies.id
LIMIT 2 OFFSET 0
Output
id price title date username value sum_price
3 6.00 title1 2013-11-25 alex € 21.00
4 15.00 title2 2013-11-25 alex € 21.00
It works, but only if the date here day(d.date) = 25 is the same as the outer one here day(dates.date) = 25
If instead I put day(d.date) = day(dates.date) which seems the logic thing to do, I get #1054 - Unknown column 'dates.date' in 'where clause'
If anyone has an idea to make this simpler let me know :)
Try to join instead of using nested correlated subqueries:
SELECT spendings.id, spendings.price, spendings.title,
dates.date, users.username, currencies.value,
y.sum_price
FROM spendings, dates, users, currencies
JOIN (
SELECT day, SUM(sum_table.price) As sum_price
FROM (
SELECT day(d.date) As day,
s.price
FROM spendings s, dates d
WHERE s.date_id = d.id
AND day(d.date) = 25
LIMIT 2 OFFSET 0
) sum_table
GROUP BY day
) y
ON y.day = day(dates.date)
WHERE spendings.date_id = dates.id
-- AND day(dates.date) = 25 <== commented since it's redundant now
AND spendings.user_id = users.id
AND spendings.curr_id = currencies.id
Some remarks:
Using old join syntax with commas is not recommended: FROM table1,table2,table2 WHERE
The recommended way of expressing joins is "new" ANSI SQL join syntax:
FROM table1
[left|right|cross|[full] outer|natural] JOIN table2 {ON|USING} join_condition1
[left|right|cross|[full] outer|natural] JOIN table3 {ON|USING} join_condition2
....
Actually this "new syntax" is quite old now, since is has been published, as I remember, in 1992 - 22 years ago. In IT industry 22 years is like 22 ages.
I have a two tables first one is called teams and second one is called cpd and I want this result required (see result screen below). I tried myself but was not successful (see practice query below).
teams table
id name sub_cat_id
1 SACRAMENTO KINGS 19
2 KINGS 19
3 MIMAMI HEAT 19
4 HEAT 20
5 KITE 20
cpd table
id team_id status added_date
1 3 1 2012-05-26
2 3 1 2012-05-27
3 3 0 2012-05-28
practice Query
SELECT
t.`id`,t.`name`,IFNULL(cpd.status,0) AS resultStatus,IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM `teams` t
LEFT JOIN cpd ON cpd.team_id = t.id
WHERE t.`sub_cat_id` = 19 OR cpd.added_date = CURDATE()
Result Screen (Required only those rows are black color in screen)
Update
Explanation ?
I am trying to get those rows who they are related with sub_cat_id = 19 like this in team table
Join team table with cpd table for cpd.status filed
cpd.status must be related with current date in cpd table like 2012-05-28
There are more than one way to get the desired result:
For example:
SELECT t.`id`,t.`name`,
IFNULL(cpd.status,0) AS resultStatus,
IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM `teams` t
INNER JOIN cpd ON (cpd.team_id = t.id AND cpd.status = 0)
WHERE t.`sub_cat_id` = 19
OR
cpd.added_date = CURDATE()
Your JOIN ON cpd.team_id = t.idonly matches one tuple with the cpd table so for the other tuples date is set as NULL (because you are doing LEFT JOIN) and hence the where query gives only one tuple
SELECT
t.id,t.name,IFNULL(cpd.status,0) AS resultStatus,IFNULL(cpd.added_date,CURDATE()) AS added_date
FROM teams t
LEFT JOIN cpd ON cpd.team_id = t.id
WHERE t.sub_cat_id = 19 OR cpd.added_date = CURDATE()
GROUP BY t.id