SQL Server - Inserting Multiple Values Into Column - sql

I'm trying to update my pizza so it will allow more than one topping. I am receiving the error Cannot insert duplicate key row in object 'dbo.PizzaToppings' with unique index 'FK_Pizza'. The duplicate key value is (3). when trying to add more than one toppingID to the column. How can I add multiple ToppingID's?
INSERT INTO dbo.PizzaToppings
(PizzaToppingsID, PizzaID, ToppingsID)
VALUES
(1, 1, 1),
(2, 2, 2),
(3, 3, 3),
(4, 4, 7),
(5, 5, 6),
(6, 6, 4),
(7, 3, 7);
The FK_Pizza that was created
CREATE UNIQUE NONCLUSTERED INDEX
FK_Pizza
ON PizzaToppings
(
PizzaID ASC
);

You're trying to store an array into a what looks like a number column.
Usually you would have a row for each individual topping:
Pizzas:
PizzaID integer
PizzaName varchar(32)
Toppings:
ToppingID integer
ToppingName varchar(32)
PizzaTopping:
PizzaID integer (fk to Pizzas.PizzaID)
ToppingID integer (fk to Toppings.ToppingID)
PizzaTopping would contain a row for each topping per pizza.
Edit for updated question:
Your error is down to a duplicate key violation. Check the constraints on the table, it looks like there is a constraint that prevents you adding more than one row per pizzaID

Each Pizza has to be Unique.....
Try this:
CREATE UNIQUE NONCLUSTERED INDEX
FK_Pizza
ON PizzaToppings
(
PizzaID ASC, ToppingsID ASC
);
Don't forget to drop the constraint first

Why is this clustered index named like a foreign key constraint?
CREATE UNIQUE NONCLUSTERED INDEX
FK_Pizza
ON PizzaToppings
(
PizzaID ASC
);
Does it have to be unique? Don't you mean this to just be an FK and not an index?
Shouldn't your unique key be PizzaToppingID?
Are you allowing identity insert? Or shouldn't you let it be an autoincremented ID and just insert PizzaID and ToppingID?
Also is there already a record with PizzaID = 3 already in this table? If you are looking to just maintain or UPSERT, then shouldn't a MERGE be better?
If this is a junction table between Pizza and topping, to show which toppings are allowed on which pizza, shouldn't it have 2 FKs and 1 unique index?

Related

Are SQL Server unique constraints "Silent" or do they raise an exception?

I want to prevent inserting duplicate values into my tables. At first, I added code to check if the value already existed in the database, but that seems like a lot of overhead / wasted time if I can prevent it at the DDL level.
So I found this and changed one of my tables (as an example) from this:
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
);
to this:
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [CK_ACTORS_Column]
UNIQUE NONCLUSTERED ([ActorId] ASC)
);
I want the constraint to prevent a second identical ActorId without whin[g]ing about it. IOW, just bypass it, don't tell me about it, don't stop the app or throw an exception.
Is this how it works (silently), or will it throw an exception?
#GMB wrote in his answer "SQL Server has built-in no option (that I know about) to ignore such error".
As #a_horse_with_no_name pointed out in the comment, there is an IGNORE_DUP_KEY option of an index that is relevant here.
You said:
I want the constraint to prevent a second identical ActorId without
whin[g]ing about it. IOW, just bypass it, don't tell me about it,
don't stop the app or throw an exception.
It can be achieved with this option.
At first, I should point out that when you create a unique constraint
CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)
the engine will create a unique index to enforce the constraint. The constraint is a logical concept, the index is a physical implementation of the concept.
You can achieve the same effect by creating just an index, a unique index. When you create an index you can specify various options, including the IGNORE_DUP_KEY option.
IGNORE_DUP_KEY = { ON | OFF }
Specifies the error response when an
insert operation attempts to insert duplicate key values into a unique
index. The IGNORE_DUP_KEY option applies only to insert operations
after the index is created or rebuilt. The option has no effect when
executing CREATE INDEX, ALTER INDEX, or UPDATE. The default is OFF.
ON A warning message will occur when duplicate key values are inserted
into a unique index. Only the rows violating the uniqueness constraint
will fail.
OFF An error message will occur when duplicate key values are inserted
into a unique index. The entire INSERT operation will be rolled back.
By default this option is OFF, so an attempt to insert a duplicate key value will fail with an error. The server will roll back the INSERT operation and send this error message to your application and it will depend on your application how to handle it. If your application doesn't expect it, it will likely throw some exception.
If you set this option to ON, your application will no longer receive an error message. It will receive a warning message, which most applications usually ignore. So, it will look like the server silently ignores duplicate values and inserts only those values that are not duplicates.
It is rarely a desired behaviour to silently ignore problems, but if you really know what you are doing, you can do it.
Here is a short demo.
Let's start with your table
CREATE TABLE [dbo].[ACTORS]
(
[Id] INT IDENTITY (1, 1) NOT NULL,
[ActorId] CHAR(9) NOT NULL,
[Actor] VARCHAR(50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC),
);
Option 1. Default. IGNORE_DUP_KEY = OFF
CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
[ActorId] ASC
) WITH (IGNORE_DUP_KEY = OFF)
The table is empty. Let's try to insert some values.
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
Let's try to insert a duplicate value:
insert into actors (actorid, actor) values('foo', 'baz');
--Msg 2601, Level 14, State 1, Line 4
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'.
--The duplicate key value is (foo ).
--The statement has been terminated.
Let's try to insert several values in a single statement with some duplicates:
insert into actors (actorid, actor) values
('foo', 'baz'),
('fo2', 'baz'),
('fo2', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo4', 'baz');
--Msg 2601, Level 14, State 1, Line 16
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'.
--The duplicate key value is (foo ).
--The statement has been terminated.
Let's see what we have in the table now.
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
+----+-----------+-------+
Only the first INSERT statement succeeded and only one row was inserted.
Now, clean up.
DROP INDEX [IX_ActorID] ON [dbo].[ACTORS];
TRUNCATE TABLE dbo.Actors;
Option 2. IGNORE_DUP_KEY = ON
CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
[ActorId] ASC
) WITH (IGNORE_DUP_KEY = ON)
The table is empty. Let's try to insert some values.
insert into actors (actorid, actor) values('foo', 'bar');
-- (1 row affected)
Let's try to insert a duplicate value:
insert into actors (actorid, actor) values('foo', 'baz');
--Duplicate key was ignored.
--(0 rows affected)
As you can see, now it is not an error message. It is just a warning "Duplicate key was ignored."
Let's try to insert several values in a single statement with some duplicates:
insert into actors (actorid, actor) values
('foo', 'baz1'),
('fo2', 'baz2'),
('fo2', 'baz3'),
('fo3', 'baz4'),
('fo3', 'baz5'),
('fo3', 'baz6'),
('fo4', 'baz7');
--Duplicate key was ignored.
--(3 rows affected)
Here you can see that out of 7 rows only 3 rows were inserted.
Let's see what we have in the table now.
SELECT * FROM Actors;
+----+-----------+-------+
| Id | ActorId | Actor |
+----+-----------+-------+
| 1 | foo | bar |
| 4 | fo2 | baz2 |
| 6 | fo3 | baz4 |
| 9 | fo4 | baz7 |
+----+-----------+-------+
You can see that the last INSERT statement inserted non-duplicate values. Also, have a close look at the values in ID column. The IDENTITY values have gaps, because they were generated for each row that was attempted to be inserted and some of these rows were rejected by the unique index.
Overall, this index option is designed primarily for the cases when you need to insert a lot of rows in bulk, in a single INSERT statement, but you expect that some of these row may violate the unique constraint. You don't want the whole large INSERT statement to fail, you want only few violating rows to be ignored. Without this option you would have to try to insert your values one-by-one, row-by-row, which may be much slower than a single INSERT statement.
Let's try that:
insert into actors (actorid,actor) values('foo', 'bar');
-- 1 row affected
insert into actors (actorid, actor) values('foo', 'baz');
-- Msg 2627 Level 14 State 1 Line 1
-- Violation of UNIQUE KEY constraint 'CK_ACTORS_Column'.
-- Cannot insert duplicate key in object 'dbo.ACTORS'. The duplicate key value is (foo ).
A unique constraint violation does raise an error. This is how the database lets you know that something went wrong.
SQL Server has built-in no option (that I know about) to ignore such error, unlike many other databases (MySQL, Postgres, SQLite...). A workaround is to rewrite the insert with not exists and a subquery:
insert into actors (actorid, actor)
select v.*
from (values ('foo', 'bar')) v(actorid, actor)
where not exists (select 1 from actor a where a.actorid = v.actorid)
Another option is the merge statement:
merge into actors a
using (values ('foo', 'bar')) v(actorid, actor)
on v.actorid = a.actorid
when not matched then insert (actorid, actor)
values (v.actorid, v.actor)

Double values in a table

Is it possible to define a ID column to be unique but every value have to be occur twice?
For example:
table TRANSLATION:
id | name_id | translation
____________|_________|____________
1 | 1 | apple
____________|_________|____________
2 | 1 | apfel
____________|_________|____________
3 | 2 | pear
____________|_________|____________
4 | 2 | birne
I want name_id values to always occur twice, not once and not three times. name_id is a FK from table with my objects that needs to be translated.
No, this is impossible to enforce, though you can attempt it using triggers this is normally a pretty messy solution.
I'd change your table structure to be something like the following:
ID
NAME_ID
LANGUAGE_ID
TRANSLATION
You could then create a unique index on NAME_ID and LANGUAGE_ID. Theoretically, you'd also have a table LANGUAGES, and the LANGUAGE_ID column would have a foreign key back into LANGUAGES.ID - you could then restrict the number of times each NAME_ID appears by not having the data in LANGUAGES.
Ultimately this means that your schema would look something like this:
create table languages (
id number
, description varchar2(4000)
, constraint pk_languages primary key (id)
);
insert into languages values (1, 'English');
insert into languages values (2, 'German');
create table names (
id number
, description varchar(4000)
, constraint pk_names primary key (id)
);
insert into names values (1, 'apple');
insert into names values (2, 'pear');
create table translations (
id number
, name_id number
, language_id number
, translation varchar2(4000)
, constraint pk_translations primary key (id)
, constraint fk_translations_names foreign key (name_id) references names (id)
, constraint fk_translations_langs foreign key (language_id) references languages (id)
, constraint uk_translations unique (name_id, language_id)
);
insert into translations values (1, 1, 1, 'apple');
insert into translations values (2, 1, 2, 'apfel');
insert into translations values (3, 2, 1, 'pear');
insert into translations values (4, 2, 2, 'birne');
and you should be unable to break the constraints:
SQL> insert into translations values (5, 1, 3, 'pomme');
insert into translations values (5, 1, 3, 'pomme')
*
ERROR at line 1:
ORA-02291: integrity constraint (FK_TRANSLATIONS_LANGS) violated - parent
key not found
SQL> insert into translations values (5, 1, 2, 'pomme');
insert into translations values (5, 1, 2, 'pomme')
*
ERROR at line 1:
ORA-00001: unique constraint (UK_TRANSLATIONS) violated
See this SQL Fiddle
Do you mean a maximum of twice? or do you mean they have to occur twice (i.e., once only is not ok)
If the former, Once only IS ok) then you could Add a bit field and make the Primary Key composite on the actual id and the bit field.
If the latter (They have to occur twice), then put two id fields in the same row and make then each a single field unique key.

Unique constraint on two fields, and their opposite

I have a data structure, where I have to store pairs of elements. Each pair has exactly 2 values in it, so we are employing a table, with the fields(leftvalue, rightvalue....).
These pairs should be unique, and they are considered the same, if the keys are changed.
Example: (Fruit, Apple) is the same as (Apple, Fruit).
If it is possible in an efficient way, I would put a database constraint on the fields, but not at any cost - performance is more important.
We are using MSSQL server 2008 currently, but an update is possible.
Is there an efficient way of achieving this?
Two solutions, both really about changing the problem into an easier one. I'd usually prefer the T1 solution if forcing a change on consumers is acceptable:
create table dbo.T1 (
Lft int not null,
Rgt int not null,
constraint CK_T1 CHECK (Lft < Rgt),
constraint UQ_T1 UNIQUE (Lft,Rgt)
)
go
create table dbo.T2 (
Lft int not null,
Rgt int not null
)
go
create view dbo.T2_DRI
with schemabinding
as
select
CASE WHEN Lft<Rgt THEN Lft ELSE Rgt END as Lft,
CASE WHEN Lft<Rgt THEN Rgt ELSE Lft END as Rgt
from dbo.T2
go
create unique clustered index IX_T2_DRI on dbo.T2_DRI(Lft,Rgt)
go
In both cases, neither T1 nor T2 can contain duplicate values in the Lft,Rgt pairs.
If you always store the values in order but store the direction in another column,
CREATE TABLE [Pairs]
(
[A] NVarChar(MAX) NOT NULL,
[B] NVarChar(MAX) NOT NULL,
[DirectionAB] Bit NOT NULL,
CONSTRAINT [PK_Pairs] PRIMARY KEY ([A],[B])
)
You can acheive exaclty what you want with one clustered index, and optimize your lookups too.
So when I insert the pair 'Apple', 'Fruit' I'd do,
INSERT [Pairs] VALUES ('Apple', 'Friut', 1);
Nice and easy. Then I insert 'Fruit', 'Apple',
INSERT [Pairs] VALUES ('Apple', 'Fruit', 0); -- 0 becuase order is reversed.
The insert fails because this is a primary key violation. To further illustrate, the pair 'Coconuts', 'Bananas' would be stored as
INSERT [Pairs] VALUES ('Bananas', 'Coconuts', 0);
For additional lookup performance, I'd add the index
CREATE NONCLUSTERED INDEX [IX_Pairs_Reverse] ON [Pairs] ([B], [A]);
If you can't control inserts to the table, it may be necessary to ensure that [A] and [B] are inserted correctly.
CONSTRAINT [CK_Pairs_ALessThanB] CHECK ([A] < [B])
But this may be an unnecessary performance hit, depending on how controlled your inserts are.
One way would be to create a computed column that combines the two values and put a unique constraint upon it:
create table #test (
a varchar(10) not null,
b varchar(10) not null,
both as case when a > b then a + ':' + b else b + ':' + a end persisted unique nonclustered
)
so
insert #test
select 'apple', 'fruit'
insert #test
select 'fruit', 'apple'
Gives
(1 row(s) affected)
Msg 2627, Level 14, State 1, Line 3
Violation of UNIQUE KEY constraint 'UQ__#test_____55252CB631EC6D26'. Cannot insert duplicate key in object 'dbo.#test'.
The statement has been terminated.
Unique constraint on two/more fields is possible but on their opposite no...
SQL Server 2005 Unique constraint on two columns
Unique constraint on multiple columns
How do I apply unique constraint on two columns SQL Server?
http://www.w3schools.com/sql/sql_unique.asp

how to create a Foreign-Key constraint to a subset of the rows of a table?

I have a reference table, say OrderType that collects different types of orders:
CREATE TABLE IF NOT EXISTS OrderType (name VARCHAR);
ALTER TABLE OrderType ADD PRIMARY KEY (name);
INSERT INTO OrderType(name) VALUES('sale-order-type-1');
INSERT INTO OrderType(name) VALUES('sale-order-type-2');
INSERT INTO OrderType(name) VALUES('buy-order-type-1');
INSERT INTO OrderType(name) VALUES('buy-order-type-2');
I wish to create a FK constraint from another table, say SaleInformation, pointing to that table (OrderType). However, I am trying to express that not all rows of OrderType are eligible for the purposes of that FK (it should only be sale-related order types).
I thought about creating a view of table OrderType with just the right kind of rows (view SaleOrderType) and adding a FK constraint to that view, but PostgreSQL balks at that with:
ERROR: referenced relation "SaleOrderType" is not a table
So it seems I am unable to create a FK constraint to a view (why?). Am I only left with the option of creating a redundant table to hold the sale-related order types? The alternative would be to simply allow the FK to point to the original table, but then I am not really expressing the constraint as strictly as I would like to.
I think your schema should be something like this
create table order_nature (
nature_id int primary key,
description text
);
insert into order_nature (nature_id, description)
values (1, 'sale'), (2, 'buy')
;
create table order_type (
type_id int primary key,
description text
);
insert into order_type (type_id, description)
values (1, 'type 1'), (2, 'type 2')
;
create table order_nature_type (
nature_id int references order_nature (nature_id),
type_id int references order_type (type_id),
primary key (nature_id, type_id)
);
insert into order_nature_type (nature_id, type_id)
values (1, 1), (1, 2), (2, 1), (2, 2)
;
create table sale_information (
nature_id int default 1 check (nature_id = 1),
type_id int,
foreign key (nature_id, type_id) references order_nature_type (nature_id, type_id)
);
If the foreign key clause would also accept an expression the sale information could omit the nature_id column
create table sale_information (
type_id int,
foreign key (1, type_id) references order_nature_type (nature_id, type_id)
);
Notice the 1 in the foreign key
You could use an FK to OrderType to ensure referential integrity and a separate CHECK constraint to limit the order types.
If your OrderType values really are that structured then a simple CHECK like this would suffice:
check (c ~ '^sale-order-type-')
where c is order type column in SaleInformation
If the types aren't structured that way in reality, then you could add some sort of type flag to OrderType (say a boolean is_sales column), write a function which uses that flag to determine if an order type is a sales order:
create or replace function is_sales_order_type(text ot) returns boolean as $$
select exists (select 1 from OrderType where name = ot and is_sales);
$$ language sql
and then use that in your CHECK:
check(is_sales_order_type(c))
You don't of course have to use a boolean is_sales flag, you could have more structure than that, is_sales is just for illustrative purposes.

How to create unique index on fields with possible null values (Oracle 11g)?

Here is the sample table with 3 columns (ID, UNIQUE_VALUE, UNIQUE_GROUP_ID)
I want below records can be allowed:
(1, NULL, NULL)
(2, NULL, NULL)
or
(3, NULL, 7)
(4, 123, 7)
or (Note: this condition is not allowed in unique index nor unique constraint)
(5, NULL, 7)
(6, NULL, 7)
and these can't be allowed:
(7, 123, 7)
(8, 123, 7)
I created a unique index on last 2 columns, but only the first 2 examples can be allowed.
Is it possible to let db check the uniqueness of these 2 columns only when both are not null?
You want to only enforce uniqueness on the rows where both UNIQUE_VALUE and UNIQUE_GROUP_ID are not null. To do this, you can use a unique function-based index:
CREATE UNIQUE INDEX func_based_index ON the_table
(CASE WHEN unique_value IS NOT NULL
AND unique_group_id IS NOT NULL
THEN UNIQUE_VALUE || ',' || UNIQUE_GROUP_ID
END);
you can use the nvl function to avoid nulls and place a different value instead ,
create unique index func_idx on TEST_TABLE (nvl(UNIQUE_VALUE,1), UNIQUE_GROUP_ID);
the disadvantage is that your index will be larger and if you would like to search for null values you will have to use the nvl function in order to avoid table_access_full.
also all of the null values will be located under one branch in the index , so make sure your histograms are updated.
I Hope this will help you :)