{"record":{"id":"ca365003a0c2f26f","repo":"apache/superset","slug":"dataset-existing-table-name-r-uuid-config-uui-ca3650","errorCode":null,"errorMessage":"Dataset {existing.table_name!r} (uuid {config['uuid']}) matches more than one existing row, so the restore-and-update cannot pick a target. Resolve the duplicate rows manually before retrying.","messagePattern":"Dataset (.+?) \\(uuid (.+?)\\) matches more than one existing row, so the restore-and-update cannot pick a target\\. Resolve the duplicate rows manually before retrying\\.","errorType":"exception","errorClass":"ImportFailedError","httpStatus":500,"severity":"error","filePath":"superset/commands/dataset/importers/v1/utils.py","lineNumber":485,"sourceCode":"        # fail because the UUID match will try to update `examples.NULL.users` to\n        # `examples.public.users`, resulting in a conflict.\n        #\n        # In the soft-deleted-restore case we cannot silently return\n        # the unmodified row: ``existing.deleted_at`` was already\n        # cleared above and the operator expects a restore-with-update.\n        # Returning the row without applying the upload would produce a\n        # half-restored state. Roll back the ``deleted_at`` clear and\n        # raise so the operator can resolve the legacy-NULL-schema\n        # ambiguity before re-uploading.\n        if is_soft_deleted_match:\n            # ``is_soft_deleted_match`` is only ever set inside the\n            # ``if existing := ...`` walrus block, so ``existing`` is\n            # guaranteed non-None here. The assert pins the invariant\n            # for mypy.\n            assert existing is not None\n            existing.deleted_at = original_deleted_at\n            db.session.flush()\n            raise ImportFailedError(\n                f\"Dataset {existing.table_name!r} (uuid {config['uuid']}) \"\n                \"matches more than one existing row, so the restore-and-\"\n                \"update cannot pick a target. Resolve the duplicate rows \"\n                \"manually before retrying.\"\n            ) from ex\n        # On the non-soft-deleted overwrite path the legacy contract\n        # holds: return the existing row unmodified. Bypasses the\n        # visibility filter so a soft-deleted duplicate can be located\n        # too — without the bypass the listener would hide the row and\n        # the ``.one()`` would raise NoResultFound, masking the\n        # original MultipleResultsFound.\n        dataset = (\n            db.session.query(SqlaTable)\n            .execution_options(**{SKIP_VISIBILITY_FILTER_CLASSES: {SqlaTable}})\n            .filter_by(uuid=config[\"uuid\"])\n            .one()\n        )\n","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/apache/superset/blob/f4587218dd19d046c3e4d00063e7d27f8a2ed354/superset/commands/dataset/importers/v1/utils.py#L467-L503","documentation":"When import_from_dict raises MultipleResultsFound for the dataset itself (legacy exports imported without schemas can collide with later schema'd rows) and the current import was a soft-deleted restore, the code rolls back the deleted_at clear (restoring the original trash timestamp) and raises ImportFailedError: the restore-and-update cannot pick which duplicate row to target, so the operator must deduplicate first. This differs from the live-overwrite path, which keeps the legacy contract of returning the existing row.","triggerScenarios":"Re-importing an old bundle to restore a soft-deleted dataset while the metadata DB holds more than one row matching the import identity (e.g. legacy NULL-schema rows plus newer schema'd rows sharing uuid/table identity).","commonSituations":"Instances upgraded from old Superset versions whose example datasets were first imported without schemas and later re-imported with schemas, leaving duplicates; manual DB surgery creating twin rows.","solutions":["Deduplicate the matching SqlaTable rows in the metadata DB (keep one, purge the others — take a backup first), then retry the import","If the duplicates are legacy example datasets, removing and re-loading the affected examples can normalize rows","Contact an admin to reconcile rows via SQL on the metadata database"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# pre-flight: count rows matching the import identity\nfrom superset.models.sql_lab import SqlaTable  # adjust import as needed\nrows = (\n    session.query(SqlaTable)\n    .filter(SqlaTable.uuid == config['uuid'])\n    .all()\n)\nif len(rows) > 1:\n    raise ValueError(f'{len(rows)} rows share uuid {config[\"uuid\"]} — dedupe the metadata DB first')","typeGuard":null,"tryCatchPattern":"from superset.commands.exceptions import ImportFailedError\ntry:\n    import_dataset(config, overwrite=True)\nexcept ImportFailedError as ex:\n    if 'matches more than one existing row' in str(ex):\n        # stop: needs operator/DBA dedup; the restore was rolled back automatically\n        ...","preventionTips":["Monitor the metadata DB for duplicate SqlaTable rows after major upgrades","Never insert dataset rows by hand without dedupe checks","Re-load legacy examples with current versions to normalize schema'd rows"],"tags":["dataset-import","duplicates","legacy-schema","restore","superset"],"backgroundTag":null,"analyzedSha":"f4587218dd19d046c3e4d00063e7d27f8a2ed354","analyzedAt":"2026-08-14T22:39:27.425Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}