{"id":"6a7ea879989e1d03","repo":"laravel/framework","slug":"unable-to-retrieve-lastinsertid-for-odbc","errorCode":null,"errorMessage":"Unable to retrieve lastInsertID for ODBC.","messagePattern":"Unable to retrieve lastInsertID for ODBC\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/Illuminate/Database/Query/Processors/SqlServerProcessor.php","lineNumber":50,"sourceCode":"        return is_numeric($id) ? (int) $id : $id;\n    }\n\n    /**\n     * Process an \"insert get ID\" query for ODBC.\n     *\n     * @param  \\Illuminate\\Database\\Connection  $connection\n     * @return int\n     *\n     * @throws \\Exception\n     */\n    protected function processInsertGetIdForOdbc(Connection $connection)\n    {\n        $result = $connection->selectFromWriteConnection(\n            'SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS int) AS insertid'\n        );\n\n        if (! $result) {\n            throw new Exception('Unable to retrieve lastInsertID for ODBC.');\n        }\n\n        $row = $result[0];\n\n        return is_object($row) ? $row->insertid : $row['insertid'];\n    }\n\n    /** @inheritDoc */\n    public function processColumns($results)\n    {\n        return array_map(function ($result) {\n            $result = (object) $result;\n\n            $type = match ($typeName = $result->type_name) {\n                'binary', 'varbinary', 'char', 'varchar', 'nchar', 'nvarchar' => $result->length == -1 ? $typeName.'(max)' : $typeName.\"($result->length)\",\n                'decimal', 'numeric' => $typeName.\"($result->precision,$result->places)\",\n                'float', 'datetime2', 'datetimeoffset', 'time' => $typeName.\"($result->precision)\",\n                default => $typeName,","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/laravel/framework/blob/bd6b5437e6ad87bb49f9b426724f07a9f64e9683/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php#L32-L68","documentation":"SqlServerProcessor::processInsertGetIdForOdbc() throws a generic Exception when the SCOPE_IDENTITY()/@@IDENTITY query returns an empty result set. Laravel uses this fallback path to retrieve the last inserted ID when the connection is configured with odbc=true, because PDO::lastInsertId() is unreliable over ODBC. An empty result means the identity retrieval query itself failed to return a row.","triggerScenarios":"Calling DB::table(..)->insertGetId($values) (or Model::create / insertAndGetId) on a SQL Server connection where the 'odbc' config option is true, and the subsequent SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS int) query returns no rows.","commonSituations":"Inserting into a table without an IDENTITY column (so SCOPE_IDENTITY returns NULL/empty) over an ODBC connection. ODBC driver version issues that drop the scope-identity context. Triggers firing after insert that reset @@IDENTITY.","solutions":["Ensure the target table has an IDENTITY (auto-increment) primary key column.","Avoid the ODBC path by setting the connection 'odbc' config to false so PDO::lastInsertId() is used.","Use an explicit OUTPUT INSERTED.ID clause via a raw statement instead of insertGetId under ODBC.","Update/replace the ODBC driver with one that reliably supports SCOPE_IDENTITY."],"exampleFix":"// before\n$id = DB::table('audit_log')->insertGetId($row);\n// odbc=true, table has no IDENTITY column\n\n// after (use OUTPUT or add IDENTITY column)\n$id = DB::selectOne('INSERT INTO audit_log (col) OUTPUT INSERTED.id VALUES (?)', [$val])->id;","handlingStrategy":"try-catch","validationCode":"// Before insertGetId, confirm the table has an IDENTITY column on ODBC\nif ($connection->getConfig('odbc') === true) {\n    $hasIdentity = $connection->selectOne(\n        \"SELECT 1 FROM sys.identity_columns ic JOIN sys.tables t ON ic.object_id = t.object_id WHERE t.name = ?\",\n        [$table]\n    );\n    if (! $hasIdentity) {\n        throw new \\LogicException(\"Table '{$table}' has no IDENTITY column; insertGetId unavailable over ODBC.\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    $id = DB::table('audit_log')->insertGetId($row);\n} catch (\\Exception $e) {\n    if (str_contains($e->getMessage(), 'Unable to retrieve lastInsertID for ODBC')) {\n        // Fallback: re-select the row by a unique business key, or use OUTPUT INSERTED.id\n        $id = DB::selectOne('INSERT INTO audit_log (col) OUTPUT INSERTED.id VALUES (?)', [$row['col']])->id;\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Ensure target tables have an IDENTITY column when using insertGetId over ODBC.","Set odbc=false in the connection config when the driver supports lastInsertId reliably.","Prefer OUTPUT INSERTED.id for SQL Server inserts that need the new ID.","Review triggers that can disturb @@IDENTITY/SCOPE_IDENTITY context."],"tags":["sqlsrv","odbc","last-insert-id","insert-get-id"],"analyzedSha":"bd6b5437e6ad87bb49f9b426724f07a9f64e9683","analyzedAt":"2026-08-06T00:28:32.783Z","schemaVersion":2}