ENote
  • ✨Home
    • Відмова від відповідальності
  • Progr
    • Python
      • Модулі
        • pip
        • cgitb
        • datetime
          • Класс datetime() модуля datetime
            • Методы объекта datetime.datetime()
            • datetime.datetime examples
          • Класс timedelta() модуля datetime
            • datetime.timedelta examples
          • Класс date() модуля datetime
            • datetime.date examples
          • Класс time() модуля datetime
            • Формат для функций .strftime() и .strptime(). модуля datetime
        • os.path
        • pathlib
          • pathlib to str
        • subproces
        • os
        • Jinja
          • Синтаксис шаблонів Jinja
          • Клас Environment()
          • Простой пример cgi-скрипта c Jinja-шаблоном
          • Создание переменных
            • Як оновити глобальну змінну зсередини оператора IF / ELSE або циклу FOR
          • Фільтри і методи
          • Число у рядок, slices
          • Вбудовані фільтри Jinja2
        • re
          • re.search - example
          • re.findall - example
        • email / smtplib
          • Как отправлять электронные письма с помощью Python
        • requests
          • Як зберегти та завантажити файли cookie в запитах Python?
          • Извлечение и установка cookies с модулем requests в Python
          • Links
        • http.cookies
          • Всё о работе с cookie в Python — класс http.cookies
        • xlrd
        • xlwt
        • borb
          • ChunkOfText
          • send_usage_statistics
          • borb clear
          • 2.1.3 vs 2.1.15
          • QR-code
          • Залежності borb
        • JSON
          • Кирилиця в JSON
        • matplotlib
        • argparse
        • click
        • configparser
        • traceback
        • sys
          • exit()
        • mysql-connector-python
        • logging
        • icrawler
        • Auto Plates
        • rembg
        • random
      • Strings
        • Built-in methods
        • Форматування виводу
        • Початкові нулі
        • Рядок в число
      • list
        • Об’єднання списків
        • list.sort(), sorted()
        • list.reverse(), reversed()
        • all(), any()
        • sum(), min(), max()
        • map(), filter(), reduce()
        • join(), split()
      • tuple
      • dict
        • Об'єднання / злиття словників
        • Сортування словника Python: значення, ключі тощо
      • set
      • class Enum
      • Середнє арифметичне
      • Virtual environment
        • web-app
      • type(), isinstance()
      • __main__
      • Files & Dirs
        • Try except for exception handling
        • Cписок файлов директории
        • User Home Dir
        • Copy file
      • *args, **qwargs
      • Links
      • Область видимости
      • Handling a File Error
      • assert
      • if
      • Числа
        • Округлення чисел
        • Отримати число з рядка
      • Обработка исключений в Python
      • Файлы и сериализация данных
      • OOP
      • Net and Web
      • Структура проекта на Python
      • Распаковка итерируемых объектов
      • Links
      • Algorithms
      • Python exit commands: quit(), exit(), sys.exit() and os._exit()
      • Цикли for / while
      • uuid
    • JavaScript
      • String
        • replaceAll() polyfill
        • Шаблонные строки
      • Array
        • Все способы перебора массива в JavaScript
      • Object
      • document.location
      • RegExp
      • Examples
        • Вычисление остатка от деления
        • Остаток от деления и деление без остатка
        • Округление числа
      • XMLHttpRequest
      • alert, prompt, confirm
      • onclick
      • hidden, display:none
      • LocalStorage, sessionStorage
      • null, undefined
      • cookies (js)
      • var, let и const
        • var vs let
        • const
      • Модифікація DOM
        • DOM select
      • JSON
        • Try
      • fetch
      • typeof
      • FormData не включає disabled набори полів
      • FormData, fdata
      • Більше одного відео YouTube на одній сторінці
    • HTML, CSS
      • favicon
      • Деякі спецсимволи
      • meta
      • ASCII table
      • lang
      • Псевдоелементи ::after і ::before
      • Cursor
      • Об использовании нестандартных пробелов
      • Картинка фоном
      • Безпечні веб-шрифти
      • Завжди внизу, незалежно від пропорцій екрану
      • напівпрозорий елемент
      • Символи з тінью
      • SVG (bootstrap)
      • rel = canonical
      • link stylesheet: integrity & crossorigin
      • rel = noopener
    • Bootstrap
      • Form Validate
      • Password show/hide
    • Errors
  • Dev
    • Git
      • clone
      • git-scm (book)
      • git config
        • files .git*
        • core.filemode
        • core.sharedRepository
      • .gitignore
      • .gitkeep
      • Видалити з репозиторію
        • Видалений файл з однієї гілки...
      • Пам’ятка
        • Перегляд історії комітів
        • Скасувати git add
        • revert
        • Скасувати внесені зміни у файл
        • Додати до коміту файл
        • Видалити історію попередніх коммітів, та почати "з нуля"
        • Додати файли в останній коміт
      • Робота з гілками
        • Порівняти гілки
      • Git за полчаса
      • Три розділи проєкту Git
      • Merge conflict
      • Pull error
        • Git Error: You have divergent branches...
      • diff
      • Video
      • Merge скасувати
      • .gitignore: Permission denied
    • GitHub
      • SSH-підключення до GitHub
      • Перенести на сервер локальный репозиторий
      • Перенести на сервер репозиторій разом з історією
      • Створення змісту
    • Security
      • robots.txt
      • Cookies
    • Аутентифікація і cookies
      • ChatGPT
  • data
    • MySQL
      • MyISAM vs InnoDB
      • Типи даних
        • NULL (todo)
        • TIMESTAMP
        • YEAR
        • JSON
        • Требования к памяти для символьных типов
        • Поиск записей в таблице, которым нет соответствия в другой
      • Приклад створення БД, та користувача
        • Права для пользователей
      • Переглянути всі індекси таблиці
      • Копіювання, клонування таблиць
      • TEMPORARY TABLE
      • JOIN
      • ALTER TABLE
      • AUTO_INCREMENT
        • AUTO_INCREMENT у складовому індексі
      • LIMIT
        • Использование MySQL LIMIT
      • 10 Примеров входной загрузки данных из текстового файла в таблицы MySQL
      • Рішення
        • Выявление и удаление несвязанных записей
        • Выборка произвольных записей
        • Коректне сортування українських літер
        • Найти записи, которые присутствуют в одной таблице и отсутствуют во второй
        • Как удобно посмотреть данные...
        • Нахождение "дыр" в нумерации
        • Знайти дубликати полів в одній таблиці
        • Дані колонки 1 табл. перенести в 2 табл.
      • Функції
        • LAST_INSERT_ID()
        • GROUP_CONCAT
        • COUNT + DISTINCT
        • Функции для работы с датами и временем
      • Автоматизируйте создание бэкапов
      • mysqldump
        • Time Zone UTC
      • Результат запиту у файл
      • Результат запиту у змінну
      • Пособие по MySQL на Python
      • Змінні
      • Эмуляция функции row_number() в MySQL
      • Изучаем хранимые процедуры MySQL
      • SELECT DISTINCT
      • Dump всієї бази даних
      • Індекси
      • FOREIGN KEY
      • MAX(), MIN()
      • LENGTH, CHAR_LENGTH
      • Встановлення
    • MariaDB
    • PostgreSQL
      • Работа с базой данных PostgreSQL
      • Работаем с PostgreSQL через командную строку в Linux
    • Domains
      • Життєвий цикл доменів
      • Статус домена
    • SQLite, MySQL и PostgreSQL: сравниваем популярные реляционные СУБД
  • Linux
    • DNS
      • Как в DNS прописать 301 редирект
      • mail
        • SPF
          • Mirohost
        • _dmarc _domainkey
        • DKIM
        • Прописати ключі DKIM в exim
        • Листи з неіснуючим адресатом
    • Server
      • Zomro
        • pip
        • SSH-доступ по ключу (zomro)
        • venv
        • UnicodeEncodeError: 'latin-1' codec can't encode character
        • Mail Ports
        • Редірект з SSL-сертификатом на транзитному сайті
        • Редірект з SSL за допомогою .htaccess
      • Створення нового користувача з привілеями sudo в Ubuntu
      • SSH-доступ по ключу
      • Часовий пояс в Ubuntu 20.04
      • SSH-підключення командний рядок
      • Як встановити Python 3.9 (нижчу) на Ubuntu 22.04
      • Автозагрузка сервисов в Ubuntu
      • Підвищення безпеки SSH
      • Ubuntu Server
        • Art 01
    • Commands
      • adduser
      • apt
      • cat
      • ls
      • tar
      • ln
      • find
      • chmod
      • chown
      • mv
      • dig
      • ping
      • passwd
      • htpasswd
      • umask
      • usermode
      • history
      • cmp
      • chattr +i
    • Config
      • Keyboard
      • windows
      • My kbdswtch
      • Затримка при завантаженні системи
      • Files
        • /etc/resolv.conf
        • /boot/grub/grub.cfg
        • .config/user-dir.dirs
        • /etc/fstab
      • Як встановити шрифти
    • Apache
      • Встановлення
      • Подключить виртуальный хост
      • Файл .htaccess
        • Установка индексного файла
        • Фільтр IP-адрес
        • ModRewrite
          • Заборонити доступ за User-Agent
          • Перенаправити на іншу сторінку
          • Додавати слеш до адреси
        • Включити SSI
        • Виконувати скрипти CGI
        • Тимчасовий перехід з одного домену на інший
      • SSI
      • SSL
      • Відключити старт Apache з системою
      • Помилки
        • Скрипт не працює
        • CGI-скрипт не виводить кирилицю
        • Could not reliably determine
        • Permissions are missing on a component of the path
        • Symbolic link not allowed or link target not accessible
      • AddType, AddLanguage, AddCharset
    • Nginx
      • 301 редирект з www. та http: на https://(без www.)domain
    • Soft
      • SublimeText
        • Plugins
      • Firefox
      • Gwenview
      • inkscape
      • Double Commander
      • nano
      • mc
        • Знайти потрібний файл
      • meld / diffuse
      • hexedit
      • Kazam - відео з екрану
      • VeraCrypt
      • XnView MP
      • LibreOffice
      • xdotool
      • System Load Monitor
      • Battery Monitor
      • qBittorrent
    • Перетворення .RPM в .DEB
    • Bash
      • Конкатенация строк в Bash
      • Page
    • Файлові часові позначки в Linux: atime, mtime, ctime
    • Права доступу для файлів і каталогів
    • Зміна паролю root
    • Быстро удалить огромное количество файлов в каталоге
    • Як узнати версію Linux?
    • USB Flash ext4
    • Clear Cache
    • Доступ до спільної папки на Windows
    • Віртуальні консолі TTY1–TTY6
    • APT. Заборона оновлення
  • Різне
    • GitBook
    • Банковское округление
    • Ім’я користувача Youtube
  • Hard
    • Hardware
      • Acer Extensa
      • Таймер Feron TM22
      • WD My Book World Edition 2Tb
        • FTP
        • SSH
          • Проблеми
        • SSHFS
      • Canon PIXMA E3340
      • Термометри
    • Auto
      • Акумулятор
    • Electro
    • USB Flash recovery
Powered by GitBook
On this page
  1. data
  2. MySQL
  3. Рішення

Выявление и удаление несвязанных записей

http://oooportal.ru/?cat=article&id=1344

Задача

У вас есть связанные таблицы (например, имеющие связь «главная-подчиненная»). Но вы подозреваете, что некоторые строки ни с чем не связаны и могут быть удалены.

Решение

Используйте LEFT JOIN для определения отсутствия соответствий и удалите выявленные значения, применяя приемы из рецепта 12.20. Или используйте процедуру замены таблицы, которая выбирает связанные записи в новую таблицу и заменяет ею исходную.

Обсуждение

В предыдущем разделе было рассказано о том, как одновременно удалять связанные записи из нескольких таблиц, используя связь, существующую между таблицами. Иногда возникает обратная задача: необходимо удалить записи на основе отсутствия связи. Такие ситуации обычно возникают, если у вас есть таблицы, которые предполагаются сопоставленными друг другу, но некоторые из записей не связаны ни с какими записями другой таблицы.Это может произойти случайно, например, при удалении родительской записи без удаления соответствующих дочерних, или наоборот. А может быть и ожидаемым последствием какого-то умышленного действия. Предположим, что форум он-лайн использует родительскую таблицу, в которой перечислены все темы обсуждений, и дочернюю таблицу, в которой хранятся все сообщения по заданной теме. Если удалить из дочерней таблицы старые записи, это может привести к тому, что у какой-то родительской записи больше не будет ни одной дочерней. Отсутствие недавних сообщений по теме, вероятно, означает, что дискуссия затихла, и родительскую запись можно удалять из таблицы тем. Тогда вы удаляете все множество дочерних записей, четко осознавая, что операция может оставить родительские записи без дочерних и сделает их кандидатами на удаление.

Однако сейчас перед вами связанные таблицы с несвязанными записями. Для восстановления целостности таблиц необходимо выявить записи, не имеющие соответствий в связанной таблице, и удалить их:

  • Для выявления несвязанных записей используйте LEFT JOIN, так как это задача отсутствия соответствий.

  • Для удаления записей, идентификаторы которых не соответствуют записям связанной таблицы, используйте для удаления записей из нескольких связанных таблиц приемы.

В примерах будут использоваться таблицы дистрибутивов программного обеспечения swdist_head и swdist_item. Создайте таблицы в их исходном состоянии, используя сценарий swdist_create.sql из каталога joins дистрибутива recipes. Они будут выглядеть так:

mysql> SELECT * FROM swdist_head;

+-----------+--------------+-----------+------------+
| dist_id   | name       | ver_num     | rel_date   |
+--------+---------------+-------------+------------+
| 1         | DB Gadgets | 1.59        | 1996-03-25 |
| 2         | NetGizmo   | 3.02        | 1998-11-10 |
| 3         | DB Gadgets | 1.60        | 1998-12-26 |
| 4         | DB Gadgets | 1.61        | 1998-12-28 |
| 5         | NetGizmo   | 4.00        | 2001-08-04 |
+-------+---------------+-----------+---------------+

mysql> SELECT * FROM swdist_item;

+-----------+-------------------+
| dist_id   | dist_file         |
+-----------+-------------------+
| 1         | README            |
| 1         | db-gadgets.sh     |
| 3         | README            |
| 3         | README.linux      |
| 3         | db-gadgets.sh     |
| 4         | README            |
| 4         | README.linux      |
| 4         | README.solaris    |
| 4         | db-gadgets.sh     |
| 2         | README.txt        |
| 2         | NetGizmo.exe      |
| 5         | README.txt        |
| 5         | NetGizmo.exe      |
+-----------+-------------------+

На данный момент записи таблиц полностью соответствуют друг другу: для каждого значения dist_id из родительской таблицы существует как минимум одна дочерняя запись, а у каждой дочерней записи есть родительская.

Чтобы нарушить целостность связи для наших целей, удалим несколько строк из каждой таблицы:

mysql> DELETE FROM swdist_head WHERE dist_id IN (1,4);
mysql> DELETE FROM swdist_item WHERE dist_id IN (2,5);

В результате в обеих таблицах появятся несвязанные записи:

mysql> SELECT * FROM swdist_head;

+-----------+--------------+-----------+-------------+
| dist_id   | name         | ver_num   | rel_date    |
+-----------+--------------+-----------+-------------+
| 2         | NetGizmo     | 3.02      | 1998-11-10  |
| 3         | DB Gadgets   | 1.60      | 1998-12-26  |
| 5         | NetGizmo     | 4.00      | 2001-08-04  |
+-----------+--------------+-----------+-------------+

mysql> SELECT * FROM swdist_item;

+-----------+-----------------+
| dist_id   | dist_file       |
+-----------+-----------------+
| 1         | README          |
| 1         | db-gadgets.sh   |
| 3         | README          |
| 3         | README.linux    |
| 3         | db-gadgets.sh   |
| 4         | README          |
| 4         | README.linux    |
| 4         | README.solaris  |
| 4         | db-gadgets.sh   |
+-----------+-----------------+

Беглый осмотр показывает, что только для дистрибутива 3 есть записи в двух таблицах. Дистрибутивы 2 и 5 из таблицы swdist_head не сопоставлены никаким записям из таблицы swdist_item. И наоборот, дистрибутивам 1 и 4 таблицы swdist_item не соответствуют никакие записи из таблицы swdist_head.

Теперь необходимо выявить несвязанные записи (каким-то способом, отличным от визуального контроля) и удалить их. Это задача для LEFT JOIN. Например, чтобы найти родительские записи без дочерних в таблице swdist_head, используем такой запрос:

mysql> SELECT swdist_head.dist_id AS 'unmatched swdist_head IDs'
-> FROM swdist_head LEFT JOIN swdist_item
-> ON swdist_head.dist_id = swdist_item.dist_id
-> WHERE swdist_item.dist_id IS NULL;
+-----------------------------+
| unmatched swdist_head IDs   |
+-----------------------------+
| 2                           |
| 5                           |
+-----------------------------+

И наоборот, чтобы найти идентификаторы «осиротевших» дочерних записей из таблицы swdist_item, поменяйте таблицы местами:

mysql> SELECT swdist_item.dist_id AS 'unmatched swdist_item IDs'
-> FROM swdist_item LEFT JOIN swdist_head
-> ON swdist_item.dist_id = swdist_head.dist_id
-> WHERE swdist_head.dist_id IS NULL;

+------------------------------+
| unmatched swdist_item IDs    |
+------------------------------+
| 1                            |
| 1                            |
| 4                            |
| 4                            |
| 4                            |
| 4                            |
+------------------------------+

Заметьте, что в данном случае идентификатор появится в списке несколько раз, если у отсутствующего родителя было несколько потомков. В зависимости от способа удаления несвязанных записей вы можете захотеть использовать DISTINCT для выбора идентификатора каждой несвязанной дочерней записи только единожды:

mysql> SELECT DISTINCT swdist_item.dist_id AS 'unmatched swdist_item IDs'
-> FROM swdist_item LEFT JOIN swdist_head
-> ON swdist_item.dist_id = swdist_head.dist_id
-> WHERE swdist_head.dist_id IS NULL;

+-------------------------------+
| unmatched swdist_item IDs     |
+-------------------------------+
| 1                             |
| 4                             |
+-------------------------------+

После выявления несвязанных записей остается только избавиться от них. Можно применить один из способов:

• Использование идентификаторов в многотабличном предложении DELETE. Вы будете одновременно удалять строки только из одной таблицы, но синтаксис этой формы DELETE все же удобен, так как он позволяет идентифицировать удаляемые записи путем соединения связанных таблиц.

• Запуск программы, которая выбирает несвязанные идентификаторы и использует их для формирования предложений DELETE.Чтобы использовать многотабличное предложение DELETE для удаления несвязанных записей, просто возьмите предложение SELECT, используемое для выявления этих записей, и замените все начало до ключевого слова FROM на DELETE имя_таблицы. Например, предложение SELECT, выбирающее родительские записи без дочерних, выглядит так:

SELECT swdist_head.dist_id AS 'unmatched swdist_head IDs'
FROM swdist_head LEFT JOIN swdist_item
ON swdist_head.dist_id = swdist_item.dist_id
WHERE swdist_item.dist_id IS NULL;

Соответствующее предложение DELETE будет таким:

DELETE swdist_head
FROM swdist_head LEFT JOIN swdist_item
ON swdist_head.dist_id = swdist_item.dist_id
WHERE swdist_item.dist_id IS NULL;

Запрос, определяющий потомков без родителей, выглядит так:

SELECT swdist_item.dist_id AS 'unmatched swdist_item IDs'
FROM swdist_item LEFT JOIN swdist_head
ON swdist_item.dist_id = swdist_head.dist_id
WHERE swdist_head.dist_id IS NULL;

Соответствующее предложение DELETE удаляет их:

DELETE swdist_item
FROM swdist_item LEFT JOIN swdist_head
ON swdist_item.dist_id = swdist_head.dist_id
WHERE swdist_head.dist_id IS NULL;

Чтобы удалить несвязанные записи из программы, выберите список идентификаторов и преобразуйте его в набор предложений DELETE. Рассмотрим программу на Perl, которая делает это сначала для родительской таблицы, а затем для дочерней:

#! /usr/bin/perl -w
use strict;
use lib qw(/usr/local/apache/lib/perl);
use Cookbook;
my $dbh = Cookbook::connect ();
# Определение идентификаторов родительских записей без дочерних
my $ref = $dbh->selectcol_arrayref (
"SELECT swdist_head.dist_id
FROM swdist_head LEFT JOIN swdist_item
ON swdist_head.dist_id = swdist_item.dist_id
WHERE swdist_item.dist_id IS NULL");
# selectcol_arrayref() возвращает ссылку на список. Преобразуем ссылку в список,
# который будет пуст, если $ref – это undef или указывает на пустой список.
my @val = ($ref ? @{$ref} : ());# Используем список идентификаторов для удаления записей по всем идентификаторам
# сразу. Если список пуст – не волнуемся, удалять нечего.
if (@val)
{
# формируем список заполнителей "?", разделенных запятыми, по одному на значение
my $where = "WHERE dist_id IN (" . join (",", ("?") x @val) . ")";
$dbh->do ("DELETE FROM swdist_head $where", undef, @val);
}
# Повторяем процедуру для дочерней таблицы. Используем SELECT DISTINCT,
# чтобы каждый идентификатор выбирался один раз.
$ref = $dbh->selectcol_arrayref (
"SELECT DISTINCT swdist_item.dist_id
FROM swdist_item LEFT JOIN swdist_head
ON swdist_item.dist_id = swdist_head.dist_id
WHERE swdist_head.dist_id IS NULL");
@val = ($ref ? @{$ref} : ());
if (@val)
{
# формируем список заполнителей "?", разделенных запятыми, по одному на значение
my $where = "WHERE dist_id IN (" . join (",", ("?") x @val) . ")";
$dbh->do ("DELETE FROM swdist_item $where", undef, @val);
}
$dbh->disconnect ();
exit (0);

Программа использует IN() для удаления всех необходимых записей из указанной таблицы разом.

Для формирования предложений DELETE также можно использовать mysql; сценарий, показывающий, как это делается, приведен в каталоге joins дистрибутива recipes.

Другой подход к решению задачи реализуется процедурой замены таблицы. Этот метод подходит к вопросу с другой стороны. Вместо того чтобы находить и удалять несвязанные записи, будем находить и хранить связанные. Например, можно использовать соединение для выбора связанные записей в новую таблицу. Затем заменить ею исходную таблицу. Несвязанные записи не подхватываются соединением, то есть фактически удаляются при замене исходной таблицы новой.

Процедура замены таблицы работает так. Для таблицы swdist_head создаем новую таблицу с той же структурой:

CREATE TABLE tmp
(
dist_id INT UNSIGNED NOT NULL AUTO_INCREMENT, # идентификатор дистрибутива
name VARCHAR(40), # название дистрибутиваver_num NUMERIC(5,2), # номер версии
rel_date DATE NOT NULL, # дата выпуска
PRIMARY KEY (dist_id)
);

Затем выбираем в таблицу tmp те записи swdist_head, для которых есть соответствия в таблице swdist_item:

INSERT IGNORE INTO tmp
SELECT swdist_head.*
FROM swdist_head, swdist_item
WHERE swdist_head.dist_id = swdist_item.dist_id;

Обратите внимание на то, что запрос использует INSERT IGNORE; родительская запись может соответствовать нескольким дочерним, но нам нужен только один экземпляр ее идентификатора. (Признаком неиспользования IGNORE является завершение запроса с ошибкой типа «duplicate key».)

Наконец, заменяем исходную таблицу новой:

DROP TABLE swdist_head;
ALTER TABLE tmp RENAME TO swdist_head;

Процедура замены дочерней таблицы таблицей, содержащей только связанные дочерние записи, аналогична, но без необходимости использования IGNORE – каждому потомку соответствует только один предок:

CREATE TABLE tmp
(
dist_id INT UNSIGNED NOT NULL, # идентификатор родительского дистрибутива
dist_file VARCHAR(255) NOT NULL # имя файла дистрибутива
);
INSERT INTO tmp
SELECT swdist_item.*
FROM swdist_head, swdist_item
WHERE swdist_head.dist_id = swdist_item.dist_id;
DROP TABLE swdist_item;
ALTER TABLE tmp RENAME TO swdist_item;
PreviousРішенняNextВыборка произвольных записей

Last updated 2 years ago