php -> sql - validate unique table entry - sql

I got an issue / question regarding unique table id´s. I am passing data from PHP -> SQL and use $id as primary key:
$id = new DateTime();
$id = $id -> format("dmYHis");
// example - 15102021102640 (15.10.2021-10:26:40)
I am afraid users could generate the same Id in future, which might crash the application or database. Would it be a good practice to validate the uniqueness within the related SQL insert statement? Like so, further please ignore SQL injection for now:
BEGIN TRANSACTION [trans1]
BEGIN TRY
INSERT INTO table1 (id) VALUES ('$id');
// pseudo code
// validate uniqueness
// while $id exist do $id + 1 else continue
// validate uniqueness
COMMIT TRANSACTION [trans1];
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION [trans1]
END CATCH";
I am not sure if a validation check, during this SQL statement would fix the security concerns.

Related

IF NOT EXISTS working in query mode but not working properly in stored procedure call from code

I have a stored procedure that does something like this:
MY_STORED_PROCEDURE:
IF NOT EXISTS(SELECT 1 FROM MY_TABLE WHERE MY_COLUMN1 = #MY_COLUMN1_VALUE_FROM_CODE AND MY_COLUMN2 = #MY_COLUMN2_VALUE_FROM_CODE)
BEGIN
--INSERT INTO MY_TABLE
This stored procedure is called automatically from C# code.
Now, this means that:
if we try to insert an entry with NEW VALUES for combination COLUMN1 and COLUMN2, then it should be inserted into MY_TABLE.
AND
If we try to insert an entry with EXISTING VALUES for combination COLUMN1 and COLUMN2, then it should not be inserted into MY_TABLE.
However, what happens is that the insertions are happening ALL THE TIME, independent of "IF NOT EXISTS" statement.
I already checked the "IF NOT EXISTS" statement directly from the query builder of SSMS and it seems that nothing is wrong with that statement.
Is there something that I do not take into consideration? What could be the error here?
This is also my C# code for invoking my SP:
public void InsertIntoMyTable(List<SqlParameter> parameters, SqlConnection connection)
{
DataTable dt = new DataTable();
SqlCommand command = new SqlCommand("MY_STORED_PROCEDURE", connection)
{
CommandType = CommandType.StoredProcedure
};
if (parameters != null)
{
foreach (SqlParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
}
dt.Load(command.ExecuteReader());
}
NOTE: MY_STORED_PROCEDURE is being called from different sources. So it is also possible that this procedure is being called at the SAME TIME from 1+ sources. I don't know if this can have some effects on my problem or not. I am reporting it, just in case it relates.
Thanks!
First, `NOT EXISTS* is the wrong way to do this, because it introduces race conditions. What you appear to want is a unique constraint. So add this as a constraint or index and then check for errors:
alter table t add constraint unq_my_table_column1_column2 on my_table (column1, column2);
Then simply do the insert . . . but in the TRY/CATCH block:
begin try
insert into my_table ( . . . )
values ( . . .);
end try;
begin catch
. . .
end catch;
If I had to speculate on the problem, then you have an issue with the parameters. Perhaps they are declared incorrectly -- such as varchar() with no length. Or perhaps they are being passed in incorrectly, as NULLs.
Something similar happened to me sometime ago because I didn't add SET NOCOUNT ON; at the beginning of my stored procedure.

How to refresh the value of a #variable from stored procedure resultset between consecutive statements?

Trying to perform multiple consecutive inserts in a table without identity key.
The unique id comes from a procedure called GetNextObjectId. GetNextObjectId is a stored procedure that has no output parameter and no return value.
Instead it selects a top 1 int field.
Tried this:
declare #nextid int;
exec #nextid = GetNextObjectId 1; insert into MyTable values (#nextid, ...);
exec #nextid = GetNextObjectId 1; insert into MyTable values (#nextid, ...);
go
Then this:
declare #nextid int; exec #nextid = GetNextObjectId 1; insert into MyTable values (#nextid, ...);
go
declare #nextid int; exec #nextid = GetNextObjectId 1; insert into MyTable values (#nextid, ...);
go
But the value of #nextid in the insert is always the same.
Question
What is the proper way to refresh the value of this variable without modifying the stored procedure?
Some context
The origin of this question is me looking for a quick way to insert test data in a table using the existing stored procedure, and not managing to do it. The question only relates to the fact the value of the variable does not get updated between statements, not to the proper way to insert data in a table. This is not production code. Also as I understand it, such a procedure is required using Entity Framework with concurrent code; as there are issues with Identity, each thread gets its own ids before saving the context as follows:
// Receive a batch of objects and persist in database
// using Entity Framework.
foreach (var req in requests)
{
// some validation
ctx.MyTable.Add(new Shared.Entities.MyTableType
{
Id = ctx.GetNextObjectId(Enums.ObjectTypes.MyTableType),
Code = req.Code,
Name = req.Name
});
// save to database every 1000 records
counter++;
if (counter % 1000 == 0)
{
ctx.SaveChanges();
counter = 0;
}
}
// save remaining if any
ctx.SaveChanges();
The procedure does this:
BEGIN TRAN T1
UPDATE [dbo].[ObjectsIds] WITH (ROWLOCK)
SET NextId = NextId + Increment
WHERE ObjectTypeId = #objectTypeId
SELECT NextId
FROM [dbo].[ObjectsIds]
WHERE ObjectTypeId = #objectTypeId
COMMIT TRAN T1
There are so many things wrong with this approach that a comment is not sufficient.
First, stored procedures return an integer which is occasionally used. When used, this should be a status value indicating success or failure. There is no requirement but that is how even Microsoft describes the value in the documentation. It sounds like your stored procedure is just running a query, not even returning a status value.
Second, using a stored procedure for this purpose means that you have race conditions. That means that even if the code seemed to work, it might not work for concurrent inserts.
Third, your code is requiring calling a stored procedure as part of every insert. That seems very dangerous, if you actually care about the value.
Fourth, you should be validating the data integrity using a unique index or constraint to prevent subsequent inserts with the same value.
What is the right solution? Well, the best solution is to simply enumerate every row with an identity() column. If you need to do specific counts by a column, then you can calculate that during querying.
If that doesn't meet your needs (although it has always been good enough for me), you can write a trigger. When writing a trigger, you need to be careful about locking the table to be sure that concurrent inserts don't produce the same value. That could suggest using a mechanism such as multiple sequences. Or it could suggest clustering the table around the groups.
The short message: triggers are the mechanism to do what you want (affect the data during a DML operation). Stored procedures are not the mechanism.

SQL query to update column in a table

I have a code based website in which an employee has to update their reward points by the coupon code provides them and when that code reflect their account means when points are updated in their account they are able to shop in the website. But there is a restriction for the code that code is deleted once used. Sometimes I found a query from customers that they update their account with the code provided but code did not reflect the account and deleted from the database and so thereafter they are not able to use the code again now I want that code only deleted when the code update points in their account. I have an another table named customer_reward where code saved after add points in the customers account but the code that not reflect account recharge is not saved in that table so I want that code only delete when that code is saved in the customer_reward table.
the complete code is given below:
<?php
if(isset($_POST['sub'])){
$db_host="localhost";
$db_username="root";
$db_password="";
$db_name="14";
$con=mysql_connect("$db_host", "$db_username", "$db_password") or die("could not connect to mysql!!!");
if($con=="")
{
echo "Database not connected!!!!";
}
else
{
$isdb=mysql_select_db("$db_name") or die("database not available!!!!");
if($isdb=="")
{
echo "database not selected!!!!";
}
else
{
$emp_ID=$_POST['emp_ID'];
$code=$_POST['code'];
$query = mysql_query("select * from oc_abhireward where `Code`='$code'") or die (mysql_error());
$data=mysql_fetch_assoc($query);
$code_db=$data['Code'];
$points_db=$data['Point'];
if($code==$code_db)
{
$query1 = mysql_query("select * from oc_customer where `emp_ID`='$emp_ID'") or die (mysql_error());
$data1=mysql_fetch_assoc($query1);
$customer_id=$data1['customer_id'];
$query2=mysql_query("INSERT INTO `oc_customer_reward` (customer_id, order_id, description, Code, points, date_added) VALUES ($customer_id, 0, 'rewarded', '$code', $points_db, NOW());");
$query4=mysql_query("INSERT INTO `oc_customer_recharge`(emp_ID, Code, points, date_added) VALUES ('$emp_ID', '$code', $points_db, NOW());");
if ($code==$code_db)
{
query5 = mysql_query("select * from oc_customer_recharge where Code='$code'")or die (mysql_error());
$data2=mysql_fetch_assoc($query4);
$emp_ID=$data2['emp_ID'];
$query6 = mysql_query("DELETE FROM oc_abhireward WHERE Code='$code'");
}
else
{
exit();
}
header("location:http://localhost/14/index.php?route=account/account");
exit();
}
else
{
}
}
}
}
?>
What's the relationship between Employee and Customer?
You are querying oc_customer by emp_ID, and getting customer_id from it. So is oc_customer unique on emp_id? If not, then the customer you end up getting (and so the one you'll apply the reward to) is effectively random. Instead, you need to pass in the customer's customer_id rather than the emp_id.
One other thing; You're using the "$POST"ed values directly in the SQL statements. That opens you up to a SQL-Injection attack. Check out How can I prevent SQL injection in PHP?
The best way possible here could be creating a trigger (for deleting the code) that will fired only when the update of points has been made.
CREATE TRIGGER trigger_name
AFTER INSERT
ON table_name FOR EACH ROW
BEGIN
-- logic for deleting the corresponding CODE
END;
Hope this will bring you closer to what you seek.
Ak
I think the problem is that you are really not veryfing whether a record was inserted in your oc_customer_reward table.
There are multiple ways of solving this problem.
You can modify your delete query to check oc_customer_reward table. This could be something on the lines of:
DELETE Table1
FROM Table1
INNER JOIN Table2 ON Table1.ID = Table2.ID
Create a trigger which will delete data in oc_reward table whenever a record is inserted in oc_customer_reward. You can look up triggers here
CREATE TABLE reward_code_table
(reward_code INT, random_col VARCHAR(50))
;
INSERT INTO reward_code_table
(`reward_code`, `random_col`)
VALUES
(1, 'First code'),
(2, 'Second code'),
(3, 'Third code')
;
CREATE TABLE insert_code_table
(customer_code INT, another_random_col VARCHAR(50))
;
DELIMITER //
CREATE TRIGGER del_after_insert
AFTER INSERT
ON insert_code_table
FOR EACH ROW
BEGIN
DELETE FROM reward_code_table WHERE reward_code = NEW.customer_code;
END;
//
DELIMITER ;
INSERT INTO insert_code_table(customer_code, another_random_col)
VALUES (2, "del 2 from reward table");
After inserting into one table, it deletes record from the other table.
You can checkout a sample SQLFiddle. Note that I have kept the delimiter as // in the fiddle example
Also consider using prepared statements to prevent basic mysql injections.

Create Database and Table Conditionally

I'm trying to write a small script to create a database if it doesn't exist, and create a table for that database if the table doesn't exist. What I have is this:
IF (db_id('db') is null) BEGIN
print 'Must create the database!';
CREATE DATABASE db;
END
USE db;
IF (object_id('test_table', 'U') is null) BEGIN
print 'Must create the table!';
CREATE TABLE test_table (
id int
);
END
I'm getting a strange error with this:
Database 'db' does not exist. Make sure that the name is entered correctly.
I'm guessing that it's parsing the script before running it and finding that 'db' doesn't exist, so it can't use it.
There must be a solution to this. Any help is appreciated.
SOLVED!
I realised 5 minutes after posting that the GO keyword solves the problem. Here is the fixed code:
IF (db_id('db') is null) BEGIN
print 'Must create the database!'
CREATE DATABASE db;
END
GO
USE db
IF (object_id('test_table', 'U') is null) BEGIN
print 'Must create the table!';
CREATE TABLE test_table (
id int
);
END
Sorry for wasting everyone's time.
SQL statements are parsed as one batch unless you break them apart. In SQL Server, you can use GO to do this. In both MySQL and SQL Server, you can use BEGIN and END.
If you want to commit the separate blocks to the database in different instances you can use BEGIN TRANS / COMMIT TRANS and START TRANSACTION / COMMIT for SQL Server and MySQL, respectively.
Something along the lines of Check if table exists in SQL Server would probably work (With a slight change)
IF (NOT EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'TheSchema'
AND TABLE_NAME = 'TheTable'))
BEGIN
--Do Stuff
END
I might suggest using the built-in SQL syntax -
CREATE DATABASE name IF NOT EXISTS;
And subsequently
CREATE TABLE name(definition) IF NOT EXISTS;

How to get IDs for bulk inserts in codeigniter?

In Codeigniter, if I create an SQL string which does multiple inserts, how do I get the inserted id of each?
// Prepare the SQL
$sql = '';
$chunk = array(array(), array(), array()); // The elements are arrays
foreach($chunk as $arr){
// The first field is the primary key (INT NOT NULL auto_increment)
$sql .= "(NULL, {$arr[0]}, {$arr[1]}, {$arr[2]}, {$arr[3]}, {$arr[4]})";
if($arr!= $last) $sql .= ', ';
}
// Start inserting into the db
$this->db->trans_start();
$this->db->query('INSERT INTO my_table VALUES '.$sql);
// A few other queries go here which need the IDs of the previous insert
$this->db->trans_complete();
This is also my first time using transactions.
You probably need to record the MAX id before the insert, and then select all the ids greater than that after the bulk insert.
EDIT:
I found this in the MySQL manual:
The correct way to use LOCK TABLES and UNLOCK TABLES with transactional tables, such as InnoDB tables, is to begin a transaction with SET autocommit = 0 (not START TRANSACTION) followed by LOCK TABLES, and to not call UNLOCK TABLES until you commit the transaction explicitly.
I decided to go with using transactions instead.