Get the number of rows per tenant of several tables - sql

Let's say I have 2 tables: Tenants and Wargles. Wargles has a Foreign Key towards Tenants called TenantId. If I want to get number of wargles per tenant, I can do this:
SELECT t.Id as TenantId, count(w.Id) as WargleCount
FROM Tenants t
JOIN Wargles w ON w.TenantId = t.Id
GROUP BY t.Id
Now, let's say I have another table, Fiddles, that, as Wargles has a FK towards Tenants. How can I add another column to the query above, so I get the number of wargles and the number of fiddles for each tenant?
I tried with this:
SELECT t.Id as TenantId, count(w.Id) as WargleCount, count(f.Id) as FiddleCount
FROM Tenants t
JOIN Wargles w ON w.TenantId = t.Id
JOIN Fiddles f ON f.TenantId = t.Id
GROUP BY t.Id
But this won't work, since it would give me the same number both for WargleCount and FiddleCount, the product of the rows from both tables.

Use two subselects
SELECT t.Id as TenantId,
(SELECT Count(1) FROM Fiddles F WHERE F.TenantId = T.Id) as FiddleCount,
(SELECT Count(1) FROM Wargles W WHERE W.TenantId = T.Id) as WargleCount
FROM Tenants t

The most efficient method is probably to use correlated subqueries:
SELECT t.Id as TenantId,
(SELECT COUNT(*)
FROM Wargles w
WHERE w.TenantId = t.Id
) as WargleCount, count(f.Id) as FiddleCount
(SELECT COUNT(*)
FROM Fiddles f
WHERE f.TenantId = t.Id
) as FiddleCount
FROM Tenants t;
In particular, this can take advantage of indexes on Wargles(TenantId) and Fiddles(TenantId).

In your case, as extendable solution, I would recommend Scalar Function usage.
/* SAMPLE DATA ARRANGE */
CREATE TABLE Tenants (Id INT, Title NVARCHAR(5)) ; INSERT INTO Tenants VALUES (1, 'A'), (2, 'B') , (3, 'C');
CREATE TABLE Wargles (Id INT,TenantId INT);INSERT INTO Wargles VALUES (1, 1), (2, 1) , (3, 1) , (4, 2), (5, 2) , (6, 1), (7, 3) , (8, 3);
CREATE TABLE Fiddles (Id INT,TenantId INT);INSERT INTO Fiddles VALUES (1, 1), (2, 1) , (3, 1) , (4, 2), (5, 2) , (6, 2), (7, 3) , (8, 2);
The Function
/*NEEDED CODE*/
CREATE FUNCTION dbo.ufnGetTenantsNo ( #Id AS INT , #Tb AS INT)
RETURNS INT
AS
BEGIN
DECLARE #Result INT = 0;
IF (#TB = 1)
SELECT #Result = COUNT(*)
FROM Wargles
WHERE TenantId = #Id
ELSE
SELECT #Result = COUNT(*)
FROM Fiddles
WHERE TenantId = #Id
RETURN #Result
END
GO
Select Statement
SELECT Id AS TenantId
,dbo.ufnGetTenantsNo(Id, 1) AS WargleCount
,dbo.ufnGetTenantsNo(Id, 2) AS FiddleCount
FROM Tenants

Related

SQL return only distinct IDs from LEFT JOIN

I've inherited some fun SQL and am trying to figure out how to how to eliminate rows with duplicate IDs. Our indexes are stored in a somewhat columnar format and then we pivot all the rows into one with the values as different columns.
The below sample returns three rows of unique data, but the IDs are duplicated. I need just two rows with unique IDs (and the other columns that go along with it). I know I'll be losing some data, but I just need one matching row per ID to the query (first, top, oldest, newest, whatever).
I've tried using DISTINCT, GROUP BY, and ROW_NUMBER, but I keep getting the syntax wrong, or using them in the wrong place.
I'm also open to rewriting the query completely in a way that is reusable as I currently have to generate this on the fly (cardtypes and cardindexes are user defined) and would love to be able to create a stored procedure. Thanks in advance!
declare #cardtypes table ([ID] int, [Name] nvarchar(50))
declare #cards table ([ID] int, [CardTypeID] int, [Name] nvarchar(50))
declare #cardindexes table ([ID] int, [CardID] int, [IndexType] int, [StringVal] nvarchar(255), [DateVal] datetime)
INSERT INTO #cardtypes VALUES (1, 'Funny Cards')
INSERT INTO #cardtypes VALUES (2, 'Sad Cards')
INSERT INTO #cards VALUES (1, 1, 'Bunnies')
INSERT INTO #cards VALUES (2, 1, 'Dogs')
INSERT INTO #cards VALUES (3, 1, 'Cat')
INSERT INTO #cards VALUES (4, 1, 'Cat2')
INSERT INTO #cardindexes VALUES (1, 1, 1, 'Bunnies', null)
INSERT INTO #cardindexes VALUES (2, 1, 1, 'playing', null)
INSERT INTO #cardindexes VALUES (3, 1, 2, null, '2014-09-21')
INSERT INTO #cardindexes VALUES (4, 2, 1, 'Dogs', null)
INSERT INTO #cardindexes VALUES (5, 2, 1, 'playing', null)
INSERT INTO #cardindexes VALUES (6, 2, 1, 'poker', null)
INSERT INTO #cardindexes VALUES (7, 2, 2, null, '2014-09-22')
SELECT TOP(100)
[ID] = c.[ID],
[Name] = c.[Name],
[Keyword] = [colKeyword].[StringVal],
[DateAdded] = [colDateAdded].[DateVal]
FROM #cards AS c
LEFT JOIN #cardindexes AS [colKeyword] ON [colKeyword].[CardID] = c.ID AND [colKeyword].[IndexType] = 1
LEFT JOIN #cardindexes AS [colDateAdded] ON [colDateAdded].[CardID] = c.ID AND [colDateAdded].[IndexType] = 2
WHERE [colKeyword].[StringVal] LIKE 'p%' AND c.[CardTypeID] = 1
ORDER BY [DateAdded]
Edit:
While both solutions are valid, I ended up using the MAX() solution from #popovitsj as it was easier to implement. The issue of data coming from multiple rows doesn't really factor in for me as all rows are essentially part of the same record. I will most likely use both solutions depending on my needs.
Here's my updated query (as it didn't quite match the answer):
SELECT TOP(100)
[ID] = c.[ID],
[Name] = MAX(c.[Name]),
[Keyword] = MAX([colKeyword].[StringVal]),
[DateAdded] = MAX([colDateAdded].[DateVal])
FROM #cards AS c
LEFT JOIN #cardindexes AS [colKeyword] ON [colKeyword].[CardID] = c.ID AND [colKeyword].[IndexType] = 1
LEFT JOIN #cardindexes AS [colDateAdded] ON [colDateAdded].[CardID] = c.ID AND [colDateAdded].[IndexType] = 2
WHERE [colKeyword].[StringVal] LIKE 'p%' AND c.[CardTypeID] = 1
GROUP BY c.ID
ORDER BY [DateAdded]
You could use MAX or MIN to 'decide' on what to display for the other columns in the rows that are duplicate.
SELECT ID, MAX(Name), MAX(Keyword), MAX(DateAdded)
(...)
GROUP BY ID;
using row number windowed function along with a CTE will do this pretty well. For example:
;With preResult AS (
SELECT TOP(100)
[ID] = c.[ID],
[Name] = c.[Name],
[Keyword] = [colKeyword].[StringVal],
[DateAdded] = [colDateAdded].[DateVal],
ROW_NUMBER()OVER(PARTITION BY c.ID ORDER BY [colDateAdded].[DateVal]) rn
FROM #cards AS c
LEFT JOIN #cardindexes AS [colKeyword] ON [colKeyword].[CardID] = c.ID AND [colKeyword].[IndexType] = 1
LEFT JOIN #cardindexes AS [colDateAdded] ON [colDateAdded].[CardID] = c.ID AND [colDateAdded].[IndexType] = 2
WHERE [colKeyword].[StringVal] LIKE 'p%' AND c.[CardTypeID] = 1
ORDER BY [DateAdded]
)
SELECT * from preResult WHERE rn = 1

EXISTS and NOT EXISTS in a correlated subquery

I've been trying to work out how to do a particular query for a day or so now and it has gotten to the point where I need some outside help. Hence my question.
Given the following data;
DECLARE #Data AS TABLE
(
OrgId INT,
ThingId INT
)
DECLARE #ReplacementData AS TABLE
(
OldThingId INT,
NewThingId INT
)
INSERT INTO #Data (OrgId, ThingId)
VALUES (1, 2), (1, 3), (1, 4),
(2, 1), (2, 4),
(3, 3), (3, 4)
INSERT INTO #ReplacementData (OldThingId, NewThingId)
VALUES (3, 4), (2, 5)
I want to find any organisation that has a "thing" that has been replaced as denoted in the #ReplacementData table variable. I'd want to see the org id, the thing it is that they have that has been replaced and the id of the thing that should replace it. So for example given the data above, I should see;
Org id, Thing Id, Replacement Thing Id org doesn't have but should have
1, 2, 5 -- As Org 1 has 2, but not 5
I've had many attempts at trying to get this working, and I just can't seem to get my head around how to go about it. The following are a couple of my attempts, but I think I am just way off;
-- Attempt using correlated subqueries and EXISTS clauses
-- Show all orgs that have the old thing, but not the new thing
-- Ideally, limit results to OrgId, OldThingId and the NewThingId that they should now have too
SELECT *
FROM #Data d
WHERE EXISTS (SELECT *
FROM #Data oldstuff
WHERE oldstuff.OrgId = d.OrgId
AND oldstuff.ThingId IN
(SELECT OldThingID
FROM #ReplacementData))
AND NOT EXISTS (SELECT *
FROM #Data oldstuff
WHERE oldstuff.OrgId = d.OrgId
AND oldstuff.ThingId IN
(SELECT NewThingID
FROM #ReplacementData))
-- Attempt at using a JOIN to only include those old things that the org has (via the where clause)
-- Also try exists to show missing new things.
SELECT *
FROM #Data d
LEFT JOIN #ReplacementData rd ON rd.OldThingId = d.ThingId
WHERE NOT EXISTS (
SELECT *
FROM #Data dta
INNER JOIN #ReplacementData rep ON rep.NewThingId = dta.ThingId
WHERE dta.OrgId = d.OrgId
)
AND rd.OldThingId IS NOT NULL
Any help on this is much appreciated. I may well be going about it completely wrong, so please let me know if there is a better way of tackling this type of problem.
Try this out and let me know.
DECLARE #Data AS TABLE
(
OrgId INT,
ThingId INT
)
DECLARE #ReplacementData AS TABLE
(
OldThingId INT,
NewThingId INT
)
INSERT INTO #Data (OrgId, ThingId)
VALUES (1, 2), (1, 3), (1, 4),
(2, 1), (2, 4),
(3, 3), (3, 4)
INSERT INTO #ReplacementData (OldThingId, NewThingId)
VALUES (3, 4), (2, 5)
SELECT D.OrgId, RD.*
FROM #Data D
JOIN #ReplacementData RD
ON D.ThingId=RD.OldThingId
LEFT OUTER JOIN #Data EXCLUDE
ON D.OrgId = EXCLUDE.OrgId
AND RD.NewThingId = EXCLUDE.ThingId
WHERE EXCLUDE.OrgId IS NULL

How to compare n:m assignments?

I have two tables (entity and kind) plus a n:m table (entity_kind).
CREATE TABLE
entity
(
entity_id INT
, name NVARCHAR(100)
, PRIMARY KEY(entity_id)
)
CREATE TABLE
kind
(
kind_id INT
, name NVARCHAR(100)
, PRIMARY KEY(kind_id)
)
CREATE TABLE
entity_kind
(
entity_id INT
, kind_id INT
, PRIMARY KEY(entity_id, kind_id)
)
Test data:
INSERT INTO
entity
VALUES
(1, 'Entity A')
, (2, 'Entity B')
, (3, 'Entity C')
INSERT INTO
kind
VALUES
(1, 'Kind 1')
, (2, 'Kind 2')
, (3, 'Kind 3')
, (4, 'Kind 4')
INSERT INTO
entity_kind
VALUES
(1, 1)
, (1, 3)
, (2, 1)
, (2, 2)
, (3, 4)
My code so far:
DECLARE
#selected_entities
TABLE
(
entity_id INT
)
DECLARE
#same_kinds BIT;
INSERT INTO
#selected_entities
VALUES
(1), (2)
-- Missing code here
SELECT
#same_kinds AS "same_kinds"
The table var #selected_entities is filled with entities that should be compared.
The logical var #same_kinds should indicate whether the selected entities have exactly the same kinds assigned.
How can I achieve this?
This is a compare two sets of things type problem. The query I'm going to show gives all pairs along with a flag. You can easily incorporate comparing a subquery by changing the first two entity tables to the table of ids you want to compare.
This query has a few parts. First, it produces all pairs of entities from the entity tables. This is important, because this will pick up even entities that have no "kinds" associated with them. You want a flag, rather than just a list of those that match.
Then the heart of the logic is to do a self-join on the entity-kinds table with the match on "kind". This is then aggregated by the two entities. The result is a count of the kinds that two entities share.
The final logic is to compare this count to the count of "kinds" on each entity. If all of these counts are the same, then the entities match. If not, they do not. This approach does assume that there are no duplicates in entity_kinds.
select e1.entity_id as e1, e2.entity_id as e2,
(case when count(ek1.entity_id) = max(ek1.numkinds) and
count(ek2.entity_id) = count(ek1.entity_id) and
max(ek1.numkinds) = max(ek2.numkinds)
then 1
else 0
end) as IsSame
from entity e1 join
entity e2
on e1.entity_id < e2.entity_id left outer join
(select ek.*, count(*) over (partition by entity_id) as numkinds
from entity_kind ek
) ek1
on e1.entity_id = ek1.entity_id left outer join
(select ek.*, count(*) over (partition by entity_id) as numkinds
from entity_kind ek
) ek2
on e2.entity_id = ek2.entity_id and
ek2.kind_id = ek1.kind_id
group by e1.entity_id, e2.entity_id;
The SQL Fiddle is here.
You can do this with two checks: First, if the kind-count on each entity is not the same, then they cannot match. Second, provided the count is the same, you just need to find one kind that doesn't match the list of an arbitrary other entity (I just take the first entity in the compare list). In code:
DECLARE #firstEntity int = (SELECT TOP 1 entity_id from #selected_entities)
IF EXISTS(SELECT TOP 1 se.entity_id FROM #selected_entities se
INNER JOIN entity_kind ek ON ek.entity_id = se.entity_id
WHERE ek.kind_id NOT IN (SELECT kind_id from entity_kind where entity_id = #firstEntity)
OR ((SELECT COUNT(1) FROM entity_kind WHERE entity_id = ek.entity_id)
<> (SELECT COUNT(1) FROM entity_kind WHERE entity_id = #firstEntity)))
SET #same_kinds = 0
ELSE
SET #same_kinds = 1
DECLARE #first_entity_id INT;
SET #first_entity_id = (SELECT TOP(1) se.entity_id FROM #selected_entities se);
DECLARE #dummyvar INT;
SELECT DISTINCT #dummyvar = COUNT(ek.kind_id)
FROM dbo.entity_kind ek
LEFT JOIN (
SELECT ek.kind_id
FROM dbo.entity_kind ek
WHERE ek.entity_id = #first_entity_id
) k ON ek.kind_id = k.kind_id
WHERE ek.entity_id IN (SELECT se.entity_id FROM #selected_entities se)
GROUP BY ek.entity_id;
SET #same_kinds = CASE WHEN ##ROWCOUNT = 1 THEN 1 ELSE 0 END;
SELECT #same_kinds AS [#same_kinds];
Note: #selected_entities should be declared thus:
DECLARE
#selected_entities
TABLE
(
entity_id INT PRIMARY KEY
)

How can I write a better multiple join that matches multiple values across rows?

I'm trying to write a SQL statement that will allow me to select a series of articles from a table based on their keywords. What I've got so far is a token table, an article table, and a many-to-many table for tokens & articles:
tokens
rowid
token
token_article
token_rowid
article_rowid
articles
rowid
What I'm doing is taking a search query, splitting it up by spaces, then select all articles that contains those keywords. So far I've come up with this:
select * from
(select * from tokens
inner join token_article on
tokens.rowid = token_article.token_rowid and
token = 'ABC'
) as t1,
(select * from tokens
inner join token_article on
tokens.rowid = token_article.token_rowid and
token = 'DEF'
) as t2
where t1.article_rowid = t2.article_rowid and t2.article_rowid = articles.rowid
Which works but of course its doing a select on all articles that match ABC and all articles that DEF then selecting them.
Now I'm trying to figure out a better way. What I imagine in my mind that would work would be to select all the articles that match ABC and from those match any with DEF. This is what I imagine it to look like but does not work (receive error message "no such columns: tokens.rowid")
select * from
(select * from
(select * from tokens
inner join token_article on
tokens.rowid = token_article.token_rowid and
token = 'ABC'
)
inner join token_article on
tokens.rowid = token_article.token_rowid and
token = 'DEF'
)
Because there is more than one way to do this...this method uses GROUP BY and HAVING clauses. The query is looking for all articles that have either the ABC or DEF token, but then grouping by the article ID where the count of tokens for the article is equal to the number of tokens being queried.
Note that I've used MSSQL syntax here, but the concept should work in most SQL implementations.
Edit: I should point out that this has a fairly clean syntax as you add more tokens to the query. If you add more tokens, then you just need to modify the t.token_in criteria and adjust the HAVING COUNT(*) = x clause accordingly.
DECLARE #tokens TABLE
(
rowid INT NOT NULL,
token VARCHAR(255) NOT NULL
)
DECLARE #articles TABLE
(
rowid INT NOT NULL,
title VARCHAR(255) NOT NULL
)
DECLARE #token_article TABLE
(
token_rowid INT NOT NULL,
article_rowid INT NOT NULL
)
INSERT INTO #tokens VALUES (1, 'ABC'), (2, 'DEF')
INSERT INTO #articles VALUES (1, 'This is article 1.'), (2, 'This is article 2.'), (3, 'This is article 3.'), (4, 'This is article 4.'), (5, 'This is article 5.'), (6, 'This is article 6.')
INSERT INTO #token_article VALUES (1, 1), (2, 1), (1, 2), (2, 3), (1, 4), (2, 4), (1, 5), (1, 6)
-- Get the article IDs that have all of the tokens
-- Use this if you just want the IDs
SELECT a.rowid FROM #articles a
INNER JOIN #token_article ta ON a.rowid = ta.article_rowid
INNER JOIN #tokens t ON ta.token_rowid = t.rowid
WHERE t.token IN ('ABC', 'DEF')
GROUP BY a.rowid
HAVING COUNT(*) = 2 -- This should match the number of tokens
rowid
-----------
1
4
-- Get the articles themselves
-- Use this if you want the articles
SELECT * FROM #articles WHERE rowid IN (
SELECT a.rowid FROM #articles a
INNER JOIN #token_article ta ON a.rowid = ta.article_rowid
INNER JOIN #tokens t ON ta.token_rowid = t.rowid
WHERE t.token IN ('ABC', 'DEF')
GROUP BY a.rowid
HAVING COUNT(*) = 2 -- This should match the number of tokens
)
rowid title
----------- ------------------
1 This is article 1.
4 This is article 4.
Here is one way to do it. The script was tested in SQL Server 2012 database.
Script:
CREATE TABLE dbo.tokens
(
rowid INT NOT NULL IDENTITY
, token VARCHAR(10) NOT NULL
);
CREATE TABLE dbo.articles
(
rowid INT NOT NULL IDENTITY
, name VARCHAR(10) NOT NULL
);
CREATE TABLE dbo.token_article
(
token_rowid INT NOT NULL
, article_rowid INT NOT NULL
);
INSERT INTO dbo.tokens (token) VALUES
('ABC'),
('DEF');
INSERT INTO dbo.articles (name) VALUES
('Article 1'),
('Article 2'),
('Article 3');
INSERT INTO dbo.token_article (token_rowid, article_rowid) VALUES
(1, 2),
(2, 3),
(1, 3),
(1, 1),
(2, 2);
SELECT out1.rowid
, out1.token
, out1.token_rowid
, out1.article_rowid
, ta2.token_rowid
, ta2.article_rowid
, t2.rowid
, t2.token
FROM
(
SELECT t.rowid
, t.token
, ta1.token_rowid
, ta1.article_rowid
FROM dbo.tokens t
INNER JOIN dbo.token_article ta1
ON ta1.token_rowid = t.rowid
WHERE t.token = 'ABC'
) out1
INNER JOIN dbo.token_article ta2
ON ta2.article_rowid = out1.article_rowid
INNER JOIN dbo.tokens t2
ON t2.rowid = ta2.token_rowid
AND t2.token = 'DEF';
Output:
rowid token token_rowid article_rowid token_rowid article_rowid rowid token
----- ----- ----------- ------------- ----------- ------------- ----- -----
1 ABC 1 2 2 2 2 DEF
1 ABC 1 3 2 3 2 DEF

Select Records that match ALL groups in a many to many join table

I have 2 tables: sets and groups. Both are joined using a 3rd table set_has_groups.
I would like to get sets that have ALL groups that I specify
One way of doing it would be
SELECT column1, column2 FROM sets WHERE
id IN(SELECT set_id FROM set_has_group WHERE group_id = 1)
AND id IN(SELECT set_id FROM set_has_group WHERE group_id = 2)
AND id IN(SELECT set_id FROM set_has_group WHERE group_id = 3)
obviously this is not the most beautiful solution
I've also tried this:
SELECT column1, column2 FROM sets WHERE
id IN(SELECT set_id FROM set_has_group WHERE group_id IN(1,2,3) GROUP BY group_id
HAVING COUNT(*) = 3
This looks prettier but the problem is that it takes forever to execute.
While the first query runs in like 200ms the 2nd one takes more than 1 minute.
Any idea why that is?
===UPDATE:
I've played with this some more and I modified the 2nd query like this
SELECT columns FROM `set` WHERE id IN(
select set_id FROM
(
SELECT set_id FROM set_has_group
WHERE group_id IN(1,2,3)
GROUP BY set_id HAVING COUNT(*) = 3
) as temp
)
that is really fast
It's the same as the 2nd query before just that I wrap it in another temporary table
Pretty strange
I am suspecting a small mistyping in the second query.
Really, I am not sure. Probably, the second query is executed via full table scan. At the same time the first one "IN" is really transformed into "EXISTS". So, you can try to use "exists". For example:
...
where 3 = (select count(*) from set_has_group
where group_id in (1, 2, 3) and set_id = id
group by set_id)
Assuming SQL Server, here is a working example with a JOIN that should work better than the IN clauses you are using as long as you have your primary and foreign keys set correctly. I have built joined 5 sets to 3 groups, but set 4 and 5 are not a part of group 3 and will not show in the answer. However, this query is not scalable (for ex. find in group 4, 5, 7, 8 and 13 will require code modifications unless you parse input params into a table variable)
set nocount on
declare #sets table
(
Id INT Identity (1, 1),
Column1 VarChar (50),
Column2 VarChar (50)
)
declare #Set_Has_Group table
(
Set_Id Int,
Group_Id Int
)
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
update #sets set column1 = 'Column1 at Row ' + Convert (varchar, id)
update #sets set column2 = 'Column2 at Row ' + Convert (varchar, id)
insert into #Set_Has_Group values (1, 1)
insert into #Set_Has_Group values (1, 2)
insert into #Set_Has_Group values (1, 3)
insert into #Set_Has_Group values (2, 1)
insert into #Set_Has_Group values (2, 2)
insert into #Set_Has_Group values (2, 3)
insert into #Set_Has_Group values (3, 1)
insert into #Set_Has_Group values (3, 2)
insert into #Set_Has_Group values (3, 3)
insert into #Set_Has_Group values (4, 1)
insert into #Set_Has_Group values (4, 2)
insert into #Set_Has_Group values (5, 1)
insert into #Set_Has_Group values (5, 2)
/* your query with IN */
SELECT column1, column2 FROM #sets WHERE
id IN(SELECT set_id FROM #set_has_group WHERE group_id = 1)
AND id IN(SELECT set_id FROM #set_has_group WHERE group_id = 2)
AND id IN(SELECT set_id FROM #set_has_group WHERE group_id = 3)
/* my query with JOIN */
SELECT * -- Column1, Column2
FROM #sets sets
WHERE 3 = (
SELECT Count (1)
FROM #Set_Has_Group Set_Has_Group
WHERE 1=1
AND sets.Id = Set_Has_Group.Set_Id
AND Set_Has_Group.Group_ID IN (1, 2, 3)
Group by Set_Id
)
Here's a solution that uses a non-correlated subquery and no GROUP BY:
SELECT column1, column2
FROM sets
WHERE id IN (
SELECT g1.set_id FROM set_has_group g1
JOIN set_has_group g2 ON (g1.set_id = g3.set_id)
JOIN set_has_group g3 ON (g1.set_id = g3.set_id)
WHERE g1.group_id = 1 AND g2.group_id = 2 AND g3.group_id = 3);