I know there are many posts about this one, but there are a few things that none of them answered so far. Not here or on the Laracast forum.
No.1: What is the best practice to do this? I saw couple different ways of doing this. Should I set it in Model or in the Controller? Is there an automated way or it's just PHP str_random(6) function?
No.2: How to approach the possibility that the newly generated key could be a duplicate? Do I have to check it manually? I know that Laravel will throw the crap error if you tried to enter a duplicate in DB and I don't want that kind of an error on the live application.
No3: Will it slow down the application speed? My guess is if it has to check for duplicates, if the DB is large, it would be slow.
No.4: Should this even be done? I want to give this key to customers on the site, as their transaction key, for example. I don't want to know them how may of transactions there were before their one or give them any info regarding that. Security reasons.
I could guess answers, but I'm not 100% sure. If anyone has done this, I would appreciate any answers.
I would use an integer primary key and add a separate column for the string key (with an unique index). Integer keys are faster and easier to handle (on joins etc.).
If the string key has a fixed length, you should use a CHAR column to get the best performance.
I would put the key generation into the controller:
$key = str_random(6);
while(YourModel::where('key', $key)->exists()) {
$key = str_random(6);
}
$yourModel->key = $key;
Or you just try a key and catch the unlikely case of a duplicate value:
$yourModel->key = str_random(6);
try {
$yourModel->save();
catch(Illuminate\Database\QueryException $e) {
<code from above>
$yourModel->save();
}
I don't think it's good design to generate random strings as primary keys.
If you want to "hide" your transaction ids you could just hash/obfuscate your table key with vinkla/laravel-hashids.
Cool link: easy-id-obfuscation-with-laravel-5
No.1 : You can use uniqid() it will generate a unique identifier based on the current time in microseconds, you can do this just to make sure it's unique $id = uniqid().str_random(5);
No.2 : using the above answer it's almost impossible to get duplicate keys but you can do this to 100% avoid it
try {
$model->id = uniqid().str_random(5);
$model->save();
}catch(\Exception $e)
{
if something wrong happens try again or you can make it recrussive until it's able to save a unique key
}
No.3 : i guess so
No.4 : i don't recommend it, create another column which is also unique but it's something you give to users but it's not the primary key.
so this way, you can give each client/user a unique key so you can get his data later using it while still using auto increment as a primary key for other operations.
Good luck
I agree with #Jonas Staudenmeir that you should not use this as a primary key. You're not doing the DB any favours if you do this. Instead, treat it like regular application data; specifically, it sounds like a slug.
If you're building a tool to generate this key, then you can validate it like any other column to ensure it is unique.
Related
So. I have searched for this problem in depth and haven't found a solution, but I solved it by mistake.
table users:
$table->id('user_id'); // I want it to be named like that specifically let's say I'm a maniac.
table whatever:
$table->foreignId('user_id')->constrained('users');
//now this should work, but it doesn't, the error seems to be that it is looking in "users" table for id instead of user_id.
What I've done is this: constrained('users', 'user_id')
//now I can't seem to find any documentation on this, to know exactly if it's OK, but it works.
I want to add that I'm a novice in programming, so the question is, is this ok to leave like this?
or should I do it the old way: ->references('user_id')->on('users') ?
Take a look at what the constrained function do behind the scenes:
public function constrained($table = null, $column = 'id')
{
return $this->references($column)->on($table ?? Str::plural(Str::beforeLast($this->name, '_'.$column)));
}
it actually using the same old way.
you can notice that the default value for the id column going to be 'id' if not provided, so the right answer will be, passing the custom id column as the second parameter will solve this issue.
In short, you did it right
I am using the commonly known reddit 'hot' algorithm on my table 'posts'. Now this hot column is a decimal number like this: 'XXXXX,XXXXXXXX'
I want this column to be an index, because when I order by 'hot', I want the query to be as fast as possible. However, I am kind of new to indexes. Does an index need to be unique?
If it has to be unique, would this work and be efficient?
$table->unique('id', 'hot');
If it does not have to be unique, would this be the right approach?
$table->index('hot');
Last question: would the following query be taking advantage of the index?
Post::orderBy('hot', 'desc')->get()
If not, how should I modify it?
Thank you very much!
Do not make it UNIQUE unless you need the constraint that you cannot insert duplicates.
Phrased differently, a UNIQUE key is two things: an INDEX (for speedy searching) and a "constraint" (to give an error when trying to insert a dup).
ORDER BY hot DESC can use INDEX(hot) (or UNIQUE(hot)). I say "can", not "will", because there are other issues where the Optimizer may decide not to use the index. (Without seeing the actual query and knowing more about the the dataset, I can't be more specific.)
If id is the PRIMARY KEY, then neither of these is of any use: INDEX(id, hot); UNIQUE(id, hot). Swapping the order of the columns makes sense. Or simply INDEX(hot).
A caveat: EXPLAIN does not say whether the index is used for ORDER BY, only for WHERE. On the other hand, EXPLAIN FORMAT=JSON does give more details. Try that.
(Yes, DECIMAL columns can be indexed.)
I am trying to come up with a solution to generate a Unique Id preferably on the Fly. Usage scope could be Order, Product or Plan Id, where there is no security involved.
I don't like idea of generating a random number and then querying the db to check its uniqueness and repeating the process if it is not in this case where security isn't an issue.
Also I don't prefer using Auto Increment id since it looks so simple.
My initial thought is to use a combination of Auto Increment id + timestamp converting the decimal value to hex so it looks like a random string. And then finally prefixing and suffixing it with 2 digit random string.
function generateUID($num){
$str = dechex(time()+ intval($num));
$prefix = dechex(rand(1,15));
$suffix = dechex(rand(1,15));
return strtoupper($suffix.$str.$prefix);
}
Where $num is the auto_increment id
Returns something like E53D42A046
Is this the right way to go about doing this, are there collision issues ?
I thank all responses..!
I acknowledge the usefulness of uniqid() but in this context to be genuinely unique Auto_Increment need to play a significant part so how will it do so in uniqid. Passing it as a prefix would result in a Product id which vary greatly in size. (153d432da861fe, 999999953d432f439bc0).
To expand the scope further, Ideally we want a unique code which looks random with fairly consistent length and could be reversed to the auto_increment id from which it was created.
Such a function already exists - uniqid()
http://php.net/manual/en/function.uniqid.php
It works based on the timestamp down to the microsecond - you can add a prefix based on the process ID to further refine it. There are a couple more robust versions out there as well - see PHP function to generate v4 UUID
I'm using the function below to create a unique field that is prefixed with a letter.
Will this cause any problems if its used for transactions with many users?
The scenario is that this function is called inside a loop and if there are many users simultaneously using the system. Will this cause any errors?Is there a better way to do this?I'm using the GLAccountID as foreign key to the other tables.
function getID(){
global $db;
$max_gl = $db->get_var("SELECT MAX(GLAccountNumber) FROM sys_glaccount");
if($max_gl == null){
$max_gl = 1;
}else if($max_gl > 0){
$max_gl = $max_gl + 1;
}
return 'GLA'.$max_gl;
}
The table looks something like this, GLAccountNumber is the primary key and I set it to auto-increment.
There are ways to do this, but you shouldn't really. Seriously. This is heavy and dangerous and the developer after you will cry a bit at night.
Please consider using the accountNumber, and just adding the GLA whenever you retrieve it? This way you have the simple, quick and correctness of the auto-id, and can pretttify it when you want.
Another option is to make it a combined key, with your prefix in one column and the number in the auto-increment, although I don't see why you should want it.
In the end, if all you do is add three letters, you don't need to actually do it in the same field :)
You can actually get the account id in the format you want without a new row:
SELECT CONCAT('GL',GLAccountNumber) AS GLaccountID FROM `sys_glaccount`
Rather than making a whole new column just to be referenced...
Alternatively, if your intention is to avoid confusion with identical fields, you can use
SELECT g.GLAccountNumber FROM `sys_glaccount` g INNER JOIN `sys_glaccount2` g2 ON g2.GLAccountNumber=g.GLAccountNumber
Without the query getting confused without unique field names
I have two functions, makeKey() and keyExists().
makeKey() simply generates a 5 digit random alphanumeric key, keyExists() accepts this key as its only argument, and looks up in a table, returning true/false depending on whether it exists.
I need to do something very simple but I cannot figure out the quickest way of doing it.
I just need to make a key, and if it exists in the table, make a key again, and so on until a unique one is returned. I think a while loop will suffice?
Thanks and please forgive the rather basic question, I think I cooked my brain in the sun yesterday.
I’d use a do-while loop:
do {
$newKey = makeKey();
} while (keyExists($newKey));
This will generate a new key on every iteration until the key does not exist yet.
Any solution that relies on creating, then checking is going to have awful performance as the key space fills up. You'd be better off generating a unique key using an autogenerated column (identity or guid). If it needs to be alphanumeric, use a mapping function to transform it into the alphabet of your choice by selecting groups of bits and using them as an index into your alphabet.
Pseudo-code
alphabet = "ABCDE...789";
key = insert new row, get autogenerated key
alphaKey = "";
while (get n bits from key)
alphaKey += alphabet[bits]
done
echo alphaKey
my php is a little rusty, so consider this pseudo-code:
$key_exists = true;
while($key_exists) {
$key = generateKey();
$key_exists = checkKey($myKeysHash, $key);
}
// $key is now unique and ready to use
Why not use a built in php function like uniqid()?
You mention a table, so I'm wondering if you are storing these keys in a database? If so, your approach is going to have a race condition - you might check a key is OK to use right before another process uses that key.
A better approach is generate a possible key and then attempt to persist it - perhaps by performing an INSERT onto a table of keys and retrying with different keys until it succeeds.
I'll also assume you're using some sort of database.
Could you not use a unique auto-increment ID column in the database? It would remove the requirement to check if the key exists since the database engine will never assign the same ID twice.
However, you'd have to change the logic in your application rather than just coding up new functions.
Does it need to be random? Just increment a variable and store the next one to be used in another field.
while (keyExists($newKey = makeKey()));
Probably the quickest way of doing the check, if a key exists it will generate a new one. If you start having a lot of collisions/needing to check the database many times before getting a new unique key, you probably will want to rethink your makeKey() algorithm. Calls to the DB are expensive, the fewer calls you can make the faster and more efficient your script will be.
If you're not fixed on a 5-digit number, you could think about using a hash of your id + a name column.