Алгоритмы обновления реляционных данных

Какие алгоритмы известны для выполнения задачи обновления базы данных путем вставки, обновления и удаления строк при наличии ограничений базы данных?

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

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

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

Какие бывают алгоритмы:

  1. Предполагается, что ограничения внешнего ключа должны применяться постоянно, но уникальные индексы не используются.
  2. Предполагается, что ограничения внешнего ключа и ограничения уникального индекса должны выполняться всегда.

Я прошу и то, и другое, потому что считаю, что №1 может быть значительно проще.

РЕДАКТИРОВАТЬ: цель здесь - решить проблему отсутствия отложенной проверки ограничений общим (или почти универсальным) способом. Я полагаю, что это должны делать высококачественные пакеты ORM. Мне нужно объяснение алгоритмов, предоставленное вами здесь или извне в академической статье и т. Д. Я не буду рассматривать указатель на программный пакет или исходный код ответом на этот вопрос.

НАИВНЫЙ АЛГОРИТМ:

Просмотрите таблицы и для каждой добавленной, измененной или удаленной строки сгенерируйте по одному оператору INSERT, UPDATE или DELETE соответственно для каждой. Прокрутите сгенерированные операторы и примените к базе данных. Если утверждение не применимо, переходите к другим утверждениям. Повторите неудачные попытки. Продолжайте повторять, пока не будет больше сбоев или пока на проходе не будет успешно выполнено ни одного оператора. Если операторы остаются, попробуйте временную настройку данных в проблемных столбцах, чтобы попытаться добиться успеха.

Это уродливый алгоритм грубой силы, и выяснение части "временной настройки" - отдельная задача. Итак, мне нужен улучшенный и полный алгоритм.

РЕДАКТИРОВАТЬ2:

RBarryYoung публикует ответ, который приближается (но без сигары) к полному решению сценария №1 при одновременном решении наиболее распространенных проблем сценария №2. Ниже приводится пример шаблона обновления сценария №1, который я видел очень часто в приложениях, но еще не нашел решения. DELETE / UPDATE-INSERT в большинстве случаев является правильным в сценарии № 1, но уловка состоит в том, чтобы выяснить, когда от него следует отклониться. Я также подозреваю, что отклонение от него увеличивает возникновение УНИКАЛЬНЫХ проблем для сценария №2, что, возможно, также увеличивает мой интерес к решению сценария №2.

Обратите внимание, что нет ни циклов, ни каких-либо измененных первичных ключей. Однако внешний ключ родителя изменяется.

CREATE TABLE A
(
    AId INT NOT NULL PRIMARY KEY
)

CREATE TABLE B
(
    BId INT NOT NULL PRIMARY KEY,
    AId INT NOT NULL FOREIGN KEY REFERENCES A (AId)
)

CREATE TABLE C
(
    CId INT NOT NULL PRIMARY KEY,
    AId INT NOT NULL FOREIGN KEY REFERENCES A (AId),
    BId INT NOT NULL FOREIGN KEY REFERENCES B (BId)
)

Перед изображениями:

A (1)
B (1,1)
C (1,1,1)

После изображений:

A (1)
B (2,1) [To be deleted: (1,1)]
C (1,1,2)

Порядок сортировки: A, B, C

Первая команда - это DELETE B (1,1), которая не выполняется из-за C (1,1,1).

Обратите внимание, что если третий столбец в C допускает NULL (чего он не делает в данном случае), чистое решение может включать в себя удаление NULL на раннем проходе, так как это позволит данному алгоритму работать нормально и иметь свои обычные преимущества для работы с большинство проблем сценария №2. Отличное решение этой проблемы должно учитывать и подобные вещи. Полная общность этого вопроса, несомненно, интересная проблема.


person Jason Kresowaty    schedule 04.07.2009    source источник
comment
В какой форме (язык, предметная область, логическая система и т. Д.) Вы ищете ответ? Просто процедурный / объектно-ориентированный псевдокод? Или декларативный SQL / Relational / D-подобный?   -  person RBarryYoung    schedule 04.07.2009
comment
Предпочтительный псевдокод алгоритма с четким путем для создания операторов SQL через алгоритм.   -  person Jason Kresowaty    schedule 04.07.2009
comment
Утверждения не поддерживают отложенную проверку ограничений, и, согласно моему пониманию реляционных логических операторов, строки могут быть для нескольких таблиц противоречивыми: отдельные модифицирующие операторы изменяют только один объект. Транзакции могут распространить это на несколько измененных объектов, но только путем отсрочки проверки ограничений до конца транзакции.   -  person RBarryYoung    schedule 04.07.2009
comment
Заранее известно, что существует некоторая последовательность операторов INSERT, UPDATE и DELETE, которая заставит базу данных синхронизироваться с представлением в памяти во время фиксации транзакции, включающей эти staetments. Этот вопрос просит найти алгоритм для генерации этой последовательности. Поскольку база данных не поддерживает отложенные ограничения, мы должны убедиться, что после каждого INSERT, UPDATE или DELETE в транзакции никакие ограничения никогда не нарушаются. В качестве базового примера мы не можем вставлять дочернюю строку перед родительской строкой, но возникает множество сложностей / сложностей.   -  person Jason Kresowaty    schedule 04.07.2009
comment
Если вы не можете указать значение первичного ключа при вставке строки, то оно не является реляционным.   -  person finnw    schedule 04.07.2009
comment
Хм, я думаю, что, возможно, я двинулся не в том направлении. Вы говорите, что 1) существует несколько DML, 2) их комбинированный эффект представлен таблицами изображений изменений в памяти, 3) их комбинированный конечный эффект является допустимым по ограничению и 4) вам необходимо сгенерировать команды SQL для достижения это комбинированный эффект данных, но при этом соблюдаются ограничения для каждой отдельной команды SQL?   -  person RBarryYoung    schedule 04.07.2009
comment
RBarryYoung: Да, я думаю, теперь вы это понимаете. finnw: реляционный в смысле типичных баз данных и приложений SQL, не обязательно теоретически чистой системы.   -  person Jason Kresowaty    schedule 04.07.2009
comment
Если все первичные ключи автоматически сгенерированы, вы не можете этого сделать, потому что вставки всегда будут генерировать новые значения, и вы не можете снова имитировать состояние после (потому что автоматически сгенерированные ключи уже были сгенерированы и не могут быть восстановлены).   -  person Jonathan Leffler    schedule 04.07.2009
comment
Джонатан: Спасибо, технически вы правы. На самом деле приложение назначает временные первичные ключи для новых строк, и база данных возвращает постоянные первичные ключи, когда выполняет INSERT. Как я уже сказал, я думаю, что это на самом деле очень практическая проблема ORM, не обязательно теоретически чистая проблема. Если это помогает, вы можете указать алгоритм, как если бы приложение всегда назначает идентификаторы GUID в качестве PK, но вы также должны соблюдать правила о том, что никогда не обновляете PK, а также не удаляете и повторно вставляете один и тот же PK. Тогда алгоритм также можно будет использовать в сценарии автогенерации.   -  person Jason Kresowaty    schedule 04.07.2009
comment
Отличается ли это от вашего предыдущего вопроса (stackoverflow.com/questions/1049641/)?   -  person John Saunders    schedule 05.07.2009
comment
Джон спрашивает, отличается ли это от моего предыдущего вопроса. На самом деле да. Более ранний был больше ориентирован на уже существующие продукты репликации. Этот ориентирован на теоретический вопрос об отсутствии отложенной проверки ограничений для целей ORM. Некоторые из наиболее тревожных аспектов являются общими для обеих проблем, поэтому хорошее решение может иметь большое значение для решения обеих этих проблем. На данный момент этот вопрос привлекает гораздо больше внимания пользователей SO, так что тем лучше.   -  person Jason Kresowaty    schedule 05.07.2009
comment
Хороший пример, надо над ним подумать. (кстати, если есть литература по этому поводу, я тоже хотел бы ее увидеть).   -  person RBarryYoung    schedule 05.07.2009


Ответы (6)


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

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

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

person KayEss    schedule 10.07.2009

Один раз я написал, но это чужой IP, поэтому я не могу вдаваться в подробности. Однако я хочу рассказать вам о процессе, который научил меня это делать. Это был инструмент для создания теневой копии клиентской «базы данных», расположенной на salesforce.com, написанной на .NET 1.1.

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

Грубая сила была отправной точкой, потому что не было уверенности, что мы вообще сможем это сделать. «Схема» salesforce.com не была настоящей реляционной схемой. Например, если я правильно помню, были некоторые столбцы, которые были внешними ключами, относящимися к одной из нескольких родительских таблиц.

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

Все откровения, которые я получил, были связаны с моей скукой, когда я смотрел, как система загружает процессор почти на 100% в течение 15-20 минут за раз, даже с небольшой базой данных. «Необходимость - мать изобретений», и «перспектива подождать еще 20 минут для тех же строк имеет тенденцию сосредотачивать внимание», и я понял, как ускорить процесс более чем в 100 раз.

person John Saunders    schedule 04.07.2009
comment
Мне хорошо известно, что упорядочивание операций INSERT, UPDATE и DELETE на основе графика ограничений внешнего ключа решает большинство проблем. Моя цель здесь - решить максимальное количество типов проблем без грубой силы, а не только большинство проблем. - person Jason Kresowaty; 05.07.2009
comment
То, что я узнал, решило все проблемы без какой-либо оставшейся грубой силы. Мне просто нужно было начать с грубой силы, а затем продолжать до тех пор, пока от скуки решение не станет очевидным. Я буду редактировать, указав, что отправной точкой была грубая сила. - person John Saunders; 05.07.2009

Хорошо, я думаю, что это все, хотя с уникальным ключом довольно сложно понять. Обратите внимание, что любые ошибки, обнаруженные при выполнении SQL, должны привести к полному откату всей транзакции.

ОБНОВЛЕНИЕ: исходный заказ, который я реализовал, был:

Каждая таблица, BottumUp (все удаления для таблицы) Каждая таблица, TopDown (все обновления, затем все вставки)

После того, как контрпример был опубликован, я считаю, что знаю, как исправить только ограниченную проблему (проблема №1, без UC): изменив порядок на:

Каждая таблица, TopDown (все вставки) Каждая таблица, TopDown (все обновления) Каждая таблица, BottumUp (все удаляются)

Это определенно НЕ будет работать с уникальными ограничениями, которые, насколько я могу понять, потребуют сортировки зависимостей на основе содержимого строки (в отличие от сортировки зависимостей FK статической таблицы, которую я использую в настоящее время). Что делает это особенно трудным, так это то, что может потребоваться получение информации о содержимом записи, отличном от измененного (в частности, проверка наличия конфликтных значений UC и дочерних зависимых записей для промежуточных шагов).

Во всяком случае, вот текущая версия:

Public Class TranformChangesToSQL
 Class ColVal
    Public name As String
    Public value As String  'note: assuming string values'
 End Class

 Class Row
    Public Columns As List(Of ColVal)
 End Class

 Class FKDef
    'NOTE: all FK''s are assumed to be of the same type: records in the FK table'
    ' must have a record in the PK table matching on FK=PK columns.'
    Public PKTableName As String
    Public FKTableName As String
    Public FK As String
 End Class

 Class TableInfo
    Public Name As String
    Public PK As String                     'name of the PK column'
    Public UniqueKeys As List(Of String)    'column name of each Unique key'
    'This table''s Foreign Keys (FK):'
    Public DependsOn As List(Of FKDef)
    'Other tables FKs that point to this table'
    Public DependedBy As List(Of FKDef)
    Public Columns As List(Of String)
    'note: all row collections are indexed by PK'
    Public inserted As List(Of Row)     'inserted after-images'
    Public deleted As List(Of Row)      'deleted before-images'
    Public updBefore As List(Of row)
    Public updAfter As List(Of row)
 End Class

 Sub MakeSQL(ByVal tables As List(Of TableInfo))
    'Note table dependencies(FKs) must NOT form a cycle'

    'Sort the tables by dependency so that'
    ' child tables (FKs) are always after their parents (PK tables)'
    TopologicalSort(tables)

    For Each tbl As TableInfo In tables
        'Do INSERTs, they *must* be done first in parent-> child order, because:'
        '   they may have FKs dependent on parent inserts'
        '   and there may be Updates that will make child records dependent on them'
        For Each r As Row In tbl.inserted
            Dim InsSQL As String = "INSERT INTO " & tbl.Name & "("
            Dim valstr As String = ") VALUES("
            Dim comma As String = ""
            For Each col As ColVal In r.Columns
                InsSQL = InsSQL & comma & col.name
                valstr = valstr & comma & "'" & col.value & "'"
                comma = ", "    'needed for second and later columns'
            Next
            AddSQL(InsSQL & valstr & ");")
        Next
    Next

    For Each tbl As TableInfo In tables
        'Do UPDATEs'
        For Each aft In tbl.updAfter
            'get the matching before-update row'
            Dim bef As Row = tbl.updBefore(aft.Columns(tbl.PK.ColName).value)
            Dim UpdSql As String = "UPDATE " & tbl.Name & " SET "
            Dim comma As String = ""
            For Each col As ColVal In aft.Columns
                If bef.Columns(col.name).value <> col.value Then
                    UpdSql = UpdSql & comma & col.name & " = '" & col.value & "'"
                    comma = ", "  'needed for second and later columns'
                End If
            Next
            'only add it if any columns were different:'
            If comma <> "" Then AddSQL(UpdSql & ";")
        Next
    Next

    'Now reverse it so that INSERTs & UPDATEs are done in parent->child order'
    tables.Reverse()

    For Each tbl As TableInfo In tables.Reverse
        'Do DELETEs, they *must* be done last, and in child->paernt order because:'
        '   Parents may have children that depend on them, so children must be deleted first,'
        '   and there may be children dependent until after Updates pointed them away'
        For Each r As Row In tbl.deleted
            AddSQL("DELETE From " & tbl.Name & " WHERE " & tbl.PK.ColName & " = '" & r.Columns(tbl.PK.ColName).value) & "';"
        Next
    Next

 End Sub
End Class
person RBarryYoung    schedule 04.07.2009
comment
Мне понадобится время, чтобы полностью переварить это, но я уже знаю некоторые способы сортировки графа внешнего ключа. Ваш алгоритм не решает всех проблем, которые технически возможно решить. На самом деле есть способы вставлять записи с циклами и способы их удаления с помощью умного обновления. Также существуют некоторые возможные уникальные ограничения, которые могут вызвать затруднения в работе вашего алгоритма. Эти комментарии не позволяют подробно объяснить достаточное количество символов, например, UC в двух столбцах: Row1: A B Row2: B A == ›Row1: B A Row2: A B - person Jason Kresowaty; 05.07.2009
comment
Насколько мне известно, это правильный ответ на ваш вопрос № 1 с теми ограничениями, которые вы разрешили (материал PK). Пока они не могут связываться с PK, порядок исходных операций не имеет значения. Однако материал UC намного сложнее и неоднозначнее ... - person RBarryYoung; 05.07.2009
comment
Кроме того, большинство баз данных SQL не допускают циклических зависимостей FK и тех, у которых нет проблем с этим. - person RBarryYoung; 05.07.2009
comment
Я должен упомянуть, что топологическая сортировка сортирует не строки, а сами таблицы, основываясь исключительно на их статических зависимостях (FK), а не на каком-либо содержании данных в строках. Этот подход также имеет то преимущество, что сводит к минимуму возможность возникновения тупиковых ситуаций. - person RBarryYoung; 05.07.2009
comment
Это становится оооочень близко, но терпит неудачу в тех случаях, которые являются подмножеством вопроса № 1, из-за которого я изначально разместил здесь сообщение! Я собираюсь пересмотреть исходный вопрос, рассмотрев дилемму. - person Jason Kresowaty; 05.07.2009

Нет, я не считаю это увлекательным. Я тоже не считаю проблему квадратуры круга увлекательной, и по этой теме тоже есть люди, которые категорически или даже категорически не согласны со мной.

Когда вы говорите, что «у него есть практическое применение», вы имеете в виду, что «решение этой проблемы имеет практическое применение»? Я предполагаю, что решение, которого не существует, по определению не может иметь «практического применения». (И я действительно предполагаю, что решение, которое вы ищете, не существует, как и квадратура круга.)

Вы что-то спорили о том, что «когда другие приложения вешают каскадные удаления ...». В вашей первоначальной формулировке проблемы вообще не упоминались «другие приложения».

Проблема, которую я нахожу более увлекательной, - это «как создать СУБД, которая была бы достаточно хорошей, чтобы программисты больше не сталкивались с подобными проблемами и не были вынуждены задавать подобные вопросы». Такая СУБД поддерживает множественное присвоение.

Желаю вам удачи.

person Community    schedule 05.07.2009
comment
Эрвин, тебе следовало отредактировать исходный вопрос, а не добавлять новый. Я рекомендую вам скопировать это в конец исходного вопроса, а затем удалить. - person John Saunders; 05.07.2009

Похоже, вы ищете инструмент Database Diff. Такой инструмент будет искать различия между двумя таблицами (или двумя базами данных) и генерировать необходимые сценарии для их выравнивания.

Дополнительную информацию см. В следующем сообщении:
https://stackoverflow.com/questions/104203/anyone-know-of-any-good-database-diff-tools.

person Robert Harvey    schedule 04.07.2009
comment
Спасибо, но это не помогает. Мне нужен реальный алгоритм. У меня такое чувство, что это теоретическая проблема, которая была изучена, поэтому кто-то точно знает, как это делать. - person Jason Kresowaty; 04.07.2009

OpenDbDiff имеет доступный исходный код. Вы можете посмотреть на это и выяснить алгоритмы.

http://opendbiff.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25206

person Robert Harvey    schedule 04.07.2009