MySQL query: selecting latest results - php

I have a MySQL table with data, and would like to select the most recent entry for a specific test that was conducted.
table: research
columns: id (int), projectid (int), test (varchar),
when (datetime), result (longtext)
Example of rows:
1, 6, "Test1", 01-01-2013 12:15, "AAAAA"
2, 6, "Test1", 01-01-2013 13:15, "BBBBB"
3, 6, "Test2", 01-01-2013 16:00, "CCCCC"
4, 6, "Test2", 01-01-2013 16:15, "DDDDD"
5, 6, "Test2", 01-01-2013 16:30, "EEEEE"
If I want the latest results for "Test 1", I use..
SELECT *
FROM research
WHERE projectid=6 AND test='Test1'
ORDER BY when DESC limit 1
But how can I get the latest result for each test in a single project, in one query? I tried:
SELECT research.*
FROM research INNER JOIN (SELECT MAX(id) AS id
FROM research
WHERE projectid=".$ProjectId."
GROUP BY test) ids
ON research.id = ids.id
WHERE research.projectid=".$ProjectId
But it does not give me the latest result from a test, it gives me the oldest..

The idea behind the subquery is that it separately gets the latest date WHEN for every projectid and test. The result of which is then joined back on the original table.
SELECT a.*
FROM research a
INNER JOIN
(
SELECT projectid, test, MAX(`when`) maxDate
FROM research
GROUP BY projectid, test
) b ON a.projectid = b.projectid AND
a.test = b.test AND
a.`when` = b.maxDate
SQLFiddle Demo

You can use a WHERE with IN that filters on the max(when) date:
select *
from research r
where r.projectid = 6
and r.`when` in (select max(`when`)
from research
group by projectid, test);
See SQL Fiddle with Demo

Try this:
SELECT test,when,result
FROM research r1
WHERE when = (SELECT max(when)
FROM research r2
WHERE r2.test=r1.test)

Please try the query below.
SELECT `e`.* FROM (
SELECT `r`.*
FROM research `r`
ORDER By `r`.`when`
DESC
)`e`
WHERE `e`.`projectid`= $ProjectId GROUP BY `e`.`test`
I Hope this helps.
Thanks.

So, to get for every test of every project, the row with the largest when column, you can use this:
SELECT r.*
FROM research AS r
JOIN
( SELECT projectid, test, MAX(`when`) AS maxw
FROM research
GROUP BY projectid, test
) AS m
ON m.projectid = r.projectid
AND m.test = r.test
AND m.maxw = r.`when` ;
For every test of a single project (say project 6):
SELECT r.*
FROM research AS r
JOIN
( SELECT test, MAX(`when`) AS maxw
FROM research
WHERE projectid = 6
GROUP BY test
) AS m
ON m.test = r.test
AND m.maxw = r.`when`
WHERE r.projectid = 6 ;
An index on (projectid, test, when) will help efficiency of both the above queries.

Partition by test and order by date within the partition. Use analytic functions. No need for complicated queries above/below...

Related

get monthwise row count from datewise data in mysql

I have two tables:
table 1.a
id--entry_date-amount
============================
2---2016-04-14--$400
3---2016-04-14--$400
4----2017-07-14--$200
5---2017-07-14--$500
6---2017-05-14--$600
7----2017-06-18--$100
table 2.b
id--entry_date
===========================
2---2016-04-14--$230
3---2016-04-14--$230
4----2017-07-14--$567
5---2017-07-14--$600
6---2017-05-14--$560
7----2017-06-18--$90
8---2016-04-14--$100
from the two tables how can i get count with montwise
my desired result:
month_name--total(count form table a)--total(count form table b)--amount(table a)--amount(table b)
========================================================
April,16-----------2-------------------3---$800-$500
May,17-----------1-------------------1 --$600--$700
June,17-----------2-------------------2--$100--$800
July,17-----------2-------------------2---$700-$400
this is the demo data.
I want to show data from two tables in a single query month wise.
How can i do this?
I tried:
SELECT MONTHNAME(r.entry_date),r.a_total FROM(
SELECT
IFNULL((SELECT COUNT(tr.id) AS amount FROM a AS tr WHERE MONTH(tr.entry_date)=MONTH(t.entry_date)),0) AS a_total
,t.entry_date
FROM(SELECT tr.id,tr.entry_date
FROM a AS tr
WHERE DATE(tr.entry_date) BETWEEN '2017-07-01' AND '2018-06-30') t
GROUP BY MONTH(t.entry_date)) r
But takes 58 seconds for simple query. How can i make this in a simple query?
You can get the counts and sum from each table individually, then use UNION to combine the two result sets into one result set. Something like this :
SELECT Month_name,
SUM(aCount) AS aCount,
SUM(bCount) AS bCount,
SUM(aAmount) AS aAmount,
SUM(bAmount) AS bAmount
FROM
(
SELECT
MONTHNAME(a.entry_date) AS Month_name,
COUNT(a.id) AS aCount,
0 AS bCount,
SUM(a.amount) AS aAmount,
0 AS bAmount
FROM a
GROUP BY MONTHNAME(a.entry_date)
UNION ALL
SELECT
MONTHNAME(b.entry_date) AS Month_name,
0 AS aCount,
COUNT(b.id) AS bCount,
0 AS aAmount,
SUM(b.amount) AS bAmount
FROM b
GROUP BY MONTHNAME(b.entry_date)
) AS t
GROUP BY Month_Name;
live demo
user9131497 has a good design for the big picture. However, I would suggest stuff like this for handling the dates:
SELECT DATE_FORMAT(entry_date, "%M,%y") AS 'Month',
COUNT(*) AS 'aCount'
FROM a
GROUP BY LEFT(entry_date, 7) -- eg, "2017-03"
Try that to see what I mean.
Note that this will work beyond a year. Or did you need January values from all years to be combined?? -- That's what your solution and user9131497's will do. Mine keeps them separate.

SQL Left Join Return Latest Row

Two tables, with a left join. For ease table 1 and table 2.
Table 1 contains a list of people and their current status, table 2 is all of their "invites". All im trying to do as part of the join is show in a list all the current "people" and then the LATEST invite status (from table 2) so return a single row from table 2.
I have everything working... but its duplicating for example if a person has had multiple invites it will put them twice on the list. I just want to limit it to
$sql = "SELECT table1.fieldname as table1fielname table2.fieldname [more fields]
FROM xxx
LEFT JOIN xxx on table1.sharedid=table2.sharedid
WHERE XXX LIMIT 1 ";`
Obvioulsy the limit 1 doesnt do what its supposed to. I have tried adding additional select statements in brackets but being honest it just breaks everything and im not an expert at all.
I'm not an expert too but I'll try. Have you tried to use DISTINCT?
For exemple:
SELECT DISTINCT column_name1,column_name2
FROM table_name; [...]
It normally delete double matches.
Here are the links:
http://www.w3schools.com/sql/sql_distinct.asp
https://www.techonthenet.com/oracle/distinct.php
Give example data. And use good table and column names. For example:
(this returns all rows that satisfy the join):
WITH people(ppl_id,ppl_name,status) AS (
SELECT 1,'Arthur','active'
UNION ALL SELECT 2,'Tricia','active'
), invites(ppl_id,inv_id,inv_date) AS (
SELECT 1,1, DATE '2017-01-01'
UNION ALL SELECT 1,2, DATE '2017-01-07'
UNION ALL SELECT 1,3, DATE '2017-01-08'
UNION ALL SELECT 2,1, DATE '2017-01-01'
UNION ALL SELECT 2,2, DATE '2017-01-08'
)
SELECT
*
FROM people
JOIN invites USING(ppl_id)
ORDER BY 1
;
ppl_id|ppl_name|status|inv_id|inv_date
1|Arthur |active| 1|2017-01-01
1|Arthur |active| 3|2017-01-08
1|Arthur |active| 2|2017-01-07
2|Tricia |active| 2|2017-01-08
2|Tricia |active| 1|2017-01-01
But we want only 'Arthur' with '2017-01-08' and 'Tricia' with '2017-01-08'.
With any database that supports ANSI 99, you could try with a temporary table containing the newest invitation date per "people id", and join that temporary table with the invitations table. We call that table newest_invite_date, and, apparently, it does what we expect it to do:
WITH people(ppl_id,ppl_name,status) AS (
SELECT 1,'Arthur','active'
UNION ALL SELECT 2,'Tricia','active'
), invites(ppl_id,inv_id,inv_date) AS (
SELECT 1,1, DATE '2017-01-01'
UNION ALL SELECT 1,2, DATE '2017-01-07'
UNION ALL SELECT 1,3, DATE '2017-01-08'
UNION ALL SELECT 2,1, DATE '2017-01-01'
UNION ALL SELECT 2,2, DATE '2017-01-08'
), newest_invite_date(ppl_id,inv_date) AS (
SELECT ppl_id,MAX(inv_date)
FROM invites
GROUP BY ppl_id
)
SELECT
people.ppl_id
, people.ppl_name
, people.status
, newest_invite_date.inv_date
FROM people
JOIN newest_invite_date USING(ppl_id)
ORDER BY 1
;
ppl_id|ppl_name|status|inv_date
1|Arthur |active|2017-01-08
2|Tricia |active|2017-01-08
Is this what you were looking for?
Happy playing ...
Marco the Sane

MySQL one row per user depending on maxDate or maxId

I have one table in MYSQL called enrollments with these fields: id, user, estimated_date.
The values in this case are:
id, user, estimated_date
1, 1, 2015-10-10
2, 1, 2015-10-10
3, 2, 2015-10-20
4, 2, 2015-10-10
I want to select one row per user: the one that has the max(estimated_date). But in case the estimated_date is equal has to select the one that has the max(id). In other words... a group by with ordering by estimated_date and id (in this order).
The output should be:
2, 1, 2015-10-10
3, 2, 2015-10-20
I have this code now:
SELECT * from enrollments m
INNER JOIN
(SELECT user,
max(estimated_date) AS maxdate
FROM enrollments
GROUP BY user
) x ON m.user = x.user
AND m.estimated_date = x.maxdate
Could you help me please? I've been searching a lot but I didn't find anything that fits this case... Thanks!
You don't need an INNER JOIN, just a ORDER by estimated_date DESC, id DESC is enough. Here is your query:
Select * FROM (
SELECT
DISTINCT `id`, `user`, `estimated_date`
FROM
enrollments
ORDER by estimated_date DESC, id DESC
) as X
GROUP BY user
full fiddle here: http://sqlfiddle.com/#!9/31799/3

How to update a group of records with incremental values

I have a table with columns "step_ID", "goal_ID" and "step_number".
step_ID would be the primary, auto-incremented key
goal_ID groups the various steps into specific goals
step_number is currently 0 everywhere, newly created
I want to UPDATE the table so that step_number is set to 0, 1, 2, 3, etc. for every group of similar "goal_ID", ordered by the step_ID.
Said a different way, I'd like to number the steps for a given goal from 0 to however many goals there are, in ascending order of step_ID.
I tried the obvious:
$query = " UPDATE steps SET step_num = step_num + 1 WHERE goal_ID='689' ORDER BY step_ID";
but that just sets all the rows to 1, (as expected.)
I tried also using various subqueries but I got an error saying I couldn't update a table used in the FROM clause.
Thoughts?
Try break the query into 2, and make use of mysql user defined variables
like
$db->query("SET #counter:=-1");
$db->query(UPDATE steps SET step_num = #counter:=#counter+1 WHERE goal_ID='689' ORDER BY step_ID");
Update steps
Set step_num = (
Select Count(*)
From steps As T1
Where T1.goal_ID = steps.goal_ID
And T1.step_ID < steps.step_ID
)
EDIT
Looks like you are running into a bug in MySQL with respect to Update queries that contain correlated subqueries. The above would be the standardized version. You can work around the limitation in MySQL like so:
Update steps
Set step_num = (
Select Count(*)
From (
Select T1.step_Id, T1.goal_Id
From steps As T1
) As Z
Where Z.goal_Id = steps.goal_ID
And Z.step_ID < steps.step_ID
)
Another solution:
Update steps As T1
Join (
Select T1.step_ID, Count(T2.step_ID) As step_num
From steps As T1
Left Join steps As T2
On T2.goal_ID = T1.goal_ID
And T2.step_ID < T1.step_ID
Group By T1.step_ID
) As T3
On T3.step_ID = T1.step_ID
Set step_num = T2.step_num

Need help in optimising query

I have two tables - incoming tours(id,name) and incoming_tours_cities(id_parrent, id_city)
id in first table is unique, and for each unique row from first table there is the list of id_city - s in second table(i.e. id_parrent in second table is equal to id from first table)
For example
incoming_tours
|--id--|------name-----|
|---1--|---first_tour--|
|---2--|--second_tour--|
|---3--|--thirth_tour--|
|---4--|--hourth_tour--|
incoming_tours_cities
|-id_parrent-|-id_city-|
|------1-----|---4-----|
|------1-----|---5-----|
|------1-----|---27----|
|------1-----|---74----|
|------2-----|---1-----|
|------2-----|---5-----|
........................
That means that first_tour has list of cities - ("4","5","27","74")
AND second_tour has list of cities - ("1","5")
Let's assume i have two values - 4 and 74:
Now, i need to get all rows from first table, where my both values are in the list of cities. i.e it must return only the first_tour (because 4 and 74 are in it's list of cities)
So, i wrote the following query
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
And that works fine.
But i generate the query dynamically, and when the count of joins is big (about 15) the query slowing down.
i.e. when i try to run
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
.........................................................
JOIN `incoming_tours_cities` tc15 ON tc15.id_parrent = t.id
AND tc15.id_city = 'some_value'
the query run's in 45s(despite on i set indexes in the tables)
What can i do, to optimaze it?
Thanks much
SELECT t.name
FROM incoming_tours t INNER JOIN
( SELECT id_parrent
FROM incoming_tours_cities
WHERE id IN (4, 74)
GROUP BY id_parrent
HAVING count(id_city) = 2) resultset
ON resultset.id_parrent = t.id
But you need to change number of total cities count.
SELECT name
FROM (
SELECT DISTINCT(incoming_tours.name) AS name,
COUNT(incoming_tours_cities.id_city) AS c
FROM incoming_tours
JOIN incoming_tours_cities
ON incoming_tours.id=incoming_tours_cities.id_parrent
WHERE incoming_tours_cities.id_city IN(4,74)
HAVING c=2
) t1;
You will have to change c=2 to whatever the count of id_city you are searching is, but since you generate the query dynamically, that shouldn't be a problem.
I'm pretty sure this works, but a lot less sure that it is optimal.
SELECT * FROM incoming_tours
WHERE
id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=4)
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=74)
...
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=some_value)
Just an hint.
If you use the IN operator in a WHERE clause, you can hope that the short-circuit of operator AND may remove unnecessary JOINs during the execution for the tours that do not respect the constraint.
Seems like an odd way to do that query, here
SELECT t.name FROM `incoming_tours` as t WHERE t.id IN (SELECT id_parrent FROM `incoming_tours_cities` as tc WHERE tc.id_city IN ('4','74'));
I think that does it, but not tested...
EDIT: Added table alias to sub-query
I've written this query using CTE's and it includes the test data in the query. You'll need to modify it so that it queries the real tables instead. Not sure how it performs on a large dataset...
Declare #numCities int = 2
;with incoming_tours(id, name) AS
(
select 1, 'first_tour' union all
select 2, 'second_tour' union all
select 3, 'third_tour' union all
select 4, 'fourth_tour'
)
, incoming_tours_cities(id_parent, id_city) AS
(
select 1, 4 union all
select 1, 5 union all
select 1, 27 union all
select 1, 74 union all
select 2, 1 union all
select 2, 5
)
, cityIds(id_city) AS
(
select 4
union all select 5
/* Add all city ids you need to check in this table */
)
, common_cities(id_city, tour_id, tour_name) AS
(
select c.id_city, it.id, it.name
from cityIds C, Incoming_tours_cities tc, incoming_tours it
where C.id_city = tc.id_city
and tc.id_parent = it.id
)
, tours_with_all_cities(id_city) As
(
select tour_id from common_cities
group by tour_id
having COUNT(id_city) = #numCities
)
select it.name from incoming_tours it, tours_with_all_cities tic
where it.id = tic.id_city

Categories