场景:使用"而不是插入"触发到另一个表(覆盖插入当前表)会引发截断错误。
问题:
TableA有nvarchar(10) desc
列,TableB有nvarchar(200) desc
列。在TableA上设置插入触发器,其中desc
列的数据长度为50个字符。
如果SET ANSI_WARNINGS ON (
为默认值),TableA会导致截断错误。
使用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]
但是当我运行第一个代码段时,causefailure
会被截断。但没有错误。运行第二个代码段会在[results]中插入一条记录。但它在第二个引发了例外。
有没有办法忽略:约束,转换问题等,初始写入[test_table]的异常?但是在触发器中完成的任何工作都有例外(例如插入[结果]甚至是[test_tables]的实际工作)?
修改:我不想更改
nvarchar(10) desc
的列大小 在TableA上。最终目标可能是"而不是触发" 无法在TableA上插入,它保存到TableB。或者它甚至可能会改变desc
的大小在触发器内动态失败并重试 插入。
希望有帮助!
答案 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中。