CREATE OR REPLACE PROCEDURE public.sp_alterdropcolumn(
    table_name TEXT,
    exclude_column TEXT
)
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
    new_index_def TEXT;
    key_columns_list TEXT[];
    include_columns_list TEXT[];
    tbl_schema TEXT;
    tbl_name TEXT;
    column_exists BOOLEAN := false;
    index_exists BOOLEAN;
    created_indexes TEXT[] := '{}';
    index_signature TEXT;
BEGIN
    -- Разделяем схему и имя таблицы
    tbl_schema := split_part(table_name, '.', 1);
    tbl_name := split_part(table_name, '.', 2);
    IF tbl_name = '' THEN
        tbl_schema := 'public';
        tbl_name := table_name;
    END IF;

    -- Проверяем есть ли колонка вообще
    SELECT EXISTS (
        SELECT 1
        FROM information_schema.columns AS cl
        WHERE cl.table_schema = tbl_schema
          AND cl.table_name = tbl_name
          AND cl.column_name = exclude_column
    )
    INTO column_exists;

    -- Если нет то возвращаемся
    IF NOT column_exists THEN
        RAISE NOTICE 'Column "%" does not exist in table "%"', exclude_column, table_name;
        RETURN;
    END IF;


    -- Проверка уникального индекса
    SELECT EXISTS (
        SELECT 1
        FROM pg_index i
        CROSS JOIN LATERAL unnest(i.indkey) AS k(attnum)
        JOIN pg_attribute a 
            ON i.indrelid = a.attrelid 
           AND k.attnum = a.attnum
        WHERE i.indrelid = format('%I.%I', tbl_schema, tbl_name)::regclass
          AND i.indisprimary = false
          AND i.indisunique = true
          AND a.attname = exclude_column
    )
    INTO index_exists;

    IF NOT index_exists THEN
        RAISE NOTICE 'Column "%" has no unique index in table "%"', exclude_column, table_name;
        RETURN;
    END IF;

    -- Обрабатываем индексы с правильным порядком полей! Порядок обязателен!
    FOR rec IN
        SELECT
            n.nspname AS idx_schema,
            ci.relname AS idx_name,
            i.indisunique AS is_unique,
            array_agg(a.attname ORDER BY k.i) FILTER (WHERE k.i <= i.indnkeyatts) AS key_columns,
            array_agg(a.attname ORDER BY k.i) FILTER (WHERE k.i > i.indnkeyatts)  AS include_columns
        FROM pg_index i
        JOIN pg_class ci ON ci.oid = i.indexrelid
        JOIN pg_namespace n ON n.oid = ci.relnamespace
        CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS k(attnum, i)
        LEFT JOIN pg_attribute a ON i.indrelid = a.attrelid AND k.attnum = a.attnum
        WHERE i.indrelid = format('%I.%I', tbl_schema, tbl_name)::regclass
          AND i.indisprimary = false
        GROUP BY n.nspname, ci.relname, i.indisunique
    LOOP
        -- Убираем ненужное нам поле из списков, сохраняя порядок
        key_columns_list := array_remove(rec.key_columns, exclude_column);
        include_columns_list := array_remove(rec.include_columns, exclude_column);

        -- Если колонка отсутствует в списке то пропускаем
        IF key_columns_list IS NULL AND include_columns_list IS NULL THEN
            CONTINUE;
        END IF;

        -- Удаляем старый индекс через проверку
        EXECUTE format('DROP INDEX IF EXISTS %I.%I', rec.idx_schema, rec.idx_name);

        -- Так же не создаём индекс если после удаления ключа из индекса он становится пустым
        IF array_length(key_columns_list, 1) IS NULL THEN
            RAISE NOTICE 'Index "%"."%" dropped (no key columns left)', rec.idx_schema, rec.idx_name;
            CONTINUE;
        END IF;

        -- Собираем индекс с учётом порядка, в том числе с include
        index_signature := array_to_string(key_columns_list, ',') || '|' || coalesce(array_to_string(include_columns_list, ','), '');

        -- Проверяем, не создадим ли дубликат. Первая проверка всегда на пустом массиве
        IF index_signature = ANY (created_indexes) THEN
            RAISE NOTICE 'Index "%"."%" skipped: duplicate definition (%s)',
                         rec.idx_schema, rec.idx_name, index_signature;
            CONTINUE;
        END IF;

        -- Формируем новый индекс. Вопрос с уникальностью открытый, я бы не стал такие делать
        new_index_def := format(
            'CREATE %s INDEX %I ON %I.%I USING btree (%s)',
            CASE WHEN rec.is_unique THEN 'UNIQUE' ELSE '' END,
            rec.idx_name,
            tbl_schema,
            tbl_name,
            array_to_string(key_columns_list, ', ')
        );

        -- Добавляем INCLUDE
        IF array_length(include_columns_list, 1) IS NOT NULL THEN
            new_index_def := new_index_def || format(' INCLUDE (%s)', array_to_string(include_columns_list, ', '));
        END IF;

        -- Выполняем
        EXECUTE new_index_def;
        RAISE NOTICE 'Index "%"."%" rebuilt without column "%"', rec.idx_schema, rec.idx_name, exclude_column;

        -- Запоминаем сигнатуру, что бы после исключать на дубли
        created_indexes := array_append(created_indexes, index_signature);
    END LOOP;

    -- Теперь удаляем колонку
    EXECUTE format('ALTER TABLE %I.%I DROP COLUMN IF EXISTS %I', tbl_schema, tbl_name, exclude_column);
    RAISE NOTICE 'Column "%"."%"."%" dropped', tbl_schema, tbl_name, exclude_column;
END;
$$;