Reputation: 103
I'm developing a small PHP app from scratch to practice/learn. The goal is to be able to create articles. Each article can have tags and images, stored in their own tables. Since an article can have many tags and a single tag can be present in many articles, I also created a 'tag/article' association table.
Now, when I want to edit or display an article, I get its data (plus tags and images) by doing several DB requests... for example, given an article id, I do something like:
$article = getArticle($articleId);
$tags = getArticleTags($articleId);
$images = getArticleImages($articleId);
It works, but I'm not sure if it's a clean approach and good performance-wise. So I was wondering, can this be done by using a single query? I tried but got a bit confused with UNIONs and JOINs. If not, is there any better/recommended approach to achieve this?
The (simplified) tables I'm using are:
articles table:
┌-----┬------------------┬------------------------┐
│ id │ title │ dateCreated │
├-----┼------------------┼------------------------┤
│ 1 │ some article │ 2015-03-17 12:38:49 │
├-----┼------------------┼------------------------┤
│ 2 │ article two │ 2015-03-17 15:00:24 │
└-----┴------------------┴------------------------┘
tags table:
┌-----┬------------------┬------------------------┐
│ id │ name │ dateCreated │
├-----┼------------------┼------------------------┤
│ 1 │ php │ 2015-03-17 15:01:05 │
├-----┼------------------┼------------------------┤
│ 2 │ jquery │ 2015-03-17 15:24:12 │
└-----┴------------------┴------------------------┘
tag-article association table:
┌-----┬--------┬-------------┬------------------------┐
│ id │ tagId │ articleId │ dateCreated │
├-----┼--------┼-------------┼------------------------┤
│ 1 │ 1 │ 1 │ 2015-03-17 15:01:06 │
├-----┼--------┼-------------┼------------------------┤
│ 2 │ 2 │ 1 │ 2015-03-17 15:24:58 │
├-----┼--------┼-------------┼------------------------┤
│ 3 │ 1 │ 2 │ 2015-03-17 15:30:38 │
└-----┴--------┴-------------┴------------------------┘
article-images table:
┌-----┬-----------┬-------------┬------------------------┐
│ id │ fileName │ articleId │ dateCreated │
├-----┼-----------┼-------------┼------------------------┤
│ 1 │ pic1.jpg │ 1 │ 2015-03-17 16:05:26 │
├-----┼-----------┼-------------┼------------------------┤
│ 2 │ pic2.jpg │ 1 │ 2015-03-17 16:06:29 │
├-----┼-----------┼-------------┼------------------------┤
│ 3 │ dog.jpg │ 2 │ 2015-03-17 17:00:14 │
├-----┼-----------┼-------------┼------------------------┤
│ 4 │ cat.jpg │ 2 │ 2015-03-17 17:01:06 │
├-----┼-----------┼-------------┼------------------------┤
│ 5 │ bird.jpg │ 2 │ 2015-03-17 17:02:34 │
└-----┴-----------┴-------------┴------------------------┘
Also, I'm using PDO this way, for example, to get an article:
public function getArticleById($id){
$sql = "SELECT * FROM articles WHERE id=:id LIMIT 1";
$query = $this->db->prepare($sql);
$parameters = array(':id' => $id);
$result = $query->execute($parameters);
return ($result) ? $query->fetch() : 0;
}
Any input will be greatly appreciated. Thanks!
@Adrian's answer is close to what I'm looking for and partially solves the question. It returns the following
The thing is that the id columns are ambiguous and I can't figure out how to end up with a clean array like this, ideally:
Array
(
[title] => some article
[id] => 1
[tags] => Array
(
[0] => Array
(
[id] => 1
[name] => php
)
[1] => Array
(
[id] => 2
[name] => jquery
)
)
[images] => Array
(
[0] => Array
(
[id] => 1
[fileName] => pic1.jpg
)
[1] => Array
(
[id] => 2
[fileName] => pic2.jpg
)
)
)
Then, combining both Daniel's and Adrian's answers, I ended up with this query, which returns something more readable that (I guess) I could process to achieve the above mentioned array (I imagine that iterating and removing duplicates):
SELECT
a.id,
a.title,
a.dateCreated as articleDate,
i.id as imageId,
i.fileName as imageFile,
t.id as tagId,
t.name as tagName
FROM
articles a
JOIN
article_tags ta ON a.id = ta.articleId
JOIN
tags t ON ta.tagId = t.id
JOIN
images i ON a.id = i.articleId
WHERE
a.id = 1
This returns:
performance-wise, since this possible solution returns many rows with duplicated data (depending on how many tags and images are associated with an article). Is this better than doing the initial multiple calls I started with?
Upvotes: 0
Views: 227
Reputation: 103
I've found a way to achieve what I wanted, by combining the answers posted by @Adrian and @Daniel who pointed me in the right direction (thanks!) and adding the use of CONCAT, GROUP_CONCAT and CAST (to mix int and varchar).
I upvoted both answers, which solved the problem partially, but I thought I should post my own answer since (I think) is a cleaner solution, at least for what I wanted, and it may help someone else.
Here's the SQL
SELECT
a.id,
a.title,
GROUP_CONCAT( DISTINCT CAST( t.id AS CHAR( 50 ) ) , ':', t.name ORDER BY t.id SEPARATOR ',' ) AS 'tags',
GROUP_CONCAT( DISTINCT CAST( i.id AS CHAR( 50 ) ) , ':', i.fileName ORDER BY i.id SEPARATOR ',' ) AS 'images'
FROM
articles a
JOIN
article_tags ta ON a.id = ta.articleId
JOIN
images i ON a.id = i.articleId
JOIN
tags t ON ta.tagId = t.id
WHERE
a.id = :id
This query returns the following, with id = 1:
A single row containing the article's data, its tags and images in a id:value format, which I think it's very easy to parse in PHP and can even be used to return a json string by just adding another concat.
Upvotes: 1
Reputation: 7791
Try this code:
EDIT:
$sql = "SELECT
articles.id,
articles.title,
articles.dateCreated as articleDate,
article-images.id as imageId,
article-images.fileName as imageFile,
tags.id as tagId,
tags.name as tagName
FROM articles
JOIN tag-article ON articles.id = tag-article.articleId
JOIN tags ON tag-article.tagId = tags.id
JOIN article-images ON article-images.articleId = articles.id
WHERE articles.id=:id"
$query = $this->db->prepare($sql);
$parameters = array(':id' => $id);
$result = $query->execute($parameters);
return ($result) ? $query->fetch() : 0;
?>
Upvotes: 1
Reputation: 62
SELECT
a.id,
a.title,
a.dateCreated as articleDate,
i.fileName as imageFile,
t.name as tagName
FROM
articles a
JOIN
images i ON a.id = i.articleId
JOIN
tag_article ta ON a.id = ta.articleId
JOIN
tags t ON ta.articleId = t.id
WHERE
a.id = ?
Upvotes: 1