SQL Server - 而不是插入触发器 - 覆盖截断错误?

时间:2018-04-04 04:05:46

标签: sql sql-server triggers

场景:使用"而不是插入"触发到另一个表(覆盖插入当前表)会引发截断错误。

  1. 尝试将数据插入TableA
  2. 而不是触发器设置为在TableA上运行
  3. 此触发器反而插入TableB
  4. TableA未写入
  5. 问题:

    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个字符且没有错误。

    3. 设置

      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]
      

      但是当我运行第一个代码段时,causefailure会被截断。但没有错误。运行第二个代码段会在[results]中插入一条记录。但它在第二个引发了例外。

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

        

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

      希望有帮助!

1 个答案:

答案 0 :(得分:0)

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

以下更改创建或更改表时的架构。在创建或更改表时,在重命名表后,它将被换出为索引视图。

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。在catch上,检查schema以进行截断。并根据需要扩展列大小。

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

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中。