Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions EXPLAIN_PLAN_ANALYSIS.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
-- ============================================================================
-- EXPLAIN PLAN ANALYSIS: DynamicCrudService.getColumnMetadata Query
-- ============================================================================
-- This query retrieves table column metadata including:
-- • Basic column properties (name, type, length, precision, scale)
-- • Primary key detection via EXISTS subquery
-- • Unique constraint detection via EXISTS subquery
-- • Foreign key relationships via LEFT JOIN to derived table
-- • Column comments from metadata
-- ============================================================================

-- Step 1: Generate the EXPLAIN PLAN
EXPLAIN PLAN SET STATEMENT_ID = 'DCRUD_METADATA' FOR
SELECT utc.column_name,
utc.data_type,
utc.nullable,
utc.column_id,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
CASE
WHEN EXISTS (
SELECT 1
FROM user_cons_columns ucc
JOIN user_constraints uc ON ucc.constraint_name = uc.constraint_name
WHERE uc.constraint_type = 'P'
AND uc.table_name = utc.table_name
AND ucc.column_name = utc.column_name
) THEN 'Y' ELSE 'N' END AS is_primary_key,
CASE
WHEN EXISTS (
SELECT 1
FROM user_cons_columns ucc
JOIN user_constraints uc ON ucc.constraint_name = uc.constraint_name
WHERE uc.constraint_type = 'U'
AND uc.table_name = utc.table_name
AND ucc.column_name = utc.column_name
) THEN 'Y' ELSE 'N' END AS is_unique,
NVL(ucc.comments, '') AS column_comment,
fkc.ref_table_name,
fkc.ref_column_name
FROM user_tab_columns utc
LEFT JOIN user_col_comments ucc ON utc.table_name = ucc.table_name
AND utc.column_name = ucc.column_name
LEFT JOIN (
SELECT ucc1.table_name,
ucc1.column_name,
ucc2.table_name AS ref_table_name,
ucc2.column_name AS ref_column_name
FROM user_cons_columns ucc1
JOIN user_constraints uc1 ON ucc1.constraint_name = uc1.constraint_name
JOIN user_constraints uc2 ON uc1.r_constraint_name = uc2.constraint_name
JOIN user_cons_columns ucc2 ON uc2.constraint_name = ucc2.constraint_name
WHERE uc1.constraint_type = 'R'
) fkc ON utc.table_name = fkc.table_name AND utc.column_name = fkc.column_name
WHERE utc.table_name = UPPER('USERS') -- Example: Replace with actual table name
ORDER BY utc.column_id;

-- Step 2: Retrieve and display the plan
SET LONG 20000 LONGCHUNKSIZE 20000
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'DCRUD_METADATA', 'ALL'));

-- Step 3: Clean up
DELETE FROM PLAN_TABLE WHERE STATEMENT_ID = 'DCRUD_METADATA';
COMMIT;

-- ============================================================================
-- PERFORMANCE ANALYSIS & OPTIMIZATION NOTES
-- ============================================================================
--
-- Current Issues:
-- 1. TWO SEPARATE EXISTS subqueries running for EACH column (is_primary_key, is_unique)
-- These cause repeated scans of user_cons_columns and user_constraints
--
-- 2. LEFT JOIN to derived table with 4-way join (ucc1→uc1→uc2→ucc2)
-- This materializes ALL foreign key relationships then filters
--
-- 3. Multiple JOINs on user_constraints (HIGH-CARDINALITY DICTIONARY VIEW)
-- Oracle's data dictionary views are not always well-indexed
--
-- Estimated Problem: O(N) EXISTS checks + Derived table materialization
-- Impact: Slow for tables with many constraints or foreign keys
--
-- ============================================================================
-- RECOMMENDED OPTIMIZATION: Collapse to Single Dictionary Query
-- ============================================================================

-- OPTIMIZED VERSION: Use all_constraints/all_cons_columns to get all metadata at once

EXPLAIN PLAN SET STATEMENT_ID = 'DCRUD_METADATA_OPT' FOR
SELECT utc.column_name,
utc.data_type,
utc.nullable,
utc.column_id,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
MAX(CASE WHEN uc.constraint_type = 'P' THEN 'Y' ELSE 'N' END) AS is_primary_key,
MAX(CASE WHEN uc.constraint_type = 'U' THEN 'Y' ELSE 'N' END) AS is_unique,
NVL(ucc_comments.comments, '') AS column_comment,
MAX(CASE WHEN uc.constraint_type = 'R' THEN ucc_fk.table_name ELSE NULL END) AS ref_table_name,
MAX(CASE WHEN uc.constraint_type = 'R' THEN ucc_fk.column_name ELSE NULL END) AS ref_column_name
FROM user_tab_columns utc
LEFT JOIN user_col_comments ucc_comments
ON utc.table_name = ucc_comments.table_name
AND utc.column_name = ucc_comments.column_name
LEFT JOIN user_cons_columns ucc_local
ON utc.table_name = ucc_local.table_name
AND utc.column_name = ucc_local.column_name
LEFT JOIN user_constraints uc
ON ucc_local.constraint_name = uc.constraint_name
LEFT JOIN user_constraints uc_ref
ON uc.constraint_type = 'R'
AND uc.r_constraint_name = uc_ref.constraint_name
LEFT JOIN user_cons_columns ucc_fk
ON uc_ref.constraint_name = ucc_fk.constraint_name
WHERE utc.table_name = UPPER('USERS') -- Example: Replace with actual table name
GROUP BY utc.column_id, utc.column_name, utc.data_type, utc.nullable,
utc.data_length, utc.data_precision, utc.data_scale, utc.data_default,
ucc_comments.comments
ORDER BY utc.column_id;

-- Step 4: Compare optimized plan
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'DCRUD_METADATA_OPT', 'ALL'));

-- Step 5: Clean up
DELETE FROM PLAN_TABLE WHERE STATEMENT_ID = 'DCRUD_METADATA_OPT';
COMMIT;

-- ============================================================================
-- HOW TO USE THIS ANALYSIS
-- ============================================================================
-- 1. Connect to your Oracle database as the same user (SYSTEM or SSF_USER)
-- 2. Run the script: @EXPLAIN_PLAN_ANALYSIS.sql
-- 3. Compare the two execution plans (original vs optimized)
-- 4. Check for:
-- - "TABLE ACCESS FULL" on user_cons_columns (indicates high cost)
-- - Multiple FILTER operations (EXISTS checks)
-- - Nested loop joins on large result sets
-- 5. Note the I/O cost and estimated rows
--
-- For large schemas with many tables/constraints, consider:
-- - Caching metadata in application layer
-- - Adding application-side metadata cache with TTL
-- - Creating materialized view of table/constraint metadata
-- ============================================================================
235 changes: 235 additions & 0 deletions OPTIMIZED_QUERY.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
-- ============================================================================
-- OPTIMIZED: DynamicCrudService.getColumnMetadata Query
-- ============================================================================
-- This version eliminates redundant EXISTS checks and uses a single LEFT JOIN
-- strategy with GROUP BY to materialize constraint metadata once.
--
-- Performance improvements:
-- - Eliminates O(N) separate EXISTS subqueries
-- - Single pass through constraints instead of N passes
-- - Replaces correlated subqueries with efficient LEFT JOINs
-- - Uses aggregation to handle 1-to-many relationships
-- ============================================================================

EXPLAIN PLAN SET STATEMENT_ID = 'DCRUD_METADATA_OPTIMIZED' FOR
SELECT
utc.column_name,
utc.data_type,
utc.nullable,
utc.column_id,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
-- Use MAX/MIN aggregation to collapse constraint checks into single pass
MAX(CASE WHEN uc.constraint_type = 'P' THEN 'Y' ELSE 'N' END) AS is_primary_key,
MAX(CASE WHEN uc.constraint_type = 'U' THEN 'Y' ELSE 'N' END) AS is_unique,
MAX(ucc_comments.comments) AS column_comment,
-- For foreign keys, take first matching reference (will be same for all matching constraint rows)
MAX(CASE WHEN uc.constraint_type = 'R' THEN uc2.table_name ELSE NULL END) AS ref_table_name,
MAX(CASE WHEN uc.constraint_type = 'R' THEN ucc_ref.column_name ELSE NULL END) AS ref_column_name
FROM
user_tab_columns utc
-- Comments: simple 1:1 LEFT JOIN
LEFT JOIN user_col_comments ucc_comments
ON utc.table_name = ucc_comments.table_name
AND utc.column_name = ucc_comments.column_name
-- Constraints: single LEFT JOIN + GROUP BY replaces 2 EXISTS checks + 1 complex join
LEFT JOIN user_cons_columns ucc_local
ON utc.table_name = ucc_local.table_name
AND utc.column_name = ucc_local.column_name
LEFT JOIN user_constraints uc
ON ucc_local.constraint_name = uc.constraint_name
-- Foreign key reference table lookup (only for FK constraints)
LEFT JOIN user_constraints uc2
ON uc.constraint_type = 'R'
AND uc.r_constraint_name = uc2.constraint_name
LEFT JOIN user_cons_columns ucc_ref
ON uc.constraint_type = 'R'
AND uc2.constraint_name = ucc_ref.constraint_name
AND ucc_ref.position = 1 -- Take first column of composite FK (99% of cases)
WHERE
utc.table_name = UPPER('USERS')
GROUP BY
utc.column_id,
utc.column_name,
utc.data_type,
utc.nullable,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default
ORDER BY
utc.column_id;

-- Display the optimized plan
SET LONG 20000 LONGCHUNKSIZE 20000
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'DCRUD_METADATA_OPTIMIZED', 'ALL'));

-- ============================================================================
-- ALTERNATIVE: Even Simpler Using Analytic Functions (Oracle 12.1+)
-- ============================================================================
-- If you're on 12.1+, use FIRST_VALUE with OVER clauses for cleaner code
-- ============================================================================

EXPLAIN PLAN SET STATEMENT_ID = 'DCRUD_METADATA_ANALYTIC' FOR
SELECT
column_id,
column_name,
data_type,
nullable,
data_length,
data_precision,
data_scale,
data_default,
MAX(is_primary_key) OVER (PARTITION BY column_id) AS is_primary_key,
MAX(is_unique) OVER (PARTITION BY column_id) AS is_unique,
FIRST_VALUE(comments) OVER (PARTITION BY column_id ORDER BY comments NULLS LAST) AS column_comment,
FIRST_VALUE(ref_table_name) OVER (PARTITION BY column_id ORDER BY ref_table_name NULLS LAST) AS ref_table_name,
FIRST_VALUE(ref_column_name) OVER (PARTITION BY column_id ORDER BY ref_column_name NULLS LAST) AS ref_column_name
FROM (
SELECT
utc.column_id,
utc.column_name,
utc.data_type,
utc.nullable,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
CASE WHEN uc.constraint_type = 'P' THEN 'Y' ELSE 'N' END AS is_primary_key,
CASE WHEN uc.constraint_type = 'U' THEN 'Y' ELSE 'N' END AS is_unique,
ucc_comments.comments,
uc2.table_name AS ref_table_name,
ucc_ref.column_name AS ref_column_name,
ROW_NUMBER() OVER (PARTITION BY utc.column_id ORDER BY uc.constraint_type, uc2.table_name) AS rn
FROM
user_tab_columns utc
LEFT JOIN user_col_comments ucc_comments
ON utc.table_name = ucc_comments.table_name
AND utc.column_name = ucc_comments.column_name
LEFT JOIN user_cons_columns ucc_local
ON utc.table_name = ucc_local.table_name
AND utc.column_name = ucc_local.column_name
LEFT JOIN user_constraints uc
ON ucc_local.constraint_name = uc.constraint_name
LEFT JOIN user_constraints uc2
ON uc.constraint_type = 'R'
AND uc.r_constraint_name = uc2.constraint_name
LEFT JOIN user_cons_columns ucc_ref
ON uc.constraint_type = 'R'
AND uc2.constraint_name = ucc_ref.constraint_name
AND ucc_ref.position = 1
WHERE
utc.table_name = UPPER('USERS')
)
WHERE
rn = 1
ORDER BY
column_id;

-- Display the analytic plan
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'DCRUD_METADATA_ANALYTIC', 'ALL'));

-- ============================================================================
-- COMPARE EXECUTION TIMES
-- ============================================================================
-- Run this to measure performance difference:

SET TIMING ON

-- Original query (slow)
SELECT COUNT(*) FROM (
SELECT utc.column_name,
utc.data_type,
utc.nullable,
utc.column_id,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
CASE
WHEN EXISTS (
SELECT 1
FROM user_cons_columns ucc
JOIN user_constraints uc ON ucc.constraint_name = uc.constraint_name
WHERE uc.constraint_type = 'P'
AND uc.table_name = utc.table_name
AND ucc.column_name = utc.column_name
) THEN 'Y' ELSE 'N' END AS is_primary_key,
CASE
WHEN EXISTS (
SELECT 1
FROM user_cons_columns ucc
JOIN user_constraints uc ON ucc.constraint_name = uc.constraint_name
WHERE uc.constraint_type = 'U'
AND uc.table_name = utc.table_name
AND ucc.column_name = utc.column_name
) THEN 'Y' ELSE 'N' END AS is_unique,
NVL(ucc.comments, '') AS column_comment,
fkc.ref_table_name,
fkc.ref_column_name
FROM user_tab_columns utc
LEFT JOIN user_col_comments ucc ON utc.table_name = ucc.table_name
AND utc.column_name = ucc.column_name
LEFT JOIN (
SELECT ucc1.table_name,
ucc1.column_name,
ucc2.table_name AS ref_table_name,
ucc2.column_name AS ref_column_name
FROM user_cons_columns ucc1
JOIN user_constraints uc1 ON ucc1.constraint_name = uc1.constraint_name
JOIN user_constraints uc2 ON uc1.r_constraint_name = uc2.constraint_name
JOIN user_cons_columns ucc2 ON uc2.constraint_name = ucc2.constraint_name
WHERE uc1.constraint_type = 'R'
) fkc ON utc.table_name = fkc.table_name AND utc.column_name = fkc.column_name
WHERE utc.table_name = UPPER('USERS')
);

-- Optimized query (fast)
SELECT COUNT(*) FROM (
SELECT
utc.column_name,
utc.data_type,
utc.nullable,
utc.column_id,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default,
MAX(CASE WHEN uc.constraint_type = 'P' THEN 'Y' ELSE 'N' END) AS is_primary_key,
MAX(CASE WHEN uc.constraint_type = 'U' THEN 'Y' ELSE 'N' END) AS is_unique,
MAX(ucc_comments.comments) AS column_comment,
MAX(CASE WHEN uc.constraint_type = 'R' THEN uc2.table_name ELSE NULL END) AS ref_table_name,
MAX(CASE WHEN uc.constraint_type = 'R' THEN ucc_ref.column_name ELSE NULL END) AS ref_column_name
FROM
user_tab_columns utc
LEFT JOIN user_col_comments ucc_comments
ON utc.table_name = ucc_comments.table_name
AND utc.column_name = ucc_comments.column_name
LEFT JOIN user_cons_columns ucc_local
ON utc.table_name = ucc_local.table_name
AND utc.column_name = ucc_local.column_name
LEFT JOIN user_constraints uc
ON ucc_local.constraint_name = uc.constraint_name
LEFT JOIN user_constraints uc2
ON uc.constraint_type = 'R'
AND uc.r_constraint_name = uc2.constraint_name
LEFT JOIN user_cons_columns ucc_ref
ON uc.constraint_type = 'R'
AND uc2.constraint_name = ucc_ref.constraint_name
AND ucc_ref.position = 1
WHERE
utc.table_name = UPPER('USERS')
GROUP BY
utc.column_id,
utc.column_name,
utc.data_type,
utc.nullable,
utc.data_length,
utc.data_precision,
utc.data_scale,
utc.data_default
);

SET TIMING OFF
Loading
Loading