How to update and delete records in a table based on a new request?

I need to update records for Guide Steps (Every recipe has steps/instruction and my table was created for storing this data)

I was able to create those records in the db with quite easy syntax:

GuideStep::insert($groupedSteps); // $groupedSteps is an array

But now I need to update those records (and remove unnecessary ones), for this moment I came up with logic that can only update records or create if there is no such records:

foreach ($groupedSteps as $step){
  GuideStep::updateOrInsert(
    ['recipe_id' => $recipeId, 'step_number' => $step['step_number']],
    ['step_text' => $step['step_text'], 'step_image' => $step['step_image']]
  );
}

migration:

Schema::create('guide_steps', function (Blueprint $table) {
            $table->id();
            $table->foreignId('recipe_id')->constrained()->cascadeOnDelete();
            $table->integer('step_number');
            $table->text('step_text');
            $table->string('step_image')->default('recipes-images/default/default_photo.png');
            $table->timestamps();
        });

I thought that I’ll be able to use upsert() but this method requires unique columns (I don’t have those)

Be grateful for some advices

1. Fetch Existing Steps

First, retrieve all existing steps for the given recipe_id from the database.

2. Compare and Process

Loop through the incoming $groupedSteps to update or insert records, and track which existing steps are still relevant.

3. Delete Unnecessary Steps

After processing, delete any steps that weren’t included in the new $groupedSteps.

Here’s the code:

use App\Models\GuideStep;

public function updateGuideSteps($recipeId, array $groupedSteps)
{
    // Step 1: Fetch existing steps for the recipe
    $existingSteps = GuideStep::where('recipe_id', $recipeId)
        ->get()
        ->pluck('step_number')
        ->toArray(); // Get array of step_numbers for comparison

    // Step 2: Process incoming steps (update or insert)
    $incomingStepNumbers = [];
    foreach ($groupedSteps as $step) {
        GuideStep::updateOrInsert(
            [
                'recipe_id' => $recipeId,
                'step_number' => $step['step_number'],
            ],
            [
                'step_text' => $step['step_text'],
                'step_image' => $step['step_image'] ?? 'recipes-images/default/default_photo.png', // Fallback to default
            ]
        );
        $incomingStepNumbers[] = $step['step_number']; // Track incoming step numbers
    }

    // Step 3: Delete steps that are no longer in the incoming data
    $stepsToDelete = array_diff($existingSteps, $incomingStepNumbers);
    if (!empty($stepsToDelete)) {
        GuideStep::where('recipe_id', $recipeId)
            ->whereIn('step_number', $stepsToDelete)
            ->delete();
    }
}