Test the sequentiality of a column with a single SQL query - sql

I have a table that contains sets of sequential datasets, like that:
ID set_ID some_column n
1 'set-1' 'aaaaaaaaaa' 1
2 'set-1' 'bbbbbbbbbb' 2
3 'set-1' 'cccccccccc' 3
4 'set-2' 'dddddddddd' 1
5 'set-2' 'eeeeeeeeee' 2
6 'set-3' 'ffffffffff' 2
7 'set-3' 'gggggggggg' 1
At the end of a transaction that makes several types of modifications to those rows, I would like to ensure that within a single set, all the values of "n" are still sequential (rollback otherwise). They do not need to be in the same order according to the PK, just sequential, like 1-2-3 or 3-1-2, but not like 1-3-4 or 1-2-3-3-4.
Due to the fact that there might be thousands of rows within a single set I would prefer to do it in the db to avoid the overhead of fetching the data just for verification after making some small changes.
Also there is the issue of concurrency. The way locking in InnoDB (repeatable read) works (as I understand) is that if I have an index on "n" then InnoDB also locks the "gaps" between values. If I combine set_ID and n to a single index, would that eliminate the problem of phantom rows appearing?
Looks to me like a common problem. Any brilliant ideas?
Thanks!
Note: using MySQL + InnoDB

Look for sequences where max - min + 1 > count:
IF EXISTS (SELECT set_ID
FROM mytable
GROUP BY set_ID
HAVING MAX(n) - MIN(n) + 1 > COUNT(n)
)
ROLLBACK
If the sequence must start at 1, do this instead:
IF EXISTS (SELECT set_ID
FROM mytable
GROUP BY set_ID
HAVING MIN(n) = 1 AND MAX(n) > COUNT(n)
)
ROLLBACK
You also need to avoid duplicate sequence numbers. But this can be done by creating a unique key on set_ID and n.

Try this:
IF EXISTS (SELECT set_ID
FROM mytable
GROUP BY set_ID
HAVING MIN(n) = 1 AND MAX(n) <> COUNT(DISTINCT n)
)
ROLLBACK
works on SQL Server (I don't have MySql to try it out):
DECLARE #YourTable table (ID int, set_ID char(5), some_column char(10),n int)
INSERT #YourTable VALUES (1, 'set-1' ,'aaaaaaaaaa' ,1)
INSERT #YourTable VALUES (2, 'set-1' ,'bbbbbbbbbb' ,2)
INSERT #YourTable VALUES (3, 'set-1' ,'cccccccccc' ,3)
INSERT #YourTable VALUES (4, 'set-2' ,'dddddddddd' ,1)
INSERT #YourTable VALUES (5, 'set-2' ,'eeeeeeeeee' ,2)
INSERT #YourTable VALUES (6, 'set-3' ,'ffffffffff' ,2)
INSERT #YourTable VALUES (7, 'set-3' ,'gggggggggg' ,1)
INSERT #YourTable VALUES (8, 'set-3' ,'ffffffffff' ,4)
INSERT #YourTable VALUES (9, 'set-3' ,'ffffffffff' ,4)
--this will list all "bad" sets
SELECT set_ID
FROM #YourTable
GROUP BY set_ID
HAVING MIN(n) = 1 AND MAX(n) <> COUNT(DISTINCT n)
OUTPUT:
set_ID
------
set-3

Related

SQL (PLSQL) number a set of rows

I attended an interview recently and the interviewer asked me number the occurrences of 'A', 'B', 'C' and so on. To put in table and columns - there is a table tab with column as col. The values in col is 'A', 'B', 'C' etc.
create table tab226 (col varchar2(3) );
insert into tab226 VALUES ('A');
insert into tab226 VALUES ('B');
insert into tab226 VALUES ('C');
insert into tab226 VALUES ('B');
insert into tab226 VALUES ('A');
insert into tab226 VALUES ('C');
insert into tab226 VALUES ('C');
insert into tab226 VALUES ('A');
insert into tab226 VALUES ('B');
The expected output is :
Interviewer told me I can use SQL or PLSQL to achieve it. I thought about it for almost 10 mins but couldn't come up with a plan let alone the solution. Does anyone know if this can be achieved in Oracle SQL or PLSQL?
Doesn't make much sense to me, but - would this do?
SQL> select col,
2 count(*) over (partition by col order by rowid) exp_output
3 from tab226
4 order by rowid;
COL EXP_OUTPUT
--- ----------
A 1
B 1
C 1
B 2
A 2
C 2
C 3
A 3
B 3
9 rows selected.
SQL>
As written, you cannot accomplish -- consistently -- what they are asking for. The problem is that SQL tables represent unordered sets. There is no way to run a query on the original data and preserve the ordering.
However, the final column appears to simply be an enumeration, so you can use row_number() for that:
select col, row_number() over (partition by col order by NULL)
from tab226;
But if you have an ordering column -- say id in this example -- then you would do:
select col, row_number() over (partition by col order by NULL)
from tab226;
Here is a db<>fiddle.

Splitting of string by fixed keyword

Hi I currently have a tables with a column that I would like to split.
ID Serial
1 AAA"-A01-AU-234-U_xyz(CY)(REV-002)
2 AAA"-A01-AU-234-U(CY)(REV-1)
3 AAA"-A01-AU-234-U(CY)(REV-101)
4 VVV"-01-AU-234-Z_ww(REV-001)
5 VVV"-01-AU-234-Z(REV-001)_xyz(CY)
6 V-VV"-01-AU-234-Z(REV-03)_xyz(CY)
7 V-VV"-01-AU-234-Z-ZZZ(REV-004)_xyz(CY)
I would like to split this column into 2 field via a select statement
The first field would consist of the text from the start and end when this scenario is satisfied
After the first "-
take all text till the next 3 hypen (-)
Take the first letter after the last hypen(-)
The second field would want to store the Value(Int) inside the (REV) bracket. Rev is always stored inside a compassing bracket (Rev-xxx) the number may stretch from 0-999 and have different form of representation
Example of output
Field 1 Field 2
AAA"-A01-AU-234-U 2
AAA"-A01-AU-234-U 1
AAA"-A01-AU-234-U 101
VVV"-01-AU-234-Z 1
VVV"-01-AU-234-Z 1
V-VV"-01-AU-234-Z 3
V-VV"-01-AU-234-Z 4
Maybe it is possible to make it better and faster, but at least it does work. If i will have some time more i will look at this again to think of better solution, but it do the job.
create table #t
(
id int,
serial nvarchar(255)
)
go
insert into #t values (1, 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)')
insert into #t values (2, 'AAA"-A01-AU-234-U(CY)(REV-1)')
insert into #t values (3, 'AAA"-A01-AU-234-U(CY)(REV-101)')
insert into #t values (4, 'VVV"-01-AU-234-Z_ww(REV-001)')
insert into #t values (5, 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)')
insert into #t values (6, 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)')
insert into #t values (7, 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)')
go
select id, serial,
left(serial,charindex('-', serial, charindex('-', serial, charindex('-', serial, charindex('"',serial) + 2) +1) + 1) + 1) as 'Field2'
,cast( replace(left(right(serial, len(serial) - charindex('REV',serial) +1 ), CHARINDEX(')',right(serial, len(serial) - charindex('REV',serial) +1 )) - 1), 'REV-', '')as int) as 'Field1'
from #t
go
gives me:
id serial Field2 Field1
1 AAA"-A01-AU-234-U_xyz(CY)(REV-002) AAA"-A01-AU-234-U 2
2 AAA"-A01-AU-234-U(CY)(REV-1) AAA"-A01-AU-234-U 1
3 AAA"-A01-AU-234-U(CY)(REV-101) AAA"-A01-AU-234-U 101
4 VVV"-01-AU-234-Z_ww(REV-001) VVV"-01-AU-234-Z 1
5 VVV"-01-AU-234-Z(REV-001)_xyz(CY) VVV"-01-AU-234-Z 1
6 VVV"-01-AU-234-Z(REV-03)_xyz(CY) VVV"-01-AU-234-Z 3
7 VVV"-01-AU-234-Z(REV-004)_xyz(CY) VVV"-01-AU-234-Z 4
I came up with a solution in php using regular expressions.I am trying to convert it into posix standards supported by mysql.Anyways in the meanwhile you can have a look at this and it works perfect.
/The first script select the values for fields 1 namely AAA"-A01-AU-234-U/
<?php
$txt='VVV"-01-AU-234-Z(REV-001)_xyz(CY)';
$re1='((?:[a-z][a-z0-9_]*))';
$re2='.*?';
$re3='(\\d+)';
$re4='.*?';
$re5='((?:[a-z][a-z0-9_]*))';
$re6='.*?';
$re7='(\\d+)';
$re8='.*?';
$re9='([a-z])';
echo $re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9;
if ($c=preg_match_all ("/".$re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9."/is", $txt, $matches))
{
$var1=$matches[1][0];
$int1=$matches[2][0];
$var2=$matches[3][0];
$int2=$matches[4][0];
$w1=$matches[5][0];
print "($var1) ($int1) ($var2) ($int2) ($w1) \n";
}
?>
/*The second script selects values for field 2 namely the last integer*/
<?php
$txt='VVV"-01-AU-234-Z_ww(REV-001)';
$re1='.*?';
$re2='\\d';
$re3='.*?';
$re4='\\d';
$re5='.*?';
$re6='\\d';
$re7='.*?';
$re8='\\d';
$re9='.*?';
$re10='\\d';
$re11='.*?';
$re12='\\d';
$re13='.*?';
$re14='\\d';
$re15='(\\d)';
if ($c=preg_match_all ("/".$re1.$re2.$re3.$re4.$re5.$re6.$re7.$re8.$re9.$re10.$re11.$re12.$re13.$re14.$re15."/is", $txt, $matches))
{
$d1=$matches[1][0];
print "($d1) \n";
}
?>
OUTPUT:
(VVV) (01) (AU) (234) (Z) //script 1
(1) //script 2
You can add database connection to the script and store the results in a new table.You can aslo iterate each row as input to the script and store corresponding results in the table.
Note:
The regular expression used for selecting field 1:
((?:[a-z][a-z0-9_]*)).*?(\d+).*?((?:[a-z][a-z0-9_]*)).*?(\d+).*?([a-z])
The regular expression used for selecting field 2:
.*?\d.*?\d.*?\d.*?\d.*?\d.*?\d.*?\d(\d)
If anybody can convert the above expressions to posix standards then the user can write a simple query like
select t.serial as field 1 from table t
where t.serial regexp 'converted exp' join
(select t1.serial as field 2 from table t1
where t1.serial regexp 'converted exp')q
on q.id=t.id;
I tried to convert it but the matching constraints were lost.You should actually change ?: to ^ and ? to [^>] and //d to [0-9] or digit.Hope it helps.
Try this solution. It uses a combination of charindex and the substring function.
DECLARE #TempTable table
(
id int,
serial nvarchar(255)
)
insert into #TempTable values (1, 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)')
insert into #TempTable values (2, 'AAA"-A01-AU-234-U(CY)(REV-1)')
insert into #TempTable values (3, 'AAA"-A01-AU-234-U(CY)(REV-101)')
insert into #TempTable values (4, 'VVV"-01-AU-234-Z_ww(REV-001)')
insert into #TempTable values (5, 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)')
insert into #TempTable values (6, 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)')
insert into #TempTable values (7, 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)')
select
id,
serial,
substring(serial, 1, P4.Pos+1) as field1,
convert(int, substring(Serial, P6.Pos , P7.Pos - P6.Pos)) as field2
from #TempTable
cross apply (select (charindex('-', Serial))) as P1(Pos)
cross apply (select (charindex('-', Serial, P1.Pos+1))) as P2(Pos)
cross apply (select (charindex('-', Serial, P2.Pos+1))) as P3(Pos)
cross apply (select (charindex('-', Serial, P3.Pos+1))) as P4(Pos)
cross apply (select (charindex('REV-', Serial,P1.Pos+1)+4)) as P6(Pos)
--+4 because 'REV-' is 4 chars long
cross apply (select (charindex(')', Serial,P6.Pos+1))) as P7(Pos);
I have updated my answer. Is this better now?
DECLARE #Table table(ID int, SERIAL nvarchar(100));
INSERT INTO #Table(ID, SERIAL)
VALUES ('1', 'AAA"-A01-AU-234-U_xyz(CY)(REV-002)'),
('2', 'AAA"-A01-AU-234-U(CY)(REV-1)'),
('3', 'AAA"-A01-AU-234-U(CY)(REV-101)'),
('4', 'VVV"-01-AU-234-Z_ww(REV-001)'),
('5', 'VVV"-01-AU-234-Z(REV-001)_xyz(CY)'),
('6', 'VVV"-01-AU-234-Z(REV-03)_xyz(CY)'),
('7', 'VVV"-01-AU-234-Z(REV-004)_xyz(CY)'),
('8', 'AAA"-A01-AU-234-U-1111-(REV-111)'),
('9', 'AAA"-A01-AU-234-U-111111-5555(CY)(REV-101)'),
('10', 'V-VV"-01-AU-234-Z-ZZZ(REV-004)_xyz(CY)')
SELECT
ID,
SERIAL,
LEFT(SERIAL, P5.Pos + 1) AS Field1,
CONVERT(int, SUBSTRING(SERIAL, P6.Pos, CHARINDEX(')', RIGHT(SERIAL, LEN(SERIAL) - P6.Pos)))) AS Field2
FROM #Table
CROSS APPLY (SELECT CHARINDEX('"-', SERIAL)) AS P1(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P1.Pos + 1)) AS P2(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P2.Pos + 1)) AS P3(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P3.Pos + 1)) AS P4(Pos)
CROSS APPLY (SELECT CHARINDEX('-', SERIAL, P4.Pos + 1)) AS P5(Pos)
CROSS APPLY (SELECT CHARINDEX('REV-', SERIAL, P5.Pos + 1) + 4) AS P6(Pos)

Order guarantee for identity assignment in multi-row insert in SQL Server

When using a Table Value Constructor (http://msdn.microsoft.com/en-us/library/dd776382(v=sql.100).aspx) to insert multiple rows, is the order of any identity column populated guaranteed to match the rows in the TVC?
E.g.
CREATE TABLE A (a int identity(1, 1), b int)
INSERT INTO A(b) VALUES (1), (2)
Are the values of a guaranteed by the engine to be assigned in the same order as b, i.e. in this case so they match a=1, b=1 and a=2, b=2.
Piggybacking on my comment above, and knowing that the behavior of an insert / select+order by will guarantee generation of identity order (#4: from this blog)
You can use the table value constructor in the following fashion to accomplish your goal (not sure if this satisfies your other constraints) assuming you wanted your identity generation to be based on category id.
insert into thetable(CategoryId, CategoryName)
select *
from
(values
(101, 'Bikes'),
(103, 'Clothes'),
(102, 'Accessories')
) AS Category(CategoryID, CategoryName)
order by CategoryId
It depends as long as your inserting the records in one shot . For example after inserting if you delete the record where a=2 and then again re insert the value b=2 ,then identity column's value will be the max(a)+1
To demonstrate
DECLARE #Sample TABLE
(a int identity(1, 1), b int)
Insert into #Sample values (1),(2)
a b
1 1
2 2
Delete from #Sample where a=2
Insert into #Sample values (2)
Select * from #Sample
a b
1 1
3 2

Select records with order of IN clause

I have
SELECT * FROM Table1 WHERE Col1 IN(4,2,6)
I want to select and return the records with the specified order which i indicate in the IN clause
(first display record with Col1=4, Col1=2, ...)
I can use
SELECT * FROM Table1 WHERE Col1 = 4
UNION ALL
SELECT * FROM Table1 WHERE Col1 = 6 , .....
but I don't want to use that, cause I want to use it as a stored procedure and not auto generated.
I know it's a bit late but the best way would be
SELECT *
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')
Or
SELECT CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')s_order,
*
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY s_order
You have a couple of options. Simplest may be to put the IN parameters (they are parameters, right) in a separate table in the order you receive them, and ORDER BY that table.
The solution is along this line:
SELECT * FROM Table1
WHERE Col1 IN(4,2,6)
ORDER BY
CASE Col1
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 6 THEN 3
END
select top 0 0 'in', 0 'order' into #i
insert into #i values(4,1)
insert into #i values(2,2)
insert into #i values(6,3)
select t.* from Table1 t inner join #i i on t.[in]=t.[col1] order by i.[order]
Replace the IN values with a table, including a column for sort order to used in the query (and be sure to expose the sort order to the calling application):
WITH OtherTable (Col1, sort_seq)
AS
(
SELECT Col1, sort_seq
FROM (
VALUES (4, 1),
(2, 2),
(6, 3)
) AS OtherTable (Col1, sort_seq)
)
SELECT T1.Col1, O1.sort_seq
FROM Table1 AS T1
INNER JOIN OtherTable AS O1
ON T1.Col1 = O1.Col1
ORDER
BY sort_seq;
In your stored proc, rather than a CTE, split the values into table (a scratch base table, temp table, function that returns a table, etc) with the sort column populated as appropriate.
I have found another solution. It's similar to the answer from onedaywhen, but it's a little shorter.
SELECT sort.n, Table1.Col1
FROM (VALUES (4), (2), (6)) AS sort(n)
JOIN Table1
ON Table1.Col1 = sort.n
I am thinking about this problem two different ways because I can't decide if this is a programming problem or a data architecture problem. Check out the code below incorporating "famous" TV animals. Let's say that we are tracking dolphins, horses, bears, dogs and orangutans. We want to return only the horses, bears, and dogs in our query and we want bears to sort ahead of horses to sort ahead of dogs. I have a personal preference to look at this as an architecture problem, but can wrap my head around looking at it as a programming problem. Let me know if you have questions.
CREATE TABLE #AnimalType (
AnimalTypeId INT NOT NULL PRIMARY KEY
, AnimalType VARCHAR(50) NOT NULL
, SortOrder INT NOT NULL)
INSERT INTO #AnimalType VALUES (1,'Dolphin',5)
INSERT INTO #AnimalType VALUES (2,'Horse',2)
INSERT INTO #AnimalType VALUES (3,'Bear',1)
INSERT INTO #AnimalType VALUES (4,'Dog',4)
INSERT INTO #AnimalType VALUES (5,'Orangutan',3)
CREATE TABLE #Actor (
ActorId INT NOT NULL PRIMARY KEY
, ActorName VARCHAR(50) NOT NULL
, AnimalTypeId INT NOT NULL)
INSERT INTO #Actor VALUES (1,'Benji',4)
INSERT INTO #Actor VALUES (2,'Lassie',4)
INSERT INTO #Actor VALUES (3,'Rin Tin Tin',4)
INSERT INTO #Actor VALUES (4,'Gentle Ben',3)
INSERT INTO #Actor VALUES (5,'Trigger',2)
INSERT INTO #Actor VALUES (6,'Flipper',1)
INSERT INTO #Actor VALUES (7,'CJ',5)
INSERT INTO #Actor VALUES (8,'Mr. Ed',2)
INSERT INTO #Actor VALUES (9,'Tiger',4)
/* If you believe this is a programming problem then this code works */
SELECT *
FROM #Actor a
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY case when a.AnimalTypeId = 3 then 1
when a.AnimalTypeId = 2 then 2
when a.AnimalTypeId = 4 then 3 end
/* If you believe that this is a data architecture problem then this code works */
SELECT *
FROM #Actor a
JOIN #AnimalType at ON a.AnimalTypeId = at.AnimalTypeId
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY at.SortOrder
DROP TABLE #Actor
DROP TABLE #AnimalType
ORDER BY CHARINDEX(','+convert(varchar,status)+',' ,
',rejected,active,submitted,approved,')
Just put a comma before and after a string in which you are finding the substring index or you can say that second parameter.
And first parameter of CHARINDEX is also surrounded by , (comma).

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);