We want to change the way we pass values from PHP to stored procedures (T-SQL). I only have minor experience with PHP but I will attempt to explain the process from discussions with our web developer.
Current Process
Example test table
In order to update a record, such as Field3 in this example, we would pass all existing values back to the stored procedure.
EXEC dbo.UpdateTest #ID = 1, #Field1 = 'ABC', #Field2 = 'DEF', #Field3 = 'GHI', #Field4 = 'JKL'
Lets say to update Field3, you must click a button. This would navigate to a new page which would run the stored procedure to update the data. As the new page is unaware of the values it has to run a SELECT procedure to retrieve the values before running an UPDATE.
The script would then redirect the user back to the page which reloads the updated data and the changes are reflected on screen.
New Process
What we would like to do is only pass the fields we want to change.
EXEC dbo.UpdateTest #ID = 1, #Field2 = 'DEF', #Field3 = 'GHI'
Our solution is simple. First we set all of the updatable fields to optional (so NULL can be passed). We then check to see if the parameter is NULL (is not passed), if it is then we ignore it and if it isn't we update it.
UPDATE
dbo.Test
SET
Field1 = NULLIF(ISNULL(#Field1,Field1),'-999')
,Field2 = NULLIF(ISNULL(#Field2,Field2),'-999')
,Field3 = NULLIF(ISNULL(#Field3,Field3),'-999')
,Field4 = NULLIF(ISNULL(#Field4,Field4),'-999')
WHERE
ID = #ID
However we still want the procedure to update the database record to NULL if a NULL value is passed. The workaround for this was to assign an arbitrary value to equal NULL (in this case -999), so that the procedure will update NULL if the arbitrary value (-999) is passed.
This solution is rather messy and, in my eyes, an inefficient way of solving the problem. Are there any better solutions? What are we doing wrong?
A huge thanks in advance to any replies
Valdimir's method is great as far as passing a flag variable to identify when the value is passed or not passed and his notes about arbitrarily picking a value are right on, but I would guess that there are some arbitrary values you may never have to worry about. such as -999 for a integer when you don't allow for negative numbers, or '|||||||' for a null string. Of course this breaks down some when you do want to use negative numbers but then you could potentially play around with numbers too big for a data type such as BIGINT as a parameter default -9223372036854775808 for an int.... The issue really comes down to your business case of whether values can or can not be allowed.
However if you go a route like that, I would suggest 2 things. 1) don't pass the value from PHP to SQL instead make that the default value in SQL and test if the parameter is the default value. 2) Add a CHECK CONSTRAINT to the table to ensure the values are not used and cannot be represented in the table
So something like:
ALTER TABLE dbo.UpdateTest
CHECK CONSTRAINT chk_IsNotNullStandInValue (Field1 <> '|||||||||||||||||||' AND Field2 <> -999)
CREATE PROCEDURE dbo.UpdateTest
#ParamId numeric(10,0)
,#ParamField1 NVARCHAR(250) = '|||||||||||||||||||'
,#ParamField2 INT = -99999 --non negative INT
,#ParamField3 BIGINT = -9223372036854775808 --for an int that can be negative
AS
BEGIN
DECLARE #ParamField3Value INT
BEGIN TRY
IF ISNULL(#ParamField3,0) <> -9223372036854775808
BEGIN
SET #ParamField3Value = CAST(#ParamField3 AS INT)
END
END TRY
BEGIN CATCH
;THROW 51000, '#ParamField3 is not in range', 1
END CATCH
UPDATE dbo.Test
SET Field1 = IIF(#ParamField1 = '|||||||||||||||||||',Field1,#ParamField1)
,Field2 = IIF(#ParamField2 = -99999,Field2,#ParamField2)
,Field3 = IIF(#ParamField3 = -9223372036854775808, Field3, #ParamField3Value)
WHERE
ID = #ParamId
END
The real problem with this method is the numeric data field allowing for negative numbers as you really don't have an appropriate way of determining when the value should be null or not unless you can pick a number that will always be out of range. And I definitely realize how bad of an idea the BIGINT for INT example is because now your procedure will accept a numeric range that it shouldn't!
Another method/slight variation of Vladimir's suggestion is to flag when to make a field null rather than when to update. This will take a little getting used to for your PHP team to remember to use but because these flags can also be optional they don't have to be burdensome to always include something like:
CREATE PROCEDURE dbo.UpdateTest
#ParamId numeric(10,0)
,#ParamField1 NVARCHAR(250) = NULL
,#MakeField1Null BIT = 0
,#ParamField2 INT = NULL
,#MakeField2Null BIT = 0
,#ParamField3 INT = NULL
,#MakeField3Null BIT = 0
AS
BEGIN
UPDATE dbo.Test
SET Field1 = IIF(ISNULL(#MakeField1Null,0) = 1,NULL,ISNULL(#ParamField1,Field1))
,Field2 = IIF(ISNULL(#MakeField2Null,0) = 1,NULL,ISNULL(#ParamField2,Field2))
,Field3 = IIF(ISNULL(#MakeField3Null,0) = 1,NULL,ISNULL(#ParamField3,Field3))
WHERE
ID = #ParamId
END
Basically if you are using the stored procedure to Update a table and it has nullable fields, I don't think I would recommend having the paramaters be optional as it leads to business cases/situations that can be messy in the future especially concerning numeric data types!
Your approach where you use a magic number -999 for the NULL value has a problem, as any approach with magic numbers have. Why -999? Why not -999999? Are you sure that -999 can not be a normal value for the field? Even if it is not allowed for a user to enter -999 for this field now, are you sure that this rule will remain in place in few years when your application and database evolve? It is not about being efficient or not, but about being correct or not.
If your fields in the table were NOT NULL, then you could pass a NULL value to indicate that this field should not be updated. In this case it is OK to use a magic value NULL, because the table schema guarantees that the field can't be NULL. There is a chance that the table schema will change in the future, so NULL can become a valid value for a field.
Anyway, your current schema allows NULLs, so we should choose another approach. Have an explicit flag for each field that would tell the procedure whether the field should be updated or not.
Set #ParamUpdateFieldN to 1 when you want to change the value of this field. Procedure would use the value that is passed in the corresponding #ParamFieldN.
Set #ParamUpdateFieldN to 0 when you don't want to change the value of this field. Set #ParamFieldN to any value (for example, NULL) and the corresponding field in the table will not change.
CREATE PROCEDURE dbo.UpdateTest
-- Add the parameters for the stored procedure here
#ParamID numeric(10,0), -- not NULL
-- 1 means that the field should be updated
-- 0 means that the fleld should not change
#ParamUpdateField1 bit, -- not NULL
#ParamUpdateField2 bit, -- not NULL
#ParamUpdateField3 bit, -- not NULL
#ParamUpdateField4 bit, -- not NULL
#ParamField1 nvarchar(250), -- can be NULL
#ParamField2 nvarchar(250), -- can be NULL
#ParamField3 nvarchar(250), -- can be NULL
#ParamField4 nvarchar(250) -- can be NULL
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
BEGIN TRY
UPDATE dbo.Test
SET
Field1 = CASE WHEN #ParamUpdateField1 = 1 THEN #ParamField1 ELSE Field1 END
,Field2 = CASE WHEN #ParamUpdateField2 = 1 THEN #ParamField2 ELSE Field2 END
,Field3 = CASE WHEN #ParamUpdateField3 = 1 THEN #ParamField3 ELSE Field3 END
,Field4 = CASE WHEN #ParamUpdateField4 = 1 THEN #ParamField4 ELSE Field4 END
WHERE
ID = #ParamID
;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- TODO: process the error
ROLLBACK TRANSACTION;
END CATCH;
END
So, parameters of the procedure are not optional, but you use #ParamUpdateFieldN flags to indicate which parameters hold useful values and which parameters should be ignored.
EXEC dbo.UpdateTest #ID = 1, #Field1 = 'ABC', #Field2 = 'DEF', #Field3 = 'GHI', #Field4 = 'JKL'
and
EXEC dbo.UpdateTest #ID = 1, #Field2 = 'DEF', #Field3 = 'GHI'
Are both valid ways to make use of the same stored procedure with MsSql or Sybase. When you don't send the values, it is the same as sending a null. Unless you set a default in the stored procedure. In that case the default is used instead of the null.
Not enough reputation to just comment.
In my opinion your solution is good enough as long as the arbitrary value cannot be a normal value for any of the fields.
However, I'd consider passing and storing something else besides NULL (“N/A” for example) when a field should not have an “actual” value and it’s purposely updated from the client side.
Related
I'm trying to write a PHP program to update a MySQL table entry according to a phone number. The phone numbers in the database are entered without limitations and are typically formatted in the XXX-XXX-XXXX way, but sometimes have other characters due to typos. In order to ensure the query works every time, I want to remove all non-numeric characters from the entries so that I can compare the entries to phone numbers formatted like XXXXXXXXXX coming from a separate source.
I've done some research and found some solutions but am unsure how to incorporate them into the PHP script. I am fairly new to MySQL and most of the solutions provided user defined MySQL functions and I don't know how to put them into the PHP script and use them with the query I already have.
Here's one of the solutions I found:
CREATE FUNCTION [dbo].[CleanPhoneNumber] (#Temp VARCHAR(1000))
RETURNS VARCHAR(1000) AS BEGIN
DECLARE #KeepValues AS VARCHAR(50)
SET #KeepValues = '%[^0-9]%'
WHILE PATINDEX(#KeepValues, #Temp) > 0
SET #Temp = STUFF(#Temp, PATINDEX(#KeepValues, #Temp), 1, '')
RETURN #Temp
END
And this is the query I need the solution for:
$sql = "SELECT pid AS pid FROM patient_data " .
"WHERE pid = '$pID' AND phone_cell = '$phone_number';";
The query should return the data in the pid column for a single patient, so if the phone number is 1234567890 and the pid is 15, 15 should be returned. I have no output at the moment.
The example function definition is Transact-SQL (i.e. for Microsoft SQL Server), it's not valid MySQL syntax.
A function like this doesn't go "into" the PHP code. The function gets created on the MySQL database as a separate step, similar to creating a table. The PHP code can call (reference) the defined function just like it references builtin functions such as DATE_FORMAT or SUBSTR.
The SELECT statement follows the pattern of SQL that is vulnerable to SQL Injection. Any potentially unsafe values that are incorporated into SQL text must be properly escaped. A better pattern is to use prepared statements with bind placeholders.
As an example of a MySQL function:
DELIMITER $$
CREATE FUNCTION clean_phone_number(as_phone_string VARCHAR(1024))
RETURNS VARCHAR(1024)
DETERMINISTIC
BEGIN
DECLARE c CHAR(1) DEFAULT NULL;
DECLARE i INT DEFAULT 0;
DECLARE n INT DEFAULT 0;
DECLARE ls_digits VARCHAR(20) DEFAULT '0,1,2,3,4,5,6,7,8,9';
DECLARE ls_retval VARCHAR(1024) DEFAULT '';
IF ( as_phone_string IS NULL OR as_phone_string = '' ) THEN
RETURN as_phone_string;
END IF;
SET n := CHAR_LENGTH(as_phone_string);
WHILE ( i < n ) DO
SET i := i + 1;
SET c := SUBSTR(as_phone_string,i,1);
IF ( FIND_IN_SET(c,ls_digits) ) THEN
SET ls_retval := CONCAT(ls_retval,c);
END IF;
END WHILE;
RETURN ls_retval;
END$$
DELIMITER ;
We can execute these statements in the mysql command line client, connected as a user with sufficient privilege, to create the function.
This isn't necessarily the best way to write the function, but it does serve as a demonstration.
Once the function is created, we can reference it a SQL statement, for example:
SELECT t.foo
, clean_phone_number(t.foo)
FROM ( SELECT '1' AS foo
UNION ALL SELECT '1-888-TAXICAB'
UNION ALL SELECT '888-555-1212'
UNION ALL SELECT '+=_-()*&^%$##"''<>?/;:"abc...xyz'
UNION ALL SELECT ''
UNION ALL SELECT NULL
) t
I want not update a record when the variable is empty when executing a UPDATE query in SQL. However, when the variable is filled, the record should be updated.
So in example, the myAge field in the database have currently a value of 20 (type int).
After executing the following query, the record should still be 20.
$age = '';
db_con->query("UPDATE info SET myAge = ".$age." WHERE account_id = 1");
Ps: I know I could check if the variable is empty with PHP, but I was wondering If this could be archieved within SQL?
You could use an IF construct in SQL to check if the value is empty:
$age = mysql_real_escape_string($age);
db_con->query("UPDATE info SET myAge = IF('".$age."' = '', myAge, ".$age.") WHERE account_id = 1");
If the passed PHP variable is empty, you set the old myAge.
Checking in PHP makes more sense, you might save a database query.
The way you are passing that variable to your database is potentially dangerous. If you didn't know about mysql_real_escape_string, look it up NOW.
Better yet, start using a database wrapper that escapes values for you.
I would add the condition into the where, if you only care about one field.
It is not clear what you mean by empty. That could be either NULL or a blank string. My guess is that myAge is a number, so NULL would be "empty":
UPDATE info
SET myAge = ".$age."
WHERE account_id = 1 AND myAge IS NOT NULL;
You can also do this in the SET, if you like:
UPDATE info
SET myAge = (CASE WHEN myAge IS NOT NULL THEN ".$age." END)
WHERE account_id = 1;
This is necessary if you have multiple columns that you want to update like this. I much prefer CASE over IF(), because CASE is ANSI standard and available in most databases.
I am trying to develop a system to display products from a database on a webpage. Normally this is no problem, except that one manufacturer has several abnormal part numbers I need to sort by properly.
Normally, I could just use an invisible column to sort things out, but then that makes inserting new items in-between two older ones much more difficult.
For example, here are some of the part numbers:
1211
1225
14-302
14-303
2015
23157
3507
35280UP
42-3309
42-3312
4241
Now, the normal order by mfgr produces the above order.
What it SHOULD be is something more like this:
14-302
14-303
42-3309
42-3312
1211
1225
2015
3507
4241
23157
35280UP
What is going to be my best bet on sorting this properly? If they were just being made in a csv file and uploaded afterwards this wouldn't be a problem. But because of automatic database changes, the server will be modifying values in real time. So manually updating it is out of the question. This means that a back end will be required to insert new items, but by what method could I insert an item between another? I would prefer to not resort to something like decimals to give me X in between values (Like I have 1.00 and 2.00 and I want to put another between them so I make it 1.50).
Any help would be appreciated.
EDIT:
The way I would like to sort it is this:
if it has a hyphen, ie 14-302, it is sorted by the 14, and then any 14-xxx is sorted by the numbers after the hyphen.
then, just numbers would be sorted by their actual numner, 802 comes before 45768.
Then any number that has a letter(s) after it will be sorted by the number, the the letter so 123a comes before 123b but after 122. And 123b comes before 124c.
and lastly anything that begins with an M- will be sorted last, and by the numbers after the hyphen.
With your adjusted question the principle is still the same.
My first instinct was to go with extra field but you could use stored function for those.
You still need to divide your partnumber in 3 parts (I take it it's not more than that).
Part1 is an INTEGER and has the first number if existing else fill it with maxint (largest for part1).
Part2 is a CHAR and contains letters if existing.
Part3 is an INTEGER containing the second number if existing.
You can sort by calling a function for these values.
Here is the complete source:
For your convenience a link to a working SQL Fiddle.
It's really sloppy/fast programming but it works. Put together in little time and i'm sure there are points for improvement. You can tweak it yourself. Later you can delete the part1, part2 and part3 from the view. (but leave it in the order by) It's only to show how the sort is done.
DROP PROCEDURE IF EXISTS `uGetParts`//
DROP FUNCTION IF EXISTS `uExtractPart1`//
DROP FUNCTION IF EXISTS `uExtractPart2`//
DROP FUNCTION IF EXISTS `uExtractPart3`//
CREATE PROCEDURE `uGetParts`(
IN ins varchar(50),
OUT num1 int unsigned,
OUT num2 int unsigned,
OUT num3 int unsigned)
NO SQL
BEGIN
SET num1=0;
SET num2=0;
SET num3=0;
WHILE (num1<length(ins)) AND
(SUBSTRING(ins,num1+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num1=num1+1;
END WHILE;
SET num2=num1;
WHILE (num2<length(ins)) AND
(SUBSTRING(ins,num2+1,1) REGEXP('(^[0-9]+$)')=0) DO
SET num2=num2+1;
END WHILE;
SET num3=num2;
WHILE (num3<length(ins)) AND
(SUBSTRING(ins,num3+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num3=num3+1;
END WHILE;
END//
CREATE FUNCTION `uExtractPart1`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num1>0 THEN
RETURN CAST(SUBSTRING(ins,1,num1) AS UNSIGNED);
ELSE
RETURN ~0 >> 32;
END IF;
END//
CREATE FUNCTION `uExtractPart2`(ins varchar(50))
RETURNS varchar(50) NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num2>num1 THEN
RETURN SUBSTRING(ins,num1+1,num2-num1);
ELSE
RETURN '';
END IF;
END//
CREATE FUNCTION `uExtractPart3`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num3>num2 THEN
RETURN CAST(SUBSTRING(ins,num2+1,num3-num2) AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END//
You can call it like this:
SELECT
id,
TYPE,
uExtractPart1(TYPE) as part1,
uExtractPart2(TYPE) as part2,
uExtractPart3(TYPE) as part3
FROM Article
ORDER BY
uExtractPart1(TYPE),
uExtractPart2(TYPE),
uExtractPart3(TYPE)
Is it possible to serialize data posted by a form, and insert it into a database, and then if it needs updating, unserialize it, and just update the data that was changed?
If it is possible, would someone be kind enough to provide/write a small script to do this?
To answer your question directly all you need to do is:
$data = serialize($_POST);
$sql = "INSERT INTO `table` (`data`) VALUES ('".addslashes($data)."')";
...
However I would strongly recommend you don't serialize the data and put it into the database. It'll make the data very difficult to search, update, etc. You'll be forced to rely on your application to maintain the data integrity!
I would suggest designing a database table that fits in with your form's structure... If your form structure is dynamic, then you'll need to create multiple tables in order to store the data.
Yes, you can do this, but it's not clear why you would want/need to. It'd be difficult/impossible to query the serialized data once it's in the database, and to update it you would have to pull the data from the database, unserialize it, update it, serialize it, and update the appropriate row.
You could encode the POSTDATA using JSON and store in database. When you need the data back, just decode the JSON.
Here's a function to extract single string values from serialized data...
Usage would be something like...
SELECT PARSE_PHP_SERIALIZED_DATA_STRING(
`serialized_data`, 'EmailAddress'
)
FROM `your_table`
WHERE `serialized_data` LIKE '%EmailAddress%'
Here's the function:
DELIMITER $$
--
-- Functions
--
DROP FUNCTION IF EXISTS `PARSE_PHP_SERIALIZED_DATA_STRING`$$
CREATE FUNCTION `PARSE_PHP_SERIALIZED_DATA_STRING`(str_data BLOB, str_index VARCHAR(252)) RETURNS varchar(255)
DETERMINISTIC
BEGIN
-- Declare variables must be done at top of BEGIN END
DECLARE start_pos INT;
DECLARE string_out VARCHAR(255);
DECLARE string_search VARCHAR(255);
DECLARE quote_string VARCHAR(6);
-- The indexes in a php serialized string are quoted and end with a semi-colon
-- Setting the quote string incase the "developer" before you (who thought
-- it was a good idea to dump serialized data in to a single column) then also
-- randomly html encoded the string every third row.
SET quote_string = '"';
SET string_search = CONCAT(quote_string, str_index, quote_string, ';');
-- search for the index
SET start_pos = LOCATE(string_search, str_data);
IF start_pos = 0 THEN
-- not found it so lets search again but with html entities
SET quote_string = '"';
SET string_search = CONCAT(quote_string, str_index, quote_string, ';');
SET start_pos = LOCATE(string_search, str_data);
END IF;
-- still not found it, then it is not there as an index
IF start_pos = 0 THEN RETURN '';
END IF;
-- cut up the string to get just the value
-- the offsets here work for string values
-- maybe a different function for integer values??
SET start_pos = start_pos + LENGTH(string_search);
SET string_out = SUBSTRING(str_data, start_pos,255);
IF SUBSTRING(string_out,1,2) = 'N;' THEN
RETURN '';
END IF;
SET string_out = SUBSTRING(string_out, LOCATE(quote_string,string_out)+LENGTH(quote_string), LOCATE(quote_string,string_out,LOCATE(quote_string,string_out)+1));
SET string_out = SUBSTRING(string_out, 1,LOCATE(quote_string,string_out)-1);
RETURN string_out;
END$$
DELIMITER ;
I'm programming a script using PHP and MySQL and I want to get a
unique id (consisting of a string: capitals and small
letters with numbers) like: gHYtUUi5b.
I found many functions in PHP that can generate such numbers but I'm afraid about how to ensure the id is unique!
UPDATE: uuid is long, I mean such id like: (P5Dc) an 11 alphanumeric char.
EDIT: This answer has been flagged for being dangerous in the context of destroying a database. Do NOT use this code to generate unique ids in databases!
I use UUID() to create a unique value.
example:
insert into Companies (CompanyID, CompanyName) Values(UUID(), "TestUUID");
You may like the way that we do it. I wanted a reversible unique code that looked "random" -a fairly common problem.
We take an input number such as 1,942.
Left pad it into a string: "0000001942"
Put the last two digits onto the front: "4200000019"
Convert that into a number: 4,200,000,019
We now have a number that varies wildly between calls and is guaranteed to be less than 10,000,000,000. Not a bad start.
Convert that number to a Base 34 string: "2oevc0b"
Replace any zeros with 'y' and any ones with 'z': "2oevcyb"
Upshift: "2OEVCYB"
The reason for choosing base 34 is so that we don't worry about 0/O and 1/l collisions. Now you have a short random-looking key that you can use to look up a LONG database identifier.
A programmatic way can be to:
add a UNIQUE INDEX to the field
generate a random string in PHP
loop in PHP ( while( ! DO_THE_INSERT ) )
generate another string
Note:
This can be dirty, but has the advantage to be DBMS-agnostic
Even if you choose to use a DBMS specific unique ID generator function (UUID, etc)
it is a best practice to assure the field HAS to be UNIQUE, using the index
the loop is statistically not executed at all, it is entered only on insert failure
If you use MySQL with version higher than 5.7.4, you can use the newly added RANDOM_BYTES function:
SELECT TO_BASE64(RANDOM_BYTES(16));
This will result in a random string such as GgwEvafNLWQ3+ockEST00A==.
How you generate the unique_ids is a useful question - but you seem to be making a counter productive assumption about when you generate them!
My point is that you do not need to generate these unique id's at the time of creating your rows, because they are essentially independent of the data being inserted.
What I do is pre-generate unique id's for future use, that way I can take my own sweet time and absolutely guarantee they are unique, and there's no processing to be done at the time of the insert.
For example I have an orders table with order_id in it. This id is generated on the fly when the user enters the order, incrementally 1,2,3 etc forever. The user does not need to see this internal id.
Then I have another table - unique_ids with (order_id, unique_id). I have a routine that runs every night which pre-loads this table with enough unique_id rows to more than cover the orders that might be inserted in the next 24 hours. (If I ever get 10000 orders in one day I'll have a problem - but that would be a good problem to have!)
This approach guarantees uniqueness and takes any processing load away from the insert transaction and into the batch routine, where it does not affect the user.
Use UUID function.
I don't know the source of your procedures in PHP that generates unique values. If it is library function they should guarantee that your value is really unique. Check in documentation. You should, hovewer, use this function all the time. If you, for example, use PHP function to generate unique value, and then you decide to use MySQL function, you can generate value that already exist. In this case putting UNIQUE INDEX on the column is also a good idea.
DELIMITER $$
USE `temp` $$
DROP PROCEDURE IF EXISTS `GenerateUniqueValue`$$
CREATE PROCEDURE `GenerateUniqueValue`(IN tableName VARCHAR(255),IN columnName VARCHAR(255))
BEGIN
DECLARE uniqueValue VARCHAR(8) DEFAULT "";
DECLARE newUniqueValue VARCHAR(8) DEFAULT "";
WHILE LENGTH(uniqueValue) = 0 DO
SELECT CONCAT(SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1),
SUBSTRING('ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789', RAND()*34+1, 1)
) INTO #newUniqueValue;
SET #rcount = -1;
SET #query=CONCAT('SELECT COUNT(*) INTO #rcount FROM ',tableName,' WHERE ',columnName,' like ''',newUniqueValue,'''');
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
IF #rcount = 0 THEN
SET uniqueValue = #newUniqueValue ;
END IF ;
END WHILE ;
SELECT uniqueValue;
END$$
DELIMITER ;
And call the stored procedure as GenerateUniqueValue('tableName','columnName'). This will give you a 8 digit unique character everytime.
To get unique and random looking tokens you could just encrypt your primary key i.e.:
SELECT HEX(AES_ENCRYPT(your_pk,'your_password')) AS 'token' FROM your_table;
This is good enough plus its reversable so you'd not have to store that token in your table but to generate it instead.
Another advantage is once you decode your PK from that token you do not have to do heavy full text searches over your table but simple and quick PK search.
Theres one small problem though. MySql supports different block encryption modes which if changed will completely change your token space making old tokens useless...
To overcome this one could set that variable before token generated i.e.:
SET block_encryption_mode = 'aes-256-cbc';
However that a bit waste... The solution for this is to attach an encryption mode used marker to the token:
SELECT CONCAT(CONV(CRC32(##GLOBAL.block_encryption_mode),10,35),'Z',HEX(AES_ENCRYPT(your_pk,'your_password'))) AS 'token' FROM your_table;
Another problem may come up if you wish to persist that token in your table on INSERT because to generate it you need to know primary_key for the record which was not inserted yet... Ofcourse you might just INSERT and then UPDATE with LAST_INSERT_ID() but again - theres a better solution:
INSERT INTO your_table ( token )
SELECT CONCAT(CONV(CRC32(##GLOBAL.block_encryption_mode),10,35),'Z',HEX(AES_ENCRYPT(your_pk,'your_password'))) AS 'token'
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = "your_table";
One last but not least advantage of this solution is you can easily replicate it in php, python, js or any other language you might use.
Below is just for reference of numeric unique random id...
it may help you...
$query=mysql_query("select * from collectors_repair");
$row=mysql_num_rows($query);
$ind=0;
if($row>0)
{
while($rowids=mysql_fetch_array($query))
{
$already_exists[$ind]=$rowids['collector_repair_reportid'];
}
}
else
{
$already_exists[0]="nothing";
}
$break='false';
while($break=='false'){
$rand=mt_rand(10000,999999);
if(array_search($rand,$alredy_exists)===false){
$break='stop';
}else{
}
}
echo "random number is : ".$echo;
and you can add char with the code like -> $rand=mt_rand(10000,999999) .$randomchar; // assume $radomchar contains char;
For uniqueness what I do is I take the Unix timestamp and append a random string to it and use that.
<?php
$hostname_conn = "localhost";
$database_conn = "user_id";
$username_conn = "root";
$password_conn = "";
$conn = mysql_pconnect($hostname_conn, $username_conn, $password_conn) or trigger_error(mysql_error(),E_USER_ERROR);
mysql_select_db($database_conn,$conn);
// run an endless loop
while(1) {
$randomNumber = rand(1, 999999);// generate unique random number
$query = "SELECT * FROM tbl_rand WHERE the_number='".mysql_real_escape_string ($randomNumber)."'"; // check if it exists in database
$res =mysql_query($query,$conn);
$rowCount = mysql_num_rows($res);
// if not found in the db (it is unique), then insert the unique number into data_base and break out of the loop
if($rowCount < 1) {
$con = mysql_connect ("localhost","root");
mysql_select_db("user_id", $con);
$sql = "insert into tbl_rand(the_number) values('".$randomNumber."')";
mysql_query ($sql,$con);
mysql_close ($con);
break;
}
}
echo "inserted unique number into Data_base. use it as ID";
?>
crypt() as suggested and store salt in some configuration file, Start salt from 1 and if you find duplicate move to next value 2. You can use 2 chars, but that will give you enough combination for salt.
You can generate string from openssl_random_pseudo_bytes(8). So this should give random and short string (11 char) when run with crypt().
Remove salt from result and there will be only 11 chars that should be enough random for 100+ millions if you change salt on every fail of random.
You might also consider using crypt()* to generate a [nearly-guaranteed] unique ID inside your contraints.
USE IT
$info = random_bytes(16);
$info[6] = chr(ord($info[6]) & 0x0f | 0x40);
$info[8] = chr(ord($info[8]) & 0x3f | 0x80);
$result =vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($info), 4));
return $result;
This generates random ids:
CREATE TABLE Persons (
ID Integer PRIMARY KEY AUTOINCREMENT,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Age int
);
You could use Twitter's snowflake.
In short, it generates a unique id based on time, server id and a sequence. It generates a 64-bit value so it is pretty small and it fits in an INT64. It also allows for sorting values correctly.
https://developer.twitter.com/en/docs/basics/twitter-ids
In sum, it allows multiple servers, highly concurrency, sorting value and all of them in 64 bits.
Here it is the implementation for MySQL
https://github.com/EFTEC/snowflake-mysql
It consists of a function and a table.