This is a solution that I've seen a few people have trouble with now, so I want to make more people aware of it:
Say you have some items that can have any of a set of associated values. These could be a list flags, it could be keywords or tags. This is often seen as the anti-pattern known as "Comma Separated List of Doom" (CSLoD). Naive developers will often create something like the following:
create table cslod ( id serial, vals varchar(255) );
insert into cslod values (NULL, "cyan,yellow" );
select * from cslod;
+----+-------------+
| id | vals |
+----+-------------+
| 1 | cyan,yellow |
+----+-------------+
I know that most people reading this already understand this is bad but let me re-iterate:
Please please please don't do this! It will be very very difficult to write meaningful queries later.
The natural solution is to create separate rows for each value in the list:
create table normalset (id int unsigned not null default 0, val varchar(12) not null default '', primary key (id, val) );
insert into normalset VALUES (1, "cyan"), (1, "yellow" );
select * from normalset;
+----+--------+
| id | val |
+----+--------+
| 1 | cyan |
| 1 | yellow |
+----+--------+
Ok, to be truly normalised, val should be an id with a foreign key to another unique list of values, but it's easier to illustrate with this not-quite-perfect structure.
Now, using the improved structure, how would we write a query that retrieves all ids that have *both* cyan and yellow as values?
insert into normalset VALUES (2, "cyan"), (2, "black" ), (3, "magenta"), (4,"cyan"), (4, "magenta"), (4, "yellow");
select * from normalset;
+----+---------+
| id | val |
+----+---------+
| 1 | cyan |
| 1 | yellow |
| 2 | cyan |
| 2 | black |
| 3 | magenta |
| 4 | cyan |
| 4 | magenta |
| 4 | yellow |
+----+---------+
We want to return 1 and 4:
SELECT id FROM normalset GROUP BY id HAVING SUM(val='cyan') AND SUM(val='yellow');
+----+
| id |
+----+
| 1 |
| 4 |
+----+
Simple! The trick is to use GROUP BY, and then you can write conditions based on aggregates and filter them in the HAVING clause. There are a few other ways to obtain the same result:
SELECT id, GROUP_CONCAT(val order by val) as theset, count(*) FROM normalset GROUP BY id HAVING theset like '%cyan%yellow%'
SELECT id, BIT_OR(CASE val WHEN 'cyan' THEN 1 WHEN 'yellow' THEN 2 ELSE 0 END) as bitvals, count(*) FROM normalset GROUP BY id HAVING bitvals=3
The first form can be easily used to find other conditions, such as all rows that have cyan as a value but NOT yellow:
SELECT id FROM normalset GROUP BY id HAVING SUM(val='cyan') AND NOT SUM(val='yellow');
+----+
| id |
+----+
| 2 |
+----+
I have also been comparing these methods to using the SET datatype, and plain ol' bitmasks. I'll add a performance comparison of various methods in another post.