Unique constraint for varbinary column - sql

I have a varbinary column in SQL, and I want this column to be always unique. However, I see that SQL doesn't allow to create a unique constraint on a varbinary column.
Is there any workaround to ensure this uniqueness? Maybe by using some other type of constraint, or something else?
Thanks!

If the varbinary is reasonably short then you could create a computed column of the hex representation and put a unique constraint on that. Ref SQL Server converting varbinary to string for how to convert varbinary to hex string.
Edit1: As pointed out by #GiorgiNakeuri the limit for unique constraints is 900 bytes, so 450 bytes in hex.
Edit2: Alternatively, if you can tollerate a (very) small risk of failure then you could create a computed column with an MD5 hash of the varbinary value and put the unique constraint on that. See the docs for HASHBYTES.

I guess you have VARBINARY(MAX). The length of it is 2^31-1 bytes, but the maximum length of key should be 900 bytes. So you are actually limited with VARBINARY(900). If the size of VARBINARY <= 900, you can add unique index.
As a workaround you can add Trigger and rollback inserts, if there is already same values in table.

A simpler solution could be to manually maintain the uniqueness of the column by checking the existence of the value to be inserted or updated before inserting or updating the column.
Example:
DECLARE #Exists BIT = (
SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END
FROM [Schema].[Table]
WHERE [DecryptedColumn] = #NewValueToCheck
)
IF (#Exists = 0)
BEGIN
-- Insert or Update
END
ELSE
-- Return Error
BEGIN
END

You could make the column a primary key. Scripted test table shown below
/****** Object: Table [dbo].[Table_1] Script Date: 02/03/2015 12:19:22 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Table_1](
[test] [varbinary](50) NOT NULL,
[rf] [nchar](10) NULL,
CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED
(
[test] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

Related

Query with null-terminated strings

I'm trying to write a T-SQL script to align two copies of a database with the
tablediff utility exposed by SQL Server but I am facing a problem with all the rows of a text field inside a table.
The problem is that all the strings stored on that text field are null-terminated (there actually is a null character at the end of the string if I export them to a text file) and the INSERT and UPDATE queries generated by tablediff are all failing due to the fact that that null terminator truncates the query.
Is there a way to prevent the generated scripts from failing?
UPDATE
Creation query generated by MSSQL for the table I'm trying to align
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[TABTEST](
[Code] [varchar](50) NOT NULL,
[Source] [text] NULL,
CONSTRAINT [PrK_TABTEST] PRIMARY KEY CLUSTERED
(
[Code] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
Insert query generated by tablediff (keep in mind that the character you don't see at the end of the Source after the NOOP is an ANSI NULL character)
UPDATE [dbo].[TABTEST] SET [Source]=N'NOOP ' WHERE [Code] = N'TestCode'
The CHAR(0) can be nasty...
My suggestion is to cut the last character away, LEN() will return the lengt including this 0x0. Try it out:
DECLARE #s VARCHAR(100)='test' + CHAR(0)
SELECT #s + 'extension' AS Result1,
LEFT(#s,LEN(#s)-1) + 'extension' AS Result2;

Update / Insert records to a table with a MERGE Statement

I did this 3 years ago with DB2 but can't remember how.
All I want to do is Update/Insert a record into a table. Rather than test for its existence and changing my DML, I want to do this with a parameterized insert/update(merge) T-SQL statement. I believe the procedure compiler optimizer will make this the most efficient method.
USE [MY_DB]
GO
/****** Object: Table [dbo].[map_locations] Script Date: 10/11/2015 9:29:26 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[map_locations](
[loc_min_lat] [varchar](5) NOT NULL,
[loc_min_lng] [varchar](6) NOT NULL,
[loc_id] [int] NULL,
[center] [varchar](20) NOT NULL CONSTRAINT [DF__map_locs__call___5E4ADDA8] DEFAULT (''),
CONSTRAINT [PK__map_map_locs__79C80F94] PRIMARY KEY CLUSTERED
(
[map_locations_lat] ASC,
[map_locations_lng] ASC,
[center] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
Simply put, I want to be able to insert a record into the above table if it does not violate the PK but update it if the record (from a PK perspective) exists.
I have been researching and all the MS SQL examples are for two table merges. I am passing in a record via parameters.
I am working in Delphi XE10 (not that that should matter) and the database is MS SQL 2012.
Any help appreciate.
When you say "rather than", I assume you're using Sql Server 2008 or later and are wanting to use its Merge command (apologies for the earlier version of this answer).
Here's a sample TransactSql script (tested and working, but using my table structure) which assumes you know the PK in advance:
declare #id int
select #id = 1
merge table1 as dest
using (values (#id, 'name1'))
as source (id, name)
on dest.id = #id
when matched then
update
set name = source.name
when not matched then
insert ( id, name)
values ( source.id, source.name);
select * from table1
From a Delphi app, you'd want to write that as a parameterized query, or, better, a parameterized call to a stored proc on the server.
These days, no Delphi-tagged q about Sql seems complete without a mention of Sql-Injection, but using a parameterized query should minimise the risk of that.

why this insert statement cannot insert values in it's second iteration?

I have this reasonably sized stored procedure that accepts 3 collections two UDTT collection and one CSV collection.
I have passed the following values to test stored procedure. However the problems occurred in one small master table called EmpDesignations with 5 columns. There is a loop I used in the store procedure to insert values to EmpDesignations. The loop is required because the values are extracted from the CSV string. As expected it does iterate 2 times with two sets of values. The first iteration the data was inserted successfully but in the second iteration data was not inserted. I have checked weather the data is empty but those variables #tmpEmpID and #tmp contain data. So cannot figure out the problem
The EmpDesignations table definition is
EmpID PK, FK not null
DesigID PK, FK not null
IsValid int not null
UpdtDT datetime not null containts to getDate()
AuthID int not null  EmpID
here is the snapshot of the table columns and types
In the first iteration the passing values to the insert statement is shown in the watch window
As you can see, ##rowcount is 1 so worked in the first round!
In the following watch shows the passing values to the insert statement, this is in the second iteration:
But the ##rowcount is not 1 therefore the control rollbacks all insertions
Here is a video link of the debugging of the insert statement
here is the video of THE SECOND INSERT statement debugging
Table definition is correct, the number of values passed matches the number of input columns, and the variables are filled with values in both iterations, so what could be the problem???
here is the script that was generated by SQL server for the table EmpDesignations
USE [SMSV100]
GO
/****** Object: Table [dbo].[EmpDesignations] Script Date: 04/12/2014 08:50:23 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[EmpDesignations](
[EmpID] [int] NOT NULL,
[DesigID] [int] NOT NULL,
[IsValid] [int] NOT NULL,
[UpdtDT] [datetime] NOT NULL,
[AuthID] [int] NOT NULL,
CONSTRAINT [PK_EmpDesignations] PRIMARY KEY CLUSTERED
(
[EmpID] ASC,
[DesigID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[EmpDesignations] WITH CHECK ADD CONSTRAINT [FK_EmpDesignations_Designations] FOREIGN KEY([DesigID])
REFERENCES [dbo].[Designations] ([DesigID])
GO
ALTER TABLE [dbo].[EmpDesignations] CHECK CONSTRAINT [FK_EmpDesignations_Designations]
GO
ALTER TABLE [dbo].[EmpDesignations] WITH CHECK ADD CONSTRAINT [FK_EmpDesignations_Employees] FOREIGN KEY([EmpID])
REFERENCES [dbo].[Employees] ([EmpID])
GO
ALTER TABLE [dbo].[EmpDesignations] CHECK CONSTRAINT [FK_EmpDesignations_Employees]
GO
ALTER TABLE [dbo].[EmpDesignations] ADD CONSTRAINT [DF_EmpDesignations_UpdtDT] DEFAULT (getdate()) FOR [UpdtDT]
GO
thanks

Adding a uniqueidentifier column and adding the default to generate new guid

I have the following SQL command:
ALTER TABLE dbo.UserProfiles
ADD ChatId UniqueIdentifier NOT NULL,
UNIQUE(ChatId),
CONSTRAINT "ChatId_default" SET DEFAULT newid()
I want to be able to make this column unique, and I want it to be able to generate a new guid every time a row is added to the table. This column is not an IDENTITY column because I already have one. This is something separate. How would I go about adding this column to a table with users already in it.
see this sample:
create table test (mycol UniqueIdentifier NOT NULL default newid(), name varchar(100))
insert into test (name) values ('Roger Medeiros')
select * from test
for add a not null field on a populated table you need this.
alter table test add mycol2 UniqueIdentifier NOT NULL default newid() with values
CREATE UNIQUE NONCLUSTERED INDEX IX_test ON dbo.test
(
mycol
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Don't use newid() as default, instead use newsequentialid(). newid() creates a lot of fragmentation and that's bad for indexes.
As far as adding the new column to a table with existing data, simply do this:
ALTER TABLE your_table
ADD your_column UNIQUEIDENTIFIER DEFAULT newsequentialid() NOT null

Trigger to change autoincremented value

I have a simple table with tax rates
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TaxRates](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_TaxRates] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
If user deleted record I want to not change autoincrementer while next insert.
To have more clearance.
Now I have 3 records with id 0,1 and 2. When I delete row with Id 2 and some time later I add next tax rate I want to have records in this table like before 0,1,2.
There shouldn't be chance to have a gap like 0,1,2,4,6. It must be trigger.
Could you help with that?
You need to accept gaps or don't use IDENTITY
id should have no external meaning
You can't update IDENTITY values
IDENTITY columns will always have gaps
In this case you'd update the clustered Pk which will be expensive
What about foreign keys? you'd need a CASCADE
Contiguous numbers can be generated with ROW_NUMBER() at read time
Without IDENTITY (whether you load this table or another) won't be concurrency-safe under load
Trying to INSERT into a gap (by an INSTEAD OF trigger) won't be concurrency-safe under load
(Edit) History tables may have the deleted values anyway
An option, if the identity column has become something passed around in your organization is to duplicate that column into a non-identity column on the same table and you can modify those new id values at will while retaining the actual identity field.
turning identity_insert on and off can allow you to insert identity values.