MySQL Optimizing Query With IN - mysql

SELECT `listener` ,
SEC_TO_TIME( SUM( TIME_TO_SEC( `call_time` ) ) ) AS total_time,
COUNT( `listener` ) AS number
FROM calls
WHERE listened_date = '2013-05-09'
AND type in ('column1','column2')
AND id
IN ( SELECT id
FROM calls
GROUP BY CONCAT( name, ' ', when ) )
GROUP BY `listener`
This query is working so slow and making other queries not working in same time. How can i make this lighter?
I think IN make it slower. What is alternative in this case?

Maybe ?:
SELECT c.`listener` ,
SEC_TO_TIME(SUM(TIME_TO_SEC(c.`call_time`))) AS total_time,
COUNT(c.`listener`) AS number
FROM calls c
WHERE c.listened_date = '2013-05-09'
AND c.TYPE IN ('column1',
'column2')
AND EXISTS (SELECT 0
FROM calls c2
WHERE c2.id = c.id)
GROUP BY c.`listener`

Related

Count three posibilities in one table

I have a table with subscribers which have two fields, actived and suscribed. I need to get the total number of subscribers, the number of activated and the number of suscribed in the same consult. I've tried to do this with a double left join but i need to group by a field that is not the primary key and i get error. I have my consult now like this:
FROM (SELECT subscribers.mailing_list, subscribers.mailing_list AS suscriptores, s2.inactivos AS inactivos, s3.excluidos AS excluidos
FROM subscribers
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS inactivos FROM subscribers WHERE subscribed = false GROUP BY id) s2 ON subscribers.id = s2.id
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS excluidos FROM subscribers WHERE excluded = true GROUP BY id) s3 ON subscribers.id = s3.id
) AS subs
GROUP BY subs.mailing_list```
Instead of needing 3 queries, you need case expressions inside an aggregate function, this is known as "conditional aggregates" e.g.
SELECT
mailing_list
, COUNT( CASE WHEN subscribed = FALSE THEN 1 END ) AS inactivos
, COUNT( CASE WHEN subscribed = TRUE THEN 1 END ) AS excluidos
, COUNT( * ) AS Total
FROM subscribers
GROUP BY
mailing_list
In MySQL, you can simplify this logic to:
SELECT mailing_list,
SUM( subscribed = FALSE ) AS inactivos,
SUM( subscribed = TRUE ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;
If subscribed is in fact boolean, you can further simplify this to:
SELECT mailing_list,
SUM( NOT subscribed ) AS inactivos,
SUM( subscribed ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;

MySQL with multiple SELECT and GROUP BY

I have a problem with my database in mysql. I would like to have a table from my database with date, pat, dureeP, dureeC but this function doesn't GROUP BY :
select *
from (SELECT date_format(p.date, "%Y-%m") AS date
,p.pat
,AVG(a) AS dureeP
FROM timing as t, patient as p
WHERE t.id_p = p.id_p
AND t.pos=6
AND t.heure_fin IS NOT NULL
GROUP BY p.pat, MONTH(p.date), YEAR(p.datede)
) as T1,
(SELECT AVG(b) AS dureeC
FROM timing as t, patient as p
WHERE t.id_p = p.id_p
AND t.pos=3
AND t.heure_fin IS NOT NULL
GROUP BY p.pathologie, MONTH(p.date), YEAR(p.date)
) as T2
With one SELECT I can have what I want but with multiple SELECT I can Group By.
Do you have an idea?
Thank you
To simplify your query you might be able to use "conditional aggregates" which basically means placing a case expression inside an aggregate function
SELECT
p.pathologie
, MONTH(p.date)
, YEAR(p.date)
, AVG(CASE WHEN t.pos = 3 THEN b END) AS dureeC
, AVG(CASE WHEN t.pos = 6 THEN a END) AS dureeP
FROM timing AS t
INNER JOIN patient AS p ON t.id_p = p.id_p
WHERE t.heure_fin IS NOT NULL
GROUP BY
p.pathologie
, MONTH(p.date)
, YEAR(p.date)

SQL request optimization

I have an SQL request that take 100% of my VM CPU while it's working. I wanna know how to optimize it :
SELECT g.name AS hostgroup
, h.name AS hostname
, a.host_id
, s.display_name AS servicename
, a.service_id
, a.entry_time AS ack_time
, ( SELECT ctime
FROM logs
WHERE logs.host_id = a.host_id
AND logs.service_id = a.service_id
AND logs.ctime < a.entry_time
AND logs.status IN (1, 2, 3)
AND logs.type = 1
ORDER BY logs.log_id DESC
LIMIT 1) AS start_time
, ar.acl_res_name AS timeperiod
, a.state AS state
, a.author
, a.acknowledgement_id AS ack_id
FROM centstorage.acknowledgements a
LEFT JOIN centstorage.hosts h ON a.host_id = h.host_id
LEFT JOIN centstorage.services s ON a.service_id = s.service_id
LEFT JOIN centstorage.hosts_hostgroups p ON a.host_id = p.host_id
LEFT JOIN centstorage.hostgroups g ON g.hostgroup_id = p.hostgroup_id
LEFT JOIN centreon.hostgroup_relation hg ON a.host_id = hg.host_host_id
LEFT JOIN centreon.acl_resources_hg_relations hh ON hg.hostgroup_hg_id = hh.hg_hg_id
LEFT JOIN centreon.acl_resources ar ON hh.acl_res_id = ar.acl_res_id
WHERE ar.acl_res_name != 'All Resources'
AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id is not null
ORDER BY a.acknowledgement_id ASC
The problem is at this part :
(SELECT ctime FROM logs
WHERE logs.host_id = a.host_id
AND logs.service_id = a.service_id
AND logs.ctime < a.entry_time
AND logs.status IN (1, 2, 3)
AND logs.type = 1
ORDER BY logs.log_id DESC
LIMIT 1) AS start_time
The table logs is really huge and some friends told me to use a buffer table/database but i pretty knew to this things and i don't know how to do it.
There is an EXPLAIN EXTENDED of the query :
It seems that he will examined only 2 row of the table logs so why it takes so much time ? (There is 560000 row in the table logs).
Here is all indexes of those tables :
centstorage.acknowledgements :
centstorage.hosts :
centstorage.services :
centstorage.hosts_hostgroups :
centstorage.hostgroups :
centreon.hostgroup_relation :
centreon.acl_resources_hg_relations :
centreon.acl_resources :
For SQL Server there is the possibility to define the maximum degree of parallelism of your query using MAXDOP
For example you can define at the end of your query
option (maxdop 2)
I'm pretty sure there's an equivalent in MySql.
You can try to approach this situation if the execution time is not relevant.
Create a Temporary Table from where condition for acknowledgements, schema will have column required in final result and used in JOIN with all your 7 tables
CREATE TEMPORARY TABLE __tempacknowledgements AS SELECT g.name AS hostgroup
, '' AS hostname
, a.host_id
, s.display_name AS servicename
, a.service_id
, a.entry_time AS ack_time
, '' AS AS start_time
, '' AS timeperiod
, a.state AS state
, a.author
, a.acknowledgement_id AS ack_id
FROM centstorage.acknowledgements a
WHERE YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id IS NOT NULL
ORDER BY a.acknowledgement_id ASC;
Or create using proper column definition
Update fields from all tables having left join, you can use Inner Join in update. You should write 7 different update statements. 2 examples are given below.
UPDATE __tempacknowledgements a JOIN centstorage.hosts h USING(host_id)
SET a.name=h.name;
UPDATE __tempacknowledgements s JOIN centstorage.services h USING(service_id)
SET a.acl_res_name=s.acl_res_name;
similar way update ctime from logs using Join with Logs, this is 8th update statement.
pick select from temp table.
drop temp table
a sp can be written for this.
Turn LEFT JOIN into JOIN unless you have a real need for LEFT.
AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id is not null
Do you have any rows with a.service_id is not null? If not, get rid of it.
As already mentioned, that date comparison does not optimize. Here is what to use instead:
AND a.entry_time >= CONCAT(LEFT(CURDATE(), 7), '-01')
AND a.entry_time < CONCAT(LEFT(CURDATE(), 7), '-01') + INTERVAL 1 MONTH
And add one of these (depending on my above comment):
INDEX(entry_time)
INDEX(service_id, entry_time)
The correlated subquery is hard to optimize. This index (on logs) may help:
INDEX(type, host_id, service_id, status)
WHERE IN is time killer!
Instead of
logs.status IN (1, 2, 3)
use
logs.status=1 or logs.status=2 or logs.status=3
I have SLIGHTLY reformatted the query for my readability reference and better seeing the relations between the tables... otherwise ignore that part.
SELECT
g.name AS hostgroup,
h.name AS hostname,
a.host_id,
s.display_name AS servicename,
a.service_id,
a.entry_time AS ack_time,
( SELECT
ctime
FROM
logs
WHERE
logs.host_id = a.host_id
AND logs.service_id = a.service_id
AND logs.ctime < a.entry_time
AND logs.status IN (1, 2, 3)
AND logs.type = 1
ORDER BY
logs.log_id DESC
LIMIT 1) AS start_time,
ar.acl_res_name AS timeperiod,
a.state AS state,
a.author,
a.acknowledgement_id AS ack_id
FROM
centstorage.acknowledgements a
LEFT JOIN centstorage.hosts h
ON a.host_id = h.host_id
LEFT JOIN centstorage.services s
ON a.service_id = s.service_id
LEFT JOIN centstorage.hosts_hostgroups p
ON a.host_id = p.host_id
LEFT JOIN centstorage.hostgroups g
ON p.hostgroup_id = g.hostgroup_id
LEFT JOIN centreon.hostgroup_relation hg
ON a.host_id = hg.host_host_id
LEFT JOIN centreon.acl_resources_hg_relations hh
ON hg.hostgroup_hg_id = hh.hg_hg_id
LEFT JOIN centreon.acl_resources ar
ON hh.acl_res_id = ar.acl_res_id
WHERE
ar.acl_res_name != 'All Resources'
AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id is not null
ORDER BY
a.acknowledgement_id ASC
I would first recommend starting with your "acknowledgements" table and have an index at a minimum of ( entry_time, acknowledgement_id ). Next, update your WHERE clause. Because you are running a function to convert the unix timestamp to a date and grabbing the YEAR (and month) respectively, I don't believe it is utilizing the index as it has to compute that for every row. To eleviate that, a unix timestamp is nothing but a number representing seconds from a specifc point in time. If you are looking for a specific month, then pre-compute the starting and ending unix times and run for that range. Something like...
and a.entry_time >= UNIX_TIMESTAMP( '2015-10-01' )
and a.entry_time < UNIX_TIMESTAMP( '2015-11-01' )
This way, it accounts for all seconds within the month up to 11:59:59 on Oct 31, just before November 1st.
Then, without my glasses to see all the images more clearly, and short time this morning, I would ensure you have at least the following indexes on each table respectively
table index
logs ( host_id, service_id, type, status, ctime, log_id )
acknowledgements ( entry_time, acknowledgement_id, host_id, service_id )
hosts ( host_id, name )
services ( service_id, display_name )
hosts_hostgroups ( host_id, hostgroup_id )
hostgroups ( hostgroup_id, name )
hostgroup_relation ( host_host_id, hostgroup_hg_id )
acl_resources_hg_relations ( hh_hg_id, acl_res_id )
acl_resources ar ( acl_res_id, acl_res_name )
Finally, your correlated sub-query field is going to be a killer as it is processed for every row, but hopefully the other index optimization ideas will help performance.

mysql - rewrite query with subqueries

Consider a table Users with columns Id, Name, Surname and a table Actions with columns Ip and Actor. I need to retrieve, for every Ip, the set of users who did as action using that Ip.
What I have now looks like:
SELECT a.ip, (
SELECT GROUP_CONCAT(t.id, '-', t.name, ' ', t.surname) FROM(
SELECT ud.id, ud.name, ud.surname
FROM users_data AS ud
JOIN actions AS a2 ON a2.actor = ud.id
WHERE a2.ip = a.ip
GROUP BY ud.id) AS t
)
FROM actions AS a
WHERE a.ip != '' AND a.ip != '0.0.0.0'
GROUP BY a.ip
It doesn't work because a.ip is unknown in the where clause in the inner subquery.
Do to performance issues, I need to avoid to use DISTINCT.
Any suggestion?
You can rewrite your query as
SELECT n.ip, GROUP_CONCAT( DISTINCT n.your_user SEPARATOR ' -- ') `users` FROM
(
SELECT a.ip AS ip, CONCAT(t.id, '-', t.name, ' ', t.surname) `your_user`
FROM users_data AS ud
JOIN actions AS a ON a.actor = ud.id
) `new_table` n
WHERE n.ip != '' AND n.ip != '0.0.0.0'
GROUP BY n.ip
Note Be aware of that the result is truncated to the maximum length
that is given by the group_concat_max_len system variable, which
has a default value of 1024
have you tried writing the condition a2.ip = a.ip outside the subquery.?
i.e. in the where clause of outer query!
I solved it using this query (still quite slow, so there's still space for improvements...):
SELECT SQL_NO_CACHE t.ip, COUNT(t.id) AS c, GROUP_CONCAT(t.id, '-', t.name, ' ', t.surname, '-', t.designerAt > 0) FROM (
SELECT a.ip, ud.id, ud.name, ud.surname, u.designerAt
FROM actions AS a
JOIN users_data AS ud ON ud.id = a.actor
JOIN users AS u ON u.id = a.actor
WHERE a.ip != ''
AND a.ip != '0.0.0.0'
AND a.actor !=0
GROUP BY a.ip, a.actor
) AS t
GROUP BY t.ip

Mysql select not duplicated values with condition

I'm looking for faster way to run this kind of statment:
SELECT *
FROM `aukcje_przedmioty`
WHERE (
(
opis NOT
IN (
SELECT opis
FROM aukcje_przedmioty
WHERE ( aktywne =1 AND user_id =6 )
)
)
AND aktywne =0
AND user_id =6
)
table aukcje_przedmioty
you can try something like
SELECT a.*
FROM `aukcje_przedmioty` a
JOIN
(
SELECT opis,user_id
FROM aukcje_przedmioty
GROUP BY opis,user_id
HAVING max(aktywne) = 0
) x
ON
x.user_id = a.user_id and
x.opis = a.opis
WHERE user_id = 6
http://sqlfiddle.com/#!2/16774/6
Use explain on your setup to see what would work best for you.