I have a table with primary key an auto incremental integer.
The table holds data of real world objects.
Now I want each row to have a uuid. I don't want to change the primary key. But I do want to be absolutely sure the assigned uuid doesn't get overwritten (So only create UUID on insert, not update)
I want to use this uuid as a QR code, so it needs be as unique to a record as the primary key is.
Any ideas how to do this?
(I'm using cakePHP2, so it might be behaviour like the created timestamp. This also only done with the original INSERT)
Purely cake options
There are many ways to do this...
The least code invasive I think, would be to add a check in beforeSave of the corresponding model, something like
public function beforeSave() {
if (!$this->id && !isset($this->data[$this->alias][$this->primaryKey])) {
// insert
$this->data[$this->alias]['uuid'] = $this->createUUID();
} else {
// edit
// if you don't want to do anything on insert, delete this "else"
}
return true;
}
(that code was copied from here).
Now, you didn't provided much details as to what the uuid should be (integer, length, readable word...). But let's assume it's a simple integer, if it's not, all the changes needs to be done here. In the same model, add a function to create the uuid
private function createUUID() {
do {
$uuid = rand(); //any logic you want to create the uuid, a md5, substring, hash...
//check for no repetition
$help = $this->find('first', array('conditions'=>array('uuid'=>$uuid)));
} while(!empty($help));
return $uuid;
}
And that way, you'll have an unique uuid, only added on insert, without changing the rest of the code.
This is the solution I like.
Now, another way to do this, for example, would be to create the UUID function that returns a value, and using it in afterSave (check if it was an insert or an update) and do an update of the record with the uuid (you'll have the id of the new created record by then).
There's not much advantage of one versus the other... the beforeSaveone allows you to set the uuid field in the database as NOT NULL, while if you use the afterSave, you need to insert the record first, and the uuid should be null for that.
Without much detail, I recommend the beforeSave option, just because I of the uuid NOT NULL thing.
You could use an insert trigger on the table to set a uuid using the mysql UUID() function. You could the use a before update trigger to prevent the uuid from being changed (i.e. NEW.uuid=OLD.uuid in the trigger).
Related
I have a database in mysql, and a table called Animals, I use this condition to add news records.
public function create()
{
$animals = Animals::all();
$last_animal_id = collect($animals)->last();
if ($last_animal_id->id == $last_animal_id->id) {
$last_animal_id->id = $last_animal_id->id + 1;
} else {
return false;
}
return view('animal.create-animals')->with('last_animal_id', $last_animal_id);
}
I work in laravel and php, and that is my controller 'AnimalsController', the condition add +1 to the last id that is registered in the table.
For example, I have 4 records and I delete the last record, without my condition, after I have added a new record the new record will take the value 6.
And that is the reason that I add manually new records, with this condition, the condition find the last id, and add +1 to the last id, not +2 if I not have this condition. Not directly, I pass the value to an input and then I send the form in my view.
Is possible to add +1 id in the table, if I delete a record in the middle, or before the last record? As the following example explains:
Table Animals
/*NOTE: The field 'id' HAVE THE FOLLOWING ATTRIBUTES:
AUTO_INCREMENT, IS 'NOT NULL','PRIMARY KEY', AND HIS TYPE IS 'INT'*/
id|name |class
1 |Dog |Mammal
2 |Cat |Mammal
3 |Sparrow|Bird
4 |Whale |Mammal
5 |Frog |Amphibian
6 |Snake |Reptile
Then I delete the id, 2, and 3.
In addition to the condition that already exists, I would like to create another condition that allows to add new records among the others, only if there are missing records in between of others.
Using the previous example:
I said that I will delete the id 2 and 3 right? The new condition must allow to create again the records with the id 2 and 3 between the records with the id 1 and 4.
If I delete another record the condition must perform the same function. Certainly replacing the records with corresponding id that were previously deleted.
For more details: I use a form to create new animals to the table Animals, previously I said in the example, that I will delete the records with id 2 and 3, then If the condition in my controller, and my form in my view, work properly then I can add again the animal with id 2, and then in a new form add again the animal with id 3.
Thus, if my question was not understood very well or you thought that my function should add the record(s) simultaneously, you understood it wrong, because It's that not the function that I would like to do in the function.
One thing to keep in mind when working with relational databases is that the id column is usually used to relate this data and as such it can and will appear in other tables. If you arbitrarily renumber things here, you're damaging those links and potentially scrambling up your data.
If ordering is important, create a column for that purpose, for example one called position or something similar. This one you can manipulate freely without concern about altering relations.
Generally your id value should be:
Always populated (e.g. NOT NULL)
Integer (e.g. INT or BIGINT)
Set as your primary key (e.g. PRIMARY KEY)
Generated automatically (e.g. AUTO_INCREMENT)
Never changed, it's permanently assigned
Never recycled and used for another record
Recycling id values is how you create enormous security problems. It's all too easy for a user to "inherit" all the data that came with an old user ID value you've recycled. The safest thing is to never, ever re-use these values.
They're just IDs. Forget about holes or lack of ordering. Any production database will end up with lots of interesting patterns there that are unavoidable, but it doesn't matter.
One exception to this is when creating seed databases. Here you can fuss over the ordering to get things arranged as you want because this is before you insert the data into the database.
At the end of the day you'll want to ensure that:
These numbers don't overflow (e.g. INT keyed table at 2.1 billion)
These numbers aren't exposed to users in a way that makes it possible to enumerate your table (e.g. ID value in a URL)
Just think of them as internal identifiers, like a serial number, and you'll be fine. In fact, MySQL now supports SERIAL as a datatype for this reason, that's an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE which is a good default for systems designed in 2018.
There is a really great answer from Tadman about the implications of your solution.
To give you an alternative to your own solution, you can do something like this....
First, create an order column, an int.
Them, instead of looking at the latest id value, do this...
$highestOrder = Animal::max('order');
And then 'up it'... :-) Just an idea.
BTW: to give you more options, you can look directly in a table as well:
DB::table('animals')->max('order');
... but I would not do that in this case. The model class is the best 'gateway' to this information, not the DB facade directly.
I am working with a legacy table schema which has a primary key that is not a standard auto-incrementing id. Instead, the primary key is VARCHAR(15) in the format:
ABC10001
ABC10002
ABC10003
ABC10004
ABC10005
Is there a way to set up the model Table so that it automatically generates the next ID in the sequence when inserting a new row?
Is there a way to set up the model Table so that it automatically generates the next ID in the sequence when inserting a new row?
Use the beforeSave() callback and implement a method to generate the next id.
public function beforeSave(Event $event, EntityInterface $entity) {
if ($entity->isNew()) {
$entity->id = $this->generateId();
}
}
public function generateId() {
// Your implementation here.
}
Generally, in order to generate custom, new primary key values, you could either use
a custom type class that implements/overrides \Cake\Database\Type::newId()
an overridden \Cake\ORM\Table::_newId() method in the respective table class, which by default asks the type associated with the column for a new ID (via Type::newId())
the Model.beforeSave callback
or a trigger in your database
In your case you'd need to have access to a specific repository, so the type class feels like the wrong place, as it would hardly be usable accross different repositories, considering that type instances are being reused and configured globally.
Personally I'd probably go with triggers in order to ensure integrity at the top most level, and in case they cannot be used for whatever reason, chose the second option, as it is only invoked where it's needed, that is, right before data is being inserted, and the generated value is only being used in case the data doesn't already contain a primary key value. All you'd need to do, is to read the last/highest ID, and return it incremented by 1.
See
API > \Cake\Database\Type::newId()
API > \Cake\ORM\Table::_newId()
Cookbook > Database Access & ORM > Table Objects > Lifecycle Callbacks > beforeSave
Google > Database Triggers
How does lastInsertId() work for tables that do not have an auto-incremented field? What about tables where the primary key is made up of 2 fields?
(I'm working with MySQL)
In both cases above it will return 0.
When using an auto_increment column, it will return the last INSERT ID even if it was specified (i.e. the auto increment was not used).
That is to say you should only use lastInsertId when using auto increment. It doesn't really make sense to use it otherwise since you would have to know the keys ahead of time anyway..
I don't think it does as it is a function specifically designed to be used to retrieve the value of an AUTO_INCREMENT field.
http://php.net/manual/en/function.mysql-insert-id.php
mysql_insert_id
Retrieves the ID generated for an AUTO_INCREMENT column by the previous query (usually INSERT).
This type of thing is easy enough to test - have you tried it to see what happens?
This is essentially the same question as CakePHP: Is it possible to insert record with pre-defined primary key value?. In CakePHP 1.2 I want to insert a record with a pre-determined id. The id's value is determined by an external system, but unlike in the linked question I have no control over that system, so the solution to that question is not an option for me.
If the id field of a model is set when calling $model->save(), Cake will always try to update the record. Cake will even set the id to false, if a record with that id does not exist already [1]:
<?php
function save($data = null, $validate = true, $fieldList = array()) {
// snip
if (!$this->__exists && $count > 0) {
$this->id = false;
}
// snip
}
Saving the model first and then updating the id manually is also not an option, because this would break referencial integrity (this is an architectural limitation that I also have no control over).
So how can I force CakePHP to insert the record with a specific primary key?
Some background around the problems I'm facing:
The application I'm working on is backed by a Postgres database, so new primary keys are determined by sequences. There is a process where an external system will SELECT nextval('my_model_sequence'), do some work with that id and then pass it to the CakePHP application, where I want to INSERT a my_model record with that id.
There is no way for me to invoke the external process after saving the record. There is also no way for me to modify the external process at all.
[1] http://api12.cakephp.org/view_source/model/#l-1260
If you want to create a record with specific id just make sure you fill id field
$this->data['ModelName']['id'] = $your_id;
Also before calling:
$this->save($this->data);
you should remember to call
$this->create();
which will reset the model state which let you create new record.
Finally you should try:
$this->ModelName->create();
$this->ModelName->save($this->data);
On the other hand as I read your post and trying to imagine your tables structure I recommend you thinking over leaving the id field alone and trying to make this foreing id a Foreign key. Is that an option for you?
I want to build a database-wide unique id. That unique id should be one field of every row in every table of that database.
There are a few approaches I have considered:
Create one master-table with an auto-increment-field and a trigger in every other table, like:
"before insert here, insert in master-table -> get the auto-increment value -> and use this value as primary-key here"
I have seen this before, but instead of making one INSERT, it does 2 INSERTS, which I expect would not be that performant.
Add a field uniqueId to every table, and fill this field with a PHP-generated integer... something like unix-timestamp plus a random number.
But I had to use BIGINT as the datatype, which means big index_length and big data_length.
Similar to the "uniqueId" idea, but instad of BIGINT I use VARCHAR and use uniqid() to populate this value.
Since you are looking for opinions... Of the three ideas you give, I would "vote" for the uniqid() solution. It seems pretty low cost in terms of execution (but possibly not implementation).
A simpler solution (I think) would be to just add a field to each table to store a guid and set the default value of the field to be MySQL's function that generates a guid (I think it is UUID). This lets the database do the work for you.
And in the spirit of coming up with random ideas... It would be possible to have some kind of offline process fill in the IDs asynchronously. Make sure every table has the appropriate field and make the default value be 0/empty. Then the offline process could simply run a query on each table to find the rows that do not yet have a unique id and it could fill them in. That would let you control the ID and even use some kind of incrementing integer. This, of course, requires that you do not need the unique ID instantly each time a record is inserted.