MYSQL GROUP BY - slow query - mysql

I have a query like below, please help to give proper index for this, the contact table have more than 20K records and it take nearly 20 secs to load.
Hope the group by clause makes the problem, if I remove the group by clause total record is more than 300k.
SELECT `a`.*, CONCAT(a.`firstname`, " ", a.`lastname`) AS `cont_name`, CONCAT(a.`position`, " / ", a.`company`) AS `comp_pos`, `e`.`name` AS `industry_name`, CONCAT(f.`firstname`, " ", f.`lastname`) AS `created_by`
FROM `contacts` AS `a`
LEFT JOIN `user_centres` AS `b` ON a.user_id = b.user_id
LEFT JOIN `group_contacts` AS `c` ON a.id = c.contact_id
LEFT JOIN `groups` AS `d` ON c.group_id = d.id
LEFT JOIN `industries` AS `e` ON e.id = a.industry_id
LEFT JOIN `users` AS `f` ON f.id = a.user_id
WHERE (1)
GROUP BY `a`.`id`
ORDER BY `a`.`created` desc
Explain shows like this - 20145 Using temporary; Using filesort

You can try these steps
There is no use of table group ( d ), so you can remove left join to group from this query
Add index for user_id, contact_id and industry_id ( I hope these ids are joining table primary_keys ) in contacts table
check these ids user_id, contact_id and industry_id types ( INT ) are same.

Related

Include zero in COUNT with GROUP BY in MySQL

I'm looking to make a query in MySQL to list all the faculties and their number of students given the following table structure:
My query looks like:
SELECT `f`.`id`, `f`.`name`, COUNT(*) `total`
FROM `student` `s`
INNER JOIN `course` `c` ON `c`.`id` = `s`.`course_id`
LEFT JOIN `faculty` `f` ON `f`.`id` = `c`.`faculty_id`
GROUP BY `c`.`faculty_id`
ORDER BY `f`.`name`
And I'm getting this result:
but I need to get all the faculties, even the ones without registered students.
If I use a LEFT JOIN with the course table I get the same results.
If you want all the faculties; your starting table for the JOIN should be the faculty table. Then do Left joins on the other table accordingly.
Use the following query:
SELECT `f`.`id`, `f`.`name`, COUNT(`s`.`id`) AS `total`
FROM `faculty` AS `f`
LEFT JOIN `course` AS `c` ON `f`.`id` = `c`.`faculty_id`
LEFT JOIN `student` AS `s` ON `c`.`id` = `s`.`course_id`
GROUP BY `f`.`id`, `f`.`name`
ORDER BY `f`.`name`
You need to revese the query, your primary tables is faculties (or use right join) :
important - since you want to count cases of zero students you need to treat NULL values that might come up, joining the students table:
SELECT
`f`.`id`, `f`.`name`, SUM(IF(s.id IS NULL, 0,1) `total`
FROM
faculty f
LEFT JOIN course c ON `f`.`id` = `c`.`faculty_id`
LEFT JOIN student s ON `c`.`id` = `s`.`course_id`
GROUP BY `c`.`faculty_id` ORDER BY `f`.`name`

Count the number of times an index exists using a query containing multiple EXIST() statements

My query gets the results of these products based on if they exist in a separate table index. I am trying to get a count of all the instances where they exist so I can ORDER the results by relevance. Everything I try seems to return the variable #priority as 0. Any ideas?
Maybe it is better to use join statements?
Thank you for your help. Here is my MySQL query:
SELECT `products` . * , #priority
FROM `products`
LEFT JOIN productstypes_index ON productstypes_index.product_id = products.id
WHERE (
EXISTS (
SELECT *
FROM `productstypes_index`
WHERE `productstypes_index`.`product_id` = `products`.`id`
AND `productstypes_index`.`_type_id` = '1'
)
AND (
(
(
EXISTS (
SELECT #priority := COUNT( * )
FROM `producthashtags_index`
WHERE `producthashtags_index`.`product_id` = `products`.`id`
AND `producthashtags_index`.`producthashtag_id` = '43'
)
)
AND (
EXISTS (
SELECT #priority := COUNT( * )
FROM `producthashtags_index`
WHERE `producthashtags_index`.`product_id` = `products`.`id`
AND `producthashtags_index`.`producthashtag_id` = '11'
)
)
)
)
)
ORDER BY `updated_at` DESC;
You could do without those exists, and without variables. Also, a left join has no sense if you have an exists condition on the joined table. Then you might as well do the more efficient inner join and put the extra type condition in the join condition.
The priority can be calculated by a count over the hash tags, but only those with id in ('43', '11').
SELECT products.*
count(distinct producthashtags_index.producthashtag_id) priority
FROM products
INNER JOIN productstypes_index
ON productstypes_index.product_id = products.id
AND productstypes_index._type_id = '1'
INNER JOIN producthashtags_index
ON producthashtags_index.product_id = products.id
AND producthashtags_index.producthashtag_id in ('43', '11')
GROUP BY products.id
ORDER BY updated_at DESC;
MySQL ignores the SELECT list in EXISTS subquery, so it makes no difference what you type in there. This is documented here.
An approach using joins would look like below:
SELECT p.id,
COUNT(case when phi.product_id is not null then 1 end) AS instances
FROM products p
INNER JOIN productstypes_index pti ON pti.product_id = p.id AND pti.`_type_id` = 1
LEFT JOIN producthashtags_index phi ON phi.product_id = p.id AND phi.producthashtag_id IN (11,43)
GROUP BY p.id
ORDER BY instances DESC;
I have removed additional backticks where I believe they are not neccessary and also if your id columns in tables are integers, you do not need quotation marks.

How to use "group by" before "join" on mysql?

I have the following query which contains duplicate user_id's. I don't want to see a user more than one. I decided to use group by as in the following:
SELECT u.`ID`, u.`user_login`, u.`user_registered`, u.`display_name`
FROM purchase_key p
LEFT JOIN users u ON p.user_id = u.id
WHERE ( `product_id` = 1 OR `product_id` = 2 )
AND `create_date` <= '2015-09-20' AND `create_date` >= '2014-09-01'
group by p.user_id order by p.`create_date` asc
But is there a way to do that grouping before join ?
Not sure why you want to do this. Unlikely that it will help with performance unless the indexes are very poor.
Not sure the LEFT OUTER JOIN is useful, as the only fields your query returns are ones from the left joined table. Hence if no user were found for some purchases you would just have a load of null rows returned.
But doing the sub query to get the list of users, with the max create date for that user within the required date range (while not strictly necessary for MySQL most flavours of SQL would give an error if you return fields that are not in the group by clause and not aggregate fields), and using an INNER JOIN gives you the following:-
SELECT u.ID, u.user_login, u.user_registered, u.display_name
FROM
(
SELECT user_id, MAX(create_date) AS max_create_date
FROM purchase_key
WHERE product_id IN (1, 2)
AND create_date BETWEEN '2014-09-01' AND '2015-09-20'
GROUP BY user_id
) p
INNER JOIN users u ON p.user_id = u.id
ORDER BY p.max_create_date ASC
SELECT u.`ID`, u.`user_login`, u.`user_registered`, u.`display_name`
FROM (SELECT * FROM purchase_key GROUP BY user_id ) p
LEFT JOIN users u ON p.user_id = u.id
WHERE ( `product_id` = 1 OR `product_id` = 2 )
AND `create_date` <= '2015-09-20' AND `create_date` >= '2014-09-01'
order by p.`create_date` asc
Hope this helps
If you don't want to see user more than once, try to use DISTINCT in your first line.

Unknown column in 'where clause'. Neste subquery to improve perfomance of 'ORDER BY'

I have an a relationship a little odd but must be so:
SELECT COUNT(DISTINCT `t`.`id`)
FROM `radcliente` `t`
LEFT OUTER JOIN `radcliente_endereco_instalacao` `endereco_instalacao`
ON (`endereco_instalacao`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_telefone` `telefones`
ON (`telefones`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_email` `emails`
ON (`emails`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radmetodo_cobranca` `metodo_cobranca`
ON (`metodo_cobranca`.`cliente_id`=`t`.`id`) AND (metodo_cobranca.arquivo = 'nao')
LEFT OUTER JOIN `radacct` `ultimo_acct`
ON (`ultimo_acct`.`username`=`t`.`login`)
AND (ultimo_acct.radacctid = (
SELECT `radacctid` FROM
(SELECT radacctid
FROM `radacct` `fUzDDUDv`
WHERE username = t.login
) AS `fUzDDUDv` ORDER BY `radacctid` DESC LIMIT 1
)
)
WHERE (ultimo_acct.framedipaddress = '177.23.209.194')
Unknown column 't.login' in 'where clause'.
UPDATE:
Yes, that would solve a problem. I created this sub query why I'm using the Yii Framework, and the has_one relationships there is no limit, ie if one 'cliente' with millions of 'acct', the framework seeks all 'acct', to take only one this gives some GB of traffic and is very slow. To solve, I used the subquery seeking only the newest id to get the 'cliente' and the last 'acct' and make it work in Active Record, until then everything was fine, but the search was slow estremamente to seek a record took 40 seconds, then I discovered that the problem was in the 'ORDER BY' (radacctid is an index and tables are innodb), hence I did to solve the 'ORDER BY' outside the subquery, then resolvou, the problem that the subquery column the table 'cliente' (t.login) is like there, as I explained above.
I've tried also sort by another field, eg 'acctstarttime', and continued slow, only solved when I did it this way:
SELECT `radacctid` FROM
(SELECT radacctid
FROM `radacct` `fUzDDUDv`
WHERE username = t.login
) AS `fUzDDUDv` ORDER BY `radacctid` DESC LIMIT 1
UPDATE:
But the problem of INNER JOIN is that if there is no result for 'acct' does not return the 'cliente'.
UPDATE
The problem is not where the t.login but it is not recognized as existing within the subquery that I can not put 'out' if the order is not slow.
UPDATE
Read my comments? This is the situation. It really is only on the production server that has inserts and updates all the time.
I hope this one could help you in some way:
SELECT
COUNT(DISTINCT `t`.`id`)
FROM
`radcliente` `t`
LEFT OUTER JOIN `radcliente_endereco_instalacao` `endereco_instalacao`
ON (`endereco_instalacao`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_telefone` `telefones`
ON (`telefones`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_email` `emails`
ON (`emails`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radmetodo_cobranca` `metodo_cobranca`
ON (`metodo_cobranca`.`cliente_id`=`t`.`id`)
AND (metodo_cobranca.arquivo = 'nao')
INNER JOIN
(
SELECT `ultimo_acct_INNER`.*
FROM
`radacct` AS `ultimo_acct_INNER`
INNER JOIN
(
SELECT `fUzDDUDv`.`username` AS `maxID_username`, MAX(`radacctid`) AS `maxID_radacctid`
FROM `radacct` `fUzDDUDv`
GROUP BY `fUzDDUDv`.`username`
) AS `radacct_MAX_ID`
ON `ultimo_acct_INNER`.`username`= `radacct_MAX_ID`.`maxID_username`
AND `ultimo_acct_INNER`.`radacctid` = `radacct_MAX_ID`.`maxID_radacctid`
) AS `ultimo_acct`
ON (`ultimo_acct`.`username`=`t`.`login`)
WHERE
(ultimo_acct.framedipaddress = '177.23.209.194')
I replaced section:
(...)
LEFT OUTER JOIN `radacct` `ultimo_acct`
ON (`ultimo_acct`.`username`=`t`.`login`)
AND (ultimo_acct.radacctid = (
SELECT `radacctid` FROM
(SELECT radacctid
FROM `radacct` `fUzDDUDv`
WHERE username = t.login
) AS `fUzDDUDv` ORDER BY `radacctid` DESC LIMIT 1
)
)
(...)
with this one:
(...)
INNER JOIN
(
SELECT `ultimo_acct_INNER`.*
FROM
`radacct` AS `ultimo_acct_INNER`
INNER JOIN
(
SELECT `fUzDDUDv`.`username` AS `maxID_username`, MAX(`radacctid`) AS `maxID_radacctid`
FROM `radacct` `fUzDDUDv`
GROUP BY `fUzDDUDv`.`username`
) AS `radacct_MAX_ID`
ON `ultimo_acct_INNER`.`username`= `radacct_MAX_ID`.`maxID_username`
AND `ultimo_acct_INNER`.`radacctid` = `radacct_MAX_ID`.`maxID_radacctid`
) AS `ultimo_acct`
ON (`ultimo_acct`.`username`=`t`.`login`)
(...)
Could you try the following modification (for optimizing performance) ?
SELECT COUNT(DISTINCT `t`.`id`)
FROM
(
SELECT DISTINCT `t_sub`.`id`
FROM
`radcliente` `t_sub`
LEFT OUTER JOIN `radacct` `ultimo_acct`
ON (`ultimo_acct`.`username`=`t_sub`.`login`)
AND (ultimo_acct.radacctid =
(SELECT `fUzDDUDv`.radacctid
FROM `radacct` `fUzDDUDv`
WHERE `fUzDDUDv`.username = `t_sub`.login
ORDER BY `fUzDDUDv`.`radacctid` DESC LIMIT 1))
WHERE (ultimo_acct.framedipaddress = '177.23.209.194')
) AS `t`
LEFT OUTER JOIN `radcliente_endereco_instalacao` `endereco_instalacao`
ON (`endereco_instalacao`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_telefone` `telefones`
ON (`telefones`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_email` `emails`
ON (`emails`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radmetodo_cobranca` `metodo_cobranca`
ON (`metodo_cobranca`.`cliente_id`=`t`.`id`) AND (metodo_cobranca.arquivo = 'nao')
It preselects records joining first the clients table to the 'big' table 'radacct', selecting only the distinct set of 'right' client id-s and then joining this (relatively) small set to the rest of the tables in the topmost query
I think I understand now. I have no experience with Yii framework, so for know (taking into account your last comment) I can't think of something better than the following:
SELECT COUNT(DISTINCT `t`.`id`)
FROM `radcliente` `t`
LEFT OUTER JOIN `radcliente_endereco_instalacao` `endereco_instalacao`
ON (`endereco_instalacao`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_telefone` `telefones`
ON (`telefones`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radcliente_email` `emails`
ON (`emails`.`cliente_id`=`t`.`id`)
LEFT OUTER JOIN `radmetodo_cobranca` `metodo_cobranca`
ON (`metodo_cobranca`.`cliente_id`=`t`.`id`) AND (metodo_cobranca.arquivo = 'nao')
LEFT OUTER JOIN `radacct` `ultimo_acct`
ON (`ultimo_acct`.`username`=`t`.`login`)
AND (ultimo_acct.radacctid =
(SELECT `fUzDDUDv`.radacctid
FROM `radacct` `fUzDDUDv`
WHERE `fUzDDUDv`.username = t.login
ORDER BY `fUzDDUDv`.`radacctid` DESC LIMIT 1))
WHERE (ultimo_acct.framedipaddress = '177.23.209.194')
I hope someone could help get more precise answer to your needs.

Delete an SQL query result set

I'm trying to delete an SQL result set but it won't work:
DELETE FROM votes
WHERE id IN (
SELECT *
FROM votes v
LEFT JOIN comments c ON f.id = v.post_id
GROUP BY v.id
HAVING COUNT(c.comment) = 0 )
It's true, that you can't use the same table from which you want to delete rows in a direct subselect, but with a little trick - a subselect on a subselect as derived table - you can do it:
DELETE FROM votes
WHERE id IN (
SELECT
t.id
FROM (
SELECT v.id, COUNT(c.comment) cnt
FROM votes v
LEFT JOIN comments c ON f.id = v.post_id
GROUP BY v.id
HAVING COUNT(c.comment) = 0
) t
);
I'm assuming that the rows without comments should be deleted.
You are close...2 changes
a subquery in a where in() statement can only return one field. Change select * to select v.id
having count = 0 doesn't quite work in a logical sense. If count = 0 then it's not there to delete anyway. I suspect with the left join syntax you've used, you are going for votes that have 0 comments? Right idea with the left join, but you want where c.comment is null (left join produces nulls...where c.comment is null means there was no comment found).
Of course this won't work due to mysql:
DELETE FROM votes
WHERE id IN (
SELECT v.id
FROM votes v
LEFT JOIN comments c ON f.id = v.post_id
where c.comments is null)
If I was stuck in MySQL...(sorry this is psuedo code, I haven't been in mysql long enough to get this exact in a text window.
select id
into #temp
FROM votes v
LEFT JOIN comments c ON f.id = v.post_id
where c.comments is null
delete from votes where id in (select id from #temp)
drop table #temp
Seems like a silly work around