从具有多个基表的视图中删除记录的最有效方法?

Posted

技术标签:

【中文标题】从具有多个基表的视图中删除记录的最有效方法?【英文标题】:Most efficient way of deleting records from a view with multiple base tables? 【发布时间】:2020-04-17 08:48:20 【问题描述】:

我需要编写一个 VB 函数,从视图的基表中删除所有已使用默认值/默认约束键值初始化的记录。

我的理解是,这样做的唯一方法是分别从每个基表中删除记录,但我不确定是否有比我尝试尝试的更简单、更有效的方法。如果可能的话,我想要一些指导/建议。

这是我能想到的唯一方法:

    运行从视图返回基表名称的查询:

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

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
    运行sp_helpconstraint N'<table_name>',它将为我提供所有默认值/默认约束类型的列表,以及我需要将默认值与每个表中的值进行比较并确定是否应该记录的列名被删除。

问题

有没有更简单/更有效的方法来尝试从视图中删除记录?

注意:我最终编写的完整功能已添加到下面的答案中,供任何对答案感兴趣的人使用

【问题讨论】:

您是在谈论构建一个适用于任何视图或特定视图的流程吗? @jmcilhinney 对于任何观点 【参考方案1】:

我不确定是否有比我尝试的更简单、更有效的方法。如果可能的话,我想要一些指导/建议

TLDR;没有


您需要了解视图不是数据库中的数据;它是一个存储的 SQL 查询,每次您从视图中选择时都会运行该查询。

SQL Server 甚至可能会获取您的查询并将其与提供视图的查询混合并优化它们并运行它们,因此它甚至不一定会运行视图查询,获取所有百万条记录视图代表,然后通过它们寻找一个叫做君士坦丁堡 Ernhardt 的人 - SQL Server 可能认为最好静默透明地重写您提供的查询,因此它的计划和运行与您可能的想法完全不同 - 它这样做是为了每个查询都在一个称为优化的过程中。

你的观点是:

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'

所以现在已经解决了,您可以看到视图只是表顶部的查询,每次您从中选择时都会运行它 - 如何从查询中删除数据?

你不能,因为查询没有数据;他们从表中检索数据

“从视图中删除数据”的唯一方法是从视图从中选择数据的表中删除数据

您只能通过相关表上的DELETE 语句来做到这一点

您可以在视图上编写INSTEAD OF 触发器,然后从视图中删除,SQL Server 将运行触发器(从基础表中删除)。看起来您正在从视图中删除数据,但实际上,您只是在使用一种机制从基础表中删除数据,就像视图是将数据从这些表中拖出的机制一样。

您可以编写一个删除数据的存储过程,但同样,这只是从基础表中删除数据的一种机制

根据您的业务目标和以某种方式封装您的软件的愿望,选择您喜欢的任何方法。例如,过去我有一个我无法更改的软件(丢失了源代码或其他),并且它已硬连接到 SELECT FROM usersDELETE FROM users - 我们希望该软件能够继续工作,即使用户表被重命名为成员。我们重命名了表,然后创建了一个名为 users 的视图,它刚刚做了 SELECT * FROM members - 允许应用程序继续工作,读取数据。然后,当应用尝试对users 视图(应用仍然认为它是一个表)执行该操作时,我们创建了INSTEAD OF 触发器来更新和删除成员表中的数据


那么为什么这么难呢?好吧,从您的视图中出来的数据甚至可能不再与表格行相关。这是一个简单的版本:

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

假设有两个人分别叫Mr Lee SmithMs Lee Smith,你已经获取了函数输出的最大值并得到了Lee Smith,现在你想通过分析视图从person表中删除Lee Smith并从 Person 表中删除... 什么?哪个记录?分组将所有记录混合在一起。一个人的最大姓名和另一个人的最小生日..

这是另一个例子,有点可笑但可以想象:

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

这很容易产生独特的价值“Penny Lane”——但它是一个人,还是一条路?如果我们从这个视图中删除 x = 'Penny Lane' 应该删除哪个

没有可以运行它的灵丹妙药,它会显示“此表使用这 3 个表”,因此您可以从中删除它们。这甚至都不是一个好的前提。您的视图可能会从一个数据表和一个查找表中进行选择,并且仅仅因为您要从用户表中删除唐纳德特朗普而从查找表中删除性别类型 1,这将是一个错误的选择

如果你想在视图上提供删除功能,你需要编写一些代码;没有一个自动的解决方案可以计算出应该删除哪些表中的哪些数据以及应该保留哪些数据。试想一下,分析一个连接 9 个表、混合连接样式、另外 8 个 VALUES 列表、3 次调用解析一些 json 的交叉应用表值函数、吊起几行生成的视图是多么困难那里有递归 CTE,还有一个枢轴......

绝对没有任何人会编写魔术按钮来将所有内容分开为“计算出基表列表以及应删除哪些数据以满足DELETE FROM MyView WHERE output_of_parsing_function = 'Hello'

【讨论】:

感谢您详尽详尽的回答。表/视图中的每一行都有一个具有唯一 id 的 id 列。这就是为什么可以做到这一点。我猜你可能没有正确理解我的问题。我已经写了我最终创建的函数作为这个线程的答案【参考方案2】:

这是我最终使用的最后一个工作函数。 (欢迎提出更好的方法)

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

【讨论】:

以上是关于从具有多个基表的视图中删除记录的最有效方法?的主要内容,如果未能解决你的问题,请参考以下文章

从向量中删除项目

从数组列表中删除元素的最有效方法? [关闭]

从字符串中删除特殊字符的最有效方法

从多维数组中删除数组的最有效方法[重复]

Numpy - 从一维数组中删除最后一个元素的最佳方法?

如何在单个查询中计算与基表结果匹配的多个表的记录?