# SQLSTATE errors in Laravel, code by code

> What SQLSTATE HY000 2002, 1045, 42000, 23000, 42S02 and 42S22 mean in a Laravel QueryException, how to tell which cause you have, and the fix for each.

A Laravel `QueryException` starts with the database's error code: `SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry …`. The five characters in brackets are the standard SQLSTATE class; the number after them is MySQL's or MariaDB's own error code, and that is the one to search for. This page covers the codes Laravel developers hit most, in the order a new app tends to meet them.

| Message starts with | Means | Section |
|---|---|---|
| `SQLSTATE[HY000] [2002]` | Can't connect to the server at all | [2002](#hy000-2002) |
| `SQLSTATE[HY000] [1045]` | Connected, but the login was refused | [1045](#hy000-1045) |
| `SQLSTATE[42S02]` | The table doesn't exist | [42S02](#42s02) |
| `SQLSTATE[42S22]` | A column doesn't exist | [42S22](#42s22) |
| `SQLSTATE[42000]` | SQL syntax error, or a limit or mode the query breaks | [42000](#42000) |
| `SQLSTATE[23000]` | A constraint refused the row: duplicate, foreign key or NULL | [23000](#23000) |

## SQLSTATE[HY000] [2002] Connection refused {#hy000-2002}

PHP couldn't reach the database server: wrong host or port, the server isn't running, or `DB_HOST=localhost` makes PHP look for a socket file that isn't there ("No such file or directory"). In Docker and Sail, the host is the service name, not `127.0.0.1`. This one has its own page: [SQLSTATE[HY000] [2002] Connection refused](/errors/sqlstate-hy000-2002/).

## SQLSTATE[HY000] [1045] Access denied for user {#hy000-1045}

```text
SQLSTATE[HY000] [1045] Access denied for user 'forge'@'10.0.1.12' (using password: YES)
```

The server is reachable and rejected the username, password or the host you connect from. `using password: NO` means Laravel sent an empty password, so `DB_PASSWORD` didn't load.

A common cause is a password with a `#` in `.env`. Without quotes, everything from `#` on is a comment. We checked it on Laravel 13.40:

```dotenv
DB_PASSWORD=ab#cd      # Laravel reads "ab"
DB_PASSWORD="ab#cd"    # Laravel reads "ab#cd"
```

Double quotes also expand `${OTHER_VAR}`; use single quotes for a password that contains `${`. Other causes:

- The MySQL user exists only for `'user'@'localhost'`, and you now connect over TCP from another host or container. Create it for the right host: `CREATE USER 'forge'@'10.0.1.%' IDENTIFIED BY '…';` and grant it the database.
- The config is cached with the old password: `php artisan config:clear && php artisan config:cache`.
- On Ubuntu, MySQL's `root` uses socket authentication and has no password. Create a separate user for the app.

## SQLSTATE[42S02]: Base table or view not found: 1146 {#42s02}

```text
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'shop.order_product' doesn't exist
```

The query names a table that isn't in the database it's connected to.

- **Migrations didn't run** on this environment. `php artisan migrate:status` shows which are pending; deploy scripts need `php artisan migrate --force`.
- **Wrong database.** The message names it (`shop.`); compare it with `DB_DATABASE`.
- **Laravel guessed the name.** A model `OrderItem` uses `order_items`; a `belongsToMany` between `Order` and `Product` uses `order_product`, in alphabetical order. Set `protected $table = 'order_lines';` on the model, or pass the pivot name: `$this->belongsToMany(Product::class, 'product_orders')`.
- **Sessions, cache or queue on the database driver** without their tables. New apps need the `sessions`, `cache` and `jobs` tables from the default migrations.

## SQLSTATE[42S22]: Column not found: 1054 {#42s22}

```text
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'discount_total' in 'field list'
```

A query names a column the table doesn't have.

- The migration that adds it hasn't run here (`migrate:status`), or ran on another database.
- A typo, or a column that was renamed in a migration while the code still uses the old name.
- `Unknown column 'updated_at'`: the table has no timestamps. Add `public $timestamps = false;` to the model, or add `$table->timestamps()` to the table.
- An accessor or appended attribute that you write back with `$model->update($request->all())`. Validate the input and pass only real columns.

## SQLSTATE[42000]: Syntax error or access violation {#42000}

42000 groups several different MySQL errors. The number after it tells them apart:

| Code | Message | Cause and fix |
|---|---|---|
| 1064 | `You have an error in your SQL syntax` | Usually a raw query: a reserved word as a column name (`order`, `group`, `key`) without backticks, or string concatenation instead of bindings. Use the query builder or `DB::select('… where id = ?', [$id])`. |
| 1055 | `… isn't in GROUP BY` | Laravel runs MySQL in strict mode, which includes `ONLY_FULL_GROUP_BY`. Select only grouped columns and aggregates, or wrap the others in `ANY_VALUE()`. Turning off `'strict'` in `config/database.php` hides the problem instead of fixing it. |
| 1071 | `Specified key was too long; max key length is 767 bytes` | MySQL before 5.7.7 or MariaDB before 10.2.2 with `utf8mb4`. Upgrade the server, or add `Schema::defaultStringLength(191);` to `AppServiceProvider::boot()`. |
| 1142 / 1044 | `… command denied to user` / `Access denied for user … to database` | The database user lacks a privilege, often `CREATE` or `ALTER` during migrations, or access to the database at all. Grant it. |

## SQLSTATE[23000]: Integrity constraint violation {#23000}

```text
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'ord_8F2K' for key 'orders.number'
```

A constraint refused the row. Again, the number decides the fix:

- **1062 Duplicate entry.** A unique index already has this value. If users submit it, validate first with `Rule::unique('orders', 'number')`. If two requests or jobs race, validation can't help: let the index decide and handle the result. Laravel throws `Illuminate\Database\UniqueConstraintViolationException` for this case:

  ```php
  use Illuminate\Database\UniqueConstraintViolationException;

  try {
      $order = Order::create(['number' => $number, 'user_id' => $user->id]);
  } catch (UniqueConstraintViolationException) {
      $order = Order::where('number', $number)->firstOrFail();
  }
  ```

  `Order::createOrFirst([...])` does the same in one call. Retried queue jobs are a common source: a job that inserts a row and then fails runs again and hits the duplicate.
- **1452 Cannot add or update a child row: a foreign key constraint fails.** The referenced row doesn't exist, often because it was created in a transaction that hasn't committed yet, or in a queued job dispatched before the commit. Dispatch with `->afterCommit()`.
- **1451 Cannot delete or update a parent row.** Other rows still reference this one. Delete them first, or use `->cascadeOnDelete()` on the foreign key.
- **1048 Column 'x' cannot be null.** The value is missing; the column needs a default, `->nullable()`, or the code must set it.

## Catch SQLSTATE errors in production

Every `QueryException` that isn't caught reaches Laravel's exception handler. With the [Laravel package](/docs/laravel/), Lampwick groups them into issues by class, code, file and line, so a thousand duplicate-entry errors from one `create()` call are one row in **Issues** with a count, the users affected and the request or job each one broke. See [work with issues](/docs/issues/) for resolving them and getting notified when one comes back.

Related: [MySQL server has gone away](/errors/mysql-server-has-gone-away/), the error you see when a connection that worked is lost.
