*04:23. Монитор мигает синим, за окном дрон-курьер ругается с беспилотной шаурмечной, а у меня курсовая по базам данных горит синим пламенем. Препод сказал: «Данные в таблице — как нейросети в головах у студентов. Если перепутал местами — переучивай, пока не починишь». Короче, надо поменять местами имена и фамилии в таблице. Погнали.*
В нашем мире у каждого второй — с нейроинтерфейсом, но базы данных остались неприкосновенными. Тут вам не хакнуть корпорацию, тут надо просто, чтобы в колонке first_name лежало имя, а в last_name — фамилия. А у меня при загрузке данных из старого терминала всё перепуталось.
Что имеем:
Таблица users, где Иван Петров почему-то оказался разложен не по полочкам: Иван в фамилиях, Петров в именах. Знакомая боль?
Уровень 1: Простое решение. Через временный столбец
Самый прямолинейный способ, который приходит в голову любому новичку: создать промежуточное хранилище, как запасной накопитель перед перепрошивкой.
-- Шаг 1. Создаем временный столбец
ALTER TABLE users ADD COLUMN temp_name VARCHAR(50);
-- Проверяем, что колонка создалась
SELECT * FROM users LIMIT 1;
-- Шаг 2. Копируем имена в "буфер обмена"
UPDATE users SET temp_name = first_name;
-- Контрольный SELECT: данные скопировались?
SELECT id, first_name, temp_name FROM users LIMIT 5;
-- Шаг 3. Переносим фамилии на место имен
UPDATE users SET first_name = last_name;
-- Проверяем промежуточный результат
SELECT id, first_name, last_name FROM users LIMIT 5;
-- Шаг 4. Возвращаем имена из временного хранилища на место фамилий
UPDATE users SET last_name = temp_name;
-- Финальная проверка
SELECT id, first_name, last_name FROM users LIMIT 5;
-- Шаг 5. Удаляем временный столбец
ALTER TABLE users DROP COLUMN temp_name;
-- Убеждаемся, что структура вернулась к исходной
\d users
Способ работает. Но пока я писал эти пять запросов с проверками, допил второй стакан кофе и задумался о смысле жизни. Неужели в PostgreSQL нет ничего элегантнее?
Уровень 2: Оптимальное решение. Прямой обмен
А потом я вспомнил, как устроены транзакции в PostgreSQL. Сервер же не тупо построчно выполняет команды — он сначала читает исходные значения, а потом применяет изменения. Это значит, мы можем обменять поля одной строчкой.
Подготовка: проверяем, что поля совместимы
-- Проверяем типы данных
SELECT
data_type
FROM
information_schema.columns
WHERE
table_name = 'users'
AND column_name IN ('first_name', 'last_name');
-- Смотрим на данные: нет ли там NULL, которые могут дать неожиданный эффект?
SELECT
COUNT(*) FILTER (WHERE first_name IS NULL) as null_first,
COUNT(*) FILTER (WHERE last_name IS NULL) as null_last
FROM users;
-- Оцениваем масштаб бедствия
SELECT COUNT(*) FROM users;
Если всё чисто — делаем магию:м
UPDATE users
SET
first_name = last_name,
last_name = first_name;
Проверяем результат:
-- Смотрим, что получилось
SELECT id, first_name, last_name FROM users ORDER BY id;
Всё встало на свои места. Никаких временных таблиц, никакого оверхеда. Сервер сам разобрался, что к чему.
Уровень 3: Сложный случай. Когда типы разные
Но, как в любом хорошем киберпанк-романе, наступает момент, когда приходит легаси-код и говорит: «У меня тут text и integer перепутались, почини, но ничего не сломай».
Исходная ситуация: в колонке name (TEXT) лежат числа, а в колонке code (INTEGER) — имена. Да, бывает и такое.
Диагностика:
-- Смотрим структуру
SELECT
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = 'products';
-- Проверяем, можно ли name преобразовать в INTEGER
SELECT
COUNT(*) as total,
COUNT(*) FILTER (WHERE name ~ '^[0-9]+$') as convertible_to_int
FROM products;
Если не все строки преобразуются — нужно чистить данные.
Рефакторинг с нуля:
-- 1. Создаем новые колонки с правильными типами
ALTER TABLE products ADD COLUMN new_name TEXT;
ALTER TABLE products ADD COLUMN new_code INTEGER;
-- 2. Подготавливаем данные: оставляем в поле name только цифры
UPDATE products
SET name = REGEXP_REPLACE(name, '[^0-9]', '', 'g')
WHERE name ~ '[^0-9]';
-- Проверяем, что мусора не осталось
SELECT name FROM products WHERE name !~ '^[0-9]*$';
-- 3. Переносим данные с конвертацией
UPDATE products SET new_name = code::TEXT;
UPDATE products SET new_code = name::INTEGER;
-- 4. Проверяем, что данные перенеслись корректно
SELECT name, new_code, code, new_name FROM products LIMIT 10;
-- 5. Удаляем старые колонки
ALTER TABLE products DROP COLUMN name, DROP COLUMN code;
-- 6. Переименовываем новые
ALTER TABLE products RENAME COLUMN new_name TO name;
ALTER TABLE products RENAME COLUMN new_code TO code;
-- Финальная проверка
SELECT * FROM products LIMIT 10;
Итоги ночного кодера
Три способа — три уровня погружения в PostgreSQL. Для себя я вынес простую истину: не надо изобретать велосипед там, где база данных уже всё умеет.
Если типы совпадают — делаем простой UPDATE с обменом полей. Это быстро, чисто и профессионально.
Если типы разные — готовимся к рефакторингу, но всегда проверяем данные до и после. Одна ошибка в конвертации может убить прод быстрее, чем любой вирус.
05:30. Глаза слипаются. Курсовая готова. Препод, если ты это читаешь — данные теперь на своих местах, проверки выполнены. А я пошел спать.