PDO insert into table with foreign keys - php

I am having a few difficulties with mysql and PDO.
I wish to insert a product into the database, however the product table contains foreign keys. Naturally, I will not know the Foreign key ID when inserting. Am I doing this right??? Is there a better way of tackling this problem?
TABLE Products
Id PK AI int
Name Varchar(20)
CategoryId Int FK
TypeId Int FK
TABLE Categories
Id Int PK
Cat varchar(20)
TABLE Types
Id Int PK
Type varchar(20)
$type = 'Gloves';
$category = 'Clothing';
$sql = 'INSERT INTO Products
SET Name = :name, CategoryId = :catId, TypeId = :typeId
WHERE
CategoryId IN (SELECT Id FROM Categories WHERE Cat = :category) AND
TypeId IN (SELECT Id FROM Types WHERE Type = :type)'
$stmt = $db->prepare($sql);
$stmt->execute(array(':name' => 'Pink childrens gloves', ':category' => $category, ':type' => $type));
As mentioned in a comment below: Normally, I would be getting the ID from a select box. I cannot do this because it will be a script executing the query, not a user.

are you sure that this is what you want?
$sql = 'INSERT INTO Products
SET Name = :name
WHERE
CategoryId IN (SELECT Id FROM Categories WHERE Cat = :category) AND
TypeId IN (SELECT Id FROM Types WHERE Type = :type)'
I think you are trying to use UPDATE
$sql = 'UPDATE Products
SET Name = :name
WHERE
CategoryId IN (SELECT Id FROM Categories WHERE Cat = :category) AND
TypeId IN (SELECT Id FROM Types WHERE Type = :type)'

MySQL allows a combination of SELECT + INSERT in a single query:
INSERT INTO tbl_temp2 (fld_id)
SELECT tbl_temp1.fld_order_id
FROM tbl_temp1 WHERE tbl_temp1.fld_order_id > 100;
... but I wouldn't care about it. You cannot do proper error checking if you do three different things in a single query.
My advice is that you first validate that there're a category and a type that match the given names. In that step, you can easily get the corresponding IDs, which will let you perform a simple INSERT. Additionally, if you need to insert many products, you can validate first and once.

In addition to #Álvaro G. Vicario's answer, you can also do something like (works in normal sql, I have not tried it with bound variables):
$sql = 'INSERT INTO Products
SET Name = :name,
CategoryId = (SELECT Id FROM Categories WHERE Cat = :category),
TypeId = (SELECT Id FROM Types WHERE Type = :type)';
But I would always check for existing categories and types first, insert where necessary and get the required id's as this will lead to unexpected results if there are no matches in the inner selects.

First of all you need to figure out which is the table that have foreign key data.
Then you need to get all possible values from foreign key table.
Finally you need to build and drop-down list or similar to give ability of select acceptable foreign key.
$q=$db->prepare('SELECT ke.referenced_table_name assoc_table,
ke.referenced_column_name assoc_col FROM
information_schema.KEY_COLUMN_USAGE ke WHERE ke.referenced_table_name IS NOT NULL
AND ke.table_schema=:database AND ke.table_name=:tablename AND ke.column_name=:col');
$q->bindValue(':database','mydatabasename'); //Set your database name here
$q->bindValue(':tablename','Departments'); //Set your table name here
$q->bindValue(':col','City'); //Set the column which foreign key values you want to have here
if($q->execute()) {
$foreingtable=$q->fetch(PDO::FETCH_ASSOC);
$q=$db->prepare('SELECT '.$foreingtable['assoc_col'].' FROM '.$foreingtable['assoc_table']);
if($q->execute())
echo json_encode($q->fetchAll(PDO::FETCH_COLUMN));
}
else {
header('http/1.1 500 Internal Server Error');
print_r($q->errorInfo());
exit;
}
More about this: Get list of possible foreign key values in MySql using PDO

Related

PHP after Delete recording with foreign key, check int the table

I have two tables
Product :
- id : int;
- name : varchar(255);
- content : varchar(255);
- categories_id : int (foreign key)
Categories :
- id : int;
- title : varchar(255);
when I want to delete a product, I want to check is that the category is already exist.
I do not want to delete a category already used in the product table
$sql = $db->query("SELECT p.*, c.* FROM product as c INNER JOIN categories as c ON c.id = p.categories_id ");
$res = $sql->fetch(PDO::FETCH_ASSOC);
$p = $res['id'];
$m_id = $_POST['id '];
$sql = $db->query("DELETE FROM categories WHERE id = $m_id AND categories_id = '$c' )");
if($sql){echo 'success';
}else{
echo 'Impossible to delete this categories becauss already exist in the table product, must be removed product in the table prodcut, ';
}
You have multiple ways to be sure, that a parent row will not being deleted when it is referenced.
Enforce the constraint using foreign keys
You can define a foreign key constraint on the tables with the ON DELETE NO ACTION option to prevent such action on the parent table.
This is the most reliable option, but it has some prerequirements in MySQL, such as:
InnoDB storage engine (both tables)
There must be at least a UNIQUE constraint on the referenced column in the parent table
MySQL will report an error when you'll try to delete a row from the categories table and there is a referencing record in the products table.
You can check in the delete query if there is a child row
DELETE FROM
categories
WHERE
id = :id -- < Parameter
NOT EXISTS (SELECT * FROM products WHERE products.categories_id = categories.id)
This will succeed in any cases, but won't delete the record when there is a referencing record.
You can check the rows affected right after the query, or you can issue a SELECT to check if the category is still there.
The delete in this form is transaction safe and handles race conditions properly.
SELECT and DELETE from code
You can check if there is a record in the child table and issue a delete when this condition is false, however THIS IS NOT TRANSACTION SAFE, NOR HANDLES RACE CONDITION on it's own.
You either have to start a transaction manually and commit/rollback it, or you can use the SELECT .. FOR UPDATE syntax to lock the records, but this won't stop other threads to INSERT into the products table.
Summary
Define the foreign key constraint in the database, this is the most reliable solution to prevent accidental deletion and to enforce data integrity.
Read more about the foreign key options in the MySQL manual: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html
You can read about locking reads (SELECT .. FOR UPDATE) on the following manual page: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
There many problems with your current code:
1. You don't need every column value when you perform SELECT query.
2. fetch(PDO::FETCH_ASSOC); this will return multiple rows not single id.
3. You haven't added WHERE clause in first query for product ID.
4. In DELETE query you don't need to specify category ID.
So you can update your code as follow.
<?php
$m_id = $_POST['id '];
$sql = $db->query("SELECT c.id FROM categories as c INNER JOIN product as p ON c.id = p.categories_id WHERE p.id=".$m_id."");
$res = $sql->fetch(PDO::FETCH_ASSOC);
if(sizeof($res)>0) { // Making sure there is no category match in categories table.
echo 'Impossible to delete this product, must be removed categories in the table categories';
} else { // No matching row found in categories table, remove product safely.
$sql = $db->query("DELETE FROM product WHERE id = $m_id");
if ($sql) {
echo 'success';
}
}
I've edited your question to meet your requirements. General practice is to use foreign key constraints and upon delete MySQL automatically throws exception if parent row exists in reference table. Complete manual on foreign keys: http://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html

How can I perform many-to-many insert using MySQL an PHP?

I have a many-to-many MySQL database with three tables entry, tag, and entry_tag.
entry table has id INT AUTO_INCREMENT PRIMARY KEY, title TEXT and entry TEXT columns
tag table has id INT AUTO_INCREMENT PRMARY KEY and tag VARCHAR(15) columns
entry_tag is a mapping tables between entry and tag, and has entry_id INT and tag_id INT, entry_id and tag_id creates PRIMARY KEY.
Now, I would like to INSERT entry.title and entry.entry and related tags tag.tag to the database and respective tables. I would also like to INSERT appropriate mapping information into entry_tag table. Could anyone tell me how can I possible do it? I am using PHP. How can I get the entry.id and tag.id and link them together in the mapping table?
I have:
$sql_query = "INSERT INTO entry SET
title = '$title',
entry = '$entry'";
if (!mysqli_query($link, $sql_query)) {
$error = 'Error adding submitted entry.';
include 'includes/error.html.php';
exit();
}
$sql_query = "INSERT INTO tag SET
tag = '$tag'";
if (!mysqli_query($link, $sql_query)) {
$error = 'Error adding submitted entry.';
include 'includes/error.html.php';
exit();
}
Now, I would like to perform:
$sql_query = "INSERT INTO entry_tag SET
entry_id = '$entry.id',
tag_id = '$tag.id'";
But I stuck on getting the right $entry.id and $tag.id.
Use mysql_insert_id() to capture the ID of the last insert

insert data from one table to another table

I am having a problem locating comments for a given user with the following table structure:
usertable (id, userid, name)
comments (id, commentname, date)
Note: usertable.id is not the same as comments.id, and they are both autoincrement
How should I go about updating these tables to fix this problem?
Update
Is this code good for all users get their own votes when someone voted as thilo savage told me ?
$sth = thumbsup::db()->prepare(
'INSERT INTO'
.thumbsup::config('database_table_prefix')
.'votes_users(vid, userid) VALUES (?,?)');
$sth->execute(array($this->vid, $userid));
You've got two options:
Add a 'uid' column to the comments table which references the usertable's 'id' column. That way, you have a way to keep track of which comments belong to which users.
Create a table 'user_comment' with the columns 'uid' and 'cid'. This option leaves the two existing tables as they are, and the 'user_comment' table is responsible for keeping track of which comments belong to which users.
EDIT: Rewritten to use many-to-many relationship because current tables can't be altered.
Create a new table called comments_users with these fields:
cuid (primary key and auto increment) | cid | uid
Then get all of a user's comments with this code:
$user_id = '1234';
// get all the user's comment ids from comments_users table
$find = mysql_query("SELECT `cid` FROM `comments_users` WHERE `uid` = '".$user_id."'");
// generate a query that grabs all those comments
$load = "SELECT * FROM `comments` WHERE ";
while ($row = mysql_fetch_array($find) {
$load .= "`id` = '".$row['cid']."' OR ";
}
// shop off the last OR
$load = substr($load,0,-4);
// put all the user's comments into comments array
$q = mysql_query($load);
while ($comment = mysql_fetch_array($q)) {
$comments[] = $comment
}
print_r($comments);
As far as inserting goes, you'll insert comments into the comments table like you normally would, but then you'd ALSO insert a row into comments_users table filling in the appropriate cid and uid for that comment

Add Prefix to auto-increment in mysql db

I have my database with table test1.
It has a primary id "Id" which is auto-increment.
Now the id is in the format 1,2,3.. . .Is it possible to store the primary Id as
PNR1,PNR2,PNR3 .. . . and so on(with auto-increment).
No. Either add the prefix in the query, or use a view instead.
Not really, but you can use another column (but a view)
this is already covered here:
MySQL Auto Increment Custom Values
Yes you can do it if you have INT prefix. You have id as INT in table
// START PREFIX
$query = mysql_query("SELECT id FROM `table_name` ORDER BY id DESC LIMIT 1");
// GET THE LAST ID MAKE SURE IN TABLE YOU 9991
while ($row = mysql_fetch_object($query)) {
$lastId = $row->id;
}
list($prefix,$Id) = explode('999',$lastId );
$Id = ($Id+1);
$new_id = '999'.$Id;
// END PREFIX
$insertQuery = mysql_query("INSERT INTO `table_name` SET id = '".$new_id."',...");
Hi, I made it work in this way :
Products Table (products):
id_prod(varchar(11), NOT NULL, PK), name(varchar(40))
Products Sequence Table (productidseq):
id(AI, PK, NOT NULL)
Before Insert Trigger in Products Table:
CREATE DEFINER=`root`#`localhost` TRIGGER `dbname`.`products_BEFORE_INSERT` BEFORE INSERT ON `products` FOR EACH ROW
BEGIN
insert into productidseq (id) values(NULL);
set new.id_prod = concat('PROD or any prefix here',last_insert_id());
set #productId = new.id_prod; -- To use outside of trigger this variable is useful.
END
When you run below query :
insert into products (name) values('Bat');
data inside tables will we be like this :
products:
id | name
---|-----
1 | Bat
productidseq:
id
---
1
If any better way than this or any cons with this, please comment below. Thanks.

How to ensure that MySQL rows are unique?

I am trying to insert a row into a MySQL table, with the condition that the row itself has to be unique.
So for example, the row would have id, name and address, but we can't have an insert with duplicate name and address.
I tried doing a select before inserting, to make sure I don't find the result, but it doesn't seem to be working.
I am currently trying to do this before inserting:
SELECT * FROM locations WHERE cityId = $cityId AND countyId = $countyId AND stateId = $stateId AND countryId = $countyId AND district = $district;
EDIT:
All those fields are allowed to duplicate, they just can't duplicate all at the same time.
The cleanest solution would be to put a UNIQUE constraint on cityId, countyId, stateId, countryId, districtId and then check whether the insert statement was successful.
To quickly test this:
ALTER TABLE your_table_here ADD UNIQUE INDEX(cityId, stateId, countryId, countyId, districtId);
And rerun the insert query. If it encounters a duplicate it will fail, which you will be able to handle in your application.
Assuming you already have a unique index set on the table you can use:
INSERT IGNORE INTO locations SET cityId = $cityId, countyId = $countyId, stateId = $stateId, countryId = $countyId, district = $district;
with such constraints, you should have made the name and address columns as composite primary key..
I think I have a better solution. First check the database and check if the IP and name exists by using:
$check = mysqli_num_rows(mysqli_query($conn, 'SELECT FROM tablename WHERE name="'.$_POST['name'].'"'));
if($check = 0) //Checks if the name does not exist
{
// Your code...
}
else
{
// Error to tell the user the name already exists....
}

Categories