Question

I am writing yet another blog engine for practice, using SQLite and Perl Dancer framework.

The tables go like this:

CREATE TABLE posts (
    p_id INTEGER PRIMARY KEY,
    p_url VARCHAR(255),
    p_title VARCHAR(255),
    p_text TEXT,
    p_date DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE tags (
    t_id INTEGER PRIMARY KEY,
    t_tag VARCHAR(255),
    t_url VARCHAR(255)
);

CREATE TABLE tags_posts_junction (
    tp_tag INTEGER NOT NULL,
    tp_post INTEGER NOT NULL,
    FOREIGN KEY(tp_tag) REFERENCES tags.t_id,
    FOREIGN KEY(tp_post) REFERENCES tags.p_id
);

All the big guys like Wordpress (or stackoverflow) can show tags right on the main page, after each question, and I'd like to implement it too. The question is how do I do it.

So far the posts are stored in the database, and when I need to render a page showing latest 20 posts I pass a hash refernece (fetchall_hashref from DBI) to the template. So how do I add tags there? Of course I can do something like

my $dbh = database->prepare('SELECT * FROM posts ORDER BY p_date DESC 
                    LIMIT 20 OFFSET 0');
$dbh->execute;
my $posts = $dbh->fetchall_hashref('p_date');
foreach my $key (keys $post) {
    my $dbh = database->prepare('SELECT * FROM tags WHERE t_id IN (
                    SELECT tp_tag FROM tags_posts_junction WHERE tp_post = ?)');
    $dbh->execute($post->{"$key"}->{"p_id"});
    my $tags = $dbh->fetchall_hashref(t_id);
    $post->{"$key"}->{"$tag_hash"} = $tags;
};

But that's ugly and that's 20 more queries per page, isn't it too much? I think there should be a better way.

So the question is how do I get tags for 20 posts the least redundant way?

Was it helpful?

Solution

Collect all the p_ids into an array and construct your query using IN instead of =, something like this, presuming @pids is your array:

my $dbh = database->prepare('SELECT * FROM tags WHERE t_id IN (
                    SELECT tp_tag FROM tags_posts_junction WHERE tp_post IN (' .
                    join(', ', ('?')x@pids).') )');
$dbh->execute(@pids);

Though you should really look to JOINs to replace your sub-queries.

OTHER TIPS

I think you could combine your first / outer query before

my $posts = $dbh->fetchall_hashref('p_date');

with your inner query and then you will be hitting the database once instead of 20 times.

You could also simplify your code by use of DBIx::Simple - https://metacpan.org/module/DBIx::Simple.

Putting this together would give something like:

my $sql =   'SELECT t.*, p.*
             FROM tags t 
             JOIN tags_posts_junction tpj ON t.t_tag = tpj.t_tag
             JOIN posts p ON p.p_id = tpj.tp_post
             WHERE tpj.tp_post IN (
                SELECT p_id FROM posts ORDER BY p_date DESC 
                LIMIT 20 OFFSET 0
              )';
my $db = DBIx::Simple->connect($dbh);
my $posts = $db->query($sql)->hashes;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top