Join using combined conditions on one join table - sql

I have join a table joining songs to genres. The table has a 'source' column that's used to identify where the genre was found. Genres are found from blogs, artists, tags, and posts.
So,
songs | song_genre | genres
id | song_id, source, genre_id | id
What I want to build is a song SELECT query that works something like this, given I already have a genre_id:
IF exists song_genre with source='artist' AND a song_genre with source='blog'
OR exists song_genre with source='artist' AND a song_genre with source='post'
OR exists song_genre with source='tag'
I'm was going to do it by doing a bunch of joins, but am sure I'm not doing it very well.
Using Postgres 9.1.

kgu87's query is correct, but likely produces a relatively expensive plan with the numerous counts over subselects. All those counts can be accumulated with one pass over the genre table with cases on source and a group by song_id. Without sample data it's hard to say whether this is faster, but I suspect it's likely. I think it's simpler at any rate.
select g.song_id
from song_genre g
group by g.song_id
having
( sum(case when g.source = 'tag' then 1 else 0 end) > 0 )
or
( sum(case when g.source = 'artist' then 1 else 0 end) > 0
and (
sum(case when g.source = 'blog' then 1 else 0 end) > 0
or
sum(case when g.source = 'post' then 1 else 0 end) > 0
)
)

select id
from
(
select distinct
id,
(
select
count(*) from
song_genre b
where a.id = b.song_id
and b.source = 'artist'
) as artist,
(
select
count(*) from
song_genre b
where a.id = b.song_id
and b.source = 'blog'
) as blog,
(
select
count(*) from
song_genre b
where a.id = b.song_id
and b.source = 'post'
) as post,
(
select
count(*) from
song_genre b
where a.id = b.song_id
and b.source = 'tag'
) as tag
from songs A
) AA
where
(AA.artist > 0 AND AA.blog > 0)
OR
(AA.artist > 0 AND AA.post > 0)
OR
(AA.tag > 0)

Related

SQL COUNT return multiple rows

I have those two Tables:
tblCommentReactions
id
Liked
CommentID
1
0
1
2
1
1
3
1
1
4
0
2
1 is Like and 0 is dislike.
tblComments:
id
userID
message
1
1
message 1
2
1
message 2
3
2
message 1
I tried to select all comments and Count the dislikes and likes and give the result in the same Row.
SELECT c.ID as CommentID, c.message,
COUNT(case when Liked = 1 AND r.CommentID = c.ID then 1 else null end) as likes,
COUNT(case when Liked = 0 AND r.CommentID = c.ID then 1 else null end) as dislikes
FROM tblcomments as c LEFT JOIN tblcommentreactions as r ON c.ID = r.CommentID
WHERE c.userID = 1;
Expected Output should be:
CommentID
message
likes
dislikes
1
message 1
2
1
2
message 2
0
1
On my Return it counts everything and only returns the first message. Could you tell me what i need to change in my request, to get my expected output?
There are two issues in your query:
you have no GROUP BY clause in presence of non-aggregated fields inside the SELECT clause, which will bring you have an error fired by the DBMS in the best case scenario, no error but random/subtle semantic errors in the worst one.
you are attempting to filter your rows (the WHERE condition) before the aggregation is applied.
In order to solve:
the first problem, you need to add the GROUP BY clause with the two missing selected fields, namely "c.ID" and "c.message"
the second problem, you need to transform your current WHERE clause into an HAVING one (as long as this one applies after the aggregation has been carried out) and add the checked field, namely "c.userID", inside the GROUP BY clause, as long as it is a field that was selected along with the fields in the SELECT clause.
SELECT c.ID as CommentID,
c.message,
COUNT(CASE WHEN Liked = 1 THEN 1 END) AS likes,
COUNT(CASE WHEN Liked = 0 THEN 1 END) AS dislikes
FROM tblComments AS c
LEFT JOIN tblCommentReactions AS r
ON c.ID = r.CommentID
GROUP BY c.ID,
c.message,
c.userID
HAVING c.userID = 1
Minor fixes on the CASE construct that doesn't require "AND r.CommentID = c.ID" as already pointed in the comments section, but also the non-required "ELSE NULL" condition, that is considered by PostgreSQL as default for this construct.
Here's a demo in MySQL, though this should work in the most common DBMS' more or less.
Try using group by clause e.g
select cr.CommentID ,c.message,
COUNT(case when Liked = 1 AND cr.CommentID = c.ID then 1 else null end) as likes ,
COUNT(case when Liked = 0 AND cr.CommentID = c.ID then 1 else null end) as dislikes
from [tblCommentReactions] cr inner join tblComments c on cr.CommentID = c.id
group by cr.CommentID , c.message
SELECT c.Id, COUNT(cr1.Liked), COUNT(cr2.Liked)
FROM Comments c
LEFT JOIN CommentReactions cr1 ON cr1.CommentId = c.Id AND cr1.Liked = 0
LEFT JOIN CommentReactions cr2 ON cr2.CommentId = c.Id AND cr2.Liked = 1
GROUP BY c.Id

Many to Many Post to Tag Query

I have a many to many relationships between posts and tags
Post
id
title
Tag
id
title
TagToPost
A | tag
B | post
Looking to perform a search for posts with a certain combination of tags.
SELECT * FROM "Post"
JOIN "TagToPost" "tag" ON "tag"."B" = "Post".id
JOIN "Tag" ON "tag"."A" = "Tag"."id"
WHERE ("Tag"."id" = '1') AND
(
("Tag"."id" = '2') OR
("Tag"."id" = '3')
)
Getting an empty result. Could someone guide me on how this query should work?
I don't think you have to join all the three tables. Since you only want the Post information.
SELECT * FROM "Post" where id in (
select postid from TagToPost Tag WHERE ("Tag"."id" = '1') AND
(("Tag"."id" = '2') OR ("Tag"."id" = '3')))
I suspect that you want posts with tag "1" and "2" or "3". If so, use aggregation:
SELECT p.*
FROM Post p JOIN
TagToPost tp
ON tp.post_id = p.id
GROUP BY p.id
HAVING SUM(CASE WHEN t.id = 1 THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN t.id IN (2, 3) THEN 1 ELSE 0 END) > 0;
Note: There are shortcuts in some databases for the HAVING. For instance, in MySQL:
HAVING SUM( t.id = 1 ) > 0 AND
SUM( t.id IN (2, 3) ) > 0
But these depend on the database.

Query to Not selecting columns if multiple

I've been scratching my head about this.
I have a table with multiple columns for the same project.
However, each project can have multiple rows of a different type.
I would like to find only projects type O and only if they don't have other types associated with them.
Ex:
Project_Num | Type
1 | O
1 | P
2 | O
3 | P
In the case above, only project 2 should be returned.
Is there a query or a method to filter this information? Any suggestions are welcome.
If I understand correctly, you want to check that the project has only record for its project number and it has type 'O'. You can use below query to implement this:
;with cte_proj as
(
select Project_Num from YourTable
group by Project_Num
having count(Project_Num) = 1)
select Project_Num from cte_proj c
inner join YourTable t on c.Project_Num = t.Project_Num
where t.Type = 'O'
You can do this using not exists:
select p.*
from projects p
where type = 'O' and
not exists (select 1
from projects p2
where p2.project_num = p.project_num and p2.type <> 'O'
);
You can also do this using aggregation:
select p.project_num
from projects p
group by p.project_num
having sum(case when p.type = 'O' then 1 else 0 end) > 0 and
sum(case when p.type <> 'O' then 1 else 0 end) = 0;
Another option (pretty fast)
SELECT p0.*
FROM project p0
LEFT JOIN project p1 ON (p0.Type<>p1.Type AND p0.Project_Num=p1.Project_Num)
WHERE p0.Type='O' AND p1.Type IS NULL;

In Oracle how to search with sub conditions on same column

In Oracle how do I find Cars which must have Feature1 and have at lest, one out of Feature2 or Feature3. Sample table and expected result should look like below screenshot. Thanks Kiran
This should work:
select t1.car, t1.feature
from yourtable t1
inner join
( -- inner select returns the cars with the Feature1 and Feature2 or Feature3
select car, feature
from yourtable
where feature = 'Feature1'
and exists (select car
from yourtable
where feature in ('Feature2', 'Feature3'))
) t2
on t1.car = t2.car
where t1.feature in ('Feature1', 'Feature2', 'Feature3') -- this excludes any other features
see SQL Fiddle with Demo
I like to do this with GROUP BY and HAVING:
select car
from t
group by car
having max(case when feature = 'Feature1' then 1 else 0 end) = 1 and
max(case when feature in ('Feature1', 'Feature2') then 1 else 0 end) = 1
This query returns the cars. To get the featuers as well, you have to join tis back in:
select t.*
from (select car
from t
group by car
having max(case when feature = 'Feature1' then 1 else 0 end) = 1 and
max(case when feature in ('Feature1', 'Feature2') then 1 else 0 end) = 1
) c join
t
on t.car = c.car
I like this method, because the same idea can be used for handling many different similar queries -- AND conditions, OR conditions, different subgroups, and different counts.

SQL Query with CASE and group by

I have a Feature Table where each feature is identified by its ID(DB column) and Bugs table where each feature has one to many relation ship with bugs table.
Feature Table has columns
id Description
Bugs Table has columns
ID Feature_ID Status
I will consider a bug as opened if its state is either 0 or 1 and as closed if Status is 2.
I am trying write a query which indicates whether a Feature can be considered as passed or failed based on it's Status.
select F.ID
CASE WHEN count(B.ID) > 0 THEN 'FAIL'
ELSE 'PASS'
END as FEATURE_STATUS
from Feature F,
Bugs B
where B.Status in (0,1)
group by F.ID;
My query always gives the Failed Features but not passed, how can modify my query to return both?
It sounds like you want something like
SELECT f.id,
(CASE WHEN open_bugs = 0
THEN 'PASS'
ELSE 'FAIL'
END) feature_status,
open_bugs,
closed_bugs
FROM (SELECT f.id,
SUM( CASE WHEN b.status IN (0,1)
THEN 1
ELSE 0
END) open_bugs,
SUM( CASE WHEN b.status = 2
THEN 1
ELSE 0
END) closed_bugs
FROM feature f
JOIN bugs b ON (f.id = b.feature_id)
GROUP BY f.id)
SELECT F.ID,
CASE WHEN SUM(CASE WHEN B.ID IN (0, 1) THEN 1 ELSE 0 END) > 0 THEN 'Fail'
ELSE 'Success' END AS FEATURE_STATUS
from Feature F
JOIN Bugs B ON B.Feature_ID = F.ID
group by F.ID