apache/superset · error · DatabaseDeleteSoftDeletedDatasetsExistFailedError

Cannot delete a database whose only remaining datasets are s

Error message

Cannot delete a database whose only remaining datasets are soft-deleted. Restore them (POST /api/v1/dataset/<uuid>/restore) and delete them permanently once a purge capability ships, or remove the underlying rows out-of-band, before deleting the database.

What it means

DatabaseDeleteSoftDeletedDatasetsExistFailedError raised in DeleteDatabaseCommand.validate (delete.py:102) when has_live is false but has_any is true: every remaining SqlaTable row referencing the database is soft-deleted, yet those rows still physically FK-reference the database (tables.database_id), so a hard delete would violate referential integrity. The long message tells the operator exactly that: restore the datasets via POST /api/v1/dataset/<uuid>/restore and delete them permanently, or remove the rows out-of-band, until a purge capability ships.

Source

Thrown at superset/commands/database/delete.py:102

                db.session.query(SqlaTable.id)
                .filter(
                    SqlaTable.database_id == self._model_id,
                    SqlaTable.deleted_at.is_(None),
                )
                .exists()
            ).scalar()
            has_any = db.session.query(
                db.session.query(SqlaTable.id)
                .filter(SqlaTable.database_id == self._model_id)
                .exists()
            ).scalar()
        # Both cases block the delete (a soft-deleted dataset still FK-references
        # the database), but the message differs: with only hidden rows left the
        # operator's dataset list looks empty, so say so explicitly.
        if has_live:
            raise DatabaseDeleteDatasetsExistFailedError()
        if has_any:
            raise DatabaseDeleteSoftDeletedDatasetsExistFailedError()

View on GitHub (pinned to f4587218dd)

Solutions

  1. For each soft-deleted dataset: POST /api/v1/dataset/<uuid>/restore, then DELETE /api/v1/dataset/<pk> (hard delete) — repeat until none remain.
  2. If the REST purge path is insufficient for bulk cleanup, remove the rows out-of-band with a targeted metadata-DB script (DELETE FROM tables WHERE database_id = <id> AND deleted_at IS NOT NULL), with a backup first.
  3. Retry DELETE /api/v1/database/<id> once both counts (live and any) are zero.
  4. Track upstream: once a dataset purge capability ships, prefer it over manual SQL.

Example fix

# before: delete fails — only soft-deleted datasets remain
DELETE /api/v1/database/5
# -> DatabaseDeleteSoftDeletedDatasetsExistFailedError

# after: purge each soft-deleted dataset, then delete the DB
POST /api/v1/dataset/<uuid>/restore
DELETE /api/v1/dataset/<pk>
DELETE /api/v1/database/5
Defensive patterns

Strategy: validation

Validate before calling

from superset.connectors.sqla.models import SqlaTable
from superset import db

def soft_deleted_dataset_ids(database_id: int) -> list:
    return [
        row[0]
        for row in db.session.query(SqlaTable.id, SqlaTable.uuid)
        .filter(SqlaTable.database_id == database_id)
        .all()
    ]  # if the live count is 0 but this is non-empty, the delete will fail

Try / catch

from superset.commands.database.exceptions import (
    DatabaseDeleteSoftDeletedDatasetsExistFailedError,
)

try:
    DeleteDatabaseCommand(model_id).run()
except DatabaseDeleteSoftDeletedDatasetsExistFailedError:
    for uuid in soft_deleted_uuids(model_id):
        requests.post(f"/api/v1/dataset/{uuid}/restore")
        requests.delete(f"/api/v1/dataset/{pk_of(uuid)}")
    DeleteDatabaseCommand(model_id).run()  # retry

Prevention

When it happens

Trigger: DELETE /api/v1/database/<id> after all its datasets were 'deleted' in the UI (which soft-deletes them); the dataset list looks empty so the operator expects the delete to succeed.

Common situations: Teams that cleaned up datasets via the UI before decommissioning a database; environments on dialects where the FK would turn this into an opaque 500 instead.

Related errors


AI-assisted analysis of apache/superset@f4587218dd (2026-08-14). Data as JSON: /api/errors/1c9cde44d1cc2468. Report an issue: GitHub.