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?
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 have a keys only query - something like this -
$Sub = $obj_store->fetchOne("SELECT key WHERE key HAS ANCESTOR KEY(Subscribe, $key)");
I set a value on one of the fields
$Sub->active = TRUE;
then do an upsert
$obj_store->upsert($Sub);
The one value is updated, but the other existing columns are erased. If I do a select * the values are not erased.
I need to avoid a full record read for billing.
How can I update a field in a record and avoid deleting all the others? I'm using the php-gds wrapper for cloud-datastore.
the desire would a query like the following
update subscribe set active = TRUE where keyid = 1234
Writes to Cloud Datastore assume that you are sending in a "whole" entity. You cannot update specific properties of an entity like you can in a relational database. Like you found out, correct way to update an entity is to first read the whole entity from the Datastore, then change one or more properties and then save.
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
For example: I create new model, set the id field to 1 and save it. It runs fine and saves with the id = 1 into a database. I open MySQL console and delete all records from the table. When I delete it, create new model again and set the id field to 1 - it actually saves it with the 2 as the id field value.
I'm guessing Yii gets the current auto_increment value from mysql and override my id value. Is there a way to prevent that behavior?
EDIT (my code sample):
$sn = new SaplingNode();
$sn->id = 1;
$sn->save();
I call it twice, between calls I delete the record using mysql console. That's all.
There is no way to do this by Yii framework. As its a default behavior of MYSQL.
You can simply do this and reset the Auto increment id after you truncate.
ALTER TABLE tablename AUTO_INCREMENT = 1
This is not a best practice, but if you really want to forcefully set the id of new record on auto increment column, you can do like below:
First under your rules() method of "SaplingNode" model put below line:
array('id', 'safe'),
Then use your code to save new record with whatever id you like, for example:
$sn = new SaplingNode();
$sn->id = 10;
$sn->save();
This code should work, I have tested it.
As you are currently using it, the answer is no, you can't do that, not with CActiveRecord. The reason for this is that Yii is retrieving the record to update based in it's primary key, and will not override that. The only way to override the primary key will be to write your own update query via a CDbCommandBuilder.
BTW: There is an interesting discussion on the subject, on Yii forum.
Have you tried
$sn = new SaplingNode();
$sn->setPrimaryKey(1);
$sn->save();
Yii primary key
If your just testing and it is okay to delete all data from your table, you can empty the table. For example, you can use phpMyAdmin, click your DB to view its tables, find the table you want and click Empty showing on that table row. You will get a confirmation message with a choice of enabling or disabling FK, click OK and all data on that table will be deleted and your auto increment id will start from 1 when saving or inserting new record.
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).