I would like to know if there is any better way to do the following query or if there is any necessity to make it better? (Considering that the DB load is not that big). The only criterion is that the 3 "variables" have to be included as AND, and the query can have more or less LEFT JOINS.
select `candidates`.* from `candidates`
inner join `candidate_tag` on `candidates`.`id` = `candidate_tag`.`candidate_id`
left join `tags` t1 on `t1`.`id` = `candidate_tag`.`tag_id` and t1.name like '%foo%'
left join `tags` t2 on `t2`.`id` = `candidate_tag`.`tag_id` and t2.name like '%baz%'
left join `tags` t3 on `t3`.`id` = `candidate_tag`.`tag_id` and t3.name like '%zoo%'
group by candidates.id
order by `candidates`.`last_name` asc
My first shot was the conditional query with AND operator but it didn't give me any results, so that is why I chose to change it to left join s.
Thanks!
Try this:
select `candidates`.id from `candidates`
inner join `candidate_tag` on `candidates`.`id` = `candidate_tag`.`candidate_id`
inner join `tags` t on `t`.`id` = `candidate_tag`.`tag_id`
group by candidates.id
HAVING SUM(CASE WHEN t.tag_name LIKE '%foo%' THEN 1 ELSE 0 END) >= 1 AND SUM(CASE WHEN t.tag_name LIKE '%bar%' THEN 1 ELSE 0 END) >= 1 AND SUM(CASE WHEN t.tag_name LIKE '%aaa%' THEN 1 ELSE 0 END) >= 1
order by `candidates`.`last_name` asc
If you want to find candidates who have one of three tags, you can also use conditional aggregation:
select c.*
from candidates c join
candidate_tag ct
on ct.candidate_id = c.id join
tags t
on ct.tag_id = t.id
where t.name like '%foo%' or
t.name like '%baz%' or
t.name like '%zoo%'
group by c.id
order by c.`last_name` asc;
You can also add group_concat(t.name) as tags to get the tags that candidates match. Or, alternatively, if you only want candidates with all three, you can add a having clause:
having count(distinct t.name) = 3
or
having (max(t.name like '%foo%') +
max(t.name like '%baz%') +
max(t.name like '%zoo%')) = 3
You need this version if there are too "foo" tags, for instance.
Related
I am having some problem with my MySQL query and I need some guidance here. I am creating a search and I want to know how many item was found in my search with my query. But the problem is, my query returning count for each rows separately, It's not returning total count. Here what I have right now.
My query
SELECT
COUNT(t.id) AS total
FROM
trouble t
LEFT JOIN district d ON d.id = t.district
LEFT JOIN country c ON c.id = t.country
LEFT JOIN multi_category mc ON mc.t_id = t.id
LEFT JOIN category ct ON ct.id = mc.ct_id
LEFT JOIN state s ON s.id = t.state
WHERE
t.name LIKE '%keyword%' OR
t.title LIKE '%keyword%' OR
t.tags LIKE '%keyword%' OR
ct.category LIKE '%keyword%' OR
c.country LIKE '%keyword%' OR
s.state LIKE '%keyword%' OR
d.district LIKE '%keyword%'
GROUP BY
t.id
I have 14 rows in my database with the keyword I am searching with and this query returning 14 rows, here is a snap.
But it is counting each rows id's individually. I don't want that. What I want is, I want the total row count which is 14 and I want it in a single row. Can you please help me achieve this?
Thanks in advance.
First get the unique number of IDs and then return the total number of IDs.
SELECT count(*) AS Total
FROM
(
SELECT DISTINCT t.id
FROM trouble t
LEFT JOIN district d ON d.id = t.district
LEFT JOIN country c ON c.id = t.country
LEFT JOIN multi_category mc ON mc.t_id = t.id
LEFT JOIN category ct ON ct.id = mc.ct_id
LEFT JOIN state s ON s.id = t.state
WHERE
t.name LIKE '%keyword%' OR
t.title LIKE '%keyword%' OR
t.tags LIKE '%keyword%' OR
ct.category LIKE '%keyword%' OR
c.country LIKE '%keyword%' OR
s.state LIKE '%keyword%' OR
d.district LIKE '%keyword%'
GROUP BY t.id
) resultTable
Use exists instead of LEFT JOINs. The joins just multiply rows when there are multiple matches:
SELECT COUNT(*)
FROM trouble t
WHERE t.name LIKE '%keyword%' OR
t.title LIKE '%keyword%' OR
t.tags LIKE '%keyword%' OR
EXISTS (SELECT 1
FROM district d
WHERE d.id = t.district AND d.district LIKE '%keyword%'
) OR
EXISTS (SELECT 1
FROM country c
WHERE c.id = t.country AND c.country LIKE '%keyword%'
) OR
EXISTS (SELECT 1
FROM state s
WHERE s.id = t.state AND s.state LIKE '%keyword%'
) OR
EXISTS (SELECT 1
FROM multi_category mc JOIN
category ct
ON ct.id = mc.ct_id
WHERE mc.t_id = t.id AND
(ct.category LIKE '%keyword%' OR
c.country LIKE '%keyword%'
)
);
By eliminating the creation of multiple rows, this should also be faster.
I want to count of condition from all employees of each area, which conditions has 2 parameters, WELL AND UNWELL. Like this,
conditions | area | count_conditions
Well AREA1 1
UNWELL AREA1 0
Well AREA2 5
UNWELL AREA2 1
...
This is the closest so far.
SELECT a.conditions, k.area,
SUM(CASE WHEN a.conditions IS NOT NULL THEN 1 ELSE 0 END) AS count_conditions
FROM tb_attended a
INNER JOIN tb_employees k ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions
My code above is running well, but if there is no entry of UNWELL OR WELL in some area, then that conditions does not appear. Like this.
conditions | area | count_conditions
Well AREA1 1
Well AREA2 5
UNWELL AREA2 1
...
This is example data that I use,
SQL Fiddle
Any suggest?
Thank you.
First you need a CROSS join of the distinct areas to the distinct conditions and then LEFT joins of the tables:
SELECT t1.condition, t2.area,
COUNT(k.nrp) AS count_conditions
FROM (SELECT DISTINCT `condition` FROM tb_attended) t1
CROSS JOIN (SELECT DISTINCT Area FROM tb_employees) t2
LEFT JOIN tb_attended a ON a.condition = t1.condition
LEFT JOIN tb_employees k ON k.area = t2.area AND a.nrp = k.nrp AND a.date = '2020-07-20'
GROUP BY t1.condition, t2.area
ORDER BY t2.area, t1.condition DESC
See the demo.
Use left join
SELECT a.conditions, k.area,
SUM(CASE WHEN a.conditions IS NOT NULL THEN 1 ELSE 0 END) AS count_conditions
FROM tb_attended a
left JOIN tb_employees k
ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions
You can use LEFT JOIN without redundant conditional statement, with SUM() aggregation :
SELECT a.conditions, k.area,
SUM(a.conditions IS NOT NULL) AS count_conditions
FROM tb_attended a
LEFT JOIN tb_employees k
ON a.nrp = k.nrp
AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions
I think you want a left join:
SELECT a.conditions, k.area, COUNT(a.conditions) AS count_conditions
FROM tb_employees k LEFT JOIN
tb_attended a
ON a.nrp = k.nrp AND a.date = '2020-07-20'
GROUP BY k.area, a.conditions;
EDIT:
I see. You want to get all rows, even those with no matches. Use a CROSS JOIN to generate the combinations of areas and conditions that you want. Then use LEFT JOIN to match to the existing data:
SELECT c.condition, e.area, COUNT(a.nrp) AS count_conditions
FROM (SELECT DISTINCT a.condition
FROM tb_attended a
) c CROSS JOIN
(SELECT DISTINCT area
FROM tb_employees
) e LEFT JOIN
tb_employees k
ON k.area = e.area LEFT JOIN
tb_attended a
ON a.nrp = k.nrp AND
a.condition = c.condition AND
a.date = '2020-07-20'
GROUP BY c.condition, e.area;
Here is a db<>fiddle.
I have a table of "Songs", "Songs_Tags" (relating songs with tags) and "Songs_Votes" (relating songs with boolean like/dislike).
I need to retrieve the songs with a GROUP_CONCAT() of its tags and also the number of likes (true) and dislikes (false).
My query is something like that:
SELECT
s.*,
GROUP_CONCAT(st.id_tag) AS tags_ids,
COUNT(CASE WHEN v.vote=1 THEN 1 ELSE NULL END) as votesUp,
COUNT(CASE WHEN v.vote=0 THEN 1 ELSE NULL END) as votesDown,
FROM Songs s
LEFT JOIN Songs_Tags st ON (s.id = st.id_song)
LEFT JOIN Votes v ON (s.id=v.id_song)
GROUP BY s.id
ORDER BY id DESC
The problem is that when a Song has more than 1 tag, it gets returned more then once, so when I do the COUNT(), it returns more results.
The best solution I could think is if it would be possible to do the last LEFT JOIN after the GROUP BY (so now there would be only one entry for each song). Then I'd need another GROUP BY m.id.
Is there a way to accomplish that? Do I need to use a subquery?
There've been some good answers so far, but I would adopt a slightly different method quite similar to what you described originally
SELECT
songsWithTags.*,
COALESCE(SUM(v.vote),0) AS votesUp,
COALESCE(SUM(1-v.vote),0) AS votesDown
FROM (
SELECT
s.*,
COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids
FROM Songs s
LEFT JOIN Songs_Tags st
ON st.id_song = s.id
GROUP BY s.id
) AS songsWithTags
LEFT JOIN Votes v
ON songsWithTags.id = v.id_song
GROUP BY songsWithTags.id DESC
In this the subquery is responsible for collating songs with tags into a 1 row per song basis. This is then joined onto Votes afterwards. I also opted to simply sum up the v.votes column as you have indicated it is 1 or 0 and therefore a SUM(v.votes) will add up 1+1+1+0+0 = 3 out of 5 are upvotes, while SUM(1-v.vote) will sum 0+0+0+1+1 = 2 out of 5 are downvotes.
If you had an index on votes with the columns (id_song,vote) then that index would be used for this so it wouldn't even hit the table. Likewise if you had an index on Songs_Tags with (id_song,id_tag) then that table wouldn't be hit by the query.
edit added solution using count
SELECT
songsWithTags.*,
COUNT(CASE WHEN v.vote=1 THEN 1 END) as votesUp,
COUNT(CASE WHEN v.vote=0 THEN 1 END) as votesDown
FROM (
SELECT
s.*,
COLLATE(GROUP_CONCAT(st.id_tag),'') AS tags_ids
FROM Songs s
LEFT JOIN Songs_Tags st
ON st.id_song = s.id
GROUP BY s.id
) AS songsWithTags
LEFT JOIN Votes v
ON songsWithTags.id = v.id_song
GROUP BY songsWithTags.id DESC
Try this:
SELECT
s.*,
GROUP_CONCAT(DISTINCT st.id_tag) AS tags_ids,
COUNT(DISTINCT CASE WHEN v.vote=1 THEN id_vote ELSE NULL END) AS votesUp,
COUNT(DISTINCT CASE WHEN v.vote=0 THEN id_vote ELSE NULL END) AS votesDown
FROM Songs s
LEFT JOIN Songs_Tags st ON (s.id = st.id_song)
LEFT JOIN Votes v ON (s.id=v.id_song)
GROUP BY s.id
ORDER BY id DESC
Your code results in a mini-Cartesian product because you are doing two Joins in 1-to-many relationships and the 1 table is on the same side of both joins.
Convert to 2 subqueries with groupings and then Join:
SELECT
s.*,
COALESCE(st.tags_ids, '') AS tags_ids,
COALESCE(v.votesUp, 0) AS votesUp,
COALESCE(v.votesDown, 0) AS votesDown
FROM
Songs AS s
LEFT JOIN
( SELECT
id_song,
GROUP_CONCAT(id_tag) AS tags_ids
FROM Songs_Tags
GROUP BY id_song
) AS st
ON s.id = st.id_song
LEFT JOIN
( SELECT
id_song,
COUNT(CASE WHEN v.vote=1 THEN id_vote END) AS votesUp,
COUNT(CASE WHEN v.vote=0 THEN id_vote END) AS votesDown
FROM Votes
GROUP BY id_song
) AS v
ON s.id = v.id_song
ORDER BY s.id DESC
SELECT SUM(case when p.status = 2 then p.value end) as 'val_accepted'
FROM
props AS p
INNER JOIN (p_contents AS pc
INNER JOIN contents AS c ON c.id = pc.library_id)
ON p.id = pc.prop_id
WHERE p.account_id = 3
GROUP BY (pc.library_id)
so, what's happening:
there are two p_contents that are associated with a prop. those two p_contents have the same library_id which points to a corresponding content.
So, the SUM of p.value is double what it should be because there are two p_contents that point to the same content
How do I not double SUM the p.value?
EDIT:
I figured out how to use DISTINCT, but I still need access to the inner columns...
SELECT c.name as 'library_name',
SUM(case when p.status = 2 then p.value end) as 'val_accepted',
FROM
props AS p
INNER JOIN
(
SELECT DISTINCT(pc.library_id), prop_id
FROM prop_contents AS pc
INNER JOIN
(
SELECT name, visibility, id, updated_at
FROM contents AS c
) as c
ON c.id = pc.library_id
)as pc
ON p.id = pc.prop_id
WHERE p.account_id = 3
GROUP BY (pc.library_id)
and now I get the error:
Unknown column 'c.name' in 'field list')
Here's one solution. First reduce the set to distinct rows in an derived table, then apply the GROUP BY to that result:
SELECT SUM(case when d.status = 2 then d.value end) as 'val_accepted'
FROM (
SELECT DISTINCT p.id, p.status, p.value, pc.library_id
FROM props p
INNER JOIN p_contents AS pc ON p.id = pc.prop_id
INNER JOIN contents AS c ON c.id = pc.library_id
WHERE p.account_id = 3) AS d
GROUP BY d.library_id
You use DISTINCT(pc.library_id) in your example, as if DISTINCT applies only to the column inside the parentheses. This is a common misconception. DISTINCT applies to all columns of the select-list. DISTINCT is not a function; it's a query modifier.
This is a query automatically generated by Taggable extension for Doctrine ORM.
SELECT t.id AS t__id, t.name AS t__name, COUNT(DISTINCT i.id) AS i__0,
(COUNT(DISTINCT i.id)) AS i__1
FROM taggable_tag t
LEFT JOIN cms__model__image_taggable_tag c ON (t.id = c.tag_id)
LEFT JOIN image i ON i.id = c.id
WHERE t.id IN
(SELECT doctrine_subquery_alias.id
FROM
(SELECT DISTINCT t2.id, (COUNT(DISTINCT i2.id)) AS i2__1
FROM taggable_tag t2
LEFT JOIN cms__model__image_taggable_tag c2 ON (t2.id = c2.tag_id)
LEFT JOIN image i2 ON i2.id = c2.id
GROUP BY t2.id HAVING i2__1 > 0
ORDER BY i2__1 DESC LIMIT 10) AS doctrine_subquery_alias)
GROUP BY t.id HAVING i__1 > 0
ORDER BY i__1 DESC
It works when using MySql, but won't work with PostgreSql.
I get: column i2__1 not found or column i__2 not found.
Are aliases disallowed when using COUNT(DISTINCT)?
How this query should look like to work on PostgreSql?
You could try to replace i2__1 by COUNT(DISTINCT i2.id) in the HAVING-clause of the sub-select, or remove the parenthesis around COUNT(DISTINCT i2.id).
You might also have to add t__name to the GROUP BY-clause of the main select.