Convert this subquery into a JOIN? - mysql

Does anyone know if it's possible to convert this subquery into a JOIN?
SELECT DISTINCT
lastname,
c.fullname,
(SELECT COUNT(lg.action) FROM tbl_log AS lg WHERE lg.userid = u.id AND lg.course = c.id) AS 'Total Course Hits Per Student'
FROM tbl_user AS u
JOIN tbl_user_enrolments AS ents ON ents.userid = u.id
JOIN tbl_enrol AS en ON ents.enrolid = en.id
JOIN tbl_course AS C ON c.id = en.courseid
JOIN tbl_context AS ctx ON c.id = ctx.instanceid
JOIN tbl_role_assignments AS ra ON ra.contextid = ctx.id AND ra.userid = u.id
LOG TABLE
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| time | | NO | | NULL | |
| userid | | NO | | NULL | |
| course | | NO | | NULL | |
| action | | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
USER Table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| username | | NO | | NULL | |
| userpassword | | NO | | NULL | |
| lastname | | NO | | NULL | |
| firstname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
COURSE table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| category | | NO | | NULL | |
| fullname | | NO | | NULL | |
| shortname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
I link the users together via the enrolment and context tables.

Cant you take a query on the log_table
SELECT
COUNT(tbl_log.action)
lastname,
c.fullname,
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_log.userid, tbl_log.course
I don't know if you need the other tables? This would provide a count, the student name and the course name if I am not mistaken. However, you only get the actual logs, so no occurences for students that haven't done anything.
Otherwise an OUTER JOIN may suffice on the log-table. I have no time to check now in SQL fiddle. Hope this already helps you a bit on your way.

Related

What is the average year_max - year_min in these sql tables?

I have 3 tables: actors, movies and cast:
mysql> desc actors;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| id | int | NO | PRI | 0 | |
| full_name | varchar(200) | YES | | NULL | |
| gender | varchar(1) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
mysql> desc movies;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int | NO | PRI | 0 | |
| title | varchar(100) | YES | | NULL | |
| year | int | YES | | NULL | |
| genre | varchar(100) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
and
mysql> desc cast;
+----------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+------+------+-----+---------+-------+
| actor_id | int | YES | MUL | NULL | |
| movie_id | int | YES | MUL | NULL | |
| salary | int | YES | | NULL | |
+----------+------+------+-----+---------+-------+
The connection between tables is: cast.movie_id = movies.id and actors.id = cast.actor_id
The question: what is the average career longevity(years between the first film and the last film) of
actors who were at least in five films during their career?
I have tried to list actors ordered by the number of films, they took part in:
mysql> select full_name, count(title) as movie_title from cast, actors, movies
-> where cast.movie_id = movies.id and actors.id = cast.actor_id
-> group by full_name
-> order by movie_title
-> desc limit 2;
+--------------------------+-------------+
| full_name | movie_title |
+--------------------------+-------------+
| Kevin Bacon | 9 |
| Bill Paxton | 3 |
...
If what I've done is correct (not sure), we have only one such actor, so the question will be how to find this span
Will be very grateful for any advice!
How about:
SELECT AVG(YearsActive) TotalAverage
FROM (
-- by actor
SELECT a.id AS ActorId,
-- GROUP_CONCAT(a.full_name) AS ActorName,
(MAX(m.year) - MIN(m.year)) AS YearsActive
FROM actors a
INNER JOIN cast c ON a.id = c.actor_id
INNER JOIN movies m ON c.movie_id = m.id
GROUP BY a.id
HAVING COUNT(0) >= 5
) B
;

Optimize not in sub-query MYSQL

I've got table in MySQL db where >20 000 000 rows, the query below executes great on small amount of rows, but takes 2-3 secs if there are more. How can I optimize this to make it run < 1 at least?
Note - the problem is in sub-query SELECT read_state FROM messages...
Query:
SELECT sql_no_cache users.id AS uid,
name,
avatar,
avatar_date,
driver,
msg,
DATE,
messages.removed,
from_id = 528798 AS outbox ,
!(0 IN
(SELECT read_state
FROM messages AS msgs FORCE KEY(user_id_2)
WHERE (msgs.from_id = messages.from_id
OR msgs.from_id = messages.user_id)
AND msgs.user_id = 528798
AND removed = 0
)) AS read_state
FROM dialog,
messages,
users
WHERE messages.id = mid
AND ((uid1 = 528798
AND users.id = uid2)
OR (uid2 = 528798
AND users.id = uid1))
ORDER BY DATE DESC;
show index from messages;
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| messages | 0 | PRIMARY | 1 | id | A | 27531939 | NULL | NULL | | BTREE | | |
| messages | 1 | to_number | 1 | to_number | A | 22 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 1 | from_id | A | 529460 | NULL | NULL | | BTREE | | |
| messages | 1 | from_id | 2 | to_number | A | 529460 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 1 | user_id | A | 655522 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 2 | read_state | A | 917731 | NULL | NULL | | BTREE | | |
| messages | 1 | user_id_2 | 3 | removed | A | 949377 | NULL | NULL | | BTREE | | |
| messages | 1 | idx_user_id | 1 | user_id | A | 809762 | NULL | NULL | | BTREE | | |
| messages | 1 | idx_from_id | 1 | from_id | A | 302548 | NULL | NULL | | BTREE | | |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
desc messages;
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| from_id | int(11) | NO | MUL | NULL | |
| user_id | int(11) | NO | MUL | NULL | |
| group_id | int(11) | NO | | NULL | |
| to_number | varchar(30) | NO | MUL | NULL | |
| msg | text | NO | | NULL | |
| image | varchar(20) | NO | | NULL | |
| date | bigint(20) | NO | | NULL | |
| read_state | tinyint(1) | NO | | 0 | |
| removed | tinyint(1) | NO | | NULL | |
+------------+-------------+------+-----+---------+----------------+
EXPLAIN EXTENDED:
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+
| 1 | PRIMARY | dialog | index_merge | uid1,uid2 | uid1,uid2 | 4,4 | NULL | 1707 | 100.00 | Using sort_union(uid1,uid2); Using where; Using temporary; Using filesort |
| 1 | PRIMARY | users | ALL | PRIMARY | NULL | NULL | NULL | 608993 | 100.00 | Range checked for each record (index map: 0x1) |
| 1 | PRIMARY | messages | eq_ref | PRIMARY | PRIMARY | 4 | numbers.dialog.mid | 1 | 100.00 | |
| 2 | DEPENDENT SUBQUERY | msgs | ref | user_id_2 | user_id_2 | 6 | const,const,const | 2607 | 100.00 | Using where |
+----+--------------------+----------+-------------+---------------+-----------+---------+--------------------+--------+----------+---------------------------------------------------------------------------+
Making a few guesses, something like this might be more efficient:-
SELECT DISTINCT users.id AS uid,
name,
avatar,
avatar_date,
driver,
msg,
`DATE`,
messages.removed,
from_id = 528798 AS outbox ,
CASE WHEN msgs.read_state IS NULL THEN 1 ELSE 0 END AS read_state
FROM messages
INNER JOIN dialog ON messages.id = dialog.mid
INNER JOIN users ON (dialog.uid1 = 528798 AND users.id = dialog.uid2) OR (dialog.uid2 = 528798 AND users.id = dialog.uid1)
LEFT OUTER JOIN messages msgs ON msgs.read_state = 0 AND msgs.user_id = 528798 AND removed = 0 AND (msgs.from_id = messages.from_id OR msgs.from_id = messages.user_id)
ORDER BY `DATE` DESC;
This is doing an extra join as a LEFT JOIN against messages again, and then using case to convert the result to 0 or 1.
the DISTINCT should cope when the LEFT JOIN can bring back multiple matching rows (if that is not possible then you can elminate the DISTINCT)
Suspect the OR clauses in the join onto users will not be that efficient. May be better to replace the INNER JOIN against users with 2 LEFT OUTER JOINs. Something like this:-
SELECT DISTINCT COALESCE(users1.id, users2.id) AS uid,
COALESCE(users1.name, users2.name),
COALESCE(users1.avatar, users2.avatar),
COALESCE(users1.avatar_date, users2.avatar_date),
COALESCE(users1.driver, users2.driver),
msg,
`DATE`,
messages.removed,
from_id = 528798 AS outbox ,
CASE WHEN msgs.read_state IS NULL THEN 1 ELSE 0 END AS read_state
FROM messages
INNER JOIN dialog ON messages.id = dialog.mid
LEFT OUTER JOIN users users1 ON (dialog.uid1 = 528798 AND users1.id = dialog.uid2)
LEFT OUTER JOIN users users2 ON (dialog.uid2 = 528798 AND users2.id = dialog.uid1)
LEFT OUTER JOIN messages msgs ON msgs.read_state = 0 AND msgs.user_id = 528798 AND removed = 0 AND (msgs.from_id = messages.from_id OR msgs.from_id = messages.user_id)
WHERE users1.id IS NOT NULL
OR users2.id IS NOT NULL
ORDER BY `DATE` DESC;

Subquery as another field

I have trying to do a SELECT query....not an UPDATE or INSERT or DELETE.
I have three tables.
The customers table
The invoices table
The invoice_items table
I want to run a query that will show me every invoice. Each invoice can have only ONE customer and MANY items...hence the existence of invoice_items
My current query looks like this
SELECT i.order_date, c.name, thedata.info from invoices i inner join customers c ON (i.customer = c.id) right join ( select x.order, group_concat( concat(x.itemname,' ', x.itemdesc) separator "\n" ) as info from invoice_items x ) thedata on (i.id = thedata.order)
When I run this query, I receive one row that contains, one customer, one invoice, and a list of any an every item regardless of invoice id or customer...???
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
| order_date | name | info |
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
| 2014-01-23 20:39:20 | Joe Customer | Boxes for boxing
Shoes for shining
2" Hermosa Plank for bobblin
Boxes for boxing
bobbles for bobblin
Lot 297 Woodale Carmel Oak |
+---------------------+--------------+---------------------------------------------------------------------------------------------------------------------------------+
My goal is to receive this same list but show all customers along with THEIR items.
What am I doing wrong?
Here are the schemas, for those that need them.
Customers
+---------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| ship_address | text | NO | | NULL | |
| ship_address2 | text | NO | | NULL | |
| ship_city | text | NO | | NULL | |
| ship_state | text | NO | | NULL | |
| ship_zip | int(6) | NO | | NULL | |
| bill_address | text | NO | | NULL | |
| bill_address2 | text | NO | | NULL | |
| bill_city | text | NO | | NULL | |
| bill_state | text | NO | | NULL | |
| bill_zip | text | NO | | NULL | |
| phone | bigint(20) | NO | | NULL | |
| email | text | NO | | NULL | |
+---------------+------------+------+-----+---------+----------------+
Invoices
+-------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| customer | int(11) | NO | | NULL | |
| order_date | datetime | NO | | NULL | |
| status | text | NO | | NULL | |
| freightcost | double | NO | | NULL | |
+-------------+----------+------+-----+---------+----------------+
Invoice_items
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| order | int(11) | NO | | NULL | |
| qty | int(11) | NO | | NULL | |
| itemname | text | NO | | NULL | |
| itemdesc | text | NO | | NULL | |
| itemprice | double | NO | | NULL | |
+-----------+---------+------+-----+---------+----------------+
try the below query, you need to use GROUP BY if you use GROUP_CONCAT().
SELECT i.order_date,
c.name,
group_concat( concat(x.itemname,' ', x.itemdesc) separator "\n" ) as info
FROM invoices i
INNER JOIN customers c ON i.customer = c.id
LEFT JOIN invoice_items x ON i.id = x.order
GROUP BY i.order_date,c.name

COUNT, Minimum Value per course without subquery

Can anyone help me find the minimum student action per course? Listed like this:
+-------------+--------------------------+
| Course | Lowest Action |
+-------------+--------------------------+
| Maths Y1 | |
| English C | |
| Science Y1 | |
for all users, even if they are not in the log table, without a subquery? My thanks to #luckylwk for assistance with my initial query. I have a solution with a subquery but want to put this into a variable for a much large query.
SELECT
COUNT(tbl_log.action)
lastname,
c.fullname,
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_log.userid, tbl_log.course
LOG TABLE
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| time | | NO | | NULL | |
| userid | | NO | | NULL | |
| course | | NO | | NULL | |
| action | | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
USER Table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| username | | NO | | NULL | |
| userpassword | | NO | | NULL | |
| lastname | | NO | | NULL | |
| firstname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
COURSE table
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| id | | NO | PRI | NULL | auto_increment |
| category | | NO | | NULL | |
| fullname | | NO | | NULL | |
| shortname | | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
I link the users together via the enrolment and context tables.
Try:
SELECT
tbl_course.course_name,
MIN(tbl_log.action) as Lowest_Action
FROM tbl_log
JOIN tbl_user ON tbl_log.userid = tbl_user.id
JOIN tbl_course ON tbl_log.course = tbl_course.id
GROUP BY tbl_course.course_name
See Fiddle Demo

Query randomly returning all items or just some items

I have 4 tables:
mysql> describe solution_sections;
+---------------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+---------------+------+-----+---------+----------------+
| solution_section_id | int(10) | NO | PRI | NULL | auto_increment |
| display_order | int(10) | NO | | NULL | |
| section_name | varchar(1000) | YES | | NULL | |
+---------------------+---------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
mysql> describe suggested_solution_comments;
+-----------------------+----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------------+----------------+------+-----+---------+----------------+
| comment_id | int(10) | NO | PRI | NULL | auto_increment |
| problem_id | int(10) | NO | | NULL | |
| suggested_solution_id | int(10) | NO | | NULL | |
| commenter_id | int(10) | NO | | NULL | |
| comment | varchar(10000) | YES | | NULL | |
| solution_part | int(3) | NO | | NULL | |
| date | date | NO | | NULL | |
+-----------------------+----------------+------+-----+---------+----------------+
mysql> describe users;
+--------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------+------+-----+---------+----------------+
| user_id | int(10) | NO | PRI | NULL | auto_increment |
| first_name | varchar(100) | NO | | NULL | |
| last_name | varchar(100) | NO | | NULL | |
| email | varchar(150) | NO | | NULL | |
| user_pass | varchar(40) | NO | | NULL | |
| zip | varchar(100) | NO | | NULL | |
| country | varchar(100) | NO | | NULL | |
| city | varchar(100) | NO | | NULL | |
| state | varchar(100) | NO | | NULL | |
| lat | float(9,6) | YES | | NULL | |
| lng | float(9,6) | YES | | NULL | |
| agreed_terms | tinyint(1) | YES | | NULL | |
| join_date | date | NO | | NULL | |
| last_login | date | NO | | NULL | |
| bio_blurb | varchar(5000) | YES | | NULL | |
+--------------+---------------+------+-----+---------+----------------+
15 rows in set (0.03 sec)
mysql> describe member_photo;
+-------------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------+------+-----+---------+----------------+
| photo_id | int(10) | NO | PRI | NULL | auto_increment |
| member_id | int(10) | NO | | NULL | |
| photo_description | varchar(3000) | YES | | NULL | |
| photo_path | varchar(1000) | NO | | NULL | |
| small_thumb | varchar(1000) | YES | | NULL | |
| mid_thumb | varchar(1000) | YES | | NULL | |
| is_main_photo | tinyint(1) | YES | | NULL | |
+-------------------+---------------+------+-----+---------+----------------+
And I have a query like this:
select comment_id,
commenter_id,
section_name,
comment,
solution_part,
display_order,
solution_section_id,
suggested_solution_id,
DAYOFMONTH(date),
DAYNAME(date),
YEAR(date),
MONTH(date),
first_name,
last_name,
email,
small_thumb,
mid_thumb
from solution_sections
left join suggested_solution_comments on
solution_sections.solution_section_id = suggested_solution_comments.solution_part
left join users on
suggested_solution_comments.commenter_id = users.user_id
left join member_photo on
suggested_solution_comments.commenter_id = member_photo.member_id
where suggested_solution_id = 61 OR
suggested_solution_id IS NULL
order by solution_section_id,
comment_id,
section_name,
comment,
solution_part,
display_order;
What its supposed to do is get each section_name from the solution_sections table, and then find the comments (and data about who commented). Sometimes there are no comments, but it should still return at least the row with section_name and all other things being null.
But for some reason it does not. And the weirdest part is that if I give it a different suggested_solution_id to match, it will return all of the rows of solution_sections.
Any ideas why such a thing might happen? Thank you!!
And I just realized one thing - if another comment has been made for any problem_id, this query won't return the row with that section.
You need a left outer join to view all records from your parent table when child records are not guaranteed to exits. I'd also avoid adding a where clause when using outer joins.. I think its more readable to keep your join in a subselect, and filter the results.. Try something like this:
select * from
(
select sc.comment_id,
sc.commenter_id,
ss.section_name,
sc.comment,
sc.solution_part,
ss.display_order,
ss.solution_section_id,
sc.suggested_solution_id,
DAYOFMONTH(sc.date),
DAYNAME(sc.date),
YEAR(sc.date),
MONTH(sc.date),
u.first_name,
u.last_name,
u.email,
mp.small_thumb,
mp.mid_thumb
from solution_sections ss
left outer join suggested_solution_comments sc on ss.solution_section_id = sc.solution_part
left outer join users u on sc.commenter_id = u.user_id
left outer join member_photo mp on sc.commenter_id = mp.member_id) a
where a.suggested_solution_id = 61 OR
a.suggested_solution_id IS NULL
order by a.solution_section_id,
a.comment_id,
a.section_name,
a.comment,
a.solution_part,
a.display_order;
EDIT:
select sc.comment_id,
sc.commenter_id,
ss.section_name,
sc.comment,
sc.solution_part,
ss.display_order,
ss.solution_section_id,
sc.suggested_solution_id,
DAYOFMONTH(sc.date),
DAYNAME(sc.date),
YEAR(sc.date),
MONTH(sc.date),
u.first_name,
u.last_name,
u.email,
mp.small_thumb,
mp.mid_thumb
from solution_sections ss
left outer join suggested_solution_comments sc on ss.solution_section_id = sc.solution_part
AND sc.suggested_solution_id = 61
left outer join users u on sc.commenter_id = u.user_id
left outer join member_photo mp on sc.commenter_id = mp.member_id
order by solution_section_id,
comment_id,
section_name,
comment,
solution_part,
display_order;
If you want to show solution_sections even if all the rest doesn't exist, you can use "left outer join":
select comment_id,commenter_id, section_name, comment, solution_part,
display_order, solution_section_id, suggested_solution_id,
DAYOFMONTH(date), DAYNAME(date), YEAR(date), MONTH(date),
first_name, last_name, email, small_thumb,mid_thumb
from solution_sections
left outer join suggested_solution_comments on solution_sections.solution_section_id = suggested_solution_comments.solution_part
and suggested_solution_id = 61
left outer join users on suggested_solution_comments.commenter_id = users.user_id
left outer join member_photo on suggested_solution_comments.commenter_id = member_photo.member_id
where solution_section_id = ????
order by solution_section_id, comment_id, section_name, comment, solution_part,display_order;
ps. try to use aliases for tables it's more readable :-)