Solve primary key violation in SSMS - sql

While trying to insert some items into a table I get the following error:
Msg 2627, Level 14, State 1, Procedure dba_create_fake_orders, Line 95 [Batch Start Line 2]
Violation of PRIMARY KEY constraint 'PK_OrderItem_1'. Cannot insert duplicate key in object 'dbo.OrderItem'. The duplicate key value is (10000, 100009).
Here's how my tables are set up and the following code is just a segment of the code used to insert those items to the table:
Table Set UP
INSERT INTO Orders(order_id, customer_id, sub_total, tax_total,
grand_total, date_created)
SELECT
#order_id, #customer_id, 0, 0,
0, #date_created
INSERT INTO OrderItem (order_id, quantity, game_id, price_id)
SELECT
#order_id, #quantity, game_id, price_id
FROM
GameVersion
WHERE
game_id = #game_id
#order and #customer_id are taken from a flat customer table and are chosen at random. The OrderItem table is empty always so I'm not sure why it's saying that there's a duplication error. The only tables that are populate is Orders and GameVersion.
GameVersion is populated way before and Orders is done just before OrderItem. I'm assuming the issue is that since I have to keys then it might be complaining since they might be the same number but I'm still not sure.

If the following query returns more than one row then it means your dataset has duplicate value in the column set you are using as PK in OrderItem.
select count(*), game_id from GameVersion GROUP BY game_id having count(*)>1

Related

How do I fix my trigger query so that the multi-part identifiers can be bound?

I'm working on a trigger which will allow me to store all the previous data inside specified columns on my Customer table into a specified audit-table once those columns are updated. The fields I'm taking from the customer table and storing are specific, as there are multiple fields on the customers table and I am only trying to store the (CustomerID, CustomerAddress and CustomerPostcode)
Here is the fields in the Customer table:
[CustomerID]
[CustomerName]
[CustomerAddress]
[CustomerPostcode]
[CustomerTelephone]
[CardNumber]
[CountyID]
I am only trying to take [CustomerID], [CustomerAddress] and [CustomerPostcode]
In order to store these specific fields I set up an audit table to the best of my ability which will store those fields, but also display auto-generated fields based on the trigger.
Here is the Audit-Table I set up:
CREATE TABLE Customer_Audit
(
Cust_UpdateID int IDENTITY (1,1),
Cust_User char (8),
Cust_Update_Date date,
CustomerID int,
CustomerAddresss nvarchar (255),
CustomerPostCode nvarchar (255),
CONSTRAINT [pk_Cust_UpdateID] PRIMARY KEY (Cust_UpdateID)
)
Here is the trigger query I've set up:
CREATE TRIGGER Customer_Update_Trigger ON tblCustomer
AFTER UPDATE
AS
BEGIN
INSERT INTO Customer_Audit (Cust_User, Cust_Update_Date, Cust_ID, CustomerAddresss, CustomerPostCode)
SELECT
CURRENT_USER,
GETDATE(),
d.CustomerID,
d.CustomerAddress,
d.CustomerPostcode
FROM deleted
END
In order to store the data I'm trying to take the fields from the deleted table but every time I do it I keep getting the following error messages:
Msg 4104, Level 16, State 1, Procedure Customer_Update_Trigger, Line 11
The multi-part identifier "d.CustomerID" could not be bound.
Msg 4104, Level 16, State 1, Procedure Customer_Update_Trigger, Line 12
The multi-part identifier "d.CustomerAddress" could not be bound.
Msg 4104, Level 16, State 1, Procedure Customer_Update_Trigger, Line 13
The multi-part identifier "d.CustomerPostcode" could not be bound
I don't know what it is I'm doing wrong, I know the field names in the deleted table match the field names in the customer table, but it still refused to process.
you are missing an alias for deleted, try adding FROM deleted as d
CREATE TRIGGER Customer_Update_Trigger ON tblCustomer
AFTER UPDATE AS
BEGIN;
INSERT INTO Customer_Audit (
Cust_User
, Cust_Update_Date
, Cust_ID
, CustomerAddresss
, CustomerPostCode
)
SELECT
CURRENT_USER
, GETDATE()
, d.CustomerID
, d.CustomerAddress
, d.CustomerPostcode
FROM deleted as d;
END;

sqlite - how to insert a record using a subselect

I have the following table:
CREATE TABLE group_members(id integer primary key AUTOINCREMENT, group_id integer, member_id integer, FOREIGN KEY (member_id) REFERENCES users(id));
I'm trying to insert a record into the group_members table by selecting the value for group_id from the user table, and then passing in a value for the member_id.
insert into group_members (group_id, member_id)
values (select id from users where code ='12345' and location='multiple', 281);
Where 281 is the member id I'm trying to pass in.
But I'm getting the following error message:
Error: near "select": syntax error
Can you point me in the right direction?
Subqueries must be written in parentheses. The parentheses in your queries already belong to the VALUES clause, so you need another pair:
INSERT INTO group_members (group_id, member_id)
VALUES ((SELECT id FROM users WHERE code ='12345' AND location='multiple'), 281);
Alternatively, use the other form of the INSERT statement that uses a query instead of the VALUES clause:
INSERT INTO group_members (group_id, member_id)
SELECT id, 281 FROM users WHERE code ='12345' AND location='multiple';

SQL Server, self-referential data, how do I add a constraint for this

Imagine I have the following structure:
DECLARE #Products TABLE (
MemberId INT,
ProductId INT,
GlobalProductId INT,
PRIMARY KEY (MemberId, ProductId));
INSERT INTO #Products VALUES (1, 1, NULL);--this is my "global product"
INSERT INTO #Products VALUES (2, 1, NULL);--this is okay
INSERT INTO #Products VALUES (2, 2, 1);--this is okay
INSERT INTO #Products VALUES (2, 3, 2);--this should fail
SELECT * FROM #Products;
The rule I want to enforce is that MemberId = 1 holds global products and all other MemberIds hold normal products. A set of normal products can be linked to a single global product.
So I want the ability for a Member's Product to be linked to a Global Product, i.e. there would be a foreign key constraint that if the GlobalProductId isn't NULL then there should exist a ProductId that matches the GlobalProductId where the MemberId = 1.
In my example above I have one global product with a ProductId = 1. Then I create three normal products:
the first has no global product;
the second is linked to the single global product I created earlier (then I could link further products to the same global product);
the third should fail as I have linked it to a global product that doesn't exist, i.e. this script will return nothing:
SELECT * FROM #Products WHERE MemberId = 1 AND ProductId = 2;
I can see that the simplest solution would be to create a new table to hold nothing but Global Products. The problem with this approach is that I have a whole set of routines to load, update, delete data from the Product table and a second set of routines to perform calculations, etc. from the same table. If I were to introduce a new "Global Products" table then I would have to duplicate dozens of UDFs to achieve this and my code would become much more complicated.
Add a computed column that's fixed as 1 and then add a foreign key:
CREATE TABLE Products (
MemberId INT,
ProductId INT,
GlobalProductId INT,
PRIMARY KEY (MemberId, ProductId),
GlobalMemberId AS 1 PERSISTED,
FOREIGN KEY (GlobalMemberId,GlobalProductID)
references Products (MemberId,ProductID)
);
INSERT INTO Products VALUES (1, 1, NULL);--this is my "global product"
INSERT INTO Products VALUES (2, 1, NULL);--this is okay
INSERT INTO Products VALUES (2, 2, 1);--this is okay
INSERT INTO Products VALUES (2, 3, 2);--this should fail
SELECT * FROM Products;
This produces these results:
Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY SAME TABLE constraint "FK__Products__7775B2CE". The conflict occurred in database "abc", table "dbo.Products".
The statement has been terminated.
MemberId ProductId GlobalProductId GlobalMemberId
----------- ----------- --------------- --------------
1 1 NULL 1
2 1 NULL 1
2 2 1 1
Why not just add a CHECK constraint:
ALTER TABLE Products ADD CONSTRAINT CHK_ColumnD_GlobalProductId
CHECK (GlobalProductId IS NULL AND MemberId = 1
OR GlobalProductId IS NOT NULL AND MemberId != 1);
and a FOREIGN KEY:
ALTER TABLE Products ADD CONSTRAINT fk_SelfProducts
FOREIGN KEY (GlobalProductId )
REFERENCES Products (ProductId)

Add unique constraint to combination of two columns

I have a table and, somehow, the same person got into my Person table twice. Right now, the primary key is just an autonumber but there are two other fields that exist that I want to force to be unique.
For example, the fields are:
ID
Name
Active
PersonNumber
I only want 1 record with a unique PersonNumber and Active = 1.
(So the combination of the two fields needs to be unique)
What is the best way on an existing table in SQL server I can make it so if anyone else does an insert with the same value as an existing value, it fails so I don't have to worry about this in my application code.
Once you have removed your duplicate(s):
ALTER TABLE dbo.yourtablename
ADD CONSTRAINT uq_yourtablename UNIQUE(column1, column2);
or
CREATE UNIQUE INDEX uq_yourtablename
ON dbo.yourtablename(column1, column2);
Of course, it can often be better to check for this violation first, before just letting SQL Server try to insert the row and returning an exception (exceptions are expensive).
Performance impact of different error handling techniques
Checking for potential constraint violations before entering TRY/CATCH
If you want to prevent exceptions from bubbling up to the application, without making changes to the application, you can use an INSTEAD OF trigger:
CREATE TRIGGER dbo.BlockDuplicatesYourTable
ON dbo.YourTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM inserted AS i
INNER JOIN dbo.YourTable AS t
ON i.column1 = t.column1
AND i.column2 = t.column2
)
BEGIN
INSERT dbo.YourTable(column1, column2, ...)
SELECT column1, column2, ... FROM inserted;
END
ELSE
BEGIN
PRINT 'Did nothing.';
END
END
GO
But if you don't tell the user they didn't perform the insert, they're going to wonder why the data isn't there and no exception was reported.
EDIT here is an example that does exactly what you're asking for, even using the same names as your question, and proves it. You should try it out before assuming the above ideas only treat one column or the other as opposed to the combination...
USE tempdb;
GO
CREATE TABLE dbo.Person
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(32),
Active BIT,
PersonNumber INT
);
GO
ALTER TABLE dbo.Person
ADD CONSTRAINT uq_Person UNIQUE(PersonNumber, Active);
GO
-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 1, 22);
GO
-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 0, 22);
GO
-- fails:
INSERT dbo.Person(Name, Active, PersonNumber)
VALUES(N'foo', 1, 22);
GO
Data in the table after all of this:
ID Name Active PersonNumber
---- ------ ------ ------------
1 foo 1 22
2 foo 0 22
Error message on the last insert:
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'uq_Person'. Cannot insert duplicate key in object 'dbo.Person'.
The statement has been terminated.
Also I blogged more recently about a solution to applying a unique constraint to two columns in either order:
Enforce a Unique Constraint Where Order Does Not Matter
This can also be done in the GUI:
Under the table "Person", right click Indexes
Click/hover New Index
Click Non-Clustered Index...
A default Index name will be given but you may want to change it.
Check Unique checkbox
Click Add... button
Check the columns you want included
Click OK in each window.
In my case, I needed to allow many inactives and only one combination of two keys active, like this:
UUL_USR_IDF UUL_UND_IDF UUL_ATUAL
137 18 0
137 19 0
137 20 1
137 21 0
This seems to work:
CREATE UNIQUE NONCLUSTERED INDEX UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL
ON USER_UND(UUL_USR_IDF, UUL_ATUAL)
WHERE UUL_ATUAL = 1;
Here are my test cases:
SELECT * FROM USER_UND WHERE UUL_USR_IDF = 137
insert into USER_UND values (137, 22, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 23, 0) --I CAN
insert into USER_UND values (137, 24, 0) --I CAN
DELETE FROM USER_UND WHERE UUL_USR_ID = 137
insert into USER_UND values (137, 22, 1) --I CAN
insert into USER_UND values (137, 27, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 28, 0) --I CAN
insert into USER_UND values (137, 29, 0) --I CAN
And if you have lot insert queries but not wanna ger a ERROR message everytime , you can do it:
CREATE UNIQUE NONCLUSTERED INDEX SK01 ON dbo.Person(ID,Name,Active,PersonNumber)
WITH(IGNORE_DUP_KEY = ON)

Dataintegrity between tables in SQL Server

Is it possible to add data integrity between columns in different tables in SQL Server?
I have table Pay with column Date and table Orders with column DateofOrder. And I would like to add the data integrity so the Date cannot be earlier than the DateofOrder.
And when the user insert there the same date or even earlier database would show error.
I think you mean something like this, here done with a trigger;
CREATE TRIGGER trig_pay ON Pay
FOR INSERT, UPDATE
AS
IF EXISTS(SELECT *
FROM [Order] o
JOIN inserted i
ON o.id = i.payment_id
WHERE DateOfOrder>[date])
BEGIN
RAISERROR ('Sorry, Dave', 16, 1)
ROLLBACK;
RETURN;
END
INSERT INTO [Order] values (1, GETDATE()); -- Order today
INSERT INTO Pay values (1, DATEADD(dd, -1, getdate())); -- Pay yesterday
> Sorry, Dave
Yes, you can do it by using INSTEAD OF INSERT Trigger.
You would have to use an INSTEAD OF or AFTER trigger to enforce this, you can't do it declaratively. Well you could use a check constraint with a TVF or something but I've never tried that.
I'd show sample code but I'm not sure what payroll has to do with orders. If a new order comes in, what pay date must be later than or equal to the order date? Is there some other column that relates these two tables?
It is possible without resorting to triggers.
The idea is to add the DateofOrder in the Orders table to its existing key -- let's call it order_id -- to create a compound superkey, then reference this superkey (rather than the simple key solely order_id) in the Pay table.
Here are the bare bones:
CREATE TABLE Orders
(
order_id CHAR(10) NOT NULL,
DateofOrder DATE NOT NULL,
UNIQUE (order_id), -- simple candidate key
UNIQUE (DateofOrder, order_id) -- compund superkey
);
CREATE TABLE Pay
(
order_id CHAR(10) NOT NULL,
DateofOrder DATE NOT NULL,
FOREIGN KEY (DateofOrder, order_id)
REFERENCES Orders (DateofOrder, order_id),
DateOfPayment DATE NOT NULL,
CHECK (DateofOrder < DateOfPayment),
UNIQUE (order_id)
);