Error trying to insert data using trigger T-SQL - sql-server

Below is a trigger used on one of our SQL tables for any insert/update action. 99/100 times this trigger works just fine however every now and then we receive this error message:
Cannot insert the value NULL into column 'TransactionDate', table
'AgentResourcesU01.dbo.TransactionLog'; column does not allow nulls.
INSERT fails. The statement has been terminated.
As you can see from the Insert statement, the columns in our transaction log table are TransactionDate, Operator, TableName, Action, TableString and UserId. I set the variable #transDate in the opening SELECT statement so as it appears to me, there should be no way a NULL gets in there unless it's bad data coming in.
Any thoughts?
BEGIN
SELECT #symetraNumber = SymetraNumber, #lastChangeOperator = LastChangeOperator, #transDate = LastChangeDate, #entityType = EntityType,
#firstName = FirstName, #lastName = LastName, #suffix = NameSuffix, #corpName = CorporateName, #controlId = ControlId
FROM inserted
IF #firstName IS NULL SET #firstName = 'NULL'
IF #lastName IS NULL SET #lastName = 'NULL'
IF #suffix IS NULL SET #suffix = 'NULL'
IF #corpName IS NULL SET #corpName = 'NULL'
IF #controlId IS NULL SET #controlId = 'NULL'
SET #tableString = 'SymNum:' + #symetraNumber + ' EntType:' + #entityType + ' Fname:' + #firstName + ' Lname:' + #lastname + ' Suff:' + #suffix +
' CorpName:' + #corpName + ' ctrlId:' + #controlId
INSERT INTO TransactionLog (TransactionDate, Operator, TableName, Action, TableString, UserId)
VALUES (#transDate, 'Op', #tableName, #action, #tableString, #lastChangeOperator)
END

To demonstrate Marc's point, you can do this in a set-based way, without all these nasty variables and IF checks:
INSERT dbo.TransactionLog
(
TransactionDate,
Operator,
TableName,
Action,
TableString,
UserId
)
SELECT
LastChangeDate,
'Op',
#TableName,
#action,
'SymNum:' + COALESCE(SymetraNumber, 'NULL')
+ ' EntType:' + COALESCE(EntityType, 'NULL')
+ ' Fname:' + COALESCE(FirstName, 'NULL')
+ ' Lname:' + COALESCE(LastName, 'NULL')
+ ' Suff:' + COALESCE(NameSuffix, 'NULL')
+ ' CorpName:' + COALESCE(CorporateName, 'NULL')
+ ' ctrlId:' + COALESCE(ControlId, 'NULL'),
LastChangeOperator
FROM inserted;
If LastChangeDate in the underlying table is NULLable, either mark it as NOT NULL, fix the problem where NULL is getting inserted, or both. The trigger shouldn't have to know about this constraint but you can work around it by doing something like this (if the value is NULL, set it to right now):
...
UserId
)
SELECT
COALESCE(LastChangeDate, CURRENT_TIMESTAMP),
'Op',
...

[1] I assume that you have an INSERT/UPDATE/DELETE trigger and when somebody try to delete rows from the base table then the inserted table from trigger will have zero rows. Look at this example:
CREATE TABLE MyTableWithTrigger (
MyID INT IDENTITY PRIMARY KEY,
LastUpdateDate DATETIME NOT NULL
);
GO
CREATE TABLE TransactionLog (
TransactionLogID INT IDENTITY PRIMARY KEY,
CreateDate DATETIME NOT NULL DEFAULT GETDATE(),
LastUpdateDate DATETIME NOT NULL,
MyID INT NOT NULL
);
GO
CREATE TRIGGER trIUD_MyTableWithTrigger_Audit
ON MyTableWithTrigger
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE #LastUpdateDate DATETIME, #MyID INT;
SELECT #LastUpdateDate=i.LastUpdateDate, #MyID=i.MyID
FROM inserted i;
INSERT TransactionLog (LastUpdateDate, MyID)
VALUES (#LastUpdateDate, #MyID)
END;
GO
PRINT 'Test 1'
INSERT MyTableWithTrigger (LastUpdateDate)
VALUES ('2011-01-01');
DELETE MyTableWithTrigger;
SELECT * FROM MyTableWithTrigger;
SELECT * FROM TransactionLog;
Output:
Msg 515, Level 16, State 2, Procedure trIUD_MyTableWithTrigger_Audit, Line 10
Cannot insert the value NULL into column 'LastUpdateDate', table 'Test.dbo.TransactionLog'; column does not allow nulls. INSERT fails.
The statement has been terminated.
[2] To correct your trigger (see Marc's comment) please read pages 192 - 199 from Alex Kuznetsov's book (Defensive Database Programming with SQL Server: Amazon, Red Gate - PDF).

Related

t-sql select count(*) to integer variable creates exception for can't be varchar type can't be converted to int?

I am not new to SQL technologies and have some experience with PL/SQL but T-SQL is really working way different than PL/SQL... I am building a database trigger that i am trying to calculate/sum two types of values and check how many times that they do exist in my table, then will simply implement a business logic over this calculation. All type declarations are done at the beginning:
USE [DB_KD_Benchmarking_SQL]
GO
/****** Object: Trigger [dbo].[InsertTrigger_int_ext_mix_2] Script Date: 12/2/2020 3:36:51 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[InsertTrigger_int_ext_mix_2] ON [dbo].[Ext_trial_2]
AFTER INSERT
AS DECLARE #Order_id_var_2 varchar(255),
#Country_var_2 varchar(50),
#Panel_brand_var_2 varchar(255),
#Int_ext_mix_var_2 char(10),
#Business_field_var_2 char(10),
#RC_Code_var_2 char(10),
#FC_Code_var_2 char(10),
#F_ident_number_var_2 varchar(255),
#Repeated_repairs_var_2 varchar(255),
#Same_order_ID_int int,
#Same_order_ID_ext int,
#Total_Same_order_ID int,
#Total_Same_order_ID_integer int;
DECLARE new_order CURSOR FOR
SELECT Country, Order_id, Panel_Brand, Internal_Ext_Mix, Business_Field, RC_Code, FC_Code, F_ident_Number, Repeated_Repairs FROM inserted
OPEN new_order;
FETCH NEXT FROM new_order INTO #Country_var_2, #Order_id_var_2, #Panel_brand_var_2, #Int_ext_mix_var_2, #Business_field_var_2, #RC_Code_var_2, #FC_Code_var_2, #F_ident_number_var_2, #Repeated_repairs_var_2;
WHILE ##FETCH_STATUS=0
BEGIN
select #Same_order_ID_int = count(*) from Ext_trial_2 where Order_id = #Order_id_var_2 and Internal_Ext_Mix = 'Int';
Print '#Same_order_ID_int ' + #Same_order_ID_int;
select #Same_order_ID_ext = count(*) from Ext_trial_2 where Order_id = #Order_id_var_2 and Internal_Ext_Mix = 'Ext';
Print '#Same_order_ID_ext ' + #Same_order_ID_ext;
set #Total_Same_order_ID = #Same_order_ID_int + #Same_order_ID_ext;
Print '#Total_Same_order_ID ' + #Total_Same_order_ID ;
IF #Total_Same_order_ID > 1 and (#Int_ext_mix_var_2 = 'Int' or #Int_ext_mix_var_2 = 'Ext')
BEGIN
Insert into Ext_trial_2 (Country, Order_id, Panel_Brand, Internal_Ext_Mix, Business_Field, RC_Code, FC_Code, F_ident_Number, Repeated_Repairs)
Values (#Country_var_2, #Order_id_var_2, #Panel_brand_var_2, 'Mix', #Business_field_var_2, #RC_Code_var_2, #FC_Code_var_2, #F_ident_number_var_2, #Repeated_repairs_var_2);
Delete from Ext_trial_2 where Order_id = #Order_id_var_2 and Internal_Ext_Mix = 'Int';
END
FETCH NEXT FROM new_order INTO #Country_var_2, #Order_id_var_2, #Panel_brand_var_2, #Int_ext_mix_var_2, #Business_field_var_2, #RC_Code_var_2, #FC_Code_var_2, #F_ident_number_var_2, #Repeated_repairs_var_2;
END
CLOSE new_order
DEALLOCATE new_order
As you might see that #Same_order_ID_int & #Same_order_ID_ext & #Total_Same_order_ID is defined as type int. Here i try to assign values to them :
select #Same_order_ID_int = count(*) from Ext_trial_2 where Order_id = #Order_id_var_2 and Internal_Ext_Mix = 'Int';
Print '#Same_order_ID_int ' + #Same_order_ID_int;
select #Same_order_ID_ext = count(*) from Ext_trial_2 where Order_id = #Order_id_var_2 and Internal_Ext_Mix = 'Ext';
Print '#Same_order_ID_ext ' + #Same_order_ID_ext;
set #Total_Same_order_ID = #Same_order_ID_int + #Same_order_ID_ext;
Print '#Total_Same_order_ID ' + #Total_Same_order_ID ;
This part of the trigger always fires an exception and creates an error and fires exception why i try to insert row for table which I have defined :
Conversion failed when converting the varchar value to datatype int for variable #Same_order_ID_int. When i change type of Same_order_ID_int to varchar(255), it works fine and return some results and assign to variable with string '1' or '0'.
Then this leads to an error on calculation method to sum these variables :
set #Total_Same_order_ID = #Same_order_ID_int + #Same_order_ID_ext;
returns string "10" or "01" or "11" which is definitely not a proper integer values. Can someone please explain me where could be problem on my calculation ?
I am using SQL Server 2016 build version 13.0.5.
Thanks for your answers in advance.
Here's your problem
declare #Same_order_ID_int int = 2
Print '#Same_order_ID_int ' + #Same_order_ID_int;
fails with
Msg 245, Level 16, State 1, Line 3
Conversion failed when converting the varchar value '#Same_order_ID_int ' to data type int.
Because you're trying to use the '+' operator with a varchar and an int. Instead use CONCAT which handles string conversion and null-to-empty string conversion for you.
Print concat('#Same_order_ID_int ', #Same_order_ID_int);

How to change the series of zeroes in a computed column

I am using the below T-SQL to create a table with a computed column which gives me IDs in 'BID(The Year)-0000'. The issue is that I would like to reset the series of zeros in the ID when the year is changed.For example, the last ID in the table is BID2017-0923 when the year is changed I want the series to be reset for example 'BID2018-0001'.
Here is the T-SQL which currently I am using.
CREATE TABLE Books
(
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
IDCreateDate DATETIME NOT NULL DEFAULT GETDATE(),
BookID AS ('BID' + LTRIM(YEAR(IDCreateDate)) + '-' + RIGHT('0000' + LTRIM(ID), 4)),
ISBN VARCHAR(32),
BookName NVARCHAR(50),
AuthorName NVARCHAR(50),
BLanguage VARCHAR(50),
StaId int,
StuId int,
CatNo int
);
UPDATE:
Furthermore, I would like the ID col to remembers its last id based on the year it has.For example, the last id in the column is BID2017-0920 when I change the year to 2010, it has to reset the number series for example BID2010-0001 but when I switch back the year to 2017, I don't want the series to be reset rather I want it to start from BID2017-0921.
Edit#1: I've updated my solution according to latest OP's comments
/*
CREATE TABLE dbo.CustomSequence (
Name VARCHAR(50) NOT NULL,
Prefix VARCHAR(10) NOT NULL,
LastValue VARCHAR(50) NULL, -- All generated values will have following pattern: PrefixYYYY-SeqvNumber
LastYear SMALLINT NOT NULL,
CONSTRAINT PK_CustomSequence_Name_LastYear PRIMARY KEY (Name, LastYear),
LastNumber SMALLINT NULL
);
GO
-- Config OrderSequence 2014
INSERT dbo.CustomSequence (Name, Prefix, LastValue, LastYear, LastNumber)
VALUES ('BookSequence', 'BID', NULL, 2014, 1)
GO
-- Config OrderSequence 2017
INSERT dbo.CustomSequence (Name, Prefix, LastValue, LastYear, LastNumber)
VALUES ('BookSequence', 'BID', NULL, 2017, 1)
GO
*/
-- Generating new seq
-- IN Parameters
DECLARE #CustomSequenceName VARCHAR(50) = 'BookSequence'
DECLARE #Year SMALLINT = 2017 --YEAR(GETDATE())
DECLARE #LenOfNumber TINYINT = 4
-- End of IN Parameters
-- OUT Parameters
DECLARE #GeneratedValue VARCHAR(50)
-- End of OUT Parameters
UPDATE s
SET LastNumber = IIF(LastValue IS NULL, LastNumber, LastNumber + 1) + IIF(LastNumber = REPLICATE('9', #LenOfNumber), 1/0, 0),
#GeneratedValue = LastValue = Prefix + LTRIM(#Year) + '-' + RIGHT(REPLICATE('0', #LenOfNumber) + LTRIM(IIF(LastValue IS NULL, LastNumber, LastNumber + 1)), #LenOfNumber)
FROM dbo.CustomSequence s
WHERE s.Name = #CustomSequenceName
AND s.LastYear = #Year
-- End of Generating new seq
SELECT #GeneratedValue
SELECT * FROM dbo.CustomSequence
--> BID2017-0001, BID2017-0002, BID2017-0003, ...
Note#1: This solution work with pessimistic concurrency control (default behavior of SQL Server Database Engine)
Note#2: If we are reaching 10000 following piece of code
... IIF(LastNumber = 9999, 1/0, 0) ...
will raise an exception
Msg 8134, Level 16, State 1, Line 30
Divide by zero error encountered.
The statement has been terminated.
Note#3: UPDATE statement could be encapsulated into a stored procedure with #CustomSequenceName VARCHAR(50) as input parameter and #GeneratedValue VARCHAR(50) OUTPUT as OUT[PUT] parameter.
Note#4: #LenOfNumber allows to customize length of sequential number (default is 4)
Edit#2:
UPDATE statement could be replaced with following combination of INSERT + UPDATE statements:
SET XACT_ABORT ON
BEGIN TRAN
IF NOT EXISTS(SELECT * FROM dbo.CustomSequence s WITH(HOLDLOCK) WHERE s.Name = #CustomSequenceName AND s.LastYear = #Year)
BEGIN
INSERT dbo.CustomSequence (Name, Prefix, LastValue, LastYear, LastNumber)
VALUES (#CustomSequenceName, #Prefix, NULL, #Year, 1)
END
UPDATE s
SET LastNumber = IIF(LastValue IS NULL, LastNumber, LastNumber + 1) + IIF(LastNumber = REPLICATE('9', #LenOfNumber), 1/0, 0),
#GeneratedValue = LastValue = Prefix + LTRIM(#Year) + '-' + RIGHT(REPLICATE('0', #LenOfNumber) + LTRIM(IIF(LastValue IS NULL, LastNumber, LastNumber + 1)), #LenOfNumber)
FROM dbo.CustomSequence s WITH(HOLDLOCK)
WHERE s.Name = #CustomSequenceName
AND s.LastYear = #Year
COMMIT

Queries with Dynamic Parameters - better ways?

I have the following stored procedure that is quite extensive because of the dynamic #Name parameter and the sub query.
Is there a better more efficient way to do this?
CREATE PROCEDURE [dbo].[spGetClientNameList]
#Name varchar(100)
AS
BEGIN
SET NOCOUNT ON;
SELECT
*
FROM
(
SELECT
ClientID,
FirstName + ' ' + LastName as Name
FROM
Client
) a
where a.Name like '%' + #Name + '%'
Shamelessly stealing from two recent articles by Aaron Bertrand:
Follow-up #1 on leading wildcard seeks - Aaron Bertrand
One way to get an index seek for a leading %wildcard - Aaron Bertrand
The jist is to create something that we can use that resembles a trigram (or trigraph) in PostgreSQL.
Aaron Bertrand also includes a disclaimer as follows:
"Before I start to show how my proposed solution would work, let me be absolutely clear that this solution should not be used in every single case where LIKE '%wildcard%' searches are slow. Because of the way we're going to "explode" the source data into fragments, it is likely limited in practicality to smaller strings, such as addresses or names, as opposed to larger strings, like product descriptions or session abstracts."
test setup: http://rextester.com/IIMT54026
Client table
create table dbo.Client (
ClientId int not null primary key clustered
, FirstName varchar(50) not null
, LastName varchar(50) not null
);
insert into dbo.Client (ClientId, FirstName, LastName) values
(1, 'James','')
, (2, 'Aaron','Bertrand')
go
Function used by Aaron Bertrand to explode string fragments (modified for input size):
create function dbo.CreateStringFragments(#input varchar(101))
returns table with schemabinding
as return
(
with x(x) as (
select 1 union all select x+1 from x where x < (len(#input))
)
select Fragment = substring(#input, x, len(#input)) from x
);
go
Table to store fragments for FirstName + ' ' + LastName:
create table dbo.Client_NameFragments (
ClientId int not null
, Fragment varchar(101) not null
, constraint fk_ClientsNameFragments_Client
foreign key(ClientId) references dbo.Client
on delete cascade
);
create clustered index s_cat on dbo.Client_NameFragments(Fragment, ClientId);
go
Loading the table with fragments:
insert into dbo.Client_NameFragments (ClientId, Fragment)
select c.ClientId, f.Fragment
from dbo.Client as c
cross apply dbo.CreateStringFragments(FirstName + ' ' + LastName) as f;
go
Creating trigger to maintain fragments:
create trigger dbo.Client_MaintainFragments
on dbo.Client
for insert, update as
begin
set nocount on;
delete f from dbo.Client_NameFragments as f
inner join deleted as d
on f.ClientId = d.ClientId;
insert dbo.Client_NameFragments(ClientId, Fragment)
select i.ClientId, fn.Fragment
from inserted as i
cross apply dbo.CreateStringFragments(i.FirstName + ' ' + i.LastName) as fn;
end
go
Quick trigger tests:
/* trigger tests --*/
insert into dbo.Client (ClientId, FirstName, LastName) values
(3, 'Sql', 'Zim')
update dbo.Client set LastName = 'unknown' where LastName = '';
delete dbo.Client where ClientId = 3;
--select * from dbo.Client_NameFragments order by ClientId, len(Fragment) desc
/* -- */
go
New Procedure:
create procedure [dbo].[Client_getNameList] #Name varchar(100) as
begin
set nocount on;
select
ClientId
, Name = FirstName + ' ' + LastName
from Client c
where exists (
select 1
from dbo.Client_NameFragments f
where f.ClientId = c.ClientId
and f.Fragment like #Name+'%'
)
end
go
exec [dbo].[Client_getNameList] #Name = 'On Bert'
returns:
+----------+----------------+
| ClientId | Name |
+----------+----------------+
| 2 | Aaron Bertrand |
+----------+----------------+
I guess search operation on Concatenated column wont take Indexes sometimes. I got situation like above and I replaced the Concatenated search with OR which gave me better performance most of the times.
Create Non Clustered Indexes on FirstName and LastName if not present.
Check the performance after modifying the above Procedure like below
CREATE PROCEDURE [dbo].[spGetClientNameList]
#Name varchar(100)
AS
BEGIN
SET NOCOUNT ON;
SELECT
ClientID,
FirstName + ' ' + LastName as Name
FROM
Client
WHERE FirstName LIKE '%' + #Name + '%'
OR LastName LIKE '%' + #Name + '%'
END
And do check execution plans to verify those Indexes are used or not.
The problem really comes down to having to compute the column (concat the first name and last name), that pretty much forces sql server into doing a full scan of the table to determine what is a match and what isn't. If you're not allowed to add indexes or alter the table, you'll have to change the query around (supply firstName and lastName separately). If you are, you could add a computed column and index that:
Create Table client (
ClientId INT NOT NULL PRIMARY KEY IDENTITY(1,1)
,FirstName VARCHAR(100)
,LastName VARCHAR(100)
,FullName AS FirstName + ' ' + LastName
)
Create index FullName ON Client(FullName)
This will at least speed your query up by doing index seeks instead of full table scans. Is it worth it? It's difficult to say without looking at how much data there is, etc.
where a.Name like '%' + #Name + '%'
This statement never can use index. In this situation it's beter to use Full Text Search
if you can restrict your like to
where a.Name like #Name + '%'
it will use index automaticaly. Moreover you can use REVERSE() function to index statement like :
where a.Name like '%' + #Name

Return all rows where at least one value in any of the columns is null

I have just completed the process of loading new tables with data. I'm currently trying to validate the data. The way I have designed my database there really shouldn't be any values anywhere that are NULL so i'm trying to find all rows with any NULL value.
Is there a quick and easy way to do this instead of writing a lengthy WHERE clause with OR statements checking each column?
UPDATE: A little more detail... NULL values are valid initially as sometimes the data is missing. It just helps me find out what data I need to hunt down elsewhere. Some of my tables have over 50 columns so writing out the whole WHERE clause is not convenient.
Write a query against Information_Schema.Columns (documentation) that outputs the SQL for your very long where clause.
Here's something to get you started:
select 'OR ([' + TABLE_NAME + '].[' + TABLE_SCHEMA + '].[' + COLUMN_NAME + '] IS NULL)'
from mydatabase.Information_Schema.Columns
order by TABLE_NAME, ORDINAL_POSITION
The short version answer, use SET CONCAT_NULL_YIELDS_NULL ON and bung the whole thing together as a string and check that for NULL (once). That way any null will propagate through to make the whole row comparison null.
Here's the silly sample code to demo the principal, up to you if you want to wrap that in an auto-generating schema script (to only check Nullable columns and do all the appropriate conversions). Efficient it ain't, but almost any way you cut it you will need to do a table scan anyway.
CREATE TABLE dbo.Example
(
PK INT PRIMARY KEY CLUSTERED IDENTITY(1,1),
A nchar(10) NULL,
B int NULL,
C nvarchar(50) NULL
) ON [PRIMARY]
GO
INSERT dbo.Example(A, B, C)
VALUES('Your Name', 1, 'Not blank'),
('My Name', 3, NULL),
('His Name', NULL, 'Not blank'),
(NULL, 5, 'It''s blank');
SET CONCAT_NULL_YIELDS_NULL ON
SELECT E.PK
FROM dbo.Example E
WHERE (E.A + CONVERT(VARCHAR(32), E.B) + E.C) IS NULL
SET CONCAT_NULL_YIELDS_NULL OFF
As mentioned in a comment, if you really expect columns to not be null, then put NOT NULL constraints on them. That said...
Here's a slightly different approach, using INFORMATION_SCHEMA:
DECLARE #sql NVARCHAR(max) = '';
SELECT #sql = #sql + 'UNION ALL SELECT ''' + cnull.TABLE_NAME + ''' as TableName, '''
+ cnull.COLUMN_NAME + ''' as NullColumnName, '''
+ pk.COLUMN_NAME + ''' as PkColumnName,' +
+ 'CAST(' + pk.COLUMN_NAME + ' AS VARCHAR(500)) as PkValue '
+ ' FROM ' + cnull.TABLE_SCHEMA + '.' + cnull.TABLE_NAME
+ ' WHERE ' +cnull.COLUMN_NAME + ' IS NULL '
FROM INFORMATION_SCHEMA.COLUMNS cnull
INNER JOIN (SELECT Col.Column_Name, col.TABLE_NAME, col.TABLE_SCHEMA
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS Tab
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE Col
ON Col.Constraint_Name = Tab.Constraint_Name AND Col.Table_Name = Tab.Table_Name
WHERE CONSTRAINT_TYPE = 'PRIMARY KEY') pk
ON pk.TABLE_NAME = cnull.TABLE_NAME AND cnull.TABLE_SCHEMA = pk.TABLE_SCHEMA
WHERE cnull.IS_NULLABLE = 'YES'
set #sql = SUBSTRING(#sql, 11, LEN(#sql)) -- remove the initial 'UNION ALL '
exec(#sql)
Rather a huge where clause, this will tell you the primary key on the table where any field in that table is null. Note that I'm CASTing all primary key values to avoid operand clashes if you have some that are int/varchar/uniqueidentifier etc. If you have a PK that doesn't fit into a VARCHAR(500) you probably have other problems....
This would probably need some tweaking if you have any tables with composite primary keys - as it is, I'm pretty sure it would just output separate rows for each member of the key instead of concatenating them, and wouldn't necessarily group them together the way you'd want.
One other thought would be to just SELECT * from ever table and save the output to a format (Excel, plain text csv) you can easily search for the string NULL.

Use master.sys.fn_varbintohexsubstring in computed column

In my sql server table, I want to add a computed column that is a hash of other columns in the same table. Below is my table structure.
Address:
AddressID(int , PK)
AddressLine1 (nvarchar)
AddressLine2 (nvarchar)
City (nvarchar)
State (nvarchar)
AddressHash(computed column)
Below is what I want to get in my computed column:
MASTER.SYS.FN_VARBINTOHEXSUBSTRING(0, HASHBYTES('SHA1',COALESCE(AddressLine1, N'') + COALESCE(AddressLine2, N'') + COALESCE(City, N'') + COALESCE(State, N'')), 1, 0)
If I right-click the table and go to design and enter the above for "Formula" under "Computed Column Specification", I get the following error:
- Unable to modify table.
A user-defined function name cannot be prefixed with a database name in this context.
So I thought I would use a user defined function to calculate the hash and map that udf to formula.
Below is the code that I am using to create UDF:
CREATE FUNCTION udfHashAddress
(
#pAddressLine1 nvarchar(50), #pAddressLine2 nvarchar(50), #pCity nvarchar(50), #pState nvarchar(50))
)
RETURNS nvarchar(max) -- not sure what the correct size would be
WITH SCHEMABINDING
AS
BEGIN
DECLARE #result nvarchar(max)
SELECT #result = MASTER.SYS.FN_VARBINTOHEXSUBSTRING(0, HASHBYTES('SHA1',COALESCE(#pAddressLine1, N'') + COALESCE(#pAddressLine2, N'') + COALESCE(#pCity, N'') + COALESCE(#pState, N'')), 1, 0)
RETURN #result
END
GO
But I get the following error with the above code:
*Cannot schema bind function 'udfHashAddress' because name 'MASTER.SYS.FN_VARBINTOHEXSUBSTRING' is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.*
When I removed the "MASTER" db prefix I got this error:
*Cannot schema bind function 'udfHashAddress' because it references system object 'SYS.FN_VARBINTOHEXSUBSTRING'.*
Am I missing something here? Would appreciate any assistance/pointers.
Since you're using SQL Server 2008, have you tried simply:
CONVERT(VARCHAR(MAX), HASHBYTES('SHA1','string'), 2);
This will return upper case instead of lower case letters, but you can fix that with LOWER() if it's important.
Here is an example with your actual schema (created in tempdb on purpose):
USE tempdb;
GO
CREATE TABLE dbo.[Address]
(
AddressID INT PRIMARY KEY,
AddressLine1 NVARCHAR(64),
AddressLine2 NVARCHAR(64),
City NVARCHAR(64),
[State] NVARCHAR(64),
AddressHash AS LOWER(CONVERT(VARCHAR(4000), HASHBYTES('SHA1',
COALESCE(AddressLine1, N'') + COALESCE(AddressLine2, N'')
+ COALESCE(City, N'') + COALESCE([State], N'')), 2))
--PERSISTED -- you could also persist it if desired
);
INSERT dbo.[Address]
VALUES(1, 'foo', 'bar', 'blat', 'splunge'),
(2, 'bar', 'foo', 'blag', 'splmger');
SELECT *, master.dbo.fn_varbintohexsubstring
(0,
HASHBYTES
(
'SHA1',
COALESCE(AddressLine1, N'') + COALESCE(AddressLine2, N'')
+ COALESCE(City, N'') + COALESCE([State], N'')
), 1, 0)
FROM dbo.[Address];
GO
DROP TABLE dbo.[Address];

Resources