SQL how to search for many-to-many relationships
I have a database with two main tables notes
and labels
. They have many-to-many relationships (similar to how stackoverflow.com has tagged questions). I am wondering how can I search for a note using multiple tags using SQL?
For example, if I have a note "test" with three labels "one", "two" and "three", and I have a second note "test2" with labels "one" and "two", what is the SQL query that find all notes associated with tags one and two?
source to share
select * from notes a
inner join notes_labels mm on (mm.note = a.id and mm.labeltext in ('one', 'two') )
Replace your actual column names of course, hopefully my assumptions about your table were correct.
And there is actually a bit of possible ambiguity in your question thanks to the English language and the way the word "and" is sometimes used. If you mean you want to see, for example, a note labeled "one" but not "two", this should work (interpret your "and" means "show me all notes labeled" one "and / plus all notes tagged "two") However, if you only want two-tagged notes, this is one way:
select * from notes a
where exists (select 1 from notes_labels b where b.note = a.id and b.labeltext = 'one')
and exists (select 1 from notes_labels c where c.note = a.id and c.labeltext = 'two')
Edit: thanks for the suggestions everyone, Monday gears in my brain are a little slower ... looks like I should have wiki'd this!
source to share
Note. I haven't really tested this. It also assumes that you have a many-to-many table named notes_labels, which may or may not exist.
If you just want the notes to have any labels it would be something like this
SELECT DISTINCT n.id, n.text
FROM notes n
INNER JOIN notes_labels nl ON n.id = nl.note_id
INNER JOIN labels l ON nl.label_id = l.id
WHERE l.label IN (?, ?)
If you need notes that have ALL the shortcuts, there is a little extra work to do
SELECT n.id, n.text
FROM notes n
INNER JOIN notes_labels nl ON n.id = nl.note_id
INNER JOIN labels l ON nl.label_id = l.id
WHERE l.label IN (?, ?)
GROUP BY n.id, n.text
HAVING COUNT(*) = 2;
? being a SQL placeholder and 2 is the number of tags you were looking for. This assumes that the reference table has both ID columns as a composite primary key.
source to share
Assuming you have a normalized database, you should have another table between notes
andlabels
Then you should use inner join
to join tables together
- Join table
labels
with binding table (many-to-many table) - Join the table
notes
to the previous query
Example:
select * from ((labels l inner join labels_notes ln on l.labelid = ln.labelid)
inner join notes n on ln.notesid = n.noteid)
This way you have linked both tables together.
Now you need to add a sentence where
... but I'll leave that up to you.
source to share
You are not saying anything about how this many-to-many relationship is implemented. I presume there are Labels (noteid: int, label: varchar) in the labels table - with a primary key spanning both?
SELECT DISTINCT n.id from notes as n, notes_labels as nl WHERE n.id = nl.noteid AND nl.text in (label1, label2);
Replace the column names and insert appropriate labels for the labels.
source to share
If you just want a list, you can use where exists
to avoid duplication. If you have multiple tags against a node in your selection criteria, you end up with duplicate lines as a result. Here's an example where exists
:
create table notes (
NoteID int not null primary key
,NoteText varchar (max)
)
go
create table tags (
TagID int not null primary key
,TagText varchar (100)
)
go
create table note_tag (
NoteID int not null
,TagID int not null
)
go
alter table note_tag
add constraint PK_NoteTag
primary key clustered (TagID, NoteID)
go
insert notes values (1, 'Note A')
insert notes values (2, 'Note B')
insert notes values (3, 'Note C')
insert tags values (1, 'Tag1')
insert tags values (2, 'Tag2')
insert tags values (3, 'Tag3')
insert note_tag values (1, 1) -- Note A, Tag1
insert note_tag values (1, 2) -- Note A, Tag2
insert note_tag values (2, 2) -- Note B, Tag2
insert note_tag values (3, 1) -- Note C, Tag1
insert note_tag values (3, 3) -- Note C, Tag3
go
select n.NoteID
,n.NoteText
from notes n
where exists
(select 1
from note_tag nt
join tags t
on t.TagID = nt.TagID
where n.NoteID = nt.NoteID
and t.TagText in ('Tag1', 'Tag3'))
NoteID NoteText
----------- ----------------
1 Note A
3 Note C
source to share