Oracle sql constraint questions - sql

I'm having problems with creating a oracle sql script. How do I create these two constraints?
If VID is null then FID has to be null as well (VID = null -> FID = null)
There has to be exactly one row where VID is null which also means that FID has to be null because of 1.
Here is what I have so far:
create table Employee(
Id int primary key,
Name varchar(15) not null,
VID int,
FID int
);
Employee

You can come very close to what you want without a trigger.
You can use a check constraint for the first:
alter table Employee add constraint chk_vid_fid
check (vid is not null or fid is null);
You can do the second using a unique constraint:
create unique index unq_Employee_vid on
Employee(case when vid is null then -1 else id end);
This formulation assumes that id is non-negative, as most ids usually are. If you do use the full range of integer values, then I would be explicit with a string:
create unique index unq_Employee_vid on
Employee(case when vid is null then 'null vid' else cast(id as varchar2(255)) end);
This guarantees at most one row where vid is null, rather than exactly one row. You cannot easily have a constraint that guarantees that exactly one row has a value, because empty tables do not match the condition -- and tables are empty when they are created.

Here is how you can enforce your requirement - to have exactly one row with VID is NULL. As I said in my Comment under your original question (and Gordon said as well in his accepted Answer), you cannot do this just with constraints. However, you can do it with a materialized view. Note that you must create the MV with refresh fast on commit, and that requires certain things for the materialized view log on the base table.
Also: If you try everything exactly as written, the constraint on the MV will fail (of course, because the base table will be empty, so there will be no row where VID is NULL). Add rows to the base table, of which exactly one has NULL for VID, then commit the transaction, and then execute the alter table command to add the constraint to the MV. From that point on, a transaction on the base table (consisting of one or more insert, delete, update or merge statements, followed by one commit) will go through if and only if it leaves exactly one row with NULL in the VID column.
One oddity to keep in mind: even though there is an ALTER MATERIALIZED VIEW statement in Oracle SQL, to add a constraint on a MV we must use the ALTER TABLE statement (with the MV's name), not ALTER MATERIALIZED VIEW.
Note that I used the name t_Employee for the base table, as I already had a table EMPLOYEE and I don't want to mess with my existing objects.
create table t_Employee(
Id int primary key,
Name varchar(15) not null,
VID int,
FID int
);
alter table t_Employee add constraint chk_vid_fid
check (vid is not null or fid is null)
;
create materialized view log on t_Employee
with rowid
(VID)
including new values
;
create materialized view mv_Employee
refresh fast on commit
as select count(*) ct
from t_Employee
where VID is null
;
alter table mv_Employee add constraint chk_vid_ct
check (ct = 1)
;

Related

how to set a constraint that sets null on only one of the fields in the composite foreign key when the parent record is deleted?

I have 2 postgres tables.
table one:
|id|user_id|master_team_id|role_id
table two:
|uuid|user_id|master_team_id|number
master_team_id in table two can be null.
user_id and master_id foreign key references the user_id and
master_team_id in table one.
in order for master_team_id in table two to not be null, the user_id
and master_team_id combo must exist in table one.
how do i add a constraint that sets null on only master_team_id in the composite key(user_id, master_team_id) in table two when the referenced row in table one is deleted?
in the FK constraint specify ON DELETE SET NULL
https://www.techonthenet.com/sql_server/foreign_keys/foreign_delete_null.php
Side Note: I would suggest using adding a new column to table two called "TableOneID" that way you can know if the matching record exists or not.
You can't do that yet.
What a coincidence. The ability to do that was committed yesterday, and (if all goes according to plan) will be included in v15, which is not due out for nearly a year.
You can use a trigger, which would look like this:
create table p (c1 int, c2 int, unique (c1,c2));
create table c (c1 int, c2 int, r double precision);
alter table c add constraint fk99999 foreign key (c1,c2) references p (c1,c2);
create function update_c() returns trigger language plpgsql as $$
BEGIN
update c set c2=null where c1=old.c1 and c2=old.c2;
return old;
END; $$;
create trigger sdjflsfd before delete ON p for each row execute function update_c();

Oracle SQL: "GENERATED ALWAYS" with a specified sequence

I have two tables that I would like to let them share the same sequence to populate the primary key ID column. However, I also don't want the user to specify or change the value for the ID column.
By using the code below, I can let two tables share the same sequence.
CREATE TABLE T1
(
ID INTEGER DEFAULT SEQ_1.nextval NOT NULL
);
This code will use its own sequence and prevent users from changing or specifying with INSERT:
CREATE TABLE T1
(
ID INTEGER GENERATED ALWAYS AS IDENTITY NOT NULL
);
Is there a way that can both world? Something like this:
CREATE TABLE T1
(
ID INTEGER GENERATED ALWAYS AS ( SEQ_1.nextval ) NOT NULL
);
Regarding the use case, as #Sujitmohanty30 asked, the reason that I raised this question:
I'm thinking to implement inheritance in the database, consider this UML diagram (I can't directly post images due to insufficient reputation, and sorry for being lack of imagination).
ANIMAL is abstract and all inheritance is mandatory. This means no instance of ANIMAL should be created. Furthermore, there is an one-to-many relationship between ANIMAL and ZOO_KEEPER.
Therefore, I came up with this idea:
CREATE SEQUENCE ANIMAL_ID_SEQ;
CREATE TABLE HORSE
(
ID INT DEFAULT ANIMAL_ID_SEQ.nextval NOT NULL PRIMARY KEY,
HEIGHT DECIMAL(3, 2) NOT NULL
);
CREATE TABLE DOLPHIN
(
ID INT DEFAULT ANIMAL_ID_SEQ.nextval NOT NULL PRIMARY KEY,
LENGTH DECIMAL(3, 2) NOT NULL
);
CREATE MATERIALIZED VIEW LOG ON HORSE WITH ROWID;
CREATE MATERIALIZED VIEW LOG ON DOLPHIN WITH ROWID;
CREATE MATERIALIZED VIEW ANIMAL
REFRESH FAST ON COMMIT
AS
SELECT 'horse' AS TYPE, ROWID AS RID, ID -- TYPE column is used as a UNION ALL marker
FROM HORSE
UNION ALL
SELECT 'dolphin' AS TYPE, ROWID AS RID, ID
FROM DOLPHIN;
ALTER TABLE ANIMAL
ADD CONSTRAINT ANIMAL_PK PRIMARY KEY (ID);
CREATE TABLE ZOO_KEEPER
(
NAME VARCHAR(50) NOT NULL PRIMARY KEY,
ANIMAL_ID INT NOT NULL REFERENCES ANIMAL (ID)
);
In this case, the use of the shared sequence is to avoid collision in ANIMAL mview. It uses DEFAULT to get the next ID of the shared sequence. However, using DEFAULT doesn't prevent users from manually INSERTing the ID field or UPDATE the value of it.
You can create a master view/table and generate the sequence in it.
Then copy it as column values into both tables while inserting.
Another option could be inserting into both tables at same time.Use SEQ.NEXTVAL to insert into first table to get a new ID, and then SEQ.CURRVAL to copy same id in the table.
No, you cant have anything like this because ID is independently generated for each of the tables and this can be done only using sequence when you are inserting the data in both the tables at the same time.
You should normalize your data schema: add column animal_type into the table and create composite primary key on both columns

MySQL: Increment numerical PK from one point forward

I have a table with a primary key with subsequent values 1,2,3,4,... 1035
I want to insert a new value somewhere in this sequence and increment all values above the said value (actually, the order would be the other way round).
Obviously, I get an error with a simple statement like
UPDATE Table
SET primary_id=primary_id+1
WHERE primary_id > 501
because primary_id 502 is incremented to 503, and 503 already exists.
It seems a very basic question, but I don't remember how to do it.
The table is in MySQL, but I don't think this really matters.
First, take a backup of your table, like this:
CREATE TABLE backup LIKE table;
INSERT INTO backup SELECT * FROM table;
Then, you can run the following queries:
-- remove auto_increment attribute and unique constraint
ALTER TABLE table MODIFY id INT NOT NULL, DROP PRIMARY KEY;
-- your original query
UPDATE table SET id = (id+1) WHERE id > 501;
-- re-add the primary key
ALTER TABLE table MODIFY id INT NOT NULL PRIMARY KEY AUTO_INCREMENT;
If results are OK, remove the backup:
DROP TABLE backup;

Will multiply insert requests to the same table with direct query and store-procedure cause collision?

Multiply users can call store procedure(SP), that will make some changes to mytable in SQL Server. This SP should insert some rows to mytable that has reference to itself through parentid column.
TABLE mytable(
id int identity(1,1) primary key,
name varchar(20) not null,
parentId int not null foreign key references mytable(id)
)
in order to insert row to such table, accordingly to other posts, I have 2 ways:
Allow null to parentid column by ALTER TABLE mytable alter column parentid int null;, insert the row, update parentid and than disable null to parentid
Allow IDENTITY by set identity_insert maytable on, insert dummy row with id=-1 and parentid=-1, insert the correct row with reference to -1, update the parentid to SCOPE_IDENTITY() and in the end set IDENTITY to off
The case:
Assume I take the 2nd way. SP managed to set identity_insert mytable on BUT didn't yet finished the execution of the rest SP. At this time, there are other INSERT requests(NOT through SP) to the mytable table like INSERT INTO mytable(name,parentid) VALUES('theateist', -1). No id is specified because they assumed that IDENTITY is off and therefore id is auto-incremental.
The Question:
Will this cause errors while inserting because IDENTITY, in this period of time, is ON and not auto-incremental any more and therefore it will require id specification? If yes, it will be better to use the 1st way, isn't it?
Thank you
identity_insert is a per-connection setting - you won't affect other connections/statements running against this table.
I definitely wouldn't suggest going the first way, if it could be avoided, since it could impact other users of the table - e.g. some other connection could do a broken insert (parentid=null) while the column definition allows it, and then your stored proc will break. Also, setting a column not null forces a full table scan to occur, so this won't work well as the table grows.
If you did stick with method 2, you've still got an issue with what happens if two connections run this stored proc simultaneously - they'll both want to insert the -1 row, at different times, and delete it also. You'll have conflicts.
I'm guessing the problem you're having is inserting the "roots" of the tree(s), since they have no parent, and so you're attempting to have them self referencing. I'd instead probably make the roots have a null parentid permanently. If there's some other key column(s), these could be used in a filtered index or indexed view to ensure that only one root exists for each key.
Imagine that we're building some form of family trees, and ignoring most of the realities of such beasts (such as most families requiring children to have two parents):
CREATE TABLE People (
PersonID int IDENTITY(1,1) not null,
Surname varchar(30) not null,
Forename varchar(30) not null,
ParentID int null,
constraint PK_People PRIMARY KEY (PersonID),
constraint FK_People_Parents FOREIGN KEY (ParentID) references People (PersonID)
)
CREATE UNIQUE INDEX IX_SoleFamilyRoot ON People (Surname) WHERE (ParentID is null)
This ensures that, within each family (as identified by the surname), exactly one person has a null ParentID. Hopefully, you can modify this example to fit your model.
On SQL Server 2005 and earlier, you have to use an indexed view instead.

How to create a unique index on a NULL column?

I am using SQL Server 2005. I want to constrain the values in a column to be unique, while allowing NULLS.
My current solution involves a unique index on a view like so:
CREATE VIEW vw_unq WITH SCHEMABINDING AS
SELECT Column1
FROM MyTable
WHERE Column1 IS NOT NULL
CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)
Any better ideas?
Using SQL Server 2008, you can create a filtered index.
CREATE UNIQUE INDEX AK_MyTable_Column1 ON MyTable (Column1) WHERE Column1 IS NOT NULL
Another option is a trigger to check uniqueness, but this could affect performance.
The calculated column trick is widely known as a "nullbuster"; my notes credit Steve Kass:
CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
Pretty sure you can't do that, as it violates the purpose of uniques.
However, this person seems to have a decent work around:
http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html
It is possible to use filter predicates to specify which rows to include in the index.
From the documentation:
WHERE <filter_predicate> Creates a filtered index by specifying which
rows to include in the index. The filtered index must be a
nonclustered index on a table. Creates filtered statistics for the
data rows in the filtered index.
Example:
CREATE TABLE Table1 (
NullableCol int NULL
)
CREATE UNIQUE INDEX IX_Table1 ON Table1 (NullableCol) WHERE NullableCol IS NOT NULL;
Strictly speaking, a unique nullable column (or set of columns) can be NULL (or a record of NULLs) only once, since having the same value (and this includes NULL) more than once obviously violates the unique constraint.
However, that doesn't mean the concept of "unique nullable columns" is valid; to actually implement it in any relational database we just have to bear in mind that this kind of databases are meant to be normalized to properly work, and normalization usually involves the addition of several (non-entity) extra tables to establish relationships between the entities.
Let's work a basic example considering only one "unique nullable column", it's easy to expand it to more such columns.
Suppose we the information represented by a table like this:
create table the_entity_incorrect
(
id integer,
uniqnull integer null, /* we want this to be "unique and nullable" */
primary key (id)
);
We can do it by putting uniqnull apart and adding a second table to establish a relationship between uniqnull values and the_entity (rather than having uniqnull "inside" the_entity):
create table the_entity
(
id integer,
primary key(id)
);
create table the_relation
(
the_entity_id integer not null,
uniqnull integer not null,
unique(the_entity_id),
unique(uniqnull),
/* primary key can be both or either of the_entity_id or uniqnull */
primary key (the_entity_id, uniqnull),
foreign key (the_entity_id) references the_entity(id)
);
To associate a value of uniqnull to a row in the_entity we need to also add a row in the_relation.
For rows in the_entity were no uniqnull values are associated (i.e. for the ones we would put NULL in the_entity_incorrect) we simply do not add a row in the_relation.
Note that values for uniqnull will be unique for all the_relation, and also notice that for each value in the_entity there can be at most one value in the_relation, since the primary and foreign keys on it enforce this.
Then, if a value of 5 for uniqnull is to be associated with an the_entity id of 3, we need to:
start transaction;
insert into the_entity (id) values (3);
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;
And, if an id value of 10 for the_entity has no uniqnull counterpart, we only do:
start transaction;
insert into the_entity (id) values (10);
commit;
To denormalize this information and obtain the data a table like the_entity_incorrect would hold, we need to:
select
id, uniqnull
from
the_entity left outer join the_relation
on
the_entity.id = the_relation.the_entity_id
;
The "left outer join" operator ensures all rows from the_entity will appear in the result, putting NULL in the uniqnull column when no matching columns are present in the_relation.
Remember, any effort spent for some days (or weeks or months) in designing a well normalized database (and the corresponding denormalizing views and procedures) will save you years (or decades) of pain and wasted resources.