prestodb/presto · error · SemanticException

MATERIALIZED_VIEW_ALREADY_EXISTS

MATERIALIZED_VIEW_ALREADY_EXISTS

Error message

Materialized view '%s' already exists

What it means

CREATE MATERIALIZED VIEW found an existing table handle at the target name and the statement did not include IF NOT EXISTS, so the materialized view already exists. The task throws SemanticException MATERIALIZED_VIEW_ALREADY_EXISTS to prevent silent replacement.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/execution/CreateMaterializedViewTask.java:109

    @Override
    public ListenableFuture<?> execute(CreateMaterializedView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List<Expression> parameters, WarningCollector warningCollector, String query)
    {
        QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getName(), metadata);
        MetadataResolver metadataResolver = metadata.getMetadataResolver(session);

        if (!metadataResolver.catalogExists(viewName.getCatalogName())) {
            throw new SemanticException(MISSING_CATALOG, "Catalog '%s' does not exist", viewName.getCatalogName());
        }

        if (!metadataResolver.schemaExists(viewName.getCatalogSchemaName())) {
            throw new SemanticException(MISSING_SCHEMA, statement, "Schema '%s' does not exist", viewName.getSchemaName());
        }

        Optional<TableHandle> viewHandle = metadataResolver.getTableHandle(viewName);
        if (viewHandle.isPresent()) {
            if (!statement.isNotExists()) {
                throw new SemanticException(MATERIALIZED_VIEW_ALREADY_EXISTS, statement, "Materialized view '%s' already exists", viewName);
            }
            return immediateFuture(null);
        }

        accessControl.checkCanCreateTable(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), viewName);
        accessControl.checkCanCreateView(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), viewName);

        Map<NodeRef<Parameter>, Expression> parameterLookup = parameterExtractor(statement, parameters);
        Analyzer analyzer = new Analyzer(session, metadata, sqlParser, accessControl, Optional.empty(), parameters, parameterLookup, warningCollector, query, new ViewDefinitionReferences());
        Analysis analysis = analyzer.analyzeSemantic(statement, false);
        checkAccessPermissions(analysis.getAccessControlReferences(), analysis.getViewDefinitionReferences(), query, session.getPreparedStatements(), session.getIdentity(), accessControl, session.getAccessControlContext());

        List<ColumnMetadata> columnMetadata = analysis.getOutputDescriptor(statement.getQuery())
                .getVisibleFields().stream()
                .map(field -> ColumnMetadata.builder()
                        .setName(metadata.normalizeIdentifier(session, viewName.getCatalogName(), field.getName().get()))
                        .setType(field.getType())
                        .build())

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Add IF NOT EXISTS to make the statement idempotent: CREATE MATERIALIZED VIEW IF NOT EXISTS ...
  2. DROP MATERIALIZED VIEW (or table) with the same name first if replacement is intended
  3. Use OR REPLACE if supported by your connector to replace the existing definition
  4. Make deployment scripts idempotent so re-runs don't collide

Example fix

// before
CREATE MATERIALIZED VIEW hive.default.mv_daily AS SELECT ...
// after
CREATE MATERIALIZED VIEW IF NOT EXISTS hive.default.mv_daily AS SELECT ...
Defensive patterns

Strategy: try-catch

Validate before calling

// check existence first, or make DDL idempotent
boolean exists = tableOrViewExists(catalog, schema, name);
if (exists && !allowReplace) {
    return; // skip creation
}

Try / catch

try {
    execute(createMvDdl);
} catch (SemanticException e) {
    if (e.getCode() == MATERIALIZED_VIEW_ALREADY_EXISTS) {
        // treat as success for idempotent deployments
    } else throw e;
}

Prevention

When it happens

Trigger: Re-running a migration/deployment script that creates the same materialized view; concurrent jobs both creating the same view; the name collides with an existing table or materialized view in the connector.

Common situations: Non-idempotent CI/CD pipelines; re-running failed scripts without cleanup; two teams independently choosing the same view name; scheduler retries after a timeout when creation actually succeeded.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/f58ba10a053a11f1. Report an issue: GitHub.