How to associate Article with multiple categories - php

New to PHP/MySQL and building a CMS. I need to know how to associated an Article with multiple Categories eg "My Article 1" has to be in 3 categories and "My Article 2" has to be in only 2 etc.
It's basically just the table set up to begin with. Here's what I have:
Table = articles
Fields = id, publicationDate, title, summary, content
Table = categories
Fields = id, categoryTitle
I know a little about joins and relationships etc but I want to get this right from the start.

You need a many-to-many table that will link article ids to category ids
CREATE TABLE articles_categories (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
articles_id INT,
category_id INT
) TYPE=myisam;
So for each category that you associate with an article you will need to insert a row into this table.
Perhaps something like:
$currentArticle = array('id' => 99, 'name' => 'Test Article');
$currentCategory = array('id' => 1, 'name' => 'First Category');
mysql_query(sprintf('INSERT INTO articles_categories (articles_id, category_id) VALUES (%d, %d)', $currentArticle['id'], $currentCategory['id']));

You need a third table, called articles_to_categories or whatever, with two fields:
article_id
category_id
Add an entry for each article and category association. Set the PK to be both columns.

Related

How to connect two MySQL tables

I have in my MySqli Database a table called "products".
Products TABLE
product_id | INT primary KEY
product_name | VARCHAR(50)
product_price | float
From PHP i enter rows in the table products like this way:
mysqli_query($con,"INSERT INTO products (product_id,product_name,product_price) VALUES
('$product_id','$product_name','$price')");
So far all work perfectly. Now i want to have a second table called "category", this table will include all the possible categories that a product can have
The Category table must have a category_id and a category_name as columns
Category TABLE
category_id | INT primary KEY
category_name | VARCHAR(50)
I'm trying to figured out a way to connect a product with the category in my PHP file
for example:
$get=mysqli_query($con, "SELECT * FROM `category`");
while ($row = mysqli_fetch_assoc($get)) {
echo $row['category_name']; //...here show all the categories
//...
//.. pick the category that the product belong
$category_Selected= .....;
}?>
..... And make the connection (with INSERT? or something) between product and category
Ι want to be able when i'm doing a search at the product table, (for a product X) to show also and the category that it belongs (so far i can show the Product_id, product_name and product_price)
Thank you
You want to join your Tables.
Take a look here:
Join Tables - w3schools
MySQL Join Tables Syntax
If a product can be only in one category then you can add a category_id in your Products table.
I would suggest a third table the:
Product_category
id | PK
product_id | Foreign key to Products.id
category_id| Foreign key to Categories.id
Now every time you insert a product you need to get also the id of your category and do an insert statement to Product_category table.
To retrieve your data you could do something like this:
$get=mysqli_query($con, "SELECT * FROM `category`");
while ($row = mysqli_fetch_assoc($get)) {
echo $row['category_name']; //...here show all the categories
$products=mysqli_query($con, "SELECT * FROM `Products` WHERE id IN
(SELECT product_id from Product_category WHERE category_id= ".(int)$row['category_id'] . ")");
while ($product = mysqli_fetch_assoc($products)) {
echo $product["product_name"] . ", " . $product["product_price"];
}
}
The above statement is as example, you could use JOIN and prepared statements.
If you choose to alter the product table and add the category_id there, then
the example code would be this:
$get=mysqli_query($con, "SELECT * FROM `category`");
while ($row = mysqli_fetch_assoc($get)) {
echo $row['category_name']; //...here show all the categories
$products=mysqli_query($con, "SELECT * FROM `Products` WHERE category_id = " . (int) $row["category_id"]);
while ($product = mysqli_fetch_assoc($products)) {
echo $product["product_name"] . ", " . $product["product_price"];
}
}
As it is, your database does not allow you to represent the relationshup between categories and products. You would need to alter your design.
I can imagine that one product belongs to a category, and that one category can have several products.
If so, I would recommend creating a categories table to store the categories, with (at least) columns category_id and category_name.
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100)
);
In the product table, you want to add a column in the products table that stores a reference to the id of the corresponding category :
ALTER TABLE products ADD
COLUMN category_id INT
FOREIGN KEY (category_fk) REFERENCES categories(id) ON DELETE CASCADE;
With this modified database design, when you insert into products, you pass the reference of the category (one could expect that the user of your application will select it from some kind of drop down list when creating the product) :
INSERT INTO products (product_id, product_name, product_price, category_id)
VALUES ( :product_id, :product_name, :price, :category_id );
And when you want to display a product along with its category name, you can use a simple JOIN :
SELECT p.*, c.category_name
FROM products p
INNER JOIN categories c ON c.category_id = p.category_id
WHERE p.produt_id = :product_id
PS : never pass POSTed values to your SQL queries like this : this exposes you to SQL injection (and also makes your queries less readable and efficient). I changed the queries to use named parameters.

Insert data in 3rd table with the values inserted in 2 other table

I have 3 table in postgres database. Created with this code:
CREATE TABLE AUTHOR(
ID SERIAL PRIMARY KEY,
NAME TEXT
);
CREATE TABLE BOOK(
ID SERIAL PRIMARY KEY,
NAME TEXT
);
CREATE TABLE BOOK_AUTHOR(
BOOK_ID INTEGER REFERENCES BOOK(ID),
AUTHOR_ID INTEGER REFERENCES AUTHOR(ID)
);
A book can have multiple author.
I want to insert multiple author in AUTHOR table.
A book in BOOK table.
And pair in BOOK_AUTHOR table.
For example: If BOOK X is written by Mr. A and Mr. B
I want the table content be like this
AUTHOR
ID-NAME
1, Mr. A
2, Mr. B
BOOK
ID-NAME
1, X
BOOK_AUTHOR
BOOK_ID-AUTHOR_ID
1,1
1,2
I am using postgres-php.
I know I can insert data in author table. Insert data in book table. Make query over them to get the ids.
Then insert in book_author table.
But is there any way to insert those data more efficiently?
What is the possible best way?
PostgreSQL has a very handy 'RETURNING' function you can use here like this:
WITH authors AS (
INSERT INTO
author (name)
VALUES
('Mr. A'), ('Mr. B')
RETURNING
id
), books AS (
INSERT INTO
book (name)
VALUES
('X')
RETURNING
id
)
INSERT INTO
book_author
SELECT
b.id
, a.id
FROM
books b
, authors a;
Just make a Cartesian product of the output and use it as input for the third insert.

Single query SELECT propagate to childs

i have this table structure:
id
name
email
child_id <- pointing to id from "child" table
how SELECT this table data and propagate select to all childs with a SINGLE QUERY?
If you assume you're working with a child table with its own name and id field, you could write...
select c.name as child_name, p.name as parent_name
from child c
join parent p
on c.id = p.child_id
I made a SQLFiddle with the following SQL:
CREATE TABLE "parent" (
"id" INTEGER PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"child_id" INTEGER
);
CREATE TABLE "child" (
"id" INTEGER PRIMARY KEY,
"name" TEXT
);
INSERT INTO parent
(id, name, email, child_id )
VALUES
(1, "Jim", "pineapple#fruit.com", 1 ),
(2, "Jane", "gizmo#gadget.com", 1 ),
(3, "Grover", "monty#python.com", 2 );
INSERT INTO child
(id, name)
VALUES
(1, "Ben" ),
(2, "Samantha" ),
(3, "Carl" );
My select query then gave me:
However, this data structure limits you to only on child per parent though I showed 2 parents for one of the kids. If you want multiple of either without having to duplicate the parent entries, you'd want to have a third table for the parent child relationship. That Table would have a parent_id, a child_id, and there are different schools of thought on whether it having it's own id would be required (it makes selecting specific rows in the relationship table easy and becomes more important if the relationship gains additional fields itself). Your query would need to join all the tables to the relationship table and if you did this, that part might look something like this:
from relationship r
join parent p
on p.relation_id = r.parent_id
join child c
on c.relation_id = r.child_id

How to model a many to many relationship?

With only a bit of previous experience with databases and no formal education with them, I'm a bit stuck as to how to model this (and retrieve the data I require from it in PHP). This is what I'm trying to model:
For each item on my site, it is allowed to have multiple tags such as file, upload, php, recursive etc. However the tags are reusable, I can have two different items and they each could have the php tag.
I've been trying to read up on how to do this and whether it is lack of experience with it or something else I don't know, but I can't seem to grasp the concept. Apparently you need a middle table which links the two together?
Also once I have this relationship and the tables defined, how would I do things such as:
- Retrieve all items with a certain tag?
- Retrieve all tags that one item has?
Thanks for your help, also if anyone could list any further reading on this to strengthen my understanding of the concept that would be great.
The db part is easy. This is just a sample so you can see how db can look like, not any particular SQL engine queries.
CREATE TABLE posts (
id INT PRIMARY KEY,
subject VARCHAR(100),
body TEXT
)
CREATE TABLE tags (
id INT PRIMARY KEY,
name VARCHAR(50)
)
CREATE TABLE post_tags (
post_id INT,
tag_id INT,
FOREIGN KEY (post_id) REFERENCES posts (id),
FOREIGN KEY (tag_id) REFERENCES posts (id)
)
To get items with yourTag tag you will just run query like this
SELECT P.*
FROM posts P
LEFT JOIN post_tags PT ON (PT.post_id = P.id)
LEFT JOIN tags T ON (T.id = PT.tag_id)
WHERE T.name = 'yourTag';
To get tags associated with post with id of 123 you run this query:
SELECT T.*
FROM tags T
LEFT JOIN post_tags PT ON (T.id = PT.tag_id)
LEFT JOIN posts P ON (PT.post_id = P.id)
WHERE P.id = 123;
For the PHP part you could use a framework. Many (if not all) frameworks can easily model such relationships. For example in CakePHP this done like that:
class Post extends AppModel {
$useTable = 'posts';
$hasAndBelongsToMany = array(
'Tag' => array(
'className' => 'Tag'
'joinTable' => 'post_tags'
'foreignKey' => 'post_id'
'associationForeignKey' => 'tag_id'
)
);
}
class Tag extends AppModel {
$useTable = 'tags';
$hasAndBelongsToMany = array(
'Post' => array(
'className' => 'Post'
'joinTable' => 'post_tags'
'foreignKey' => 'tag_id'
'associationForeignKey' => 'post_id'
)
);
}
You should use an intermediate table, to relate the two entities:
-------- 1:n ------------ ---------
| ITEM |-¦---------<| ITEM_TAG | n:1 | TAG |
| Id | | ItemId |>-------¦-| Id |
| Name | | TagId | | Name |
¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯¯¯¯ ¯¯¯¯¯¯¯¯¯
Then for querying the data, you should join your tables in a select statement:
All the items in the tag "FooTag"
SELECT item.* FROM item
JOIN item_tag on item.id = item_tag.itemId
JOIN tag on item_tag.tagId = tag.id
WHERE tag.Name = 'FooTag'
All the tags for the item with name "FooItem"
SELECT tag.* FROM tag
JOIN item_tag on tag.id = item_tag.tagId
JOIN item on item_tag.itemId = item.id
WHERE item.Name = 'FooItem'
You're right, many-to-many relationships are implemented using additional table, for instance:
Blog_entry(entry_id, entry_body)
Tag(tag_id, tag_name)
Entry_tag(entry_id, tag_id)
Any operations are being done using multiple joins. For instance, if you want to select all entries with tag 'foo', using tables from my example, you have to execute:
select *
from
blog_entry, tag, entry_tag
where
tag.tag_name = 'foo' and
entry_tag.tag_id = tag.tag_id and
entry_tag.entry_id = blog_entry.entry_id
(update) To retrieve all tags that certain entry (here with ID 123) has:
select tag_name
from
blog_entry, tag, entry_tag
where
Blog_entry.entry_id = 123
entry_tag.tag_id = tag.tag_id and
entry_tag.entry_id = blog_entry.entry_id
Yeah, many-to-many relationship needs additional, third table, called association table.
Database part is not that hard, it's harder to use it in code with all those left joins and it can get pretty messy :)
My advice is to use ORM framework, like Doctrine or Propel (though I prefer Doctrine), which handle even some complex queries for you.

SQL Query to pull records from 2 tables related to each other - Table 2 depending upon Table 1

I have 2 tables. Table 1 is 'articles' and Table 2 is 'article_categories'. When a user creates an article, it is stored into 'articles'. The user, while creating the article can select various categories under which this article can appear. Currently, an article can be selected to belong to anywhere from 10-25 categories(may be increased in future). These Categories under which the article is filed, are stored in 'article_categories'. So this means a single article ID can have multiple related values in table 'article_categories'. When retrieving all the values from both the tables, I would need the all the values from 'article_categories' to be pulled and the values to be stored in an array.
My question is about what SQL query to use in order to do so? Should I use Inner Join, Left Join, Outer Join...? What would be the best way to do that? I did try some of those joins in phpmyadmin and they give me repeating values of the same article, when in fact, the article should be fetched only once and all the related categories to be fetched. I wanted to do this all in the same query without having to split the query into 2 different in order to accomplish this. I am attaching my table structures so that it's easy for you:
CREATE TABLE IF NOT EXISTS `articles` (
`article_id` int(11) unsigned NOT NULL auto_increment,
`creator_id` int(11) unsigned NOT NULL,
`article_title` varchar(150) NOT NULL,
`article_status` varchar(10) NOT NULL,
PRIMARY KEY (`article_id`),
KEY `buyer_id` (`creator_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
--
-- Dumping data for table `articles`
--
INSERT INTO `articles` (`article_id`, `creator_id`, `article_title`, `article_status`) VALUES
(1, 1, 'My article 1', 'Pending');
CREATE TABLE IF NOT EXISTS `article_categories` (
`article_id` int(11) unsigned NOT NULL,
`category_id` smallint(3) unsigned NOT NULL,
PRIMARY KEY (`article_id`,`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `article_categories`
--
INSERT INTO `article_categories` (`article_id`, `category_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(1, 36),
(1, 71);
Also please note that I have a composite key on the article_id and category_id keys in article_categories table. A sample query that I used is below:
SELECT *
FROM articles, article_categories
WHERE articles.article_id = article_categories.article_id
AND articles.article_id = 1;
This results in:
article_id creator_id article_title article_status article_id category_id
1 1 My article 1 Pending 1 1
1 1 My article 1 Pending 1 2
1 1 My article 1 Pending 1 3
1 1 My article 1 Pending 1 4
1 1 My article 1 Pending 1 5
1 1 My article 1 Pending 1 36
1 1 My article 1 Pending 1 71
As can be seen, the value from the articles table is repeating and it's also able to get all the categories(it's the last column, in case the formatting is messed up). I wanted to get the values from the articles table only once and get the category_id in a loop, so that I can add those looped values in an array and carry on my processing. This is what I intend to do after fetching the values from above:
<?php
//i wanted to check if the article_id exists before i pull the related categories.
//If I do it this way and output using mysql_num_rows, it gives me value 7,
//when in fact, the there's only 1 article with such Id. This means it also counts
// the number of categories. Is there a way to uniquely identify only the number of
// articles (just to see if it exists or not, in the place)
$result = mysql_query("SELECT *
FROM articles, article_categories
WHERE articles.article_id = article_categories.article_id
AND articles.article_id = 1");
while ( $rows = mysql_fetch_array($result) )
{ //i don't think this the following 2 assignments should be done in the loop
$article_id = $rows['article_id'];
$article_title = $rows['article_title'];
//(loop all the category_id from the 2nd table and add to the array below)
$categories_id[] .= ??????? --> How do i do this?
}
?>
Obviously, I cannot do a LIMIT 1 on the above as that will limit my ability to retrieve all the category IDs.
So my question would be how do I get all the category_id from the 2nd table (in a loop) and add them to the array and at the same time, make sure that the values from table 1 are fetched only once (I do realize that the values fetched from the table 1 are the same but does not make sense to loop on them). To achieve this, I would like to get your input on what kind of Join I should use to execute the query with maximum efficiency and use the minimum resources, all in a single query to minimize hits on the DB. I hope that make sense.
Thanks in advance.
EDIT:
SELECT articles.article_id, articles.article_title, GROUP_CONCAT(article_categories.category_id SEPARATOR ',') as category_id
FROM articles
LEFT JOIN article_categories ON (articles.article_id = article_categories.article_id)
-- WHERE CLAUSE IF YOU WANT/NEED --
GROUP BY articles.article_id;
EDIT:
Added column alias for group concat GROUP_CONCAT(article_categories.category_id SEPARATOR ',') as category_id
<?php
$result = mysql_query("SELECT articles.article_id, articles.article_title, GROUP_CONCAT(article_categories.category_id SEPARATOR ',') as category_id
FROM articles, article_categories
WHERE articles.article_id = 1
GROUP BY articles.article_id;");
while ( $rows = mysql_fetch_array($result) )
{
$article_id = $rows['article_id'];
$article_title = $rows['article_title'];
//(loop all the category_id from the 2nd table and add to the array below)
$categories_id = explode(',', $rows['category_id']);
}
?>
Beware of group concat though as it does have limit:
The result is truncated to the maximum length that is given by the
group_concat_max_len system
variable, which has a default value of
1024. The value can be set higher,
although the effective maximum length
of the return value is constrained by
the value of max_allowed_packet.
EDIT:
Also without using the group concat i would go ahead and do it the way you had it... just make the category id your primary looping stcuture:
<?php
$result = mysql_query("SELECT articles.article_id, articles.article_title, article_categories.category_id
FROM articles, article_categories
WHERE articles.article_id = 1");
$articles = array();
while ( $rows = mysql_fetch_array($result) )
{
if(!array_key_exists($rows['article_id'], $articles)
{
$articles[$rows['article_id']] = array(
'article_id' => $rows['article_id'],
'article_title' => $rows['article_title']
'categories_id' => array()
);
}
$articles[$rows['article_id']][] = $rows['categories_id'];
}
?>
This way you only query once, bur you would then have to loop over the article for the operation on the articles' data.
In this many-to-many scenario you describe, you can't avoid duplicate data in any single result set you get.
Here's an idea. Do a separate query to build an array of category names, with their database key as the array index.
$sql = "SELECT category_id, category_name FROM Categories";
$result = mysql_query($sql);
$arrCategories = array();
while ( $row = mysql_fetch_assoc($result) {
$arrCategories[$row['category_id']] = $row['category_name'];
}
Now, you have the names of all the categories in an array.
When you're selecting articles, you'll have to do a separate query that pulls its category_ids from the join table. You can use a two-dimensional array to have a list of article ids and their associated categories
$arrArticleCategoryIds = array();
$result = mysql_query("SELECT *
FROM articles, article_categories
WHERE articles.article_id = article_categories.article_id
AND articles.article_id = 1");
while ( $rows = mysql_fetch_array($result) )
{
// why bother doing this when you've already hard-coded "1"?
$article_id = $rows['article_id'];
$article_title = $rows['article_title'];
//(loop all the category_id from the 2nd table and add to the array below)
// $categories_id[] .= ??????? --> How do i do this?
// here's how:
$sql = "SELECT category_id
FROM `article_categories`
WHERE article_id = $article_id
";
$category_id_results = mysql_query($sql);
while ( $category_id_row = mysql_fetch_assoc($category_id_results) ) {
$arrArticleCategoryIds[$article_id][] = $row['category_id'];
}
}
You'll wind up with two arrays:
$arrCategories
Array
(
[1] => apple
[2] => banana
...
)
$arrArticleCategoryIds
Array
(
[1] => Array
(
[1] => 13
[2] => 9
)
[3] => Array
(
[1] => 5
[2] => 7
)
)
)
Where '1' and '3' are article ids, and 13, 9, 5, and 7 are category ids that belong to the article id they're found under.

Categories