MySQL - optimize query with a sub-query - mysql

I am currently bit stuck at the query which needs a bit of optimization - I am looking for a way how to optimize (if possible) following query (I have no idea what to do here at the moment :/):
SELECT count(distinct(pj.id)) as qty
FROM `project_jobs` `pj`
JOIN `projects` `p` ON pj.project_id = p.id AND p.status NOT IN ("CANCELED","DELETED","ARCHIVED")
WHERE
(
(
(pj.job_type_service_id IN (SELECT id FROM job_type_services WHERE job_type_id IN (4,2,3)))
AND
(pj.new_status_id IN ("wip","completed","delivered"))
)
AND (pj.status<>'DELETED' AND pj.status<>'CANCELED')
)
AND
(pj.due_date >= '2010-04-01 00:00:00' AND pj.due_date <= '2018-05-09 23:59:59')
and exists
(SELECT * FROM project_job_parents pjp
WHERE pjp.project_job_id IN
(SELECT id FROM project_jobs WHERE job_type_id IN (1,24,7,8,32,34,33))
and
pjp.parent_id = pj.id
)
EXPLAIN gives following info:
Is there anything what can be done here to optimize and speed up the query?

EXISTS subqueries usually perform significantly better than IN subqueries, at least in most MySQL versions (transformed the query below).
You didn't provide the tables structure, so it will be hard to tell which indexes exist and which columns they contain. So, I'll just specify which indexes you should have.
Indexes to add:
ALTER TABLE `job_type_services` ADD INDEX `job_type_services_idx_id_id` (`job_type_id`,`id`);
ALTER TABLE `project_job_parents` ADD INDEX `project_job_parents_idx_id` (`parent_id`);
ALTER TABLE `project_job_parents` ADD INDEX `project_job_parents_idx_id` (`project_job_id`);
ALTER TABLE `project_jobs` ADD INDEX `project_jobs_idx_id_id_status_id` (`new_status_id`,`project_id`,`status`,`id`);
ALTER TABLE `project_jobs` ADD INDEX `project_jobs_idx_id` (`job_type_service_id`);
ALTER TABLE `project_jobs` ADD INDEX `project_jobs_idx_id` (`id`);
ALTER TABLE `project_jobs` ADD INDEX `project_jobs_idx_id_id` (`job_type_id`,`id`);
ALTER TABLE `projects` ADD INDEX `projects_idx_status_id` (`status`,`id`);
Transformed query:
SELECT
count(DISTINCT (`pj`.id)) AS qty
FROM
`project_jobs` `pj`
JOIN
`projects` `p`
ON `pj`.project_id = `p`.id
AND `p`.status NOT IN (
'CANCELED',
'DELETED',
'ARCHIVED')
WHERE
(
(
(
EXISTS (
SELECT
1
FROM
job_type_services
WHERE
job_type_services.job_type_id IN (
4, 2, 3
)
AND `pj`.job_type_service_id = job_type_services.id
)
)
AND (
`pj`.new_status_id IN (
'wip', 'completed', 'delivered'
)
)
)
AND (
`pj`.status <> 'DELETED'
AND `pj`.status <> 'CANCELED'
)
)
AND (
`pj`.due_date >= '2010-04-01 00:00:00'
AND `pj`.due_date <= '2018-05-09 23:59:59'
)
AND EXISTS (
SELECT
*
FROM
project_job_parents pjp
WHERE
EXISTS (
SELECT
1
FROM
project_jobs
WHERE
project_jobs.job_type_id IN (
1, 24, 7, 8, 32, 34, 33
)
AND pjp.project_job_id = project_jobs.id
)
AND pjp.parent_id = `pj`.id
)

Related

Keep the latest 7 records and delete all other query issue

I have a mysql table
CREATE TABLE IF NOT EXISTS `mytable` (
`i_contact_id` int(16) NOT NULL AUTO_INCREMENT,
`s_contact_name` char(48) NOT NULL,
`ts_contact_scraped` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Date Time when contact is last scraped.',
PRIMARY KEY (`i_contact_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
INSERT INTO `mytable` (`i_contact_id`, `s_contact_name`, `ts_contact_scraped`) VALUES
(1, 'aaaa', '2018-07-27 02:30:30'),
(2, 'bbbb', '2017-03-28 04:13:08'),
(3, 'cccc', '2017-03-12 03:52:57'),
(4, 'dddd', '2017-04-18 07:13:34'),
(5, 'eeee', '2018-05-29 15:22:23'),
(6, 'ffff', '2018-02-23 13:27:24'),
(7, 'gggg', '2016-10-17 22:50:24'),
(8, 'hhhh', '2018-07-20 14:02:14'),
(9, 'iiii', '2020-03-24 10:56:02');
I want to keep 7 latest rows and delete all oldest rows based on ts_contact_scraped field but it don't work properly.
Here is my delete query
DELETE FROM `mytable`
WHERE i_contact_id <= (
SELECT i_contact_id
FROM (
SELECT i_contact_id
FROM `mytable`
ORDER BY ts_contact_scraped DESC
LIMIT 1 OFFSET 7
) foo
)
My original table has more than 1100000 rows, I want to run above query periodically using PHP to purge oldest rows, there is some other logic involved so I want to delete the oldest rows based on ts_contact_scraped field.
When I run this query on my original table it deletes more than expected rows.
Here is fiddle
http://sqlfiddle.com/#!9/9414e2/1/0
You can use JOIN:
DELETE t
FROM `mytable` t JOIN
(SELECT i_contact_id
FROM `mytable`
ORDER BY ts_contact_scraped DESC
LIMIT 1 OFFSET 7
) tt
ON tt.i_contact_id = t.i_contact_id
In your delete statement you are relying on a higher ts_contact_scraped also meaning a higher i_contact_id. At least in your example this is not given.
So stick to ts_contact_scraped instead:
DELETE FROM `mytable`
WHERE ts_contact_scraped <= (
SELECT ts_contact_scraped
FROM (
SELECT ts_contact_scraped
FROM `mytable`
ORDER BY ts_contact_scraped DESC
LIMIT 1 OFFSET 7
) foo
);
Here is your altered fiddle: http://sqlfiddle.com/#!9/610cb4/1
(If there can be duplicate ts_contact_scraped, though, things will get more complicated.)
DELETE t1.*
FROM `mytable` t1
LEFT JOIN ( SELECT i_contact_id
FROM `mytable`
ORDER BY ts_contact_scraped DESC
LIMIT 7 ) t2 ON t1.i_contact_id = t2.i_contact_id
WHERE t2.i_contact_id IS NULL;
fiddle
or
DELETE t1.*
FROM `mytable` t1, ( SELECT ts_contact_scraped
FROM `mytable`
ORDER BY ts_contact_scraped DESC
LIMIT 1 OFFSET 7 ) t2
WHERE t1.ts_contact_scraped <= t2.ts_contact_scraped;
fiddle

Optimize MySQL query - Data fetching form views table and Main table

I am fetching data from MySQL views table and Main table. I have created Indexes and Primary keys in Main table but I cannot create Indexes and primary keys on views table.
When I execute the below query it is taking around 10 seconds. I want to optimize the below query to less time.
SELECT DISTINCT
`Emp_No`, `Name`
FROM
`ResLookup`
WHERE
`IsActive` = 1
AND `Department` IN ('SDG' , 'HDD', 'ENG', 'PDN')
AND (`Emp_No` IN (SELECT DISTINCT
ProjList.PM_No
FROM
ProjList
WHERE
ProjList.PM_No != 1749 UNION SELECT DISTINCT
ProjList.PL_No
FROM
ProjList
WHERE
ProjList.PL_No != 1749)
OR Emp_No IN (SELECT
MEMBER_ID
FROM
s_group_details
WHERE
GROUP_ID = 'GRP109'
AND MEMBERSHIP_LEVEL = 30));
Only s_group_details table have Indexes and primary key. Remaining all tables are fetching from views table.
Using Explain Query I have the below output
I don't know your query requirements but still check below query helpful or not
SELECT DISTINCT
`Emp_No`, `Name`
FROM
`ResLookup` inner join (SELECT DISTINCT
ProjList.PM_No ,ProjList.PL_No
FROM
ProjList
WHERE
ProjList.PM_No != 1749
or
ProjList.PL_No != 1749) a
on ResLookup.Emp_No = a.PM_No
and ResLookup.Emp_No = a.PL_No
OR Emp_No IN (SELECT
MEMBER_ID
FROM
s_group_details
WHERE
GROUP_ID = 'GRP109'
AND MEMBERSHIP_LEVEL = 30)
WHERE
`IsActive` = 1
AND `Department` IN ('SDG' , 'HDD', 'ENG', 'PDN');
It may be better to turn things somewhat inside-out:
SELECT `Emp_No`,
( SELECT Name
FROM ResLookup
WHERE Emp_No = u.PM_No
) AS Name
FROM
( SELECT PM_No FROM ProjList WHERE PM_No != 1749 )
UNION DISTINCT
( SELECT PL_No FROM ProjList WHERE PL_No != 1749 )
UNION DISTINCT
( SELECT MEMBER_ID
FROM s_group_details AS d
WHERE d.GROUP_ID = 'GRP109'
AND d.MEMBERSHIP_LEVEL = 30
) AS u
JOIN `ResLookup` AS r ON u.PM_No = r.Emp_No
WHERE r.`IsActive` = 1
AND r.`Department` IN ('SDG' , 'HDD', 'ENG', 'PDN');
Indexes needed:
ResLookup: (Emp_No, IsActive, Department)
s_group_details: (GROUP_ID, MEMBERSHIP_LEVEL, MEMBER_ID)

Select from an explicit table in mysql

I am trying to do a join on data that does not exist in my database, and never changes.
I want to do:
SELECT val, campaign FROM values
LEFT JOIN (SELECT campaign, start, end FROM (
('Spring 2104', '2014-05-01', '2014-08-01'),
('Winter 2014', '2014-08-01', '2014-12-31')
) as campaign_table ON (
values.date > campaign_table.start AND
values.date < campaign_table.end
)
Is that possible? I could create a temporary table, but for what I am trying to do that does not actually work.
You could use union all to create the dummy set. This is a viable solution considering there are only a handful of rows in your dummy dataset.
SELECT val
,campaign
FROM
VALUES
LEFT JOIN (
SELECT 'Spring 2104' campaign
,'2014-05-01' start
,'2014-08-01' [end]
UNION ALL
SELECT 'Winter 2014'
,'2014-08-01'
,'2014-12-31'
) AS campaign_table ON
VALUES.DATE > campaign_table.start
AND
VALUES.DATE < campaign_table.[end]
Maybe you need this executing all queries at once:
CREATE TABLE IF NOT EXISTS `tempo`( `campaign_name` VARCHAR(100), `from` DATE, `to` DATE );
INSERT INTO tempo(campaign_name, `start`, `end`) VALUES ('Spring 2104', '2014-05-01', '2014-08-01'),('Winter 2014', '2014-08-01', '2014-12-31');
SELECT t1.val, t1.campaign, t2.campaign_name FROM `values` t1, `tempo` t2 WHERE t1.date BETWEEN t2.start AND t2.end;
DROP TABLE `tempo`;
Also you can make: CREATE TEMPORARY TABLE
Try!

Mysql Date Comparison with IN Statement

I have an sql query
I have the following Queries
SELECT * FROM articles where `id` =1 AND `datatime` > ='datetime1';
UNION ALL
SELECT * FROM articles where `id` =2 AND `datatime` > ='datetime2';
UNION ALL
SELECT * FROM articles where `id` =3 AND `datatime` > ='datetime3';
UNION ALL
SELECT * FROM articles where `id` =4 AND `datatime` > ='datetime4';
Which is working fine
Now the problem is that if there is bigger list, maybe more than 10000, then how do I handle this query.
Is there is any other way to do this query?
Instead of unioning, you should do this in one query.
SELECT * FROM articles where
(`id` =1 AND `datatime` > ='datetime1')
or
(`id` =2 AND `datatime` > ='datetime2')
or
(`id` =3 AND `datatime` > ='datetime3')
or
(`id` =4 AND `datatime` > ='datetime4');
You can also do it like this:
SELECT * FROM articles where
(id, `datatime`) IN (SELECT 1, 'datetime1'
UNION ALL
SELECT 2, 'datetime2'
UNION ALL
SELECT 3, 'datetime3'
UNION ALL
SELECT 4, 'datetime4'
);
If the datatime value is always the same, you can do it like this:
SELECT * FROM articles where
id IN (1, 2, 3, 4)
and datatime = 'datetime_value';
If your list of values gets really big, it's best to put those values in a table first and join it.
SELECT * FROM articles a
INNER JOIN your_values_table yvt ON a.id = yvt.id AND a.datatime = yvt.datatime;

How can I refer to a TEMPORARY table more than once in the same query?

the MySQL docs say: "You cannot refer to a TEMPORARY table more than once in the same query."
I know this has been asked before. But I can't find a specific solution for the following.
I'm doing a preselection into a temporary table
CREATE TEMPORARY TABLE preselection AS SELECT ...;
now I wanna do some (around 20 or even 30) unions
(SELECT FROM preselection ...)
UNION
(SELECT FROM preselection ...)
UNION
......
UNION
(SELECT FROM preselection ...)
I could make 20 or 30 copies of preselection and do each select on each table but if I understand it right this is the same as invoke the preselection-query above in every SELECT inside the UNION chain as a subquery.
Is there a way to work around this issue?
Greetings,
chris
Full query:
CREATE TEMPORARY TABLE preselection AS
(
SELECT id, title, chapter, date2, date, snid, max(score) FROM `movies`
WHERE
(
cluster is not NULL
)
AND
(
`date` <= '2012-02-20 05:20:00'
AND `date` > '2012-02-19 17:20:00'
AND (TIMEDIFF(date, date2) < '12:00:00')
)
GROUP BY cluster
)
UNION
(
SELECT id, title, chapter, date2, date, snid, score FROM `movies`
WHERE cluster IS NULL
AND
(
`date` <= '2012-02-20 05:20:00' AND `date` > '2012-02-19 17:20:00' AND (TIMEDIFF(date, date2) < '12:00:00')
)
);
(SELECT * FROM preselection WHERE snid=1 AND chapter LIKE '#A_OT%'
DESC LIMIT 4)
UNION
…
UNION
(SELECT * FROM preselection WHERE snid=19 AND chapter LIKE '#A_OT%'
LIMIT 4)
UNION
... for each chapter from A to J and every snid from 1 to 19 ...
UNION
(SELECT * FROM preselection WHERE snid=1 AND chapter LIKE '#J_OT%'
LIMIT 4)
UNION
…
UNION
(SELECT * FROM preselection WHERE snid=19 AND chapter LIKE '#J_OT%'
LIMIT 4)
ORDER BY `score` DESC, `date`;
I think the error message is clear: you can't do that with a single temporary table. Does creating a view of the data, instead of a temporary tables, do the trick?
Views in mysql
Yes it can be frustrating. The solutions I typically use involve avoiding the temporary table with a more complex query or using more temporary tables.
A simple copy (but like you mentioned this is no use to you)
CREATE TEMPORARY TABLE preselectionCopy AS
SELECT * FROM preselection;
Or a temporary table to hold your results
CREATE TEMPORARY TABLE result (
id INT NULL,
title VARCHAR(256) NULL
...
);
...which can be as simple as
CREATE TEMPORARY TABLE result AS
SELECT * FROM preselection WHERE snid=1 AND chapter LIKE '#A_OT%'
LIMIT 4;
INSERT INTO result
SELECT * FROM preselection WHERE snid=19 AND chapter LIKE '#A_OT%'
LIMIT 4;
...
SELECT * FROM result
ORDER BY `score` DESC, `date`;