unique key on two columns commutatively - sql-server

please suggest me how to apply unique key on two columns comparatively. i.e. suppose we have two columns FK_Col1 and FK_Col2 and if we insert 2 and 6 in both columns then we should not be able to insert again 2 and 6 or 6 and 2 in both columns.
Plaese suggest me how to achieve this.
Thanks in advance

Create a calculated column and a unique constraint on it. The trick is that we want the calculated column to have the same value for both (2),(6) and for (6),(2):
create table #t (a int, b int,
uq_col as (case when a>b then cast(a as varchar)+'|'+cast(b as varchar) else cast(b as varchar)+'|'+cast(a as varchar) end),
constraint uq_t__a_b unique(uq_col))

WHILE creating table
CREATE TABLE table1(
COLUMN1 INT NOT NULL,
COLUMN2 VARCHAR(50) NOT NULL,
CONSTRAINT unique_1 UNIQUE (column1, column2)
)
or
ALTER TABLE table1 ADD CONSTRAINT unique_1 UNIQUE(column1, column2)

Related

Is Identity Column hold the duplicate values?

I have the table with the identity column. This is looks like identity column is duplicated. What are all the possibilities that column hold the duplicate values.
Table structure looks like below,
create table Table1(ID INT identity, value varchar(10))
if the column has no unique constraint, it is possible to get duplicate values in it.
One way to do this is like this
SET IDENTITY_INSERT Table1 ON
INSERT INTO Table1 (ID, value)
VALUES (1, 'hello')
SET IDENTITY_INSERT Table1 OFF
Another way is to reseed the table, check the answer of Satheesh on how to do that.
If you dont want that, make this column primary key or create an unique constraint on that column.
how to make a unique index :
CREATE UNIQUE NONCLUSTERED INDEX idx_Table1_ID
ON dbo.Table1(ID)
WHERE ID IS NOT NULL;
The WHERE ID IS NOT NULL; is needed in your example because you allow null values in the ID column, which I do not recommend
I am reasonably sure your table should look like this
Create table Table1 (
ID int identity not null,
Value varchar(10),
constraint PK_Table1_ID primary key (ID)
)
Of course and Identity Column can have Duplicate values.
But since the values are auto-populated by the Table itself, there is no chance that the Duplicates can be created by the system.
But you can add Duplicate values using the INSERT INTO Statement. or if you RESEED the identity column without removing the Existing values the system itself will create Duplicates.
because of The identity column in Not having a UNIQUE constraint by default.
see the below Example
CREATE TABLE Temp
(
SeqNo INT IDENTITY(1,1),
MyStr VARCHAR(10)
)
INSERT INTO Temp
VALUES('A')
Result
SeqNo MyStr
1 A
SET IDENTITY_INSERT TEMP ON
INSERT INTO TEMP(SeqNo,MyStr)
VALUES(1,'B')
SET IDENTITY_INSERT TEMP OFF
Result
SeqNo MyStr
1 A
1 B
Inserted Couple more records
INSERT INTO TEMP
VALUES('C')
INSERT INTO TEMP
VALUES('D')
Result
SeqNo MyStr
1 A
1 B
2 C
3 D
Perform identity Reseed and insert a New value
DBCC CHECKIDENT ('TEMP', RESEED, 1)
INSERT INTO TEMP
VALUES('E')
Final result
SeqNo MyStr
1 A
1 B
2 C
3 D
2 E

SQL Server: check constraint if relationship exists, else insert

I am trying to create a constraint to validate if a relation exists
I have tried to create a procedure and then use it in check constraint. Apparently that does not seem to work.
These are my tables:
STOCKITEMS table:
StockItemId INT
StockItemName VARCHAR
ColorId INT
COLOR table:
ColorId INT
ColorName VARCHAR
This is my stored procedure:
CREATE PROCEDURE USP_ValidateColor
(#Color NVARCHAR(50))
AS
IF NOT EXISTS(SELECT ColorName FROM WareHouse.Colors WHERE ColorName = #Color)
BEGIN
DECLARE #Id INT
SET #Id = (SELECT TOP(1) ColorId + 1 FROM Warehouse.Colors
ORDER BY ColorId DESC)
INSERT INTO Warehouse.Colors
VALUES (#Id, #Color)
PRINT 'Does not exist';
END;
ELSE
PRINT 'Exists';
So if a user insert into the table stock items, I want a check that checks if the colorId already exists in the color table
If it does not, then insert that colorname into colors and. I was thinking about using a constraint check with my procedure, but can't fix the query.
Don't use an SP to check a constraint, use a foreign key:
CREATE TABLE Colour (ColourID int PRIMARY KEY, --This should really have a name
ColourName varchar(20));
CREATE TABLE StockItem (StockItemID int PRIMARY KEY, --This should really have a name too
StockItemName varchar(20),
ColourID int);
ALTER TABLE dbo.StockItem ADD CONSTRAINT Colour_FK FOREIGN KEY (ColourID) REFERENCES dbo.Colour(ColourID);
Then, if you try to insert something into the StockItem table, it'll fail unless the colour exists:
INSERT INTO dbo.Colour (ColourID,
ColourName)
VALUES (1,'Green'),(2,'Blue');
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES(1,'Paint',1); --works
GO
INSERT INTO dbo.StockItem (StockItemID,
StockItemName,
ColourID)
VALUES (1,'Wood Panels',3); --fails
GO
--clean up
DROP TABLE dbo.StockItem;
DROP TABLE dbo.Colour;
For checking, use a UNIQUE check constraint. If you want to insert a color only if it doesn't exist, use INSERT .. FROM .. WHERE to check for existence and insert in the same query.
The only "trick" is that FROM needs a table. This can be fixed using a table value constructor to create tables out of the values to insert. If the stored procedure accepts a table-valued parameter, there's no problem.
This example uses a LEFT JOIN to insert non-matching values :
declare #colors table (Color nvarchar(10) UNIQUE)
insert into #colors VALUES ('green')
select * from #colors;
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (1 row affected)
insert into #Colors (Color)
select new.Color
from (VALUES ('red'),
('green')) new(Color)
left outer join #Colors old on old.Color=new.Color
where old.Color is NULL
-- (0 rows affected)
select * from #colors;
-- green
-- red
The same using a subquery:
insert into #Colors (Color)
select new.Color
from
(VALUES ('red'),
('green')) new(Color)
where not exists (select 1
from #colors
where color=new.Color);
By using the UNIQUE constraint we ensure that duplicate entries can't be inserted

sql server 2005 create temporary table using xml parameter

I want to create a temporary table from a xml input parameter.
This is my XML:
<Offices>
<Group id="22807">
<Office>185901</Office>
<Office>185902</Office>
<Office>185944</Office>
</Group>
</Offices>
This is my SQL:
DECLARE #GroupsOfficeIDs xml
SET #GroupsOfficeIDs = '<Offices><Group id="22807"><Office>185901</Office><Office>185902</Office><Office>185944</Office></Group></Offices>'
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
INSERT INTO #GroupOfficeID VALUES (#GroupsOfficeIDs)
SELECT PK,
group.ref.value('#id', 'int') AS GroupID,
group.ref.value('(Office/text())[1]', 'varchar(20)') AS OfficeID
FROM #GroupOfficeID go cross apply go.IdXml.nodes('/Offices/Group') group(ref)
This returns 1 row:
PK GroupID OfficeID
1 22807 185901
I would like it to return the following:
PK GroupID OfficeID
1 22807 185901
2 22807 185902
3 22807 185944
Is it my XML that is wrong or my query?
Thanks!
UPDATE
I got a little bit further...
My query is now this:
DECLARE #GroupsOfficeIDs xml
SET #GroupsOfficeIDs = '<Offices><Group id="22807"><Office>185901</Office><Office>185902</Office><Office>185944</Office></Group></Offices>'
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
INSERT INTO #GroupOfficeID VALUES (#GroupsOfficeIDs)
SELECT PK,
group.ref.value('#id', 'int') AS GroupID,
office.ref.value('(Office/text())[1]', 'varchar(20)') AS OfficeID
FROM #GroupOfficeID go cross apply go.IdXml.nodes('/Offices/Group') group(ref)
cross apply go.IdXml.nodes('/Offices/Group/Office') as office(ref)
It produces this:
PK GroupID OfficeID
1 22807 185901
1 22807 185902
1 22807 185944
Why doesn't the primary key increment by 1?
You got the same value for PK because the temp table stores one XML data in one row with one PK, which means all data that come from the same XML source will have the same primary key :
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), IdXml xml)
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- here you associate one XML data to one identity id
You may want to alter temporary table structure to store one GroupID-OfficeID combination as one row instead :
CREATE TABLE #GroupOfficeID (PK int primary key identity(1,1), GroupID int, OfficeID int)
Then the query for insert would be like so (avoid using keyword like GO and GROUP for alias!) :
INSERT INTO #GroupOfficeID(GroupID,OfficeID)
SELECT
g.value('#id','int') GroupID,
o.value('.','int') OfficeID
FROM #GroupsOfficeIDs.nodes('/Offices/Group') grp(g)
CROSS APPLY g.nodes('Office') office(o)
Then SELECT * FROM #GroupOfficeID will produce the correct expected result :
PK GroupID OfficeID
1 22807 185901
2 22807 185902
3 22807 185944
SQL Fiddle Demo

Get inserted table identity value and update another table

I have two tables with foreign key constraint on TableB on TablesAs KeyA column. I was doing manual inserts till now as they were only few rows to be added. Now i need to do a bulk insert, so my question if i insert multiple rows in TableA how can i get all those identity values and insert them into TableB along with other column values. Please see the script below.
INSERT INTO Tablea
([KeyA]
,[Value] )
SELECT 4 ,'StateA'
UNION ALL
SELECT 5 ,'StateB'
UNION ALL
SELECT 6 ,'StateC'
INSERT INTO Tableb
([KeyB]
,[fKeyA] //Get value from the inserted row from TableA
,[Desc])
SELECT 1 ,4,'Value1'
UNION ALL
SELECT 2 ,5,'Value2'
UNION ALL
SELECT 3 ,6, 'Value3'
You can use the OUTPUT clause of INSERT to do this. Here is an example:
CREATE TABLE #temp (id [int] IDENTITY (1, 1) PRIMARY KEY CLUSTERED, Val int)
CREATE TABLE #new (id [int], val int)
INSERT INTO #temp (val) OUTPUT inserted.id, inserted.val INTO #new VALUES (5), (6), (7)
SELECT id, val FROM #new
DROP TABLE #new
DROP TABLE #temp
The result set returned includes the inserted IDENTITY values.
Scope identity sometimes returns incorrect value. See the use of OUTPUT in the workarounds section.

Split table and insert with identity link

I have 3 tables similar to the sctructure below
CREATE TABLE [dbo].[EmpBasic](
[EmpID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[Name] [varchar](50),
[Address] [varchar](50)
)
CREATE TABLE [dbo].[EmpProject](
[EmpID] [int] NOT NULL primary key, // referencing column with EmpBasic
[EmpProject] [varchar](50) )
CREATE TABLE [dbo].[EmpFull_Temp](
[ObjectID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[T1Name] [varchar](50) ,
[T1Address] [varchar](50) ,
[T1EmpProject] [varchar](50)
)
The EmpFull_Temp table has the records with a dummy object ID column... I want to populate the first 2 tables with the records in this table... But with EmpID as a reference between the first 2 tables.
I tried this in a stored procedure...
Create Table #IDSS (EmpID bigint, objID bigint)
Insert into EmpBasic
output Inserted.EmpID, EmpFull_Temp.ObjectID
into #IDSS
Select T1Name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from #IDSS as A, EmpFull_Temp as B
Where A.ObjID = B.ObjectID
But it says.. The multi-part identifier "EmpFull_Temp.ObjectID" could not be bound.
Could you please help me in achieving this...
Edit : There is no guarantee that [Name]+[Address] would be unique across [EmpBasic] Table
With your EmpProject join table, you probably don't want the primary key constraint on only the EmpID column
DECLARE #Count int
DECLARE #NextEmpID int
DECLARE #StartObjectID int
DECLARE #EndObjectID int
-- range of IDs to transfer (inclusive)
SET #StartObjectID = 1
SET #EndObjectID = 105
BEGIN TRAN
-- lock tables so IDENT_CURRENT is valid
SELECT #Count = COUNT(*) FROM [EmpBasic] WITH (TABLOCKX, HOLDLOCK)
SELECT #Count = COUNT(*) FROM [EmpProject] WITH (TABLOCKX, HOLDLOCK)
SELECT #NextEmpID = IDENT_CURRENT('EmpBasic')
SET IDENTITY_INSERT [EmpBasic] ON
INSERT [EmpBasic] ([EmpID], [Name], [Address])
SELECT #NextEmpID + ROW_NUMBER() OVER(ORDER BY ObjectID), [T1Name], [T1Address]
FROM [EmpFull_Temp]
WHERE [ObjectID] BETWEEN #StartObjectID AND #EndObjectID
SET IDENTITY_INSERT [EmpBasic] OFF
INSERT [EmpProject]([EmpID], [EmpProject])
SELECT #NextEmpID + ROW_NUMBER() OVER(ORDER BY ObjectID), [T1EmpProject]
FROM [EmpFull_Temp]
WHERE [ObjectID] BETWEEN #StartObjectID AND #EndObjectID
COMMIT TRAN
The solution to this problem depends on whether the "parent" table (i.e. the one with the IDENTITY column) has a natural key (i.e. one or more fields which, when combined, are guaranteed to be unique, other than the surrogate primary key).
For example, in this case, is the combinaton of Name and Address aways going to be unique?
If the answer is yes then you can simply insert into EmpBasic without bothering to output and store the generated IDs. You can then insert into EmpProject joining back on to EmpBasic using the natural key (e.g. name and address) to fnd the correct EmpID.
Insert into EmpBasic
Select T1Name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from EmpBasic as A, EmpFull_Temp as B
Where A.Name = B.Name And A.Address = B.Address
If the answer is no then there is no easy solution I know of - in SQL Server 2005 (I've no idea if this is any different in 2008), it's not possible to OUTPUT values that are not inserted. I've got around this issue in the past by using one of the other fields (e.g. Name) to temporarily store the original ID (in this case, ObjectID), use that to join when inserting the child records as described above and then gone back to update the parent records o remove/replace the temporary values. It's not nice but I've not found a better way.
Insert into EmpBasic
Select cast(ObjectID as varchar(50)) as name, T1Address from EmpFull_Temp
Where ObjectID < 106
Insert into EmpProject
Select A.EmpID, B.T1EmpProject from EmpBasic as A, EmpFull_Temp as B
Where A.Name = cast(B.ObjectID as varchar(50))
Update EmpBasic
Set Name = B.T1Name
from EmpBasic as A, EmpFull_Temp as B
Where A.Name = cast(B.ObjectID as varchar(50))
Please note: I've not tested the sample SQL given above but I hope it gives you an idea of how you might approach this.
Add an ObjectID column to the EmpBasic table to facilitate the data transfer then drop it when you're done. I'm assuming this is a one-time operation, I don't recommend adding and dropping a column if this is on-going
I have used the Stack Exchange Data Explorer to investigate alternative solutions. The only one with promise at the moment is shown here. It is effectively #ScotHauder's answer, except using a temporary table that has the ObjectID column and using IDENTITY_INSERT to move the generated EmpId values into EmpBasic.
If you have to do this multiple times you need to get the EmpBasic_Temp EmpId IDENTITY starting value to be Max(EmpBasic.EmpID)+1.

Resources