Looking for suggestions on what table constraints to have to achieve uniqueness - sql

Expecting data like below in a sql-server table:
A resource with id1 will entries for different versions and can also have different names for different versions.
But Name cannot be shared among resources. Once id1 use NameX, other resource should not be able to use same name.
Please suggest sql-table constraints I can define to achieve this:
Id Name Version
------------------
id1 Name1 1
id1 Name1 2
id1 NameA 3
id1 NameX 4
id2 Name2 1
id2 NameX 2 --invalid record, NameX is already used for id1

You can use an indexed view with a couple of unique indexes to ensure that each name only appears once per id value in the view and then to make the complete set of names unique:
create table dbo.Ix (ID varchar(20) not null, Name varchar(20) not null,
Version int not null)
go
create view dbo.DRI_Ix_Unique_Names
with schemabinding
as
select
Id,Name,COUNT_BIG(*) as Cnt
from
dbo.Ix
group by
ID,Name
go
create unique clustered index IX_DRI_IX_Unique_Names on dbo.DRI_Ix_Unique_Names (Id,Name)
go
create unique nonclustered index IX_DRI_IX_Unique_Names_Only on
dbo.DRI_Ix_Unique_Names(Name)
go
insert into dbo.Ix(ID,Name,Version) values
('id1','Name1',1)
go
insert into dbo.Ix(ID,Name,Version) values
('id1','Name1',2)
go
insert into dbo.Ix(ID,Name,Version) values
('id1','NameA',3)
go
insert into dbo.Ix(ID,Name,Version) values
('id1','NameX',4)
go
insert into dbo.Ix(ID,Name,Version) values
('id2','Name2',1)
go
insert into dbo.Ix(ID,Name,Version) values
('id2','NameX',2)
This results in five successful inserts followed by an error because the final insert violates the nonclustered unique index.
I'm not sure how the version column factors into your requirements and am not using it in any of the constraints.

create a trigger that checks the existence of the values before inserting a new record and throw an error if the record exists
like this
CREATE TRIGGER ti_CheckRecord
on YourTable before insert
begin
if exists(select 1 from inserted where exists(select 1 from yourtable where name = inserted.name and id <> inserted.id))
begin
--write your error code here
end
end

Related

Auto-increment primary keys in SQL

I need help with the insert statements for a plethora of tables in our DB.
New to SQL - just basic understanding
Summary:
Table1
Col1 Col2 Col3
1 value1 value1
2 value2 value2
3 value3 value3
Table2
Col1 Col2 Col3
4 value1 value1
5 value2 value2
6 value3 value3
Multiple tables use the same sequence of auto-generated primary keys when user creates a static data record from the GUI.
However, creating a script to upload static data from one environment to the other is something I'm looking for.
Example from one of the tables:
Insert into RULE (PK_RULE,NAME,RULEID,DESCRIPTION)
values
(4484319,'TESTRULE',14,'TEST RULE DESCRIPTION')
How do I design my insert statement so that it reads the last value from the PK column (4484319 here) and auto inserts 4484320 without explicitly mentioning the same?
Note: Our DB has hundreds and thousands of records.
I think there's something similar to (SELECT MAX(ID) + 1 FROM MyTable) which could potentially solve my problem but I don't know how to use it.
Multiple tables use the same sequence of auto-generated primary keys when user creates a static data record from the GUI.
Generally, multiple tables sharing a single sequence of primary keys is a poor design choice. Primary keys only need to be unique per table. If they need to be unique globally there are better options such as UUID primary keys.
Instead, one gives each table their own independent sequence of primary keys. In MySQL it's id bigint auto_increment primary key. In Postgres you'd use bigserial. In Oracle 12c it's number generated as identity.
create table users (
id number generated as identity,
name text not null
);
create table things (
id number generated as identity,
description text not null
);
Then you insert into each, leaving off the id, or setting it null. The database will fill it in from each sequence.
insert into users (name) values ('Yarrow Hock'); -- id 1
insert into users (id, name) values (null, 'Reaneu Keeves'); -- id 2
insert into things (description) values ('Some thing'); -- id 1
insert into things (id, description) values (null, 'Shiny stuff'); -- id 2
If your schema is not set up with auto incrementing, sequenced primary keys, you can alter the schema to use them. Just be sure to set each sequence to the maximum ID + 1. This is by far the most sane option in the long run.
If you really must draw from a single source for all primary keys, create a sequence and use that.
create sequence master_seq
start with ...
Then get the next key with nextval.
insert into rule (pk_rule, name, ruleid, description)
values (master_seq.nextval, 'TESTRULE', 14, 'TEST RULE DESCRIPTION')
Such a sequence goes up to 1,000,000,000,000,000,000,000,000,000 which should be plenty.
The INSERT and UPDATE statements in Oracle have a ...RETURNING...INTO... clause on them which can be used to return just-inserted values. When combined with a trigger-and-sequence generated primary key (Oracle 11 and earlier) or an identity column (Oracle 12 and up) this lets you get back the most-recently-inserted/updated value.
For example, let's say that you have a table TABLE1 defined as
CREATE TABLE TABLE1 (ID1 NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
COL2 NUMBER,
COL3 VARCHAR2(20));
You then define a function which inserts data into TABLE1 and returns the new ID value:
CREATE OR REPLACE FUNCTION INSERT_TABLE1(pCOL2 NUMBER, vCOL3 VARCHAR2)
RETURNS NUMBER
AS
nID NUMBER;
BEGIN
INSERT INTO TABLE1(COL2, COL3) VALUES (pCOL2, vCOL3)
RETURNING ID1 INTO nID;
RETURN nID;
END INSERT_TABLE1;
which gives you an easy way to insert data into TABLE1 and get the new ID value back.
dbfiddle here

SQL Server: Unique Index on single values of two columns (!!! Not Combination)

I have a table for teams where each team has two codes. A code for teammembers and a code for the teamleader.
TeamId Name MemberCode LeaderCode
--------------------------------------------
1 Team1 CodeXY CodeXYZ
2 Team2 CodeAB CodeBC
...
There are two unique indexes, one on MemberCode and one on LeaderCode securing that MemberCodes and LeaderCodes are unique.
But how can I define the not only MemberCodes itself are unqiue, but MemberCodes and LeaderCodes?
No MemberCode should be a LeaderCode.
Someone got an idea?
P.S.: A unique index on the two columns like Create Unique index UIDX_12 On tbl (MemberCode, LeaderCode) is no option!
With this data structure, I think you would have to have a trigger.
You can reformat the data, so you have one table and (at least) three columns:
TeamId
Code
CodeType
Then you can add constraints:
codetype is only 'member' or 'leader'
code is unique
teamid is in the teamid table
teamid/codetype is unique
This will allow you to store exactly one of each of these values for each team (assuming that the values are not NULL).
In a create table statement, this might look something like:
create table . . .
check codetype in ('member', 'leader'),
unique(code),
teamid references teams(teamid),
unique (teamid, codetype)
. . .
You can enforce this constraint with an indexed view. Something like:
create table dbo.MColumnUnique (
MemberName int not null,
LeaderName int not null
)
go
create table dbo.Two (ID int not null primary key,constraint CK_Two_ID CHECK (ID in (1,2)))
go
insert into dbo.Two(ID) values (1),(2)
go
create view dbo.MColumnUnique_Enforcer (Name)
with schemabinding
as
select
CASE WHEN ID = 1 THEN MemberName ELSE LeaderName END
from
dbo.MColumnUnique
cross join
dbo.Two
go
create unique clustered index IX_MColumnUnique_Enforcer on dbo.MColumnUnique_Enforcer (Name)
go
insert into dbo.MColumnUnique (MemberName,LeaderName) values (1,2),(3,4) --Works
go
insert into dbo.MColumnUnique (MemberName,LeaderName) values (4,5) --Fails
go
insert into dbo.MColumnUnique (MemberName,LeaderName) values (6,6) --Fails
Where hopefully you can see the parallels between my above structure and your tables.
dbo.Two is just a generally helpful helper table that contains exactly two rows, and is used to perform a limited unpivot on the data into a single column.
You could do it with a trigger, but I would use a CHECK CONSTRAINT.
Create a function that takes a varchar parameter (or whatever the datatype you use for MemberCode and LeaderCode), and returns a bit: 0 if there is no LeaderCode or MemberCode that matches the parameter value, or 1 if there is a match.
Then put a check constraint on the table that specifies:
MemberCode <> LeaderCode AND
YourFunction(MemberCode) = 0 AND
YourFunction(LeaderCode) = 0
EDIT based on Damien's comment:
To prevent the function from including the row you just added, you need to also pass the [code] column (which you say is UNIQUE), and not count the row with that value for [code].

How to add data to two tables linked via a foreign key?

If I were to have 2 tables, call them TableA and TableB. TableB contains a foreign key which refers to TableA. I now need to add data to both TableA and TableB for a given scenario. To do this I first have to insert data in TableA then find and retrieve TableA's last inserted primary key and use it as the foreign key value in TableB. I then insert values in TableB. This seems lika a bit to much of work just to insert 1 set of data. How else can I achieve this? If possible please provide me with SQL statements for SQL Server 2005.
That sounds about right. Note that you can use SCOPE_IDENTITY() on a per-row basis, or you can do set-based operations if you use the INSERT/OUTPUT syntax, and then join the the set of output from the first insert - for example, here we only have 1 INSERT (each) into the "real" tables:
/*DROP TABLE STAGE_A
DROP TABLE STAGE_B
DROP TABLE B
DROP TABLE A*/
SET NOCOUNT ON
CREATE TABLE STAGE_A (
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE STAGE_B (
CustomerKey varchar(10),
OrderNumber varchar(100))
CREATE TABLE A (
Id int NOT NULL IDENTITY(51,1) PRIMARY KEY,
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE B (
Id int NOT NULL IDENTITY(1123,1) PRIMARY KEY,
CustomerId int,
OrderNumber varchar(100))
ALTER TABLE B ADD FOREIGN KEY (CustomerId) REFERENCES A(Id);
INSERT STAGE_A VALUES ('foo', 'Foo Corp')
INSERT STAGE_A VALUES ('bar', 'Bar Industries')
INSERT STAGE_B VALUES ('foo', '12345')
INSERT STAGE_B VALUES ('foo', '23456')
INSERT STAGE_B VALUES ('bar', '34567')
DECLARE #CustMap TABLE (CustomerKey varchar(10), Id int NOT NULL)
INSERT A (CustomerKey, Name)
OUTPUT INSERTED.CustomerKey,INSERTED.Id INTO #CustMap
SELECT CustomerKey, Name
FROM STAGE_A
INSERT B (CustomerId, OrderNumber)
SELECT map.Id, b.OrderNumber
FROM STAGE_B b
INNER JOIN #CustMap map ON map.CustomerKey = b.CustomerKey
SELECT * FROM A
SELECT * FROM B
If you work directly with SQL you have the right solution.
In case you're performing the insert from code, you may have higher level structures that help you achieve this (LINQ, Django Models, etc).
If you are going to do this in direct SQL, I suggest creating a stored procedure that takes all of the data as parameters, then performs the insert/select identity/insert steps inside a transaction. Even though the process is still the same as your manual inserts, using the stored procedure will allow you to more easily use it from your code. As #Rax mentions, you may also be able to use an ORM to get similar functionality.

conditional unique constraint

I have a situation where i need to enforce a unique constraint on a set of columns, but only for one value of a column.
So for example I have a table like Table(ID, Name, RecordStatus).
RecordStatus can only have a value 1 or 2 (active or deleted), and I want to create a unique constraint on (ID, RecordStatus) only when RecordStatus = 1, since I don't care if there are multiple deleted records with the same ID.
Apart from writing triggers, can I do that?
I am using SQL Server 2005.
Behold, the filtered index. From the documentation (emphasis mine):
A filtered index is an optimized nonclustered index especially suited to cover queries that select from a well-defined subset of data. It uses a filter predicate to index a portion of rows in the table. A well-designed filtered index can improve query performance as well as reduce index maintenance and storage costs compared with full-table indexes.
And here's an example combining a unique index with a filter predicate:
create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;
This essentially enforces uniqueness of ID when RecordStatus is 1.
Following the creation of that index, a uniqueness violation will raise an arror:
Msg 2601, Level 14, State 1, Line 13
Cannot insert duplicate key row in object 'dbo.MyTable' with unique index 'MyIndex'. The duplicate key value is (9999).
Note: the filtered index was introduced in SQL Server 2008. For earlier versions of SQL Server, please see this answer.
Add a check constraint like this. The difference is, you'll return false if Status = 1 and Count > 0.
http://msdn.microsoft.com/en-us/library/ms188258.aspx
CREATE TABLE CheckConstraint
(
Id TINYINT,
Name VARCHAR(50),
RecordStatus TINYINT
)
GO
CREATE FUNCTION CheckActiveCount(
#Id INT
) RETURNS INT AS BEGIN
DECLARE #ret INT;
SELECT #ret = COUNT(*) FROM CheckConstraint WHERE Id = #Id AND RecordStatus = 1;
RETURN #ret;
END;
GO
ALTER TABLE CheckConstraint
ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
SELECT * FROM CheckConstraint;
-- Id Name RecordStatus
-- ---- ------------ ------------
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 2
-- 1 No Problems 1
-- 2 Oh no! 1
-- 2 Oh no! 2
ALTER TABLE CheckConstraint
DROP CONSTRAINT CheckActiveCountConstraint;
DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;
You could move the deleted records to a table that lacks the constraint, and perhaps use a view with UNION of the two tables to preserve the appearance of a single table.
You can do this in a really hacky way...
Create an schemabound view on your table.
CREATE VIEW Whatever
SELECT * FROM Table
WHERE RecordStatus = 1
Now create a unique constraint on the view with the fields you want.
One note about schemabound views though, if you change the underlying tables you will have to recreate the view. Plenty of gotchas because of that.
For those still searching for a solution, I came accross a nice answer, to a similar question and I think this can be still useful for many. While moving deleted records to another table may be a better solution, for those who don't want to move the record can use the idea in the linked answer which is as follows.
Set deleted=0 when the record is available/active.
Set deleted=<row_id or some other unique value> when marking the row
as deleted.
If you can't use NULL as a RecordStatus as Bill's suggested, you could combine his idea with a function-based index. Create a function that returns NULL if the RecordStatus is not one of the values you want to consider in your constraint (and the RecordStatus otherwise) and create an index over that.
That'll have the advantage that you don't have to explicitly examine other rows in the table in your constraint, which could cause you performance issues.
I should say I don't know SQL server at all, but I have successfully used this approach in Oracle.
Because, you are going to allow duplicates, a unique constraint will not work. You can create a check constraint for RecordStatus column and a stored procedure for INSERT that checks the existing active records before inserting duplicate IDs.

SQL Constraints Question

In Sql Server 2005, I have a table with two integer columns, call them Id1 and Id2.
I need them to be unique with in the table (easy enough with a unique index that spans both columns). I also need them to be unique in the table if the values are transposed between the two columns.
For example, SELECT * FROM MyTable returns
Id1 Id2
---------
2 4
5 8
7 2
4 2 <--- values transposed from the first row
How do I make a constraint that would prevent the last row from being entered into the table because they are the transposed values from the first row?
Create a check constraint that is bound to a user defined function that performs a select on the table to check for the transposed value.
Create table mytable(id1 int, id2 int)
go
create Function dbo.fx_Transposed(#id1 int, #id2 int)
returns bit as
Begin
Declare #Ret bit
Set #ret = 0
if exists(Select 1 from MyTable
Where id2 = #id1 and id1 = #id2)
Set #ret = 1
Return #ret
End
GO
Alter table mytable add
CONSTRAINT [CHK_TRANSPOSE] CHECK
(([dbo].[fx_Transposed]([ID1],[ID2])=(0)))
GO
Insert into mytable (id1, id2) values (1,2)
Insert into mytable (id1, id2) values (2,1)
Does the order between Id1 and Id2 have any significance? If not and this is a large table it may be more performent to enforce Id1 < Id2 in addition to your unique index. This would impact any process inputing records so it may not be feasible.
I would create a trigger that executed on insert and update would verify using a select statement that the values were unique in the two columns when transposed and when not transposed. This would allow you to reject any changes to the table that would break your rules for uniqueness at the point of the change and you could remove the unique index because it only enforces part of the requirement.