diff --git a/internal/diff/diff.go b/internal/diff/diff.go
index 98d0d0c9..c998ec6e 100644
--- a/internal/diff/diff.go
+++ b/internal/diff/diff.go
@@ -908,14 +908,108 @@ func GenerateMigration(oldIR, newIR *ir.IR, targetSchema string) []Diff {
 // collectMigrationSQL populates the collector with SQL statements for the diff
 // The collector must not be nil
 func (d *ddlDiff) collectMigrationSQL(targetSchema string, collector *diffCollector) {
+	// Pre-drop materialized views that depend on tables being modified/dropped
+	// This must happen BEFORE table operations to avoid dependency errors
+	preDroppedViews := d.generatePreDropMaterializedViewsSQL(targetSchema, collector)
+
 	// First: Drop operations (in reverse dependency order)
-	d.generateDropSQL(targetSchema, collector)
+	d.generateDropSQL(targetSchema, collector, preDroppedViews)
 
 	// Then: Create operations (in dependency order)
 	d.generateCreateSQL(targetSchema, collector)
 
 	// Finally: Modify operations
-	d.generateModifySQL(targetSchema, collector)
+	d.generateModifySQL(targetSchema, collector, preDroppedViews)
+}
+
+// generatePreDropMaterializedViewsSQL drops materialized views that depend on
+// tables being modified or dropped. This must happen before table operations.
+// Returns a set of pre-dropped view keys (schema.name) to avoid duplicate drops.
+func (d *ddlDiff) generatePreDropMaterializedViewsSQL(targetSchema string, collector *diffCollector) map[string]bool {
+	preDropped := make(map[string]bool)
+
+	// Build set of tables being modified or dropped
+	affectedTables := make(map[string]*ir.Table)
+	for _, table := range d.droppedTables {
+		key := table.Schema + "." + table.Name
+		affectedTables[key] = table
+	}
+	for _, tableDiff := range d.modifiedTables {
+		key := tableDiff.Table.Schema + "." + tableDiff.Table.Name
+		affectedTables[key] = tableDiff.Table
+	}
+
+	if len(affectedTables) == 0 {
+		return preDropped
+	}
+
+	// Check modifiedViews with RequiresRecreate for dependencies on affected tables
+	for _, viewDiff := range d.modifiedViews {
+		if !viewDiff.RequiresRecreate || !viewDiff.New.Materialized {
+			continue
+		}
+
+		viewKey := viewDiff.Old.Schema + "." + viewDiff.Old.Name
+		if preDropped[viewKey] {
+			continue
+		}
+
+		// Check if this view depends on any affected table
+		for _, table := range affectedTables {
+			if viewDependsOnTable(viewDiff.Old, table.Schema, table.Name) {
+				// Pre-drop this materialized view
+				viewName := qualifyEntityName(viewDiff.Old.Schema, viewDiff.Old.Name, targetSchema)
+				sql := fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName)
+
+				context := &diffContext{
+					Type:                DiffTypeMaterializedView,
+					Operation:           DiffOperationDrop,
+					Path:                fmt.Sprintf("%s.%s", viewDiff.Old.Schema, viewDiff.Old.Name),
+					Source:              viewDiff.Old,
+					CanRunInTransaction: true,
+				}
+				collector.collect(context, sql)
+
+				preDropped[viewKey] = true
+				break
+			}
+		}
+	}
+
+	// Check droppedViews for dependencies on dropped tables (for CASCADE scenario)
+	for _, view := range d.droppedViews {
+		if !view.Materialized {
+			continue
+		}
+
+		viewKey := view.Schema + "." + view.Name
+		if preDropped[viewKey] {
+			continue
+		}
+
+		// Check if this view depends on any dropped table
+		for _, table := range d.droppedTables {
+			if viewDependsOnTable(view, table.Schema, table.Name) {
+				// Pre-drop this materialized view before the table CASCADE
+				viewName := qualifyEntityName(view.Schema, view.Name, targetSchema)
+				sql := fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName)
+
+				context := &diffContext{
+					Type:                DiffTypeMaterializedView,
+					Operation:           DiffOperationDrop,
+					Path:                fmt.Sprintf("%s.%s", view.Schema, view.Name),
+					Source:              view,
+					CanRunInTransaction: true,
+				}
+				collector.collect(context, sql)
+
+				preDropped[viewKey] = true
+				break
+			}
+		}
+	}
+
+	return preDropped
 }
 
 // generateCreateSQL generates CREATE statements in dependency order
@@ -989,7 +1083,8 @@ func (d *ddlDiff) generateCreateSQL(targetSchema string, collector *diffCollecto
 }
 
 // generateModifySQL generates ALTER statements
-func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollector) {
+// preDroppedViews contains views that were already dropped in the pre-drop phase
+func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollector, preDroppedViews map[string]bool) {
 	// Modify schemas
 	// Note: Schema modification is out of scope for schema-level comparisons
 
@@ -1002,8 +1097,8 @@ func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollecto
 	// Modify tables
 	generateModifyTablesSQL(d.modifiedTables, targetSchema, collector)
 
-	// Modify views
-	generateModifyViewsSQL(d.modifiedViews, targetSchema, collector)
+	// Modify views - pass preDroppedViews to skip DROP for already-dropped views
+	generateModifyViewsSQL(d.modifiedViews, targetSchema, collector, preDroppedViews)
 
 	// Modify functions
 	generateModifyFunctionsSQL(d.modifiedFunctions, targetSchema, collector)
@@ -1014,7 +1109,8 @@ func (d *ddlDiff) generateModifySQL(targetSchema string, collector *diffCollecto
 }
 
 // generateDropSQL generates DROP statements in reverse dependency order
-func (d *ddlDiff) generateDropSQL(targetSchema string, collector *diffCollector) {
+// preDroppedViews contains views that were already dropped in the pre-drop phase
+func (d *ddlDiff) generateDropSQL(targetSchema string, collector *diffCollector, preDroppedViews map[string]bool) {
 
 	// Drop triggers from modified tables first (triggers depend on functions)
 	generateDropTriggersFromModifiedTables(d.modifiedTables, targetSchema, collector)
@@ -1025,8 +1121,9 @@ func (d *ddlDiff) generateDropSQL(targetSchema string, collector *diffCollector)
 	// Drop procedures
 	generateDropProceduresSQL(d.droppedProcedures, targetSchema, collector)
 
-	// Drop views
-	generateDropViewsSQL(d.droppedViews, targetSchema, collector)
+	// Drop views - filter out pre-dropped ones to avoid duplicate drops
+	viewsToDrop := filterPreDroppedViews(d.droppedViews, preDroppedViews)
+	generateDropViewsSQL(viewsToDrop, targetSchema, collector)
 
 	// Drop tables
 	generateDropTablesSQL(d.droppedTables, targetSchema, collector)
@@ -1041,6 +1138,22 @@ func (d *ddlDiff) generateDropSQL(targetSchema string, collector *diffCollector)
 	// Note: Schema deletion is out of scope for schema-level comparisons
 }
 
+// filterPreDroppedViews returns views that haven't been pre-dropped
+func filterPreDroppedViews(views []*ir.View, preDropped map[string]bool) []*ir.View {
+	if len(preDropped) == 0 {
+		return views
+	}
+
+	filtered := make([]*ir.View, 0, len(views))
+	for _, view := range views {
+		key := view.Schema + "." + view.Name
+		if !preDropped[key] {
+			filtered = append(filtered, view)
+		}
+	}
+	return filtered
+}
+
 // getTableNameWithSchema returns the table name with schema qualification only when necessary
 // If the table schema is different from the target schema, it returns "schema.table"
 // If they are the same, it returns just "table"
diff --git a/internal/diff/view.go b/internal/diff/view.go
index 14c9fee1..949a7326 100644
--- a/internal/diff/view.go
+++ b/internal/diff/view.go
@@ -79,36 +79,53 @@ func generateCreateViewsSQL(views []*ir.View, targetSchema string, collector *di
 }
 
 // generateModifyViewsSQL generates CREATE OR REPLACE VIEW statements or comment changes
-func generateModifyViewsSQL(diffs []*viewDiff, targetSchema string, collector *diffCollector) {
+// preDroppedViews contains views that were already dropped in the pre-drop phase
+func generateModifyViewsSQL(diffs []*viewDiff, targetSchema string, collector *diffCollector, preDroppedViews map[string]bool) {
 	for _, diff := range diffs {
 		// Handle materialized views that require recreation (DROP + CREATE)
 		if diff.RequiresRecreate {
+			viewKey := diff.New.Schema + "." + diff.New.Name
 			viewName := qualifyEntityName(diff.New.Schema, diff.New.Name, targetSchema)
 
-			// DROP the old materialized view
-			dropSQL := fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName)
-			createSQL := generateViewSQL(diff.New, targetSchema)
+			// Check if already pre-dropped
+			if preDroppedViews != nil && preDroppedViews[viewKey] {
+				// Skip DROP, only CREATE since view was already dropped in pre-drop phase
+				createSQL := generateViewSQL(diff.New, targetSchema)
 
-			statements := []SQLStatement{
-				{
-					SQL:                 dropSQL,
-					CanRunInTransaction: true,
-				},
-				{
-					SQL:                 createSQL,
+				context := &diffContext{
+					Type:                DiffTypeMaterializedView,
+					Operation:           DiffOperationCreate,
+					Path:                fmt.Sprintf("%s.%s", diff.New.Schema, diff.New.Name),
+					Source:              diff,
 					CanRunInTransaction: true,
-				},
-			}
+				}
+				collector.collect(context, createSQL)
+			} else {
+				// DROP the old materialized view
+				dropSQL := fmt.Sprintf("DROP MATERIALIZED VIEW %s RESTRICT;", viewName)
+				createSQL := generateViewSQL(diff.New, targetSchema)
+
+				statements := []SQLStatement{
+					{
+						SQL:                 dropSQL,
+						CanRunInTransaction: true,
+					},
+					{
+						SQL:                 createSQL,
+						CanRunInTransaction: true,
+					},
+				}
 
-			// Use DiffOperationAlter to categorize as a modification
-			context := &diffContext{
-				Type:                DiffTypeMaterializedView,
-				Operation:           DiffOperationAlter,
-				Path:                fmt.Sprintf("%s.%s", diff.New.Schema, diff.New.Name),
-				Source:              diff,
-				CanRunInTransaction: true,
+				// Use DiffOperationAlter to categorize as a modification
+				context := &diffContext{
+					Type:                DiffTypeMaterializedView,
+					Operation:           DiffOperationAlter,
+					Path:                fmt.Sprintf("%s.%s", diff.New.Schema, diff.New.Name),
+					Source:              diff,
+					CanRunInTransaction: true,
+				}
+				collector.collectStatements(context, statements)
 			}
-			collector.collectStatements(context, statements)
 
 			// Add view comment if present
 			if diff.New.Comment != "" {
@@ -330,3 +347,27 @@ func viewDependsOnView(viewA *ir.View, viewBName string) bool {
 	// This can be enhanced with proper dependency parsing later
 	return strings.Contains(strings.ToLower(viewA.Definition), strings.ToLower(viewBName))
 }
+
+// viewDependsOnTable checks if a view depends on a specific table
+// by checking if the table name appears in the view definition
+func viewDependsOnTable(view *ir.View, tableSchema, tableName string) bool {
+	if view == nil || view.Definition == "" {
+		return false
+	}
+
+	def := strings.ToLower(view.Definition)
+	tableNameLower := strings.ToLower(tableName)
+
+	// Check for unqualified table name
+	if strings.Contains(def, tableNameLower) {
+		return true
+	}
+
+	// Check for qualified table name (schema.table)
+	qualifiedName := strings.ToLower(tableSchema + "." + tableName)
+	if strings.Contains(def, qualifiedName) {
+		return true
+	}
+
+	return false
+}
diff --git a/testdata/diff/dependency/table_to_materialized_view/diff.sql b/testdata/diff/dependency/table_to_materialized_view/diff.sql
new file mode 100644
index 00000000..f36e6cf6
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/diff.sql
@@ -0,0 +1,11 @@
+DROP MATERIALIZED VIEW expensive_products RESTRICT;
+
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
diff --git a/testdata/diff/dependency/table_to_materialized_view/new.sql b/testdata/diff/dependency/table_to_materialized_view/new.sql
new file mode 100644
index 00000000..34373ef8
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/new.sql
@@ -0,0 +1,11 @@
+-- Table structure changed (added column), materialized view updated to use it
+CREATE TABLE products (
+    id integer PRIMARY KEY,
+    name text NOT NULL,
+    price numeric(10,2),
+    category text
+);
+
+-- Materialized view now includes category
+CREATE MATERIALIZED VIEW expensive_products AS
+SELECT id, name, price, category FROM products WHERE price > 100;
diff --git a/testdata/diff/dependency/table_to_materialized_view/old.sql b/testdata/diff/dependency/table_to_materialized_view/old.sql
new file mode 100644
index 00000000..7ffdf807
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/old.sql
@@ -0,0 +1,10 @@
+-- Base table referenced by a materialized view
+CREATE TABLE products (
+    id integer PRIMARY KEY,
+    name text NOT NULL,
+    price numeric(10,2)
+);
+
+-- Materialized view that depends on the products table
+CREATE MATERIALIZED VIEW expensive_products AS
+SELECT id, name, price FROM products WHERE price > 100;
diff --git a/testdata/diff/dependency/table_to_materialized_view/plan.json b/testdata/diff/dependency/table_to_materialized_view/plan.json
new file mode 100644
index 00000000..45d8a407
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/plan.json
@@ -0,0 +1,32 @@
+{
+  "version": "1.0.0",
+  "pgschema_version": "1.4.3",
+  "created_at": "1970-01-01T00:00:00Z",
+  "source_fingerprint": {
+    "hash": "55a7680d1ce73c3b56fbc34065ad9197a57668cc1cdcd0854bce421c2fa33d4b"
+  },
+  "groups": [
+    {
+      "steps": [
+        {
+          "sql": "DROP MATERIALIZED VIEW expensive_products RESTRICT;",
+          "type": "materialized_view",
+          "operation": "drop",
+          "path": "public.expensive_products"
+        },
+        {
+          "sql": "ALTER TABLE products ADD COLUMN category text;",
+          "type": "table.column",
+          "operation": "create",
+          "path": "public.products.category"
+        },
+        {
+          "sql": "CREATE MATERIALIZED VIEW IF NOT EXISTS expensive_products AS\n SELECT id,\n    name,\n    price,\n    category\n   FROM products\n  WHERE price > 100::numeric;",
+          "type": "materialized_view",
+          "operation": "create",
+          "path": "public.expensive_products"
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/diff/dependency/table_to_materialized_view/plan.sql b/testdata/diff/dependency/table_to_materialized_view/plan.sql
new file mode 100644
index 00000000..f36e6cf6
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/plan.sql
@@ -0,0 +1,11 @@
+DROP MATERIALIZED VIEW expensive_products RESTRICT;
+
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
diff --git a/testdata/diff/dependency/table_to_materialized_view/plan.txt b/testdata/diff/dependency/table_to_materialized_view/plan.txt
new file mode 100644
index 00000000..cb23cfe7
--- /dev/null
+++ b/testdata/diff/dependency/table_to_materialized_view/plan.txt
@@ -0,0 +1,27 @@
+Plan: 1 to add, 1 to modify.
+
+Summary by type:
+  tables: 1 to modify
+  materialized views: 1 to add
+
+Tables:
+  ~ products
+    + category (column)
+
+Materialized views:
+  + expensive_products
+
+DDL to be executed:
+--------------------------------------------------
+
+DROP MATERIALIZED VIEW expensive_products RESTRICT;
+
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
diff --git a/testdata/diff/dependency/table_to_view/diff.sql b/testdata/diff/dependency/table_to_view/diff.sql
new file mode 100644
index 00000000..6e8021e5
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/diff.sql
@@ -0,0 +1,9 @@
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE OR REPLACE VIEW expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
diff --git a/testdata/diff/dependency/table_to_view/new.sql b/testdata/diff/dependency/table_to_view/new.sql
new file mode 100644
index 00000000..0afd50a7
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/new.sql
@@ -0,0 +1,11 @@
+-- Table structure changed (added column), view updated to use it
+CREATE TABLE products (
+    id integer PRIMARY KEY,
+    name text NOT NULL,
+    price numeric(10,2),
+    category text
+);
+
+-- View now includes category
+CREATE VIEW expensive_products AS
+SELECT id, name, price, category FROM products WHERE price > 100;
diff --git a/testdata/diff/dependency/table_to_view/old.sql b/testdata/diff/dependency/table_to_view/old.sql
new file mode 100644
index 00000000..cf71ae47
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/old.sql
@@ -0,0 +1,10 @@
+-- Base table referenced by a view
+CREATE TABLE products (
+    id integer PRIMARY KEY,
+    name text NOT NULL,
+    price numeric(10,2)
+);
+
+-- View that depends on the products table
+CREATE VIEW expensive_products AS
+SELECT id, name, price FROM products WHERE price > 100;
diff --git a/testdata/diff/dependency/table_to_view/plan.json b/testdata/diff/dependency/table_to_view/plan.json
new file mode 100644
index 00000000..13bf2cad
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/plan.json
@@ -0,0 +1,26 @@
+{
+  "version": "1.0.0",
+  "pgschema_version": "1.4.3",
+  "created_at": "1970-01-01T00:00:00Z",
+  "source_fingerprint": {
+    "hash": "cb42ebddb31b25aff61341a057c2793b8c6dda37e3e1a6eee296bfb31ec71c50"
+  },
+  "groups": [
+    {
+      "steps": [
+        {
+          "sql": "ALTER TABLE products ADD COLUMN category text;",
+          "type": "table.column",
+          "operation": "create",
+          "path": "public.products.category"
+        },
+        {
+          "sql": "CREATE OR REPLACE VIEW expensive_products AS\n SELECT id,\n    name,\n    price,\n    category\n   FROM products\n  WHERE price > 100::numeric;",
+          "type": "view",
+          "operation": "alter",
+          "path": "public.expensive_products"
+        }
+      ]
+    }
+  ]
+}
diff --git a/testdata/diff/dependency/table_to_view/plan.sql b/testdata/diff/dependency/table_to_view/plan.sql
new file mode 100644
index 00000000..6e8021e5
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/plan.sql
@@ -0,0 +1,9 @@
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE OR REPLACE VIEW expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
diff --git a/testdata/diff/dependency/table_to_view/plan.txt b/testdata/diff/dependency/table_to_view/plan.txt
new file mode 100644
index 00000000..9843fea7
--- /dev/null
+++ b/testdata/diff/dependency/table_to_view/plan.txt
@@ -0,0 +1,25 @@
+Plan: 2 to modify.
+
+Summary by type:
+  tables: 1 to modify
+  views: 1 to modify
+
+Tables:
+  ~ products
+    + category (column)
+
+Views:
+  ~ expensive_products
+
+DDL to be executed:
+--------------------------------------------------
+
+ALTER TABLE products ADD COLUMN category text;
+
+CREATE OR REPLACE VIEW expensive_products AS
+ SELECT id,
+    name,
+    price,
+    category
+   FROM products
+  WHERE price > 100::numeric;
