Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bshan's avatar
Level 10

How to use SoftDeletes and Unique together?

PROBLEM:

Say you want your users table with unique emails but at the same time you want to soft delete. After being soft deleted, you want that user to be able to re-register with the same email but with a new user id . If you create your column the classic way:

$table->string('email')->unique();

you'll get a "Integrity constraint violation: 1062 Duplicate entry" when the user tries to re-register with the same email.

SOLUTION:

Use a "Generated Column" at the database level (MySQL, PostgreSQL or SQLite only) which will be automatically set to 1 when the user is not deleted, but it will be set to null when the user is deleted. Laravel migrations support the "generated columns" with "storedAs" method.

Example:

Schema::create('users', function (Blueprint $table) {

            $table->id();

            $table->string('email');

            $table->timestamps();

            $table->softDeletes();

            $table->boolean('active')->storedAs('IF(deleted_at IS NULL, 1, NULL)');

            $table->unique(['email', 'active']);

}); 

The "active" column is an automatically calculated column at the database level. You just create it and forget about it. But it will be a part of your unique constraint.

0 likes
7 replies
LaryAI's avatar
Level 58

To achieve the desired functionality of allowing users to re-register with the same email after being soft deleted, you can indeed use a generated column. This column will help you enforce the uniqueness constraint only for active (non-deleted) users. Below is a detailed solution using Laravel migrations:

  1. Create the Migration:

    You need to create a migration for the users table with the necessary columns, including the generated column active.

    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    class CreateUsersTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('users', function (Blueprint $table) {
                $table->id();
                $table->string('email');
                $table->timestamps();
                $table->softDeletes();
                $table->boolean('active')->storedAs('IF(deleted_at IS NULL, 1, NULL)');
                $table->unique(['email', 'active']);
            });
        }
    
        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('users');
        }
    }
    
  2. Explanation:

    • $table->id();: Creates an auto-incrementing primary key.
    • $table->string('email');: Creates an email column.
    • $table->timestamps();: Adds created_at and updated_at columns.
    • $table->softDeletes();: Adds a deleted_at column for soft deletes.
    • $table->boolean('active')->storedAs('IF(deleted_at IS NULL, 1, NULL)');: Adds a generated column active that is 1 if deleted_at is NULL (i.e., the user is not deleted) and NULL otherwise.
    • $table->unique(['email', 'active']);: Adds a unique constraint on the combination of email and active.
  3. Usage:

    With this setup, when a user is soft deleted, the active column will be set to NULL, allowing another user to register with the same email. When querying for active users, you can simply check for active = 1.

  4. Querying Active Users:

    When you want to query only active users, you can use the following Eloquent query:

    $activeUsers = User::where('active', 1)->get();
    

This solution ensures that the email uniqueness constraint is enforced only for active users, allowing re-registration with the same email after a user has been soft deleted.

1 like
s4muel's avatar

and what happens if the second user is deleted too? seems it cant be, because the joined unique key is taken already.

dont you want to restore their original account when they "register" the second time? they might benefit from restoring the account to gt some history, connections, etc... if this is not the way and you wont be restoring the deleted account. i would suggest to make the email column unique during soft deletion. i mean something like changing the email address from [email protected] to [email protected]<some random hash> so it doesnt collide in the future

bshan's avatar
Level 10

@s4muel duplicate values are permitted when one of the composite keys is null. So you can have the same email as many times as you want as long as active is set to null.

1 like
Snapey's avatar

if you are not rejoining the user to the previous account, then why keep it?

The best answer is to obfuscate their previous email address, when they left in the first place, which is also better for data privacy.

bshan's avatar
Level 10

@Snapey Say you have other tables referring to this user and you don't want to lose that information. Example: there's a comment that deleted user has done, you can still show the user's name next to the comment.

Snapey's avatar

@bshan and you can still do that if you obfuscate the email

mabdullahsari's avatar

This is why you don't use soft deletions: it's a raised middle finger for database constraints. You can tackle this problem in 20 other ways. Soft deletion is probably the worst one. My 0.02.

Off the top of my head:

  • Creating a dedicated table for inactive_users
  • Having an aggregation table for deleted_models

Please or to participate in this conversation.