Let's turn it outsideinside-out so we can see that it is starting at the right places. The Optimizer won't do this work for us.
- Start with each table that might say 'urgent'
UNION
them. (UNION DISTINCT
is slightly slower thanUNION ALL
, but you might get two duplicate rows. You decide.)- Join to
tasks
to get theproject_id
- Finally, reach into
projects
for the few rows that are needed. (Note how both of your formulations effectively require fetching all ofp
before figuring out that most of the rows aren't needed.)
Switching from OR
to UNION
was a good idea, but IN ( SELECT ... )
is not an efficient construct.
SELECT p.*
FROM (
SELECT t.project_id
FROM task_comments tc
JOIN tasks t ON t.id = tc.task_id
WHERE tc.text = 'urgent' -- see Note
) UNION DISTINCT (
SELECT t.project_id
FROM task_tags tt
JOIN tasks t ON t.id = tt.task_id
WHERE tt.value = 'urgent'
) AS x
JOIN projects p ON p.id = x.project_id
That will need
tc: INDEX(text, task_id) -- see Note
t: (I assume you have PRIMARY KEY(id))
tt: INDEX(value, task_id)
p: (I assume you have PRIMARY KEY(id))
Note: Perhaps you really want to check for "urgent" anywhere in tc.text
? If so, the best way to optimize it is to have
tc: FULLTEXT(text)
and switch to
WHERE MATCH(tc.text) AGAINST ('+urgent' IN BOOLEAN MODE)