Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One-to-Many relationships in (Postgre)SQL

Tags:

sql

postgresql

I have two tables:

posts:

 id | ... other stuff ... |     tags                         
----+---------------------+--------------
  1 |         ...         | <foo><bar>
  2 |         ...         | <foo><baz><blah>
  3 |         ...         | <bar><blah><goo>

and tags:

     tag         
--------------
 <foo>
 <bar>
 <baz>
 <blah>
 <goo>

posts.tags and tags.tag are both of type text. What I want is a relation from tags.tag to rows in posts such that querying <foo> would give me rows corresponding to posts 1 and 2, querying <blah> gives me 2 and 3, <bar> gives me 1 and 3, etc.

I've looked at foreign keys, but I'm not sure it's what I want. (and honestly, I'm not exactly sure what it does). From what I can tell a foreign key must be equal to a primary key/unique column of a table. But what I want is all rows such that posts.tags ~ '.*<foo>.*', etc. I also want to be able to, say, get all tags that start with b, eg:

CREATE VIEW startswithB AS
SELECT tag
FROM tags
WHERE tag ~ '<b.*>';

SELECT DISTINCT * FROM posts, startswithB WHERE posts.tags ~ ('.*' || startswithB || '.*');

How do I get the relation I'm looking for? Is it possible?

EDIT:

Okay, what I've done:

create post_tags:

SELECT posts.id, tags.tag 
INTO post_tags 
FROM posts, tags 
WHERE posts.tags ~ ('.*' || tags.tag || '.*');

select all posts with tag <foo>:

SELECT *
FROM posts
WHERE posts.id IN (
    SELECT id
    FROM post_tags
    WHERE tag = '<foo>'
);
like image 708
Eric W. Avatar asked Mar 07 '12 18:03

Eric W.


2 Answers

What you actually have going on here is a many-to-many relationship. Think about it: each tag can be on several posts, and each post can have several tags.

The correct relational architecture for this is to add another table in the middle like this:

CREATE TABLE post_tags (
  id INTEGER REFERENCES posts,
  tag VARCHAR REFERENCES tags
);

Then drop the tags column on your posts table.

This solves all your issues, because you can get the set of tags on a post or the set of posts with a given tag by joining against post_tags in different directions. You can also get the list of tags that start with something using a regular LIKE query, which will be more difficult if you have a bunch of strings concatenated in one field.

like image 67
Daniel Lyons Avatar answered Sep 18 '22 17:09

Daniel Lyons


As Daniel mentioned, you have a many-to-many relationship. Just for clarification, here's how all 3 tables would look with a many-to-many setup:

Posts:

    id | ... other stuff ...
    ---+---------------------
    1  | ...
    2  | ...

Tags:

    tag
    ---
    <foo>
    <bar>

Post_Tags mapping table:

    post_id | tag
    --------+------
    1       | <foo>
    1       | <bar>
like image 31
Jason Iwinski Avatar answered Sep 17 '22 17:09

Jason Iwinski