Assume that I have 3 tables namely sales_order , sales_order_items , invoice and set ON UPDATE = NO ACTION ON DELETE = NO ACTION.
Now I need to have the ability do delete the sales order. But if there is a relation exists in any other table it means that that sales order is used somewhere else and i need to prevent deletion.
Ex: I have sales_order_id = 34 , I want to check its exists in any other tables.
Previously to achieve the same thing I used transactions, something like below
$db = new database();
//start transaction
$db->start_trans();
//try to delete the sales order with ID = 34
$db->exec( 'DELETE FROM SALES_ORDER WHERE ID = 34' );
//check transaction success or failure
if( $db->trans_status() == true ){
//THERE IS NO RELATION EXISTS
//ROLLBACK
$db->rollback_trans();
#Soft Delete the record
$db->exec( 'UPDATE sales_order SET is_deleted = 1 WHERE id = 34' );
}else{
//RELATION EXISTS FOR ID = 34 IN SOME OTHER TABLES
}
the above code works, but the problem is with sales_order_items. Because it is a child table of sales_order and if it have contents then the transaction will fail and will try to execute the else part.
But in fact sales_order_items is a property of sales_order and I need to DELETE that particular sales_order (don't care about items).
I am expecting something like this
$relations = $db->get_relation( 'sales_order.id', '34' );
Expected Output
array( 'sales_order_items','invoice','another_table' .... );
Note: The above is an example only, I have number of tables and it's not possible to go through each table and check ID exists.
select * from information_schema.table_constraints
where constraint_schema = DATABASE();
select * from information_schema.key_column_usage
where constraint_schema = DATABASE();
SQLFIDDLE
Thanks Revoua for providing a good answer. Here is little modified version to suite my needs. Please correct me if it have some better way.
function check_relation( $table_name , $table_column ,$table_value ){
//Get relation tables
$qry = "SELECT table_name,
column_name,
referenced_table_name,
referenced_column_name
FROM information_schema.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA = ?
AND REFERENCED_TABLE_NAME is not null
AND referenced_table_name = ?
AND referenced_column_name = ?";
$db = $this->crm->db;
//database name
$database = $db->dbprefix.$db->database;
//predefined array to store table names
$related_tables = array();
//execute the query
$qry = $db->query( $qry , array( $database , $table_name ,$table_column ) );
unset($database);
//Make sure that query is success
if( $qry != FALSE && $qry->row_count() > 0 ){
//Get as array
$result = $qry->to_array();
unset($qry);
foreach( $result as $result_item ){
//Get count based on table_value
$qry = "SELECT COUNT(*)as total FROM ".$result_item['table_name']." WHERE ".$result_item['column_name']." = ? and is_del = 0";
$qry = $db->query($qry,array($table_value));
if( $qry!= FALSE && $qry->row_count() > 0 ) {
$qry = $qry->to_array(1);
//There are some entries ..
if( intval($qry['total']) > 0 ){
$related_tables[] = $result_item['table_name'];
}
}
}
return $related_tables;
}else{
return $related_tables;
}
}
Usage:
$relation = $db->check_relation("SALES_ORDER","ID",34);
var_dump( $relation ) ==>
array( 'SALES_ORDER_ITEM','INVOICE' ... );
Now in business logic
if( empty( $relation ) ){
// No relation exists
}elseif( in_array( 'invoice', $relation ) ){
$message->set_message("Cannot delete Sales Order, Invoice exists.");
}else ...
Related
I explain my question in detail, I have a table in my db, which incerementates of many records, every time, in my code I have correctly implemented the backup function of deleted data. But I have a problem in my query. I make a trivial example to make people understand.
Example, I have 100 records in my table, the number 1 is the one recorded the longest and the number 100 is the youngest. I want to count 50 records, and delete from 51 to 100, so that the 50 oldest records are deleted and backed up, and the 50 younger ones remain, creating a continuous cycle of deletion and backup.
Stupid example that cannot occur but is only to make people understand:
if I have 30 records, do not delete anything until I have at least 50, 50 or more, delete 50
I'm 30, don't delete anything up to 50
I have 51, delete1, the oldest and I have 50 again.
I have 60, delete 10 from the oldest and I have 50 again.
My code that I put below for those who want to do tests, create a folder and a backup file and correctly record every deletion field, but it doesn't do what I wrote that I need, but every time the script is launched, eventually erases all records in the db
<?php
//Start the session
session_start();
//Include connection
include 'connessione.php';
//Query to get stuff from database
$query_string = "SELECT * FROM utenti ORDER BY id ASC LIMIT 1 ";
$query = mysqli_query($connessione, $query_string);
//Get results
$results = mysqli_fetch_all($query, MYSQLI_ASSOC);
//Make that into a JSON array
$results = json_encode( $results );
function makeDirectory($path, $mode)
{
$return = mkdir($path, $mode, true);
return $return === true || is_dir($path);
}
$path = 'backup_LOG';
$mode = 0777;
$risultato = makeDirectory($path, $mode);
//Put those results in a file (create if file not exist)
$fileName = 'backup_LOG/backup_file_' . time() . '.txt';
$file = fopen( $fileName , 'a' );
fwrite( $file, $results );
fclose( $file );
//Delete the rows that you just backed up, but only if there are 50 or more.
if( sizeof( $results ) >= 1 )
{
$query_delete = "DELETE FROM utenti ORDER BY id DESC LIMIT 1";
mysqli_query( $connessione, $query_delete );
}
?>
FINAL CODE
<?php
//Start the session
session_start();
//Include connection
include 'connessione.php';
$query = $connessione->query("
SELECT *
FROM utenti
ORDER BY id DESC
LIMIT 9999999999999999 -- just a very high number
OFFSET 1
");
$results = $query->fetch_all(MYSQLI_ASSOC);
$results = json_encode( $results );
if (count($results) > 0) {
// #todo backup the data
function makeDirectory($path, $mode)
{
$return = mkdir($path, $mode, true);
return $return === true || is_dir($path);
}
$path = 'backup_LOG';
$mode = 0777;
$risultato = makeDirectory($path, $mode);
$results = json_encode( $results );
//Put those results in a file (create if file not exist)
$fileName = 'backup_file_' . time() . '.txt';
$file = fopen( $fileName , 'a' );
fwrite( $file, $results );
fclose( $file );
// delete fetched rows
$firstId = reset($results)['id'];
$lastId = end($results)['id'];
$stmt = $connessione->prepare("DELETE FROM utenti WHERE id BETWEEN ? and ?");
$stmt->bind_param('ii', $firstId, $lastId);
$stmt->execute();
}
?>
Use OFFSET 50 to skip 50 "jungest" rows. Then use first and last id from the fetched data to delete the rows between them.
$query = $connessione->query("
SELECT *
FROM utenti
ORDER BY id DESC
LIMIT 9999999999999999 -- just a very high number
OFFSET 50
");
$results = $query->fetch_all(MYSQLI_ASSOC);
if (count($results) > 0) {
// #todo backup the data
// delete fetched rows
$firstId = reset($results)['id'];
$lastId = end($results)['id'];
$stmt = $connessione->prepare("DELETE FROM utenti WHERE id BETWEEN ? and ?");
$stmt->bind_param('ii', $firstId, $lastId);
$stmt->execute();
}
Note: LIMIT 9999999999999999 is a workaround. We don't want a LIMIT. But in MySQL it's not possible to use OFFSET without LIMIT. If you don't taht "magic" number, you can also use the following query instead:
SELECT *
FROM utenti
WHERE id <= (
SELECT id
FROM utenti
ORDER BY id DESC
LIMIT 1
OFFSET 50
)
You will need to replace your first query with something like this:
SELECT *
FROM utenti
WHERE id < (SELECT MIN(id) FROM (SELECT id FROM utenti ORDER BY id DESC LIMIT 50))
ORDER BY id ASC;
In your php, between calling mysqli_fetch_all() and json_encode(), add this line:
$maxDeleteID = $results[count($results)-1]['id'];
Change your delete query to use this new php variable:
"DELETE FROM utenti WHERE id <= ". $maxDeleteID;
This should pull all records prior to the newest 50, and then make sure that nothing that is not in that backup is deleted. Records could be added to the table between your select and delete so you need to save that max id from your select and use it to make sure you dont delete anything that wasn't backed up.
In MySQL 8, you could use window functions in a subquery to rank and count the records, and then join it with the main table:
DELETE u
FROM utenti u
INNER JOIN (
SELECT
id,
ROW_NUMBER() OVER(ORDER BY id) rn,
COUNT(*) OVER() cnt
FROM utenti
) x ON u.id = x.id
WHERE x.cnt - 50 - x.rn >= 0
NB : as most operations that modify data, you want to run this inside a database transaction.
I have 2 tables properties as parent table and sale_listings as a child.
I want, programmatically, to insert or update sale listing record into
sale_listings table while properties table's data is inserted.
If the previous record is found in sale_listings table then update the record
else insert new sale listing record.
Both tables data are successfully inserted without checking the previous
records in the sale_listing(child table).
When I try to check the previous record in sale_listing using rowCount,
the application does not affect any row at all.
I want to know how I can achieve this with PHP-Mysql?
Below is the source code:
//Assign Property Values
$property_type = isset($_POST["property_type"] ) ? $_POST["property_type"]: '';
$agent = isset($_POST["agent"] ) ? $_POST["agent"]: '';
$suburb = isset($_POST["suburb"] ) ? $_POST["suburb"]: '';
$street_no = isset($_POST["street_no"] ) ? $_POST["street_no"]: '';
$street_name = isset($_POST["street_name"] ) ? $_POST["street_name"]: '';
$desc = isset($_POST["desc"] ) ? $_POST["desc"]: '';
$property_status = isset($_POST["property_status"] ) ? $_POST["property_status"]: '';
$num_bathrooms = isset($_POST["num_bathrooms"] ) ? $_POST["num_bathrooms"]: '';
$num_beds = isset($_POST["num_beds"] ) ? $_POST["num_beds"]: '';
$num_garages = isset($_POST["num_garages"] ) ? $_POST["num_garages"]: '';
$num_lounges = isset($_POST["num_lounges"] ) ? $_POST["num_lounges"]: '';
$air_con = isset($_POST["air_con"] ) ? $_POST["air_con"]: '';
$pool = isset($_POST["pool"] ) ? $_POST["pool"]: '';
$cottage = isset($_POST["cottage"] ) ? $_POST["cottage"]: '';
$price = isset($_POST["price"] ) ? $_POST["price"]: '';
if((!empty($agent)) || (!empty($suburb)) || (!empty($property_type)) ||
(!empty($street_no)) || (!empty($street_name)) ||(!empty($desc)) ||
(!empty($property_status)) || (!empty($num_bathrooms)) ||
(!empty($num_beds)) || (!empty($num_garages)) || (!empty($num_lounges)) ||
(!empty($air_con)) || (!empty($pool)) || (!empty($cottage)) ||
(!empty($price))){
//Insert data into Properties table
$query = "INSERT INTO properties (agent_id, suburb_id, property_type,
property_status, street_no, street_name, property_desc, num_bathrooms,
num_beds, num_garages, num_lounges, air_con, pool, cottage, price)
VALUES('$agent', '$suburb', '$property_type', '$property_status',
'$street_no', '$street_name', '$desc', '$num_bathrooms', '$num_beds',
'$num_garages', '$num_lounges', '$air_con', '$pool', '$cottage',
'$price')";
$row_count = $this->conn->exec($query);
//Retrieve the last inserted Property ID
$last_insert_property_id = $this->conn->lastInsertId();
if($row_count){
$query = "UPDATE suburbs SET total_properties = total_properties + 1
WHERE suburb_id = '$suburb'";
$row_count = $this->conn->exec($query);
//Check if the last inserted property ID exists
if($last_insert_property_id){
//If the last property_id exists, make it equal to property ID
$property_id = $last_insert_property_id;
//Check if previous sale listing exist
$query_sel = "SELECT sale_listing_id, property_id,
sale_listing_amount, discount_percent, total_listing
FROM sale_listings";
$result = $this->conn->query($query_sel);
if($result->rowCount() > 0){
$query = "UPDATE sale_listings SET total_listing = total_listing + 1 WHERE property_id = '$property_id'";
$row_count = $this->conn->exec($query);
}else{
$sale_amount = 65;
$discount = 0;
$total_listing = 1;
$sql = "INSERT INTO sale_listings (property_id,
sale_listing_amount, discount_percent, total_listing)
VALUES('$property_id', '$sale_amount', '$discount',
'$total_listing')";
$row_count = $this->conn->exec($sql);
}
}else{
echo("Unable to insert or update record!");
}
}
return $row_count;
}
You miss a very essential part of your UPDATE query, right now your update query would update all records in your table instead, you should add a WHERE statement so only the field you want gets updated. For example:
UPDATE sale_listings
SET total_listing = total_listing+1
WHERE sale_listing_id = 'current_sale_listing_id'
I also miss the where statement in your select to check if the previous listing exists, so your row count would always be incorrect. This should be:
SELECT sale_listing_id, property_id,
sale_listing_amount, discount_percent, total_listing
FROM sale_listings WHERE property_id = 'last_property_id'
Also look into SQL injection, it is never a good idea to insert variables directly into your query.
You use the property_id to check/update the sale listings, however each time a new property is listed it will generate a new, unique id. This causes your insert to always create a new property instead of updating the old one because the newly generated ID will never be in the sale listings. I would recommend to add the address to the sale listing and perform the where clause on this column/these columns to update the sale listings.
How to select one row after update in one query with PDO and PHP?
like this:
function giftUpdate($username,$id,$date,$conn){
$update = $conn->prepare("UPDATE gifts SET used = 1 , user = :user , date = :date WHERE id = :id");
$update->bindParam("id" , $id );
$update->bindParam("date" , $date );
$update->bindParam("user" , $username );
$update->execute();
return $update ? true : false;
}
function giftGet($username,$currnetTime,$conn) {
$statment = $conn->prepare("SELECT * FROM gifts WHERE used = 0 ORDER BY `id` ASC");
$statment->execute();
$gift = $statment->fetch(PDO::FETCH_OBJ);
if($gift){
$voucher = $gift->code;
$voucherid = $gift->id;
$vouchertype = $gift->type;
$voucherkey = $gift->keys;
if($voucher){
$giftUpdate = giftUpdate($username,$voucherid,$currnetTime,$conn);
if($giftUpdate){
useUpdate($username , $conn);
}
}
}
usleep(50000);
return $gift ? $gift : false;
}
After update I need select the same row to send it to user.
(i`am one table for vouchers list with 1000row and column 'used' with 0 by default , and with user request i need in one query update this row to be used 1 and select this row.)
my problem in this functions is Some users will receive a duplicate vouchers And a code is assigned to several users.
guys ... for this case Is it possible to use the transaction method? Or do you suggest using a transaction at all?
Edited:
I eventually changed the code below, but I'm still in line :
SELECT id FROM gifts WHERE used = 0 ORDER BY id ASC limit 1 FOR UPDATE)");
But when I put an id from the database instead of this code, it works!
function giftGet($username, $currnetTime, $conn)
{
$uniqid = uniqid('vouchers_');
$update = $conn->prepare("UPDATE gifts SET used = 1, uniqueid =:uniqueid , user = :user , date = :date WHERE id = (
SELECT id FROM gifts WHERE used = 0 ORDER BY `id` ASC limit 1 FOR UPDATE)");
$update->bindParam("date", $currnetTime);
$update->bindParam("uniqueid", $uniqid);
$update->bindParam("user", $username);
$update->execute();
$updaterowCount = $update->rowCount();
if ($updaterowCount)
{
$statment = $conn->prepare("SELECT * FROM gifts WHERE uniqueid = :uniqueid ORDER BY `id` ASC");
$statment->bindParam("uniqueid", $uniqid);
$statment->execute();
$gift = $statment->fetch(PDO::FETCH_OBJ);
$voucherid = $gift->id;
if ($gift)
{
useUpdate($username, $voucherid, $conn);
}
}
return $gift ? $gift : false;
}
I would alter the table gifts and add a column uniqueid(varchar).
then use
$uniqid = uniqid('vouchers_');
and rewrite your update
"UPDATE gifts SET used = 1, uniqueid =:UNIQUEID , user = :user , date = :date WHERE id = (
SELECT id FROM gifts WHERE used = 0 ORDER BY `id` ASC limit 1 FOR UPDATE)";
now you can identify the updated record by uniqueid.
In Your Code there is a time-gap between the select and the update. 2 or more users can start at the same time the select, recieve the same voucher before it is updated with used = 1
I have a "matrix" table with the following columns filled in.
matrix_id, user_id, position_1, position_2, position_3
1 1 1982 2251 5841
2 2 6204 0 0
3 3 0 0 0
4 4 0 0 0
I basically want to do the following.
Find a row with the lowest user_id and that has an empty position.
In the example above, that would be user_id 2 and position_2.
I update the row with a query.
I then move on to the next next empty position. Since user_id 2 still has an empty position_3, I update the row again with a query.
Since that row is complete, I move on to the next highest user_Id that has empty positions. In this case, it's user_id 3 and then user_id 4 the one after that.
I know I can do all of the above if I know what the user_id is. But assume in this case, I have no clue what the user_ids are. How would the queries look then?
Here is what I have so far.
$find_user = $db->prepare("SELECT * FROM matrix WHERE user_id > :user_id");
$find_user->bindValue(':user_id', 0);
$find_user->execute();
$result_user = $find_user->fetchAll(PDO::FETCH_ASSOC);
if(count($result_user) > 0) {
foreach($result_user as $row) {
$matrix_id = $row['matrix_id'];
$user_id = $row['user_id'];
$position_1 = $row['position_1'];
$position_2 = $row['position_2'];
$position_3 = $row['position_3'];
}
} else {
$errors[] = 'User Id not found in Matrix.';
}
$update_user = $db->prepare("UPDATE matrix SET position_2 = :position_2 WHERE user_id = :user_id");
$update_user->bindValue(':position_2', 1564;
$update_user->bindParam(':user_id', $user_id);
if($update_user->execute()) {}
This should go through all your users from smallest user_id to largest.
For each user it will check the relevant columns in order and apply a new value to the empty ones.
$new_val = 1999;
$result = $db->query("SELECT * FROM matrix ORDER BY user_id");
$users = $result->fetchAll(PDO::FETCH_ASSOC);
if(count($users) > 0) {
// prepare all the possible queries
// make use of prepare once execute many times
$stmt1 = $db->prepare("UPDATE `matrix` SET `position_1` = :pos WHERE `user_id` = :id");
$stmt2 = $db->prepare("UPDATE `matrix` SET `position_2` = :pos WHERE `user_id` = :id");
$stmt3 = $db->prepare("UPDATE `matrix` SET `position_3` = :pos WHERE `user_id` = :id");
foreach($users as $user) {
if ( $user['$position_1'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
if ( $user['$position_2'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
if ( $user['$position_3'] == 0 ) {
$stmt1->execute( array(':pos'=>++$new_val,':id'=>$user['user_id']) );
}
}
} else {
$errors[] = 'User Id not found in Matrix.';
}
You could reduce the rows to process by changing the query a bit to only find users with columns to fix
$result = $db->query("SELECT *
FROM matrix
WHERE position_1 = 0
OR position_2 = 0
OR position_3 = 0
ORDER BY user_id");
important thing here, is that you are working with the row, not columns
so check all the prerequisites and update the row.
$find_user = $db->prepare("SELECT * FROM matrix order by user_id asc");
$find_user->execute();
$result_user = $find_user->fetchAll(PDO::FETCH_ASSOC);
foreach($result_user as $row) {
$matrix_id = $row['matrix_id'];
$user_id = $row['user_id'];
$position_1 = $row['position_1'];
$position_2 = $row['position_2'];
$position_3 = $row['position_3'];
}
$str = ''
if (!$position_2){
$str = "position_2 = :position_2"
} else if (!$position_2 && !$position_3){
$str = "position_2 = :position_2 and position_3 = :position_3"
}
$update_user = $db->prepare("UPDATE matrix SET " . $str . " WHERE user_id = :user_id");
$update_user->bindValue(':position_2', 1564);
$update_user->bindValue(':position_3', 1564);
$update_user->bindParam(':user_id', $user_id);
if($update_user->execute()) {}
Also, get all the rows in the matrix table ordered by used_id and process every row, depending on your condition.
Can you please take a look at this code and let me know why I am not able to UPDATE a temporary table on $query3
$query = "CREATE TEMPORARY TABLE IF NOT EXISTS `charts_ecolo_yes` (
SET `econo_sum_projects` = (SELECT COUNT(`project`) FROM `ecolo-cu-yes` WHERE c_1000=1 ),
SET `econo_sum_powerline` = (SELECT SUM(`powerline_length`) FROM `ecolo-cu-yes` WHERE c_1000=1 ),
SET `econo_sum_roads` = (SELECT SUM(`road_length`) FROM `ecolo-cu-yes` WHERE c_1000=1 ),
SET `econo_sum_cost` = (SELECT SUM(`cost_per_year`) FROM `ecolo-cu-yes` WHERE c_1000=1 ),
SET `econo_sum_penstlock` = (SELECT SUM(`penstlock` FROM `ecolo-cu-yes` WHERE c_1000=1 )
";
$con->query($query3);
$query4 = "SELECT * FROM `charts_ecolo_yes`" ;
$results = $con->query($query4);
if ($results) {
$row = $results->fetch_array(MYSQLI_NUM);
$row = array_map('floatval', $row); // Convert strings to numbers
echo json_encode($row);
}
Here is the sample result page, which you can see even after running the $con->query($query3); I am still getting default values(100) at last 5th columns.
Thanks
Your UPDATE query syntax is wrong, you don't need SET for all column. It should be
$query3= " UPDATE `charts_ecolo_yes`
SET `econo_sum_projects` = some_value,
`econo_sum_powerline` = some_other_value,