SQL Constraints Question - sql

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.

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

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

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

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].

sql unique constraint on a 2 columns combination

How can you create a unique constraint on a combination of two values in two columns.
meaning
column1 column2
2 1
looking for the constraint to disallow
column1 column2
1 2
If your database allows expressions in an index you can do something like this (ANSI SQL):
CREATE UNIQUE INDEX on your_table (least(column1, column2)
, greatest(column1, column2));
Note this is a unique index not a unique constraint. The only difference for most DBMS is that you can't have a unique index as the target of a foreign key, but otherwise they serve the same purpose.
If your DBMS does not have least() or greatest() you can replace that using a CASE expression:
create unique index on your_table
(case column1 < column2 then column1 else column2 end,
case column2 > column1 then column2 else column1 end));
Looking at the documentation, found this for the ORACLE SGBD :
CREATE TABLE b(
b1 INT,
b2 INT,
CONSTRAINT bu1 UNIQUE (b1, b2)
USING INDEX (create unique index bi on b(b1, b2)),
CONSTRAINT bu2 UNIQUE (b2, b1) USING INDEX bi);
Chapter "Specifying the Index Associated with a Constraint" on the page ORACLE documentation.
Hop this help.
A unique constraint on 2 columns only prevents those exact 2 values being inserted (switching them is allowed):
So you need A TRIGGER like this (ORACLE):
CREATE TRIGGER trig1
BEFORE INSERT ON TAB
REFERENCING NEW AS NEW
FOR EACH ROW
DECLARE
FOUND NUMBER;
BEGIN
SELECT COUNT(1) into FOUND FROM TAB WHERE
(COLUMN1=:NEW.column2 AND COLUMN2=:NEW.column1)
OR (COLUMN1=:NEW.column1 AND COLUMN2=:NEW.column2);
IF FOUND>0 THEN
raise_application_error (-20001,'INSERT not allowed');
END IF;
END trig1;
Warning: syntax not checked.

Create custom "auto-increment" Compound Primary Key?

I have a set of parent-child tables (1 to many relationships). I'm building the tables, and have some doubts about the use of PKs and auto-increment.
Parent table has an autonumber PK (is used for storing sales ticket header). One record here means on ticket.
Child table is used for storing ticket details. One record here is one line item in the ticket (e.g. coke, mars bar, etc)
I understand that PK for child table should have 2 fields:
Parent tables's PK
A number that makes the line item unique within this ticket
If I use IDENTITY, it will not "restart" after parent's PK changes.
I'll show it with an example:
A) What SQL does
Parent table
Col1 Col2
1 1000
2 2543
3 3454
Note: Col1 is IDENTITY
Child Table
Col1 Col2 Col3
1 1 Coke
1 2 Mars Bar
2 3 Sprite
3 4 Coke
3 5 Sprite
3 6 Mars Bar
Note: Col1 is taken from Parent Table; Col2 is IDENTITY
B) What I want to achieve
Parent table is the same as above
Child Table
Col1 Col2 Col3
1 1 Coke
1 2 Mars Bar
2 1 Sprite
3 1 Coke
3 2 Sprite
3 3 Mars Bar
Note: Col1 is taken from Parent Table; Col2 resets after change in Col1; Col1 composed with Col2 are unique.
Does SQL Server implement this use of keys? Or should I need to code it?
Just as an example:
create table dbo.tOrders (
OrderID int not null identity primary key,
CustomerID int not null
);
create table dbo.tOrderPos (
OrderID int not null foreign key references dbo.tOrders,
OrderPosNo int null,
ProductID int null
);
create clustered index ciOrderPos on dbo.tOrderPos
(OrderID, OrderPosNo);
go
create trigger dbo.trInsertOrderPos on dbo.tOrderPos for insert
as begin
update opo
set OrderPosNo = isnull(opo2.MaxOrderPosNo,0) + opo.RowNo
from (select OrderID, OrderPosNo,
RowNo = row_number() over (partition by OrderID order by (select 1))
from dbo.tOrderPos opo
where OrderPosNo is null) opo
cross apply
(select MaxOrderPosNo = max(opo2.OrderPosNo)
from dbo.tOrderPos opo2
where opo2.OrderID = opo.OrderID) opo2
where exists (select * from inserted i where i.OrderID = opo.OrderID);
end;
go
declare #OrderID1 int;
declare #OrderID2 int;
insert into dbo.tOrders (CustomerID) values (11);
set #OrderID1 = scope_identity();
insert into dbo.tOrderPos (OrderID, ProductID)
values (#OrderID1, 1), (#OrderID1, 2), (#OrderID1, 3);
insert into dbo.tOrders (CustomerID) values (12);
set #OrderID2 = scope_identity();
insert into dbo.tOrderPos (OrderID, ProductID)
values (#OrderID2, 4), (#OrderID2, 5);
insert into dbo.tOrderPos (OrderID, ProductID)
values (#OrderID1, 6);
select * from dbo.tOrderPos;
go
drop trigger dbo.trInsertOrderPos;
drop table dbo.tOrderPos;
drop table dbo.tOrders;
go
The difficulty has been to allow multiple inserts and delayed inserts.
HTH
Another option is using an instead-of-trigger:
create trigger dbo.trInsertOrderPos on dbo.tOrderPos instead of insert
as begin
insert into dbo.tOrderPos
(OrderID, OrderPosNo, ProductID)
select OrderID,
OrderPosNo =
isnull( (select max(opo.OrderPosNo)
from dbo.tOrderPos opo
where opo.OrderID = i.OrderID), 0) +
row_number() over (partition by OrderID order by (select 1)),
ProductID
from inserted i;
end;
Unfortunately it doesn't seem to be possible to set the OrderPosNo "not null" because multiple inserts would lead to a duplicate key. Therefor I couldn't use a primary key and used a clustered index instead.
You don't have a one-to-many relationship.
You have a many-to-many relationship.
A parent can have many items.
A coke can belong to more than one parent.
You want three tables. The in-between table is sometimes called a junction table.
http://en.wikipedia.org/wiki/Junction_table
Note: In the wiki article they only show two columns in the junction table, I believe a best practice is for that table to also have a unique auto-incrementing field.
Note: The two joining fields are usually made a unique index.
You will have to code the logic for this yourself. You might make the task easier by implementing it through triggers, and using window functions (row_number() over (partition by parent_id order by ...).
You can also let the primary key be simply an identity column (the parent_id doesn't have to be part of the PK), and have a "Sequence_Num" column to keep track of the int that you want to reset with each parent_id. You can even do this and still set a clustered index on the parent_id / sequence_num cols.
IMHO the 2nd option is better because it allows more flexibility without any major drawback. It also makes the window function easier to write because you can order by the surrogate key (the identity column) to preserve the insert order when regenerating the sequence_num's. In both cases you have to manage the sequencing of your "sequenec_num" column yourself.