SQL Server Instead Of Insert trigger on View causes Cannot Insert Null - sql

We have an Instead-Of-Insert trigger on a view which copies all values from the INSERTED virtual-table to another table.
One of the fields in the list is non-nullable for the target table, and has a default value specified.
What we are experiencing, is, some application code is sending an insert command, and not specifying the non-nullable field - which (if the insert were executed against the actual table) would normally result in SQL Server inserting the column's default value. But, the trigger is explicit for all fields, so the trigger tries to insert null for that field... resulting in an error.
What I DONT want, is code like this...
INSERT INTO XXXX (col1, col2, col3)
SELECT
ISNULL(col1, 0), ISNULL(COL2, 0), ISNULL(COL3, 0)
FROM INSERTED
I don't want the trigger to need to know what the actual default values of each column should be (from a maintainability perspective)...
Does anyone have a better solution?
Thanks

when your application is sending NULL values to a not nullable column, there are not to many options. specialy when you dont want to use input validation with isnull.
we are using default values in this case. if it is possible you can alter your table:
ALTER TABLE xxxx ADD CONSTRAINT DF_col1 DEFAULT N'default' FOR col1;

I can think of an ugly and inefficient way of doing this. The idea is to insert a default row and then update the columns one at a time, using try/catch to ignore errors.
declare #Id int;
insert int XXX DEFAULT VALUES;
set #id = ##IDENTITY;
begin try
update XXX set col1 = val1 where id = #id;
end try
begin catch
end catch;
begin try
update XXX set col2 = val2 where id = #id;
end try
begin catch
end catch;
. . .
If you have to do this on 100 columns, then that could be a bad idea. If you only have two or three columns causing the problems, then this might solve your problem.

Related

Trigger works based on type of insert SQL Server

I am working in SQL Server Management Studio v18, and I have the following trigger:
CREATE TRIGGER [dbo].[update_surface]
ON [dbo].[my_table]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #surface_m2 REAL
SET #surface_m2 = (SELECT cast(round(CAST(Dimension1*Dimension2 as decimal)/ cast(1000000 as decimal),3,3) as decimal(10,3)) AS surface FROM my_table WHERE Surface_m2 IS NULL)
UPDATE dbo.my_table SET Surface_m2 = #surface_m2
END
I have two columns in my_table, which are Dimesion1 and Dimension2. I want that the trigger multiplies them, and set the result to other column in the same table, which is Surface_m2, whenever this column is null. The trigger does his function, but based on the type of insert I do:
If I insert a row in my_table by the graphic environment the trigger works as I wish. With each new row, Surface_m2 has his own result.
But if I insert by INSERT INTO my_table VALUES ().... (query) the trigger updates Surface_m2 column of all previous rows with the result of each new insert.
Why is the trigger working like that? Is there any other simple way to do what I am trying to do?
Thanks.
Insert trigger gives you actual values that are inserted in a special table called... "inserted".
So what you need to do is join this table against your main table and perform the logic needed, no variables required.
Something like this untested code
create trigger...
begin
UPDATE t
SET Surface_m2 = cast(round(CAST(t.Dimension1*t.Dimension2 as decimal)
/ cast(1000000 as decimal),3,3) as decimal(10,3))
from dbo.my_table t
inner join inserted i
ON i.ID = t.ID
WHERE t.Surface_m2 IS NULL
end
This begs the question though, why can't you just insert the Surface_m2 value yourself. Or even better, change Surface_m2 to be a computed column if it's always depends on Dimension1 and Dimension2

Checking some columns for specific values before insert/update using a trigger

I have a database FOO with several columns, among those I have one column "Url". I need to write a trigger before insert/update that will check the Url columns whether the newer value matches any existing values, i.e. "hello" except some predefined value. That means if "hello" is inserted or updated multiple times no error will happen otherwise it will check for duplicity. And if it finds some aborts the insertion update. This will also return some code so that my script calling for the insertion/update will know a failure has occurred. I know there might be other workarounds but I will need to have it this way. I am pretty new to SQL.
Foo {
Url
}
Here is the algorithm
Before update insert
if new value of Url is not "hello1" o "hello 2"
check if new value of Url already exists in Foo.Url if so abort otherwise allow update/insert
return something if aborted/success
try something like this.. you'll need to index your table..
IF EXISTS(SELECT URL FROM Foo.Url)
BEGIN
SELECT 'URL Exists Already'
END
ELSE
BEGIN
INSERT/UPDATE
END
A unique constraint wouldn't do what you want but you could create an instead of trigger with content something like as:
Create TRIGGER [dbo].[Trig_Insert_XXX]
ON [dbo].[XXX]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO xxx ([url], field1, field2, fieldN)
SELECT [url], field1, field2, fieldN
FROM inserted i
WHERE i.url = 'hello' OR NOT EXISTS (SELECT * FROM xxx t2 WHERE t2.url = i.url);
END;
I suppose you're looking for a UNIQUE constraint & a CHECK constraint as
CREATE TABLE Foo(
Url VARCHAR(250) NOT NULL,
CONSTRAINT UQ_Url UNIQUE(Url),
CONSTRAINT CHK_Url CHECK (Url NOT IN ('hello1', 'hello2'))
);
See how it's working online.
If you are using SQL Server 2008 or newer version you can use MERGE as well, the syntax is like the following :
MERGE [TableName] AS TARGET
USING ( SELECT #UrlName ) AS SOURCE (UrlName) ON SOURCE.UrlName = TARGET.UrlName
WHEN MATCHED THEN
UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ()
VALUES ();

Set value of the column to default for NULL values

I have a table with a column which has default value to 0. However when null is inserted to this table, I want it to set it to default.
As the insert query is generic and used by other databases too, I cannot make any changes to the insert statement.
Can I have constraints or case statement on create table, so that default 0 value is inserted whenever null is passed.
If you can not change an insert statement you have no way other then creating an instead of insert trigger:
CREATE TRIGGER trTableName
ON SchemaName.TableName
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO TableName (ColumnA, ColumnB, ...)
SELECT ISNULL(ColumnA, 0), ISNULL(ColumnB, 0), ...
FROM INSERTED
END
You can do an update using a trigger on insert.
CREATE TRIGGER [dbo].[YourTriggerName]
ON [dbo].[YourTable]
AFTER INSERT
AS
BEGIN
UPDATE t SET YourCol = 0
FROM YourTable t
JOIN Inserted i ON t.Id = i.Id
WHERE i.YourCol IS NULL
END
Inserting null is the same as inserting a specific value, and so the default value is bypassed.
To use the default setting the insert statement shouldn't insert anything to this column.
If you can't use Coalesce (value, 0) in the select bit of the into statement, try using it in your select queries to disguise the result.

Help with SQL Server Trigger to truncate bad data before insert

We consume a web service that decided to alter the max length of a field from 255. We have a legacy vendor table on our end that is still capped at 255. We are hoping to use a trigger to address this issue temporarily until we can implement a more business-friendly solution in our next iteration.
Here's what I started with:
CREATE TRIGGER [mySchema].[TruncDescription]
ON [mySchema].[myTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [mySchema].[myTable]
SELECT SubType, type, substring(description, 1, 255)
FROM inserted
END
However, when I try to insert on myTable, I get the error:
String or binary data would be
truncated. The statement has been
terminated.
I tried experimenting with SET ANSI_WARNINGS OFF which allowed the query to work but then simply didn't insert any data into the description column.
Is there any way to use a trigger to truncate the too-long data or is there another alternative that I can use until a more eloquent solution can be designed? We are fairly limited in table modifications (i.e. we can't) because it's a vendor table, and we don't control the web service we're consuming so we can't ask them to fix it either. Any help would be appreciated.
The error cannot be avoided because the error is happening when the inserted table is populated.
From the documentation:
http://msdn.microsoft.com/en-us/library/ms191300.aspx
"The format of the inserted and deleted tables is the same as the format of the table on which the INSTEAD OF trigger is defined. Each column in the inserted and deleted tables maps directly to a column in the base table."
The only really "clever" idea I can think of is to take advantage of schemas and the default schema used by a login. If you can get the login that the web service is using to reference another table, you can increase the column size on that table and use the INSTEAD OF INSERT trigger to perform the INSERT into the vendor table. A variation of this is to create the table in a different database and set the default database for the web service login.
CREATE TRIGGER [myDB].[mySchema].[TruncDescription]
ON [myDB].[mySchema].[myTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [VendorDB].[VendorSchema].[VendorTable]
SELECT SubType, type, substring(description, 1, 255)
FROM inserted
END
With this setup everything works OK for me.
Not to state the obvious but are you sure there is data in the description field when you are testing? It is possible they change one of the other fields you are inserting as well and maybe one of those is throwing the error?
CREATE TABLE [dbo].[DataPlay](
[Data] [nvarchar](255) NULL
) ON [PRIMARY]
GO
and a trigger like this
Create TRIGGER updT ON DataPlay
Instead of Insert
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [tempdb].[dbo].[DataPlay]
([Data])
(Select substring(Data, 1, 255) from inserted)
END
GO
then inserting with
Declare #d as nvarchar(max)
Select #d = REPLICATE('a', 500)
SET ANSI_WARNINGS OFF
INSERT INTO [tempdb].[dbo].[DataPlay]
([Data])
VALUES
(#d)
GO
I am unable to reproduce this issue on SQL 2008 R2 using:
Declare #table table ( fielda varchar(10) )
Insert Into #table ( fielda )
Values ( Substring('12345678901234567890', 1, 10) )
Please make sure that your field is really defined as varchar(255).
I also strongly suggest you use an Insert statement with an explicit field list. While your Insert is syntactically correct, you really should be using an explicit field list (like in my sample). The problem is when you don't specify a field list you are at the mercy of SQL and the table definition for the field order. When you do use a field list you can change the order of the fields in the table (or add new fields in the middle) and not care about your insert statements.

Setting multiple scalar variables from a single row in SQL Server 2008?

In a trigger, I have code like:
SET #var1 = (SELECT col1 FROM Inserted);
SET #var2 = (SELECT col2 FROM Inserted);
Is it possible to write the above in a single line? Something conceptually like:
SET (#var1,#var2) = (SELECT col1,col2 FROM Inserted);
Obviously I tried the above, without success; am I just stuck with the first method?
Even if possible, is that a good idea?
Thanks!
yes, use first method.
Or...
SELECT
#var1 = col1
,#var2 = col2
FROM
Inserted;
However, it is a major red flag if you are expecting to set variable values like that in a trigger. It generally means the trigger is poorly designed and needs revision. This code expects there will be only one record in inserted and this is something that is not going to be true in all cases. A multiple record insert or update will have multiple records in inserted and the trigger must account for that (please without using a trigger!!!). Triggers should under no circumstances be written to handle only one-record inserts/updates or deletes. They must be written to handle sets of data.
Example to insert the values from inserted to another table where the trigger is on table1:
CREATE TRIGGER mytrigger on table1
AFTER INSERT
AS
INSERT table2 (field1, field2, field3)
SELECT field1, 'test', CASE WHEN field3 >10 THEN field3 ELSE 0 END
FROM inserted
No, it is not possible. SET accepts a single target and value. AFAIK.