MySQL на дубликате обновления клавиш с нулевым столбцом в уникальном ключе

StackOverflow https://stackoverflow.com/questions/1298105

  •  18-09-2019
  •  | 
  •  

Вопрос

Наша база данных MySQL Web Analytics содержит сводную таблицу, которая обновляется в течение дня, когда новая деятельность импортируется. Мы используем в дублировании обновления ключей для того, чтобы суммирование перезаписывает более ранние расчеты, но испытываем трудности, потому что один из столбцов в уникальном ключе Свладения - это необязательный FK и содержит нулевые значения.

Эти нули предназначены для обозначения «отсутствия, и все такие случаи эквивалентны». Конечно, MySQL обычно рассматривает нуль как значение «неизвестно, и все такие случаи не являются эквивалентными».

Основная структура заключается в следующем:

Таблица «деятельность», содержащая запись для каждого сеанса, каждая из которых принадлежит кампании, с дополнительными идентификаторами фильтра и транзакциями для некоторых записей.

CREATE TABLE `Activity` (
    `session_id` INTEGER AUTO_INCREMENT
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `transaction_id` INTEGER DEFAULT NULL
    , PRIMARY KEY (`session_id`)
);

Таблица «Резюме», содержащая ежедневные обрушения общего количества сеансов в таблице деятельности, и общее количество тех сеансов, которые содержат идентификатор транзакции. Эти резюме разделены, с одним для каждой комбинации кампании и (необязательного) фильтра. Это не транзакционная таблица с использованием Myisam.

CREATE TABLE `Summary` (
    `day` DATE NOT NULL
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `sessions` INTEGER UNSIGNED DEFAULT NULL
    , `transactions` INTEGER UNSIGNED DEFAULT NULL
    , UNIQUE KEY (`day`, `campaign_id`, `filter_id`)
) ENGINE=MyISAM;

Фактический запрос на суммирование - это что -то вроде следующего, подсчитывая количество сеансов и транзакций, а затем группировка по кампании и (необязательно) фильтру.

INSERT INTO `Summary` 
    (`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
    SELECT `day`, `campaign_id`, `filter_id
        , COUNT(`session_id`) AS `sessions`
        , COUNT(`transaction_id` IS NOT NULL) AS `transactions`
    FROM Activity
    GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
    `sessions` = VALUES(`sessions`)
    , `transactions` = VALUES(`transactions`)
;

Все отлично работает, за исключением кратких случаев, когда Filter_ID является нулевым. В этих случаях пункт об обновлении дублирования ключей не соответствует существующей строке, и каждый раз записывается новая строка. Это связано с тем, что «null! = Null». Однако нам нужно «null = null» при сравнении уникальных ключей.

Я ищу идеи для обходных путей или отзывы о тех, кого мы придумали до сих пор. Обходные пути, о которых мы думали до сих пор.

  1. Удалите все сводные записи, содержащие значение нулевого ключа перед выполнением суммирования. (Это то, что мы делаем сейчас). Это имеет отрицательный побочный эффект возврата результатов с отсутствующими данными, если запрос выполняется во время процесса суммирования.

  2. Измените нулевый столбец по умолчанию на 0, что позволяет последовательно соответствовать уникальному клавишу. Это имеет отрицательный побочный эффект, чрезмерно усложняющий разработку запросов против сводной таблицы. Это заставляет нас использовать много "case filter_id = 0, а затем null else filter_id end", и делает неудобное соединение, поскольку все другие таблицы имеют фактические нули для filter_id.

  3. Создайте представление, которое возвращает "case filter_id = 0, затем null else filter_id end" и используя это представление вместо таблицы напрямую. Сводная таблица содержит несколько сотен тысяч рядов, и мне сказали, что представление о производительности довольно плохая.

  4. Разрешите создавать дубликаты записей и удалить старые записи после завершения суммирования. Имеет аналогичные проблемы, чтобы удалить их заранее.

  5. Добавьте суррогатный столбец, который содержит 0 для NULL, и используйте этот суррогат в уникальном клавише (на самом деле мы могли бы использовать первичный ключ, если все столбцы не являются нулевыми).
    Это решение кажется разумным, за исключением того, что пример выше является только примером; Фактическая база данных содержит полдюжины сводных таблиц, один из которых содержит четыре нулевых столбца в уникальном ключе. Некоторые есть опасения, что накладные расходы слишком много.

У вас есть лучший обходной путь, структура таблицы, процесс обновления или лучшая практика MySQL, которые могут помочь?

РЕДАКТИРОВАТЬ: чтобы уточнить «значение нуля»

Считается, что данные в кратких строках, содержащих нулевые столбцы, принадлежат друг другу только в том смысле, что это единственная строка «улов» в кратких отчетах, суммируя те элементы, для которых эта точка данных не существует или неизвестна. Таким образом, в контексте самой сводной таблицы значение является «суммой тех записей, для которых не известно никакой стоимости». В реляционных таблицах, с другой стороны, это действительно нулевые результаты.

Единственная причина для того, чтобы поместить их в уникальный ключ в сводной таблице,-это разрешить автоматическое обновление (по дублированию обновления ключей) при повторной разведении сводных отчетов.

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

Изменение остальных таблиц данных, чтобы иметь явную значение "THE_IS_NO_ZIP_CODE" 0, и размещение специальной записи в таблице ZipCodePrefix, представляющей это значение, является ненадлежащим-эти отношения действительно являются нулевыми.

Это было полезно?

Решение

Я думаю, что что -то вроде (2) - это действительно лучший выбор - или, по крайней мере, это было бы, если бы вы начали с нуля. В SQL NULL означает неизвестный. Если вы хотите какого -то другого значения, вы действительно должны использовать специальное значение для этого, а 0, безусловно, является хорошим выбором.

Вы должны сделать это через весь База данных, а не только эта таблица. Тогда вы не должны оказываться со странными особыми случаями. На самом деле, вы должны быть в состоянии избавиться от многих ваших текущих (пример: в настоящее время, если вам нужна сводная строка, где нет фильтра, у вас есть специальный случай «Фильтр нулевой», а не в обычном случае "Filter =?".)

Вы также должны продолжить и создать запись «не присутствующего» в указанной таблице, чтобы сохранить ограничение FK допустимым (и избежать особых случаев).

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

Редактировать 1

Хммм, в этом случае, вам действительно нужно обновление на дубликате ключей? Если вы делаете вставку ... выберите, тогда вы, вероятно, делаете. Но если ваше приложение поставляет данные, просто сделайте это вручную - сделайте обновление (отображение zip = null к zip is null), проверьте, сколько строк было изменено (MySQL возвращает это), если 0 сделайте вставку.

Другие советы

Измените нулевый столбец по умолчанию на 0, что позволяет последовательно соответствовать уникальному клавишу. Это имеет отрицательный побочный эффект, чрезмерно усложняющий разработку запросов против сводной таблицы. Это заставляет нас использовать много "case filter_id = 0, а затем null else filter_id end", и делает неудобное соединение, поскольку все другие таблицы имеют фактические нули для filter_id.

Создайте представление, которое возвращает "case filter_id = 0, затем null else filter_id end" и используя это представление вместо таблицы напрямую. Сводная таблица содержит несколько сотен тысяч рядов, и мне сказали, что представление о производительности довольно плохая.

Просмотр производительности в MySQL 5.x будет в порядке, так как представление ничего не делает, кроме как заменить ноль на нуль. Если вы не используете агрегаты/сортировки в представлении, большинство любого запроса против представления будут переписаны оптимизатором запроса, чтобы просто попасть в базовую таблицу.

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

С современными версиями MariaDB (ранее MySQL), UpSerts можно сделать просто с помощью вставки на дублирующих операторах обновления ключей, если вы пойдете с суррогатным путем № 5. Добавление сгенерированных хранимых столбцов MySQL или стойких виртуальных столбцов MariaDB для применения ограничения уникальности на нулевых полях косвенно удерживает бессмысленные данные из базы данных в обмен на некоторое раздувание.

например

CREATE TABLE IF NOT EXISTS bar (
    id INT PRIMARY KEY AUTO_INCREMENT,
    datebin DATE NOT NULL,
    baz1_id INT DEFAULT NULL,
    vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED,
    baz2_id INT DEFAULT NULL,
    vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED,
    blam DOUBLE NOT NULL,
    UNIQUE(datebin, vbaz1_id, vbaz2_id)
);

INSERT INTO bar (datebin, baz1_id, baz2_id, blam)
    VALUES ('2016-06-01', null, null, 777)
ON DUPLICATE KEY UPDATE
    blam = VALUES(blam);

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

MySQL сгенерированные столбцы Мариадб виртуальные столбцы

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top