【问题标题】:SQL Server - Instead of Insert Trigger - override truncation error?SQL Server - 而不是插入触发器 - 覆盖截断错误?
【发布时间】:2018-04-04 04:05:45
【问题描述】:

场景:对不同的表使用“Instead of Insert”触发器(覆盖当前表的插入)会引发截断错误。

  1. 尝试将数据插入 TableA 中
  2. Instead Of Trigger 设置为在 TableA 上运行
  3. 此触发器改为插入到 TableB 中
  4. TableA 未写入

问题:

TableA 有一个nvarchar(10) desc 列,TableB 有一个nvarchar(200) desc 列。在 TableA 上设置了插入触发器,其中 desc 列的数据长度为 50 个字符。

  1. 使用SET ANSI_WARNINGS ON (默认值),TableA 会导致截断错误。

  2. 对于SET ANSI_WARNINGS OFF(危险),TableB 上的截断被忽略。因此,如果 desc 以 400 个字符输入,它将被截断为 200 个字符而没有错误。

设置

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[results]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [causefailure] [nvarchar](5) NULL, 
    [type] [nvarchar](50) NOT NULL, 
    [description] [nvarchar](200) NULL,
    [rundate] [datetime] NOT NULL DEFAULT (getdate())
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[test_table]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [description] [nvarchar](10) NULL,
    [rundate] [datetime] NOT NULL DEFAULT (getdate())
) ON [PRIMARY]
GO


CREATE TRIGGER [dbo].[InsteadTrigger] 
ON [dbo].[test_table]
INSTEAD OF INSERT
AS
    SET NOCOUNT ON
    SET ANSI_WARNINGS ON
BEGIN
    INSERT INTO results([type], [description])
        SELECT 
            (CASE SUBSTRING([description], 1, 1)
                WHEN 'a' 
                   THEN 'causes failure or truncation'
                   ELSE ''
             END AS [causefailure],
             'Instead Of Trigger' AS [type],
             [description]
        FROM inserted
    END;
GO

ALTER TABLE [dbo].[test_table] ENABLE TRIGGER [InsteadTrigger]
GO

第一次尝试:

SET ANSI_WARNINGS OFF

INSERT INTO [dbo].[test_table]([description])
    SELECT 'atest12345678910' AS [description]

第二次尝试:

SET ANSI_WARNINGS ON

INSERT INTO [dbo].[test_table]([description])
    SELECT 'btest12345678910' AS [description]
INSERT INTO [dbo].[test_table]([description])
    SELECT 'atest12345678910' AS [description]

但是当我运行第一个代码 sn-p 时,causefailure 被截断。但是没有错误。运行第二个 sn-p 在 [results] 中插入一条记录。但它会在第二次引发异常。

有没有办法忽略:约束、转换问题等,初始写入 [test_table] 的异常?但是在触发器中完成的任何工作是否有例外(例如插入 [results] 甚至可能是 [test_tables] 的实际工作)?

编辑:我不想更改nvarchar(10) desc 的列大小 在表 A 上。最终目标可能是“代替触发器” 无法在 TableA 上插入,它保存到 TableB。或者它甚至可能改变 desc 的大小在触发器内部动态失败并重试 插入。

希望对您有所帮助!

【问题讨论】:

  • 我也很感兴趣,是否有可以用 CLR C# 编写的“代替插入”触发器之类的东西。但我需要先解决上述问题。
  • 目标是在插入失败时更改 test_table 的模式并再次尝试插入。就像在尝试/捕获中一样。我会在问题中包含这个。
  • 使用存储过程来处理这个问题,而不是使用不透明的触发器来重定向您的 INSERT。触发器中的错误无论如何都会使批处理失败,除非您使用 TRY/CATCH
  • trigger 对截断没有任何作用。当触发器触发时,截断已经发生
  • 然后让 EF 正确设计表格。您要求的解决方案很糟糕。

标签: sql sql-server triggers


【解决方案1】:

以下是如何解决上述问题的不完整想法。有点回避的意思。但有趣的方法是测试您可以在多大程度上重做数据库。

以下内容会在创建或更改表时更改架构。在创建或更改表时,将在表重命名后将其换出为索引视图。

ALTER TRIGGER trigger_CreateTable
    ON DATABASE
    AFTER CREATE_TABLE, ALTER_TABLE
AS
BEGIN
    --SELECT EVENTDATA()

    DECLARE @Prefix AS nvarchar(256) = N'PleaseUseView_'

    DECLARE @Event AS XML = EVENTDATA()
    DECLARE @SchemaName AS nvarchar(255) = (@Event.value('(/EVENT_INSTANCE/SchemaName)[1]',  'NVARCHAR(255)')) 
    DECLARE @TableName AS nvarchar(255) = (@Event.value('(/EVENT_INSTANCE/ObjectName)[1]',  'NVARCHAR(255)')) 
    DECLARE @ObjectType AS nvarchar(255) = (@Event.value('(/EVENT_INSTANCE/ObjectType)[1]',  'NVARCHAR(255)'))
    DECLARE @TableWithSchema AS nvarchar(512) = '[' + @SchemaName + '].[' + @TableName + ']'

    CREATE TABLE #SchemaBindingDependencies
    (
        [id] int NOT NULL IDENTITY,
        [schema] nvarchar(256) NOT NULL,
        [name] nvarchar(256) NOT NULL
    )

    INSERT INTO #SchemaBindingDependencies([schema], [name])
        SELECT DISTINCT s.name AS [schema], o.name
        FROM sys.objects AS o
            INNER JOIN sysdepends AS d
                ON d.id = o.[object_id]
            INNER JOIN sys.schemas AS s
                ON s.[schema_id] = o.[schema_id]
        WHERE     o.type ='V' AND d.depid = OBJECT_ID(@TableWithSchema)
              AND SUBSTRING(@TableName, LEN(@Prefix) + 1, 256) LIKE o.[name]

    IF (EXISTS(SELECT 1 FROM #SchemaBindingDependencies))
    BEGIN
        DECLARE @Index AS int = (SELECT MAX(id) FROM #SchemaBindingDependencies)
        WHILE (@Index > 0)
        BEGIN
            DECLARE @ViewName1 AS nvarchar(256) = (SELECT [name] FROM #SchemaBindingDependencies WHERE id = @Index)
            IF (@ViewName1 IS NOT NULL)
            BEGIN
                DECLARE @SchemaName1 AS nvarchar(256) = (SELECT [schema] FROM #SchemaBindingDependencies WHERE id = @Index)
                DECLARE @DropSchemaBoundViewQuery AS nvarchar(1000) = 'DROP VIEW [' + @SchemaName + '].[' + @ViewName1 + ']'

                EXEC(@DropSchemaBoundViewQuery)
            END

            SET @Index = @Index - 1
        END
    END

    IF (SUBSTRING(@TableName, 1, LEN(@Prefix)) <> @Prefix)
    BEGIN
        DECLARE @NewTableName AS nvarchar(512) = @Prefix + @TableName + ''
        DECLARE @NewTableWithSchema AS nvarchar(512) = '[' + @SchemaName + '].[' + @NewTableName + ']'

        EXEC sp_rename @TableWithSchema, @NewTableName

        SET @TableName = @NewTableName
        SET @TableWithSchema = '[' + @SchemaName + '].[' + @NewTableName + ']'
    END

    DECLARE @Columns AS nvarchar(max) = (STUFF((SELECT ',[' + x.[name] + ']' FROM (
        SELECT c.[name]
        FROM sys.columns AS c
            INNER JOIN sys.tables AS t
                ON t.[object_id] = c.[object_id]
            INNER JOIN sys.schemas AS s
                ON s.[schema_id] = t.[schema_id]
        WHERE t.[name] = @TableName AND s.[name] = @SchemaName) AS x FOR XML PATH('')), 1, 1, ''))

    DECLARE @ViewName AS nvarchar(256) = SUBSTRING(@TableName, LEN(@Prefix) + 1, 256)
    DECLARE @ViewWithSchema AS nvarchar(512) = '[' + @SchemaName + '].[' + @ViewName + ']'

    DECLARE @Query AS nvarchar(max) =
        N'CREATE VIEW ' + @ViewWithSchema + N' ' + CHAR(10) + CHAR(13) +
        N'WITH SCHEMABINDING ' + CHAR(10) + CHAR(13) +
        N'AS ' + CHAR(10) + CHAR(13) +
        N'  SELECT ' + @Columns + ' ' + CHAR(10) + CHAR(13) +
        N'  FROM ' + @TableWithSchema + N' '
    --SELECT @Query
    EXEC(@Query)

    SET @Query =
        N'CREATE UNIQUE CLUSTERED INDEX [CIX_' + @ViewName + N'] ' + CHAR(10) + CHAR(13) +
        N'ON ' + @ViewWithSchema + N'(' + @Columns + N')'
    EXEC(@Query)

    -- TODO: Use the below double commented to build a variable insert statement for the "Instead of TRIGGER"
    --
    ----DECLARE @tv_source TABLE (id int)
    ----declare @XML xml;
    ----set @XML = 
    ----  (
    ----  select top(0) *
    ----  from @tv_source
    ----  for XML RAW, ELEMENTS, XMLSCHEMA  
    ----  );
    ----SELECT T.c.query('.'), T.c.value('@name', 'nvarchar(256)')
    ----FROM @XML.nodes('/*/*/*/*/*') AS T(c)
    --
    --SET @Query =
    --  N'CREATE TRIGGER [Trigger_' + @ViewName + N'] ' + CHAR(10) + CHAR(13) +
    --  N'ON ' + @ViewWithSchema + N' ' + CHAR(10) + CHAR(13) +
    --  N'INSTEAD OF INSERT ' + CHAR(10) + CHAR(13) +
    --  N'AS BEGIN ' + CHAR(10) + CHAR(13) +
    --  N'BEGIN TRY ' + CHAR(10) + CHAR(13) +
    --  N' INSERT INTO ' + @TableWithSchema + N'(' + @Columns + N')'
    --  N'   SELECT ' + @Columns + 
    --EXEC(@Query)
END

理想情况下,您应该为表使用不同的架构。并使用 dbo 或 视图的默认值。

一旦Instead of TRIGGER 工作,您可以在它周围包裹一个TRY/CATCH。抓住机会,检查架构是否被截断。并根据需要扩展列大小。

不完整的解决方案。但这是我现在会坚持的答案。 如果有人有更好的答案或完整的解决方案,请补充!

tl;博士

一个有趣的事情是下面的查询

DECLARE @tv_source TABLE (id int)
declare @XML xml;
select top(0) *
from @tv_source
for XML RAW, ELEMENTS, XMLSCHEMA  

您可以返回架构。上述 XML 的 SOAP。或者 JsonSchema 或 Avro,如果使用 SQL 2016+ Json 版本来构建具有模式感知能力的 Restful API。通过架构感知,应用程序网关可以将许多分散的 Micro-Rest API 自动抓取到一个看似庞大的 Rest API 中。

【讨论】:

  • 虽然有不同的数据库技术将模式视为二等公民(大多数非关系型,例如 MongoDB/PostgreSQL-NoSQL),但我目前仍坚持使用 SQL Server。我希望 SQL Server 更多。但我知道它就是这样。
猜你喜欢
  • 2022-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
相关资源
最近更新 更多