Framework: SQLSRV driver does not support ID inserts

Created on 5 Mar 2019  路  7Comments  路  Source: laravel/framework

  • Laravel Version: 5.8.0
  • PHP Version: 7.2
  • Database Driver & Version: MSSQL 2012 with SQLSRV driver

Description:

As part of an enterprise project, my client needs to use MSSQL. Trying to insert a seed with a specific id gives an error:

DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);

SQLSTATE[23000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot insert explicit value for identity column in table 'test_table' when IDENTITY_INSERT is set to OFF. (SQL: insert into [test_table] ([id], [name]) values (1, example))

This is expected behavior, as MSSQL works a bit differently to most other databases, and does not allow IDs to be inserted by default. The method to solve is to turn IDENTITY_INSERT to ON for the table, insert the record, then turn it OFF again:

DB::statement('SET IDENTITY_INSERT test_table ON');
DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);
DB::statement('SET IDENTITY_INSERT test_table OFF');

Except this doesnt work, and MSSQL still thinks the IDENTITY_INSERT is OFF. If you run the above as native SQL queries - it works, so the problem is how Laravel is handling it.

Technically, we should be able to wrap the whole thing in a transaction, but that doesnt work either:

DB::beginTransaction();
DB::statement('SET IDENTITY_INSERT test_table ON');
DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);
DB::statement('SET IDENTITY_INSERT test_table OFF');
DB::commit();

Googling around, I found this SO thread which people have the same issue. There is a manual workaround, but the answer given there is not practical for me or suitable for long term use.

I only really need the id inserting for seeding and a few edge cases, - but this seems like a bug to me?

needs more info

Most helpful comment

Use unprepared() instead of statement():

DB::unprepared('SET IDENTITY_INSERT test_table ON');
DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);
DB::unprepared('SET IDENTITY_INSERT test_table OFF');

The difference is that statement() executes a prepared statement, while unprepared() uses PDO::exec() instead. Every prepared statement starts a new session (inside the same database connection) and because the SET IDENTITY_INSERT query is session-specific, it doesn't affect other prepared statements like the INSERT query. PDO::exec() affects the whole database connection.

All 7 comments

I'm wondering if we need to create some extra commands in the SQL driver - and do something like:

DB::beginTransaction();
DB::setIdentityInsertOn();
DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);
DB::setIdentityInsertOff();
DB::commit();

Or even better:

DB::table('test_table')->insertWithIdentity(['id' => 1, 'name' => 'example']);

which would wrap the insert with ON and OFF

I'll need someone else using SQLSRV to confirm this.

Use unprepared() instead of statement():

DB::unprepared('SET IDENTITY_INSERT test_table ON');
DB::table('test_table')->insert(['id' => 1, 'name' => 'example']);
DB::unprepared('SET IDENTITY_INSERT test_table OFF');

The difference is that statement() executes a prepared statement, while unprepared() uses PDO::exec() instead. Every prepared statement starts a new session (inside the same database connection) and because the SET IDENTITY_INSERT query is session-specific, it doesn't affect other prepared statements like the INSERT query. PDO::exec() affects the whole database connection.

@staudenmeir - you are a genius - that resolves it.

So then the next question is - how do we document this moving forward? I'll update the 2-3 forum threads/SO topics I found on Google about this issue, but should there be something in the docs? unprepared() is not mentioned at all?

edit: p.s. could you explain why that solves it?

I've added the explanation to my previous post.

We could append the unprepared() method to https://laravel.com/docs/5.8/database > "Running A General Statement" and add bindings to the statement() example to show the difference.

Feel free to send in a pr to the docs for this!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fideloper picture fideloper  路  3Comments

YannPl picture YannPl  路  3Comments

felixsanz picture felixsanz  路  3Comments

PhiloNL picture PhiloNL  路  3Comments

shopblocks picture shopblocks  路  3Comments