Самый эффективный способ удаления записей из представления с несколькими базовыми таблицами? - PullRequest
0 голосов
/ 17 апреля 2020

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

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

Это единственный способ сделать это:

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

DECLARE @vn as nvarchar(max) = 'dbo.RM_GRIT_SALT_BINS'

SELECT referenced_server_name, referenced_database_name, referenced_entity_name as SourceTable,referenced_minor_name as SourceColumn, referenced_minor_id as depnumber
             FROM sys.dm_sql_referenced_entities (@vn, 'OBJECT')
             where referenced_minor_name  IS NOT NULL
             ORDER BY referenced_entity_name, referenced_minor_id
run sp_helpconstraint N'<table_name>', который даст мне список всех значений по умолчанию / типов ограничений по умолчанию, а также имен столбцов. Мне нужно будет сравнить значения по умолчанию со значениями в каждой таблице и определить, должна ли запись быть удаленным из них (см. ниже пи c, например, что возвращает этот запрос):

enter image description here

Вопросы

Существует ли более простой / более эффективный способ удаления записи из представления?

ПРИМЕЧАНИЕ: полная функция, которую я в итоге написал, была добавлена ​​в приведенные ниже ответы для всех, кто интересуется в ответ

Ответы [ 2 ]

0 голосов
/ 29 апреля 2020

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

If DI.CommonDataFunctions.IsThisAView(BaseTableName, Globals.dif) Then
                Dim viewColumnNames As New List(Of String)
                Dim tableName As String
                Dim viewTables As New List(Of String)
                Dim isDefault
                Dim primaryKeyName As String

                //return table names from view
                Dim qp As New List(Of SqlParameter)
                qp.Add(New SqlParameter("@vn", $"dbo.{BaseTableName}"))
                Dim sql As String = "
            SELECT 
                referenced_entity_name as SourceTable,referenced_minor_name as SourceColumn
            FROM 
                sys.dm_sql_referenced_entities (@vn, 'OBJECT')
            WHERE 
                referenced_minor_name  IS NOT NULL
            ORDER BY 
                referenced_entity_name, referenced_minor_id"

                Using dr As New DataReader(Globals.dif.GetDBDetails)
                    Dim constraintKeys As New Dictionary(Of String, String)()
                    Dim primaryKeyList As New List(Of Int32)
                    Dim table As String
                    dr.ExecuteReader(sql, qp)

                    Do While dr.Read
                        tableName = dr.Item("SourceTable").ToString.ToUpper.Trim
                        viewColumnNames.Add(dr.Item("SourceColumn").ToString.ToUpper.Trim)

                        If Not viewTables.Contains(tableName) Then
                            viewTables.Add(tableName)
                        End If
                    Loop

                    For Each table In viewTables
                        Dim columnName As String
                        Dim defaultConstraintValue
                        isDefault = True
                        table = table

                        dr.ExecuteReader("
                    SELECT Col.Column_Name from 
                        INFORMATION_SCHEMA.TABLE_CONSTRAINTS Tab, 
                        INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE Col 
                    WHERE 
                        Col.Constraint_Name = Tab.Constraint_Name
                    AND Col.Table_Name = Tab.Table_Name
                    AND Constraint_Type = 'PRIMARY KEY'
                    AND Col.Table_Name = '" + table + "'")

                        While dr.Read
                            primaryKeyName = dr.Item(0)
                        End While

                        //return default constraints
                        dr.ExecuteReader("
                    SELECT 
                        ColumnName = c.name,
                        TableName = t.name,
                        df.definition
                    FROM 
                        sys.default_constraints df
                    INNER JOIN 
                        sys.tables t ON df.parent_object_id = t.object_id
                    INNER JOIN 
                        sys.columns c ON c.object_id = df.parent_object_id AND df.parent_column_id = c.column_id
                    WHERE 
                        t.Name = N'" + table + "'")

                        While dr.Read
                            defaultConstraintValue = dr.Item("definition").ToString

                            //delete "(( ))" Or "( )" from default constraint
                            If defaultConstraintValue.StartsWith("((") AndAlso defaultConstraintValue.EndsWith("))") Then
                                defaultConstraintValue = defaultConstraintValue.Substring(0, defaultConstraintValue.Length - 2)
                                defaultConstraintValue = defaultConstraintValue.Substring(2)
                            ElseIf defaultConstraintValue.StartsWith("(") AndAlso defaultConstraintValue.EndsWith(")") Then
                                defaultConstraintValue = defaultConstraintValue.Substring(0, defaultConstraintValue.Length - 1)
                                defaultConstraintValue = defaultConstraintValue.Substring(1)
                            End If

                            If defaultConstraintValue.StartsWith("'") AndAlso defaultConstraintValue.EndsWith("'") Then
                                defaultConstraintValue = defaultConstraintValue.Substring(0, defaultConstraintValue.Length - 1)
                                defaultConstraintValue = defaultConstraintValue.Substring(1)

                                If Not IsNumeric(defaultConstraintValue) Then
                                    defaultConstraintValue = "'" + defaultConstraintValue + "'"
                                End If
                            End If

                            columnName = dr.Item("ColumnName").ToString.ToUpper.Trim

                            constraintKeys.Add(columnName, defaultConstraintValue)
                        End While
                    Next

                    Dim sql2 = "SELECT " + primaryKeyName + " FROM " + BaseTableName

                    If constraintKeys IsNot Nothing Then
                        Dim isFirstFilter = True
                        sql2 &= " WHERE "

                        For Each constraintKey In constraintKeys
                            If viewColumnNames.Contains(constraintKey.Key) AndAlso constraintKey.Key <> "FAMILY_UID" Then
                                If isFirstFilter = False Then
                                    sql2 &= " And "
                                End If

                                If IsNumeric(constraintKey.Value) Then
                                    Dim intConverted = CInt(constraintKey.Value)

                                    sql2 &= constraintKey.Key + " = " + intConverted.ToString + " "

                                    If isFirstFilter = True Then
                                        isFirstFilter = False
                                    End If
                                Else
                                    sql2 &= constraintKey.Key + " = " + constraintKey.Value + " "

                                    If isFirstFilter = True Then
                                        isFirstFilter = False
                                    End If
                                End If
                            End If
                        Next
                    End If

                    dr.ExecuteReader(sql2)

                    While dr.Read
                        primaryKeyList.Add(dr.Item(primaryKeyName))
                    End While

                    If primaryKeyList.Count > 0 Then
                        For Each table In viewTables
                            Dim isFirstFilter = True
                            Dim sql3 = "DELETE FROM " + table + " WHERE " + primaryKeyName + " IN ("

                            For Each primaryKey In primaryKeyList
                                sql3 &= primaryKey.ToString

                                If Not primaryKey = primaryKeyList(primaryKeyList.Count - 1) Then
                                    sql3 &= ", "
                                End If
                            Next

                            sql3 &= ")"

                            Using CEx As New CommandExecutor(Globals.dif)
                                CEx.ExecuteNonQuery(sql3)
                            End Using
                        Next
                    End If
                End Using
            End If
0 голосов
/ 17 апреля 2020

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

TLDR; нет


Вы должны понимать, что представление - это НЕ данные в базе данных; это сохраненный SQL запрос, который запускается каждый раз, когда вы выбираете в представлении.

Может даже случиться, что SQL Сервер берет ваш запрос и смешивает его с запросом, который предоставляет представление, оптимизирует их и запускает их, поэтому даже не обязательно, что он выполняет запрос представления, получает все миллионы записей, которые представляет представление, а затем просматривает их, ища одного парня по имени Константинополь Эрнхардт - SQL Сервер может посчитать лучшим, чтобы молча и прозрачно переписать запрос, который вы дали, так что он запланирован и работает совершенно иначе, чем вы может подумать - он делает это для каждого запроса, в процессе, называемом оптимизацией.

Ваше мнение:

CREATE VIEW MyView AS
SELECT * FROM Person p JOIN Address a on p.AddressId = a.Id

Вы пишете:

SELECT * FROM MyView WHERE Name = 'Abc' and HouseName = 'def'

Вы можете подумать он делает это (и концептуально, вы правы):

SELECT * FROM 
(
  SELECT * FROM Person p JOIN Address a on p.AddressId = a.Id
) x WHERE Name = 'Abc' and HouseName = 'def'

Но это, вероятно, переписано так:

SELECT * FROM Person p JOIN Address a on p.AddressId = a.Id WHERE Name = 'Abc' and HouseName = 'def'

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

* 10 28 * Вы не можете, потому что запросы не имеют данных; они извлекают данные из таблиц

Единственный способ «удалить данные из представления» - это удалить данные из таблицы, в которой представление выбирает данные из

. Вы можете только сделать это с помощью DELETE операторов соответствующих таблиц

Существует средство, в которое можно записать триггер INSTEAD OF для представления, а затем удалить из представления, и SQL Сервер будет запустить триггер (который удаляет из базовых таблиц). Может показаться, что вы удаляете данные из представления, но на самом деле вы просто вызываете механизм удаления данных из базовых таблиц так же, как представление - это механизм, который вытаскивает данные из этих таблиц.

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

Выберите любой метод, который вам нравится, в соответствии с вашими бизнес-целями и желанием инкапсулирование вашего программного обеспечения определенным образом. Например, в прошлом у меня было программное обеспечение, которое я не мог изменить (потерял исходный код или что-то еще), и оно было жестко привязано к SELECT FROM users или DELETE FROM users - мы хотели, чтобы программное обеспечение продолжало работать, даже если Таблица пользователей была переименована в члены. Мы переименовали таблицу, затем создали представление с именем users, которое только что SELECT * FROM members - которое позволяет приложению продолжать работать, считывая данные. Затем мы создали INSTEAD OF триггеры для обновления и удаления данных в таблице членов, когда приложение попыталось выполнить эту операцию в представлении users (которое приложение все еще считало таблицей)


Итак почему это так сложно? Ну, данные, которые выходят из вашего представления, могут даже не относиться к строке таблицы. Вот простая версия:

CREATE VIEW MyView AS
SELECT MAX(SUBSTR(Name, 4, 999)) MaxFirstName FROM Person GROUP BY Gender

Предположим, что было два человека с именами Mr Lee Smith и Ms Lee Smith, вы взяли максимум вывода функции и получили Lee Smith и теперь хотите удалить Lee Smith из таблицы Person путем анализа представления и удаления ... что из таблицы Person? Какая запись? Группировка собрала все записи вместе. МАКСИМАЛЬНОЕ имя одного и даты рождения MIN от другого ..

Вот еще один пример, немного более нелепый, но мыслимый:

  CREATE VIEW MyView AS
  SELECT name as x FROM person
  UNION
  SELECT street FROM address

Это может легко привести к значению "Penny Lane" - но это человек или дорога? Который должен быть удален, если мы удалим из этого представления, где x = 'Penny Lane'

Не существует пули magi c, в которой вы можете ее запустить, и будет написано "эта таблица использует эти 3 таблицы" так что вы можете удалить из них. Это даже не будет хорошей предпосылкой. Ваше представление может выбрать одну таблицу данных и одну таблицу поиска и удалить тип пола 1 из таблицы поиска только потому, что вы удаляете Дональда Трампа из таблицы пользователей, будет плохим вызовом

Если вы хотите предоставить средства для удаления в представлении, вам нужно что-то кодировать; не существует автоматического решения, которое могло бы определить, какие данные из каких таблиц следует удалить, а какие должны остаться. Только представьте, как трудно было бы проанализировать представление, объединяющее 9 таблиц, со смесью стилей объединения, плюс еще 8 списков ЗНАЧЕНИЙ, 3 вызова функции таблицы с перекрестными приложениями, которая анализирует некоторый json, отбрасывая пару там есть строки, генерирующие рекурсивные CTE, а также опорная точка ...

Абсолютно нет шансов, что кто-нибудь напишет кнопку magi c, чтобы разбить все это на части, "составить список базовых таблиц и какие данные должен быть удален, чтобы удовлетворить DELETE FROM MyView WHERE output_of_parsing_function = 'Hello'

...