I am in the midst of writing a command in my Laravel project which inserts categories to a database table, but depending on whether or not they already exist.
I investigated the way to do this and came across firstOrCreate method, so I wrote the following in my command:
$comCats = new CommunicationsCategories();
$comCats->firstOrCreate(
['name' => 'Job Updates'], ['region_id' => 1]);
$comCats->firstOrCreate(
['name' => 'Alerts'], ['region_id' => 1]);
Basically, I need to create these two categories in the communications_categories table with a region ID of 1. The Job Updates category already exists, so it skipped that as expected but when it tries to create the Alerts category which doesn't exist I get the following error in my console:
SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value
violates unique constraint "communications_categories_pkey"
DETAIL: Key (id)=(2) already exists. (SQL: insert into
"communications_categories" ("name", "region_id", "updated_at",
"created_at") values (Alerts, 1, 2018-06-19 09:38:20, 2018-06-19
09:38:20) returning "id")
It appears that it's trying to allocate a primary key ID of 2 when it already exists - but the table structure has a nextval on the primary key which I thought would take the last ID added and create a new one after that. According to the Laravel documentation on Eloquent Inserts here there's no mention of having to stipulate the actual primary key id itself, and the fillable elements are only name and region_id.
Any help on this appreciated, as I'm reasonably new to Laravel and the eloqent database methods. If it helps, I'm using a PostgreSQL database.
The other answer seems to have assumed that you need to have firstOrCreate explained to you, when in fact you have actually encountered a bug in the Laravel framework and also assumed that Key (id)=(2) refers to region_id instead of your autoincrementing primary key id.
You most likely want a unique index on communications_categories.name.
You can instead use this SQL in postgres (assuming you have a unique index on name) which should be more reliable than firstOrCreate, but if you were planning to use other laravel features with firstOrCreate (like observers, or more query builders), their functionality could be lost.
INSERT INTO communications_categories (name, region_id, created_at, updated_at)
VALUES ('Job Updates', 1, NOW(), NOW())
ON CONFLICT (name) DO
UPDATE SET region_id = excluded.region_id,
updated_at = NOW()
RETURNING *;
which can be used like:
$values = DB::select($sql); // will return Array of StdClass
or without returning * if you don't need the id values.
DB::insert($sql); // will return true
If you need the Eloquent object returned, I would recommend including returning * or returning id and passing that to CommunicationsCategories::findOrFail($values[0]['id']). Putting that complicated insert statement into an Eloquent select will probably be a lot of work. Eloquent also has a function called hydrate which could work without additional SQL calls.
The SQL statement will insert the desired values, except when there is a conflict on the unique constraint, then which it will apply the values excluded by conflict from insert to the pre-existing rows via an update.
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the attributes from the first parameter, along with those in the optional second parameter.
$comCats->firstOrCreate(
['name' => 'Job Updates'], ['region_id' => 1]);
here you are trying to locate first a name with "Job Updates" and a region_id with 1 ,if laravel cant find the specific data, it will try to create or insert the given parameters, but when the system tries to insert, region_id with value of "1" already exist.
if region_id is your primary key try:
$comCats->firstOrCreate(
['name' => 'Job Updates']);
Pardon me for I cannot post a comment yet...
If somebody encounter this same issue about postgresql, this is definitely a bug. In my case this bug occurred after I imported a local postgresql database using heroku following this procedure:
Workaround for pushing to heroku database on windows machince.
I tried running the query again and it worked... it fails sometimes too. Now this sucks. I'll update this I found another way around this issue.
Edit:
After looking at more searches about the issue, I found the following which perfectly matches my situation:
PostgreSQL: Unique violation: 7 ERROR: duplicate key value violates unique constraint “users_pkey”
Related
I have created migration from the PHP artisan command and it had created a table in my Postgres database, with id set to auto_increment.
I have made some seeder in laravel and three rows of data are fed to the previously created table through php artisan db:seed command.
When I am inserting data through some form in the same table, it is giving me an error.
error:SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "roles_pkey" DETAIL: Key (id)=(1) already exists. (SQL: insert into "roles" ("name", "guard_name", "updated_at", "created_at") values (staff, web, 2019-07-03 07:38:37, 2019-07-03 07:38:37) returning "id")
Sequences are objects that return a value that is one greater on each request, regardless of which transaction it was called on. A sequence by default starts at 1 and can be applied to a table, or many tables, so it cannot know how many values there are in your table already. If you want your insert to work you will need to manually set it.
SELECT setval('roles_id_seq', (SELECT coalesce((SELECT max(id) from roles),1)))
This query is assuming that the sequence used was created on the column "id" on the "roles" table, if not the sequence name can be found by checking that columns DDL e.g. NOT NULL DEFAULT setval('the_sequence_name') and use that to set the value.
It appear I am not getting latest row when rows have actually same created_at value.
Using $model->latest()->first() - I am getting first row rather than last row of created_at.
How to solve this?
latest() will use the created_at column by default.
If all of your created_at values are the exact same, this obviously won't work...
You can pass a column name to latest() to tell it to sort by that column instead.
You can try:
$model->latest('id')->first();
This assumes you have an incrementing id column.
This will entirely depend on what other data you have in your table. Any query of a relational database does not take the "physical position" into account - that is, there is no such thing as being able to get the "last inserted row" of a table until you are able check some value in the table that indicates it is the probably the last row.
One of the common ways to do this is to have a auto-incrementing unique key in the database (often the Primary Key), and you can simply get the largest value in that set. It's not guaranteed to be the last row inserted, but for most applications this is usually true.
What you need is the equivalent query to be executed
SELECT * FROM Table WHERE created_at = ? ORDER BY ID DESC LIMIT 1
or, in Eloquent ORM
$model->where('created_at', '=', ?)->orderBy('id', 'desc')->take(1)->first();
Keep in mind that you'll probably need other filters, since it is entirely possible other users or processes may insert records at the same time generating the same creation date, and you'll end up with somebody elses records.
I fail to understand the logic of the unique constraint when it's based on 2 fields.
I have the following table named DESCRIPTIONS including 3 columns: ID_DESCRIPTION, NAME, ID_DESCRIPTION_TYPE
Now ID_DESCRIPTION is the primary key, and there is a unique constraint UK_DESCRIPTION on couple (ID_DESCRIPTION, NAME).
If I try to run the following query:
UPDATE DESCRIPTIONS SET NAME = 'USA' WHERE ID_DESCRIPTION = 9255813
I'm getting an ORA-00001 exception, saying that unique constraint UK_DESCRIPTION is violated.
Now this would mean that the couple (9255813,'USA') already exists right ?
However, I don't see how this is possible since the ID_DESCRIPTION is a primary key and therefore unique AND the results of the query
SELECT * FROM DESCRIPTIONS WHERE ID_DESCRIPTION = 9255813
only return 1 result, the one I want to update.
What am I failing to understand here ?
I am going to guess that uk_description is in fact a unique key based on the single column of NAME.
"It is unfortunately not."
Okay, the other explanation is that it is a multi-column key based on a different set of columns from what you think. (NAME, ID_DESCRIPTION_TYPE) would also fit the described behaviour.
To be fair, a unique key on(NAME, ID_DESCRIPTION_TYPE) makes more sense. For example, this is the key you'd want when the table is a single reference data look-up (which is a horrible model but common enough). Whereas a compound key of ID_DESCRIPTION, NAME) would do nothing but undermine the primary key.
I have an applications table, containing user_id, email, phone, etc.. An applicant may or may not create a user account, because registration is optional; hence user_id is nullable. They might also apply multiple times with the same email address, in which case email in applications table would be duplicated across several records.
Now, the objective is to fetch all Application model instances excluding duplicates. EX: if Jack applied five times with jack#gmail.com, the applications table would have 5 records with email column set to jack#gmail.com. When fetching Applications, I'd like to get only one record with jack#gmail.com, the other four should be ignored.
So, I tried multiple approaches, but they either fetched all records or didn't execute at all:
groupBy('email') fails with get() because of non-aggreagated column email:
Illuminate\Database\QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'applications.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
At the same time, groupBy('email')->get(['email']) works well, but the problem is that I need other columns too (created_at at least), ideally - the full Application model instance.
Application::whereIn('id', function($query) { $query->select('id')->groupBy('email'); })->get() returns all records from the table
Application::select('*', 'DISTINCT email')->get() results in
Column not found: 1054 Unknown column 'DISTINCT email' in 'field list'
The Eloquent approach Application::distinct()->get(['email']) gives the proper list of emails, but once again, I need more columns that just the email. Once I start selecting more columns, I keep getting all records as results.
All of the following queries fail due to the strict mode being enabled:
SELECT * FROM ( SELECT * FROM applications ORDER BY id DESC ) AS tmp GROUP BY email
SELECT * FROM applications WHERE id IN(SELECT id FROM applications GROUP BY email)
SELECT email, id, created_at from ( SELECT email, id, created_at FROM applications) apps group by email
Can anyone help me to figure out the proper approach? I'd love to use Eloquent and work with actual models, rather than stdObj or other, if Eloquent can accommodate my problem/limitations.
P.S. The SQL engine I'm using is MySQL with strict mode enabled.
First of all, if you want to put an expression inside your eloquent you must use DB::raw('DISTINCT email') for that.
Second, I believe you should go with this approach:
Application::select('id', 'email', 'more_fields')->groupBy('email')->get();
You must have the column you group by it in your select() function.
Last option:
You can just use the ->distinct() function on your eloquent query to remove duplicates.
I'm trying to create a new row in a table. There are two constraints on the table -- one is on the key field (DB_ID), the other constrains a value to be one of several the the field ENV. When I do an insert, I do not include the key field as one of the fields I'm trying to insert, yet I'm getting this error:
unique constraint (N390.PK_DB_ID) violated
Here's the SQL that causes the error:
insert into cmdb_db
(narrative_name, db_name, db_type, schema, node, env, server_id, state, path)
values
('Test Database', 'DB', 'TYPE', 'SCH', '', 'SB01', 381, 'TEST', '')
The only thing I've been able to turn up is the possibility that Oracle might be trying to assign an already in-use DB_ID if rows were inserted manually. The data in this database was somehow restored/moved from a production database, but I don't have the details as to how that was done.
Any thoughts?
Presumably, since you're not providing a value for the DB_ID column, that value is being populated by a row-level before insert trigger defined on the table. That trigger, presumably, is selecting the value from a sequence.
Since the data was moved (presumably recently) from the production database, my wager would be that when the data was copied, the sequence was not modified as well. I would guess that the sequence is generating values that are much lower than the largest DB_ID that is currently in the table leading to the error.
You could confirm this suspicion by looking at the trigger to determine which sequence is being used and doing a
SELECT <<sequence name>>.nextval
FROM dual
and comparing that to
SELECT MAX(db_id)
FROM cmdb_db
If, as I suspect, the sequence is generating values that already exist in the database, you could increment the sequence until it was generating unused values or you could alter it to set the INCREMENT to something very large, get the nextval once, and set the INCREMENT back to 1.
Your error looks like you are duplicating an already existing Primary Key in your DB. You should modify your sql code to implement its own primary key by using something like the IDENTITY keyword.
CREATE TABLE [DB] (
[DBId] bigint NOT NULL IDENTITY,
...
CONSTRAINT [DB_PK] PRIMARY KEY ([DB] ASC),
);
It looks like you are not providing a value for the primary key field DB_ID. If that is a primary key, you must provide a unique value for that column. The only way not to provide it would be to create a database trigger that, on insert, would provide a value, most likely derived from a sequence.
If this is a restoration from another database and there is a sequence on this new instance, it might be trying to reuse a value. If the old data had unique keys from 1 - 1000 and your current sequence is at 500, it would be generating values that already exist. If a sequence does exist for this table and it is trying to use it, you would need to reconcile the values in your table with the current value of the sequence.
You can use SEQUENCE_NAME.CURRVAL to see the current value of the sequence (if it exists of course)