I have the following influences table:
I need to get the latest influence for a stakeholder...
I have the following query but it's mixing up the row data because MAX isn't returning a full record:
SELECT stakeholder_id, MAX(created_at) AS maxca, influence
FROM influences
WHERE
project_id = 1 AND
deleted_at IS NULL
GROUP BY stakeholder_id
You can see that the influence against that maxca and stakeholder_id should be 3, not 5.
How do I overcome this issue?
My full existing statement was this:
SELECT `stakeholders`.*, `influences`.`influence`
FROM `project_stakeholder`
INNER JOIN `stakeholders` ON `project_stakeholder`.`stakeholder_id` = `stakeholders`.`id`
INNER JOIN `stakeholder_profiles` ON `stakeholder_profiles`.`stakeholder_id` = `stakeholders`.`id`
LEFT JOIN `stakeholder_profile_tag` ON `stakeholder_profile_tag`.`stakeholder_profile_id` = `stakeholder_profiles`.`id`
LEFT JOIN `stakeholder_profile_group` ON `stakeholder_profile_group`.`stakeholder_profile_id` = `stakeholder_profiles`.`id`
LEFT JOIN `influences` ON `influences`.`stakeholder_id` = `stakeholders`.`id`
INNER JOIN `projects` ON `project_stakeholder`.`project_id` = `projects`.`id`
LEFT JOIN (
/*! This is the bit that doesn't work */
SELECT stakeholder_id, MAX(created_at) AS maxca
FROM influences
WHERE
project_id = 1 AND
deleted_at IS NULL
GROUP BY stakeholder_id
)
iu ON `iu`.`stakeholder_id` = influences.stakeholder_id AND
iu.maxca = influences.created_at
WHERE `projects`.`id` = '1'
GROUP BY `stakeholders`.`id`
This seems to work:
SELECT `stakeholders`.*, iu.influence
FROM `project_stakeholder`
INNER JOIN `stakeholders` ON `project_stakeholder`.`stakeholder_id` = `stakeholders`.`id`
INNER JOIN `stakeholder_profiles` ON `stakeholder_profiles`.`stakeholder_id` = `stakeholders`.`id`
LEFT JOIN `stakeholder_profile_tag` ON `stakeholder_profile_tag`.`stakeholder_profile_id` = `stakeholder_profiles`.`id`
LEFT JOIN `stakeholder_profile_group` ON `stakeholder_profile_group`.`stakeholder_profile_id` = `stakeholder_profiles`.`id`
INNER JOIN `projects` ON `project_stakeholder`.`project_id` = `projects`.`id`
LEFT JOIN (
select i1.*
from influences i1
join
(
SELECT stakeholder_id, MAX(created_at) AS maxca
FROM influences
WHERE project_id = 1
AND deleted_at IS NULL
GROUP BY stakeholder_id
) i2 on i1.stakeholder_id = i2.stakeholder_id
and i1.created_at = i2.maxca
) iu ON `iu`.`stakeholder_id` = stakeholders.id
WHERE `projects`.`id` = '1'
GROUP BY `stakeholders`.`id`
select i1.*
from influences i1
join
(
SELECT stakeholder_id, MAX(created_at) AS maxca
FROM influences
WHERE project_id = 1
AND deleted_at IS NULL
GROUP BY stakeholder_id
) i2 on i1.stakeholder_id = i2.stakeholder_id
and i1.created_at = i2.maxca
Related
How would I transpile this query to Laravel style
SELECT
gr.name,
p.ma_status AS status,
COUNT(p.ma_status) AS total
FROM accounts u
LEFT JOIN accounts_prop p ON p.account_id = u.account_id
AND (
SELECT j.iid
FROM accounts_prop AS j
WHERE u.account_id = j.account_id
AND j.ma_status IS NOT NULL
ORDER BY j.von DESC LIMIT 1
) = p.iid
LEFT JOIN `deprecators` gr ON gr.id = p.group_id
LEFT JOIN `deprecators` unit ON unit.id = p.unit_id
LEFT JOIN `deprecators` team ON team.id = p.team_id
WHERE p.group_id IS NOT NULL
AND u.account_status = 'A'
GROUP BY p.ma_status, gr.id
I do not have any idea how would I go with the following statement
LEFT JOIN accounts_prop p ON p.account_id = u.account_id
AND (
SELECT j.iid
FROM accounts_prop AS j
WHERE u.account_id = j.account_id
AND j.ma_status IS NOT NULL
ORDER BY j.von DESC LIMIT 1
) = p.iid
Been banging my head against the wall and cannot solve this :\
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
`geo_checkin_on_campus`.`datetime_created` as checkin_time
FROM `student_class_relationships`
LEFT OUTER JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT OUTER JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT OUTER JOIN `geo_checkin_on_campus`
ON `students`.`id` = (
SELECT MIN(`geo_checkin_on_campus`.`student`)
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
)
WHERE `class` = 56
The expected result is many rows that have only one entry per students.id.
Here is my schema
It is not the best query from performance perspective,
but just to fix your query here is my attempt:
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
geoCheckinOnCampus.datetimeCreated as checkin_time
FROM `student_class_relationships`
LEFT JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT JOIN
(
SELECT
student,
MAX(datetime_created) datetimeCreated
FROM `geo_checkin_on_campus`
GROUP BY `student`
) geoCheckinOnCampus
ON `students`.`id` = geoCheckinOnCampus.`student`
WHERE `class` = 56
Note According to #xQbert answer I would really change MIN to MAX function if you are looking for the latest datetime.
If i assume you want the most recent checkin (and not the earliest created date) for each student in go_checkin_on_Campus then...
SELECT
`people`.*,
`students`.*,
`student_class_relationships`.*,
B.`datetime_Updated` as checkin_time
FROM `student_class_relationships`
LEFT OUTER JOIN `students`
ON `student_class_relationships`.`student` = `students`.`id`
LEFT OUTER JOIN `people`
ON `students`.`student` = `people`.`id`
LEFT OUTER JOIN (
SELECT max(datetime_updated), student
FROM `geo_checkin_on_campus`
group by student
) B
ON `students`.`id` = B.Student
WHERE `class` = 56
NOTE: This is a probable answer. I will edit / modify this according to comments from OP.
This basically does nothing. This is as good as just joining on the student id
LEFT OUTER JOIN `geo_checkin_on_campus`
ON `students`.`id` = (
SELECT MIN(`geo_checkin_on_campus`.`student`)
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
)
If you want the min (or earliest) datetime_created use something like
LEFT OUTER JOIN (
SELECT `geo_checkin_on_campus`.`student` student,
MIN(`geo_checkin_on_campus`.`datetime_created`) dt
FROM `geo_checkin_on_campus`
WHERE `geo_checkin_on_campus`.`student` = `students`.`id`
GROUP BY `geo_checkin_on_campus`.`student`
) t
ON `students`.`id` = t.student
I don't know much about query optimization but I know the order in which queries get executed
FROM clause
WHERE clause
GROUP BY clause
HAVING clause
SELECT clause
ORDER BY clause
This the query I had written
SELECT
`main_table`.forum_id,
my_topics.topic_id,
(
SELECT MAX(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id
) AS `maxpostid`,
(
SELECT my_posts.admin_user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `admin_user_id`,
(
SELECT my_posts.user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `user_id`,
(
SELECT COUNT(my_topics.topic_id) FROM my_topics WHERE my_topics.forum_id = main_table.forum_id ORDER BY my_topics.forum_id DESC LIMIT 1
) AS `topicscount`,
(
SELECT COUNT(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_topics.topic_id DESC LIMIT 1
) AS `postcount`,
(
SELECT CONCAT(admin_user.firstname,' ',admin_user.lastname) FROM admin_user INNER JOIN my_posts ON my_posts.admin_user_id = admin_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `adminname`,
(
SELECT forum_user.nick_name FROM forum_user INNER JOIN my_posts ON my_posts.user_id = forum_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `nickname`,
(
SELECT CONCAT(ce1.value,' ',ce2.value) AS fullname FROM my_posts INNER JOIN customer_entity_varchar AS ce1 ON ce1.entity_id = my_posts.user_id INNER JOIN customer_entity_varchar AS ce2 ON ce2.entity_id=my_posts.user_id WHERE (ce1.attribute_id = 1) AND (ce2.attribute_id = 2) AND my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `fullname`
FROM `my_forums` AS `main_table`
LEFT JOIN `my_topics` ON main_table.forum_id = my_topics.forum_id
WHERE (forum_status = '1')
And now I want to know if there is any way to optimize it ? Because all the logic is written in Select section not From, but I don't know how to write the same logic in From section of the query ?
Does it make any difference or both are same ?
Thanks
Correlated subqueries should really be a last resort, they often end up being executed RBAR, and given that a number of your subqueries are very similar, trying to get the same result using joins is going to result in a lot less table scans.
The first thing I note is that all of your subqueries include the table my_posts, and most contain ORDER BY my_posts.post_id DESC LIMIT 1, those that don't have a count with no group by so the order and limit are redundant anyway, so my first step would be to join to my_posts:
SELECT *
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
WHERE forum_status = '1';
Here the subquery just ensures you get the latest post per topic_id. I have shortened your table aliases here for my convenience, I am not sure why you would use a table alias that is longer than the actual table name?
Now you have the bulk of your query you can start adding in your columns, in order to get the post count, I have added a count to the subquery Maxp, I have also had to add a few more joins to get some of the detail out, such as names:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
t2.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id,
MAX(post_id) AS post_id,
COUNT(*) AS postcount
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
LEFT JOIN
( SELECT forum_id, COUNT(*) AS topicscount
FROM my_topics
GROUP BY forum_id
) AS t2
ON t2.forum_id = f.forum_id
WHERE forum_status = '1';
I am not familiar with your schema so the above may need some tweaking, but the principal remains - use JOINs over sub-selects.
The next stage of optimisation I would do is to get rid of your customer_entity_varchar table, or at least stop using it to store things as basic as first name and last name. The Entity-Attribute-Value model is an SQL antipattern, if you added two columns, FirstName and LastName to your forum_user table you would immediately lose two joins from your query. I won't get too involved in the EAV vs Relational debate as this has been extensively discussed a number of times, and I have nothing more to add.
The final stage would be to add appropriate indexes, you are in the best decision to decide what is appropriate, I'd suggest you probably want indexes on at least the foreign keys in each table, possibly more.
EDIT
To get one row per forum_id you would need to use the following:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
MaxT.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN
( SELECT t.forum_id,
COUNT(DISTINCT t.topic_id) AS topicscount,
COUNT(*) AS postCount,
MAX(t.topic_ID) AS topic_id
FROM my_topics AS t
INNER JOIN my_posts AS p
ON p.topic_id = p.topic_id
GROUP BY t.forum_id
) AS MaxT
ON MaxT.forum_id = f.forum_id
LEFT JOIN my_topics AS t
ON t.topic_ID = Maxt.topic_ID
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
WHERE forum_status = '1';
I need to remove the 2nd row of results which contain a duplicate of the total record from the 1st row. My result set is as follows
1 | 1025
1 | NULL --- I need to remove this row
My query is as follows:
SELECT SUM( mdl_quiz.fcpd ) AS cpdtotal, mdl_user.id AS userid
FROM mdl_grade_grades
INNER JOIN mdl_user ON mdl_grade_grades.userid = mdl_user.id
INNER JOIN mdl_grade_items ON mdl_grade_grades.itemid = mdl_grade_items.id
INNER JOIN mdl_quiz ON mdl_grade_items.itemname = mdl_quiz.name
INNER JOIN mdl_course ON mdl_grade_items.courseid = mdl_course.id
INNER JOIN mdl_user_info_data ON mdl_user.id = mdl_user_info_data.userid
WHERE mdl_user_info_data.fieldid =1
AND mdl_grade_items.itemname IS NOT NULL
AND mdl_user.annualCPDReportActive = 'Y'
AND (
mdl_course.category =27
)
AND mdl_user.id =1025
AND YEAR( FROM_UNIXTIME( mdl_grade_grades.timemodified ) ) =2013
GROUP BY mdl_user.id
WITH ROLLUP
You should remove WITH ROLLUP
more information here.
In the query below, I am trying to use the first table in a left outer join. However I am getting an error.
SELECT
products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
LEFT OUTER JOIN (SELECT COUNT( q.id ) AS ans
, pq.product_id
FROM products_quotes pq
LEFT JOIN quotes q
ON pq.quote_id = q.id
WHERE q.deleted = 0
AND pq.deleted = 0
AND q.stage <> 4
AND (pq.qty_shipped < pq.product_qty)
AND pq.product_id = products.id
GROUP BY pq.product_id
) AS customfields_oo
ON customfields_oo.product_id = products.id
LEFT JOIN products_cstm
ON products.id = products_cstm.id_c
WHERE products.deleted = 0
ORDER BY ans DESC
When I run the query it gives me the following error:
Error Code : 1054
Unknown column 'products.id' in 'where clause'
It is not allowing first "products" table in left outer join query.
The issue is that customfields_oo is a derived table not a correlated subquery. Thus, you cannot reference the outer table from within the definition of the derived table. In this case, you cannot refer to the outer products table from within the customfields_oo definition. Instead, you must do that filter in the On clause outside the dervied table definition.
Select products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
Left Join (
Select pq1.product_id
, Count( q1.id ) As ans
From products_quotes As pq1
Left Join quotes As q1
On pq1.quote_id = q1.id
Where q1.deleted = 0
And pq1.deleted = 0
And q1.stage <> 4
And pq1.qty_shipped < pq1.product_qty
Group By pq1.product_id
) As customfields_oo
On customfields_oo.product_id = products.id
Left Join products_cstm
On products.id = products_cstm.id_c
Where products.deleted = 0
Order By customfields_oo.ans Desc
Now, you have stated in comments that this is too slow because, say products where deleted <> 0 might be evaluated in the derived table. If that is the case, then simply expand the derived table to include the filters on the outer products table.
Select products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
Left Join (
Select pq1.product_id
, Count( q1.id ) As ans
From products_quotes As pq1
Join products As p1
On p1.products.id = pq1.product_id
Left Join quotes As q1
On pq1.quote_id = q1.id
Where q1.deleted = 0
And pq1.deleted = 0
And q1.stage <> 4
And pq1.qty_shipped < pq1.product_qty
And p1.deleted = 0
Group By pq1.product_id
) As customfields_oo
On customfields_oo.product_id = products.id
Left Join products_cstm
On products.id = products_cstm.id_c
Where products.deleted = 0
Order By customfields_oo.ans Desc
You do not need to have AND pq.product_id = products.id in the where statement. Because you are LEFT JOINing on that. So I think something like this will work:
AND (pq.qty_shipped < pq.product_qty)
GROUP BY pq.product_id) AS customfields_oo
ON customfields_oo.product_id = products.id
LEFT JOIN products_cstm
ON products.id = products_cstm.id_c
WHERE products.deleted = 0
ORDER BY openorder DESC
EDIT
You do not need to LEFT JOIN on the table you are COUNTing on. You can also do ot like this:
SELECT
.....
(
SELECT
COUNT( q.id )
FROM products_quotes pq
LEFT JOIN quotes q
ON pq.quote_id = q.id
WHERE q.deleted = 0
AND pq.deleted = 0
AND q.stage <> 4
AND (pq.qty_shipped < pq.product_qty)
AND pq.product_id = products.id
) AS ans
FROM products
.....