Is it possible in Oracle SQL developer to do something like this
CREATE FUNCTION fnCheckValid(accountidd IN NUMBER)
RETURN NUMBER
IS retval NUMBER(4,0);
BEGIN
SELECT COUNT(accountid_fk)
INTO retval
FROM tbl_AccountAuthentications
WHERE accountid_fk = accountidd;
RETURN(retval);
END;
/
ALTER TABLE tbl_AccountAuthentications
ADD CONSTRAINT chkCheckvalid CHECK(fnCheckValid(accountid_fk) <= 1);
The error i keep getting is
Error starting at line 999 in command:
ALTER TABLE tbl_AccountAuthentications
ADD CONSTRAINT chkCheckvalid CHECK(fnCheckValid(accountid_fk) <= 1)
Error report:
SQL Error: ORA-00904: "FNCHECKVALID": invalid identifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
The function is being created and i can find it but when i am trying to call it i keep getting that error
this is what i am trying to achieve
AccountID RegularID OpenID
1 5 null
1 null 10
1 null 11
1 6 <-- Forbidden
so that a user cannot make 2 regular accounts but can have as many OpenID accounts as he wants
The table is set up as follows
CREATE TABLE tbl_AccountAuthentications(
newAuthID NUMBER(4,0)
CONSTRAINT naid_pk PRIMARY KEY,
accountid_fk NUMBER(4,0)
CONSTRAINT accid_fk
REFERENCES tbl_UserAccounts(account_id),
regularid_fk NUMBER(4,0)
CONSTRAINT rgid_fk
REFERENCES tbl_StrongRoom(password_id),
openid_fk NUMBER(4,0)
CONSTRAINT opid_fk
REFERENCES tbl_OpenID(openid)
);
No you can't do that, see Restrictions on Check Constraints:
Calls to user-defined functions
But you can make a workaround using virtual columns
ALTER TABLE tbl_AccountAuthentications ADD (fnCheck NUMBER GENERATED ALWAYS AS (fnCheckValid(accountid_fk)) VIRTUAL);
ALTER TABLE tbl_AccountAuthentications
ADD CONSTRAINT chkCheckvalid CHECK(fnCheck <= 1);
Note, function has to be DETERMINISTIC, otherwise it does not work. Oracle does not verify whether your function is actually deterministic, it just checks for the keyword. This one is allowed (although it does not make any sense at all):
CREATE OR REPLACE FUNCTION DET_FUNCTION RETURN NUMBER DETERMINISTIC IS
BEGIN
RETURN DBMS_RANDOM.RANDOM();
END;
/
You want to ensure that the columns AccountID and RegularID, together, are unique, no matter how many values of OpenID there are.
The only way of doing this, as you've determined, is to constrain it. You note in the comments that you've experimented with triggers. This does not constrain the values within the database, it only ensures that it attempts to verify when the trigger is enabled. I expect that, when you attempted it, you got the error "ORA-04091: is mutating, trigger/function may not see it." as you're selecting from a table you're in the processes of changing (insert or update).
If you have to constrain this then that's what you should do; the problem with doing so is that your table is not normalised. So, normalise it. Use two tables instead of the one you have.
Firstly, you need your RegularID to be unique over AccountID this means it should be stored at this level. It appears as though tbl_UserAccounts is unique on this identifier so alter this table and store your RegularID there.
Next, you want a table that has as many OpenID s as a user might want. This means that you need a table unique on AccountID and OpenID 1.
create table openid_account_auth (
, account_id number(4,0)
, open_id number(4,0)
, constraint pk_openid_account_auth
primary key (account_id, open_id)
, constraint fk_openid_account_auth_accid
foreign key (account_id)
references tbl_UserAccounts(account_id)
, constraint fk_openid_account_auth_open
foreign key (open_id)
references tbl_OpenID (openid)
);
One point on this table (and your own), it means that multiple accounts can have the same OpenID. If you did not intend this then you should add AccountID as a foreign key in tbl_OpenID, which would be the only way to ensure that each OpenID is associated with one, and only one, AccountID.
You can then create a view in order to get the information in the same manner, if you really feel the need to use this. I'm not certain why you would.
create or replace view AccountAuthentications as
select account_id, regular_id, null
from user_accounts
union all
select account_id, null, open_id
from openid_account_auth;
Simply put, unless under severe constraints, you should always store data at it's natural level and use the database to ensure that integrity is maintained. If you then need to use the data slightly differently you can use views, or materialized views etc., to do so.
1. I'm sorry but I can't bring myself to prefix the name of every table with tbl_.
Related
I have two tables, one of student and one of staff that look as such:
create table student (
id int not null primary key
)
create table staff (
id int not null primary key
)
I want the id in each to be unique. I know this isn't how it should be in production but I'm just trying to see why my check constraint doesn't work and I'm using a simpler example to explain.
I then alter the tables to include the check as follows:
alter table student add constraint not_staff check (id not in (select id from staff))
alter table staff add constraint not_student check (id not in (select id from student))
These checks seem to be invalid.
My question is whether we're allowed to have these kinds of SQL statements inside of a check constraint. If so, why is the above constraint invalid and how would I go about fixing it.
Thanks!
You can't use queries in a check constraint in Db2. Refer to the description of the CREATE TABLE statement.
CHECK (check-condition)
Defines a check constraint. The search-condition must be true or unknown for every row of the table.
search-condition
The search-condition has the following restrictions:
...
The search-condition cannot contain any of the following (SQLSTATE 42621):
Subqueries
The easiest way to achieve your goal is not to create constraints, but create a sequence and use it in before triggers on both tables.
I have mytable which has 3 integer fields: id, status, project_id.
I've told people that they should not progress status past 4 before assigning it a project_id value. Naturally people don't listen and then there are problems down the road.
Is there a way to return an error if someone tries to update from status 4 to 5 while project_id column is null? I still need people to be able to update status from 2 or 3 to status 4 regardless of it having a project_id.
You can use CHECK constraint as suggested by #stickbit if you need very simple checks.
If you need a more complicated logic, you can use TRIGGER functionality
CREATE FUNCTION check_status()
RETURNS trigger AS
$mytrigger$
BEGIN
IF OLD.status = 4 AND NEW.status >= 5 AND NEW.project_id IS NULL THEN
RAISE EXCEPTION 'Project ID must be assigned before progressing to status 5';
END IF;
RETURN NEW;
END
$mytrigger$
LANGUAGE plpgsql;
CREATE TRIGGER project_id_check
BEFORE UPDATE ON "MyTable"
FOR EACH ROW EXECUTE PROCEDURE check_status();
How about a check constraint on the table:
CHECK (project_id IS NOT NULL OR status < 5)
If you have data violating your desired rules, you can still use a CHECK constraint like demonstrated by sticky bit. Just make it NOT VALID:
ALTER TABLE mytable ADD CONSTRAINT project_id_required_for_status_5_or_higher
CHECK project_id IS NOT NULL OR status < 5) NOT VALID;
Then the constraint is only applied to subsequent inserts and updates. Existing rows are ignored. (But any new update must fix violating values or it will fail.)
You should also have a FOREIGN KEY constraint enforcing referential integrity for project_id, else the constraint can easily be circumvented with dummy values.
Fine point: a CHECK constraint not only prohibits the updates you mentioned, but inserts with a violating state as well.
Once all rows are fixed to comply with the new rule, you can VALIDATE the constraint:
ALTER TABLE mytable VALIDATE CONSTRAINT project_id_required_for_status_5_or_higher;
More:
Trigger vs. check constraint
I have a table which is:
CREATE SEQUENCE id_seq;
CREATE TABLE public."UserInfo"
(
id bigint NOT NULL DEFAULT nextval('id_seq'),
phone text,
password text,
name text,
surname text,
middle_name text,
email text,
company text,
title text,
image_id text,
CONSTRAINT "UserInfo_pkey" PRIMARY KEY (id),
CONSTRAINT "UserInfo_image_id_key" UNIQUE (image_id),
CONSTRAINT "UserInfo_phone_key" UNIQUE (phone)
)
WITH (
OIDS=FALSE
);
ALTER SEQUENCE id_seq OWNED BY public."UserInfo".id;
ALTER TABLE public."UserInfo"
OWNER TO postgres;
When I make bad request for insert like same value for unique column. "id" is increasing... Here is bad id request;
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, 9921455867, mg123209, name, surname, , namesurname#xxx.com, Company Name, Title Of Person, 123asd).
********** Error **********
Here is my table result ;
1;"1234477867";"qweff";"Name";"Surname";"''";"namesurname#qwe.com";"Company";"Title";"qwer1234"
4;"5466477868";"1235dsf";"Name";"Surname";"''";"banesyrna#pwqe.com";"Company";"Title";"qwer1235"
6;"5051377828";"asd123";"Name";"Surname";"''";"qweg#sdcv.com";"Company";"Title";"qwesr1235"
Please help me how I can solve this issue, I want order like 1,2,3.. sequential..
This is the way sequences work.
Important: To avoid blocking concurrent transactions that obtain
numbers from the same sequence, a nextval operation is never rolled
back; that is, once a value has been fetched it is considered used and
will not be returned again. This is true even if the surrounding
transaction later aborts, or if the calling query ends up not using
the value.
As pointed out in the comments there's no harm in having gaps in sequences. If you delete some rows in your table for what ever reason you are creating gaps in your primary key values and you wouldn't normally bother with resetting them to make them sequential.
If you insist on creating a gapless sequence, read this article: http://www.varlena.com/GeneralBits/130.php and be prepared for slow inserts.
When generating an artificial primary key of this type it's important to clearly understand that the value of the primary key has no meaning. I'll emphasize that point again - THE VALUE OF THE KEY HAS NO MEANING, and any "meaning" which is ascribed to it by developers, managers, or users is incorrect. It's a value which is unique, non-null, and unchanging - that's it. It does not matter what the number is. It does not matter if it's in some "order" relative to other keys in the table. Do not fall into the trap of believing that these values need to be ordered, need to be increasing, or must in some other manner conform to some external expectations of "how they should look".
Unique. Non-null. Unchanging. That's all you know, and all you need know.
Best of luck.
Hi I'm having trouble getting my sql syntax correct. I want to create a unique constraint that looks at the newly added foreign key, looks at some properties of the newly related entity to decided if the relationship is allowed.
CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1"
FOR EACH ROW
BEGIN
Select "Table_2"."number"
(CASE "Table_2"."number" > 0
THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
END)
from "Table_1"
WHERE "Table_2"."ID" = :new.FK_Table_2_ID
END;
Edit: APC answer is wonderfully comprehensive, however leads me to think im doing it in the wrong way.
The situation is I have a table of people with different privilege levels, and I want to check these privilege levels, e.g. A user, 'Bob', has low level privileges and he tries to become head of department which requires requires high privileges so the system prevents this happening.
There is a follow-up question which poses a related scenario but with a different data model. Find it here.
So the rule you want to enforce is that TABLE_1 can only reference TABLE_2 if some column in TABLE_2 is zero or less. Hmmm.... Let's sort out the trigger logic and then we'll discuss the rule.
The trigger should look like this:
CREATE or replace TRIGGER "New_Trigger"
AFTER INSERT OR UPDATE ON "Table_1"
FOR EACH ROW
declare
n "Table_2"."number".type%;
BEGIN
Select "Table_2"."number"
into n
from "Table_2"
WHERE "Table_2"."ID" = :new.FK_Table_2_ID;
if n > 0
THEN RAISE_APPLICATION_ERROR(-20000, 'this is not allowed');
end if;
END;
Note that your error message should include some helpful information such as the value of the TABLE_1 primary key, for when you are inserting or updating multiple rows on the table.
What you are trying to do here is to enforce a type of constraint known as an ASSERTION. Assertions are specified in the ANSI standard but Oracle has not implemented them. Nor has any other RDBMS, come to that.
Assertions are problematic because they are symmetrical. That is, the rule also needs to be enforced on TABLE_2. At the moment you check the rule when a record is created in TABLE_1. Suppose at some later time a user updates TABLE_2.NUMBER so it is greater than zero: your rule is now broken, but you won't know that it is broken until somebody issues a completely unrelated UPDATE on TABLE_1, which will then fail. Yuck.
So, what to do?
If the rule is actually
TABLE_1 can only reference TABLE_2 if
TABLE_2.NUMBER is zero
then you can enforce it without triggers.
Add a UNIQUE constraint on TABLE_2 for (ID, NUMBER); you need an additional constraint because ID remains the primary key for TABLE_2.
Add a dummy column on TABLE_1 called TABLE_2_NUMBER. Default it to zero and have a check constraint to ensure it is always zero. (If you are on 11g you should consider using a virtual column for this.)
Change the foreign key on TABLE_1 so (FK_Table_2_ID, TABLE_2_NUMBER) references the unique constraint rather than TABLE_2's primary key.
Drop the "New_Trigger" trigger; you don't need it anymore as the foreign key will prevent anybody updating TABLE_2.NUMBER to a value other than zero.
But if the rule is really as I formulated it at the top i.e.
TABLE_1 can only reference TABLE_2 if
TABLE_2.NUMBER is not greater than zero (i.e. negative values are okay)
then you need another trigger, this time on TABLE_2, to enforce it the other side of the rule.
CREATE or replace TRIGGER "Assertion_Trigger"
BEFORE UPDATE of "number" ON "Table_2"
FOR EACH ROW
declare
x pls_integer;
BEGIN
if :new."number" > 0
then
begin
Select 1
into x
from "Table_1"
WHERE "Table_1"."FK_Table_2_ID" = :new.ID
and rownum = 1;
RAISE_APPLICATION_ERROR(-20001, :new.ID
||' has dependent records in Table_1');
exception
when no_data_found then
null; -- this is what we want
end;
END;
This trigger will not allow you to update TABLE_2.NUMBER to a value greater than zero if it is referenced by records in TABLE_2. It only fires if the UPDATE statement touches TABLE_2.NUMBER to minimise the performance impact of executing the lookup.
Don't use a trigger to create a unique constraint or a foreign key constraint. Oracle has declarative support for unique and foreign keys, e.g.:
Add a unique constraint on a column:
ALTER TABLE "Table_1" ADD (
CONSTRAINT table_1_uk UNIQUE (column_name)
);
Add a foreign key relationship:
ALTER TABLE "ChildTable" ADD (
CONSTRAINT my_fk FOREIGN KEY (parent_id)
REFERENCES "ParentTable" (id)
);
I'm not clear on exactly what you're trying to achieve with your trigger - it's a bit of a mess of SQL and PL/SQL munged together which will not work, and seems to refer to a column on "Table_2" which is not actually queried.
A good rule of thumb is, if your trigger is querying the same table that the trigger is on, it's probably wrong.
I'm not sure, but are you after some kind of conditional foreign key relationship? i.e. "only allow child rows where the parent satisfies condition x"? If so, the problem is in the data model and should be fixed there. If you provide more explanation of what you're trying to achieve we should be able to help you.
I'm working on migration function. It reads data from old table and inserts it into the new one. All that stuff working in background thread with low priority.
My steps in pseudo code.
sqlite3_prepare_stmt (select statement)
sqlite3_prepare_stmt (insert statement)
while (sqlite3_step (select statement) == SQLITE_ROW)
{
get data from select row results
sqlite3_bind select results to insert statement
sqlite3_step (insert statement)
sqlite3_reset (insert statement)
}
sqlite3_reset (select statement)
I'm always getting 'constraint failed' error on sqlite3_step (insert statement). Why it's happend and how i could fix that?
UPD: As i'm understand that's happend because background thread use db handle opened in main thread. Checking that guess now.
UPD2:
sqlite> select sql from sqlite_master where tbl_name = 'tiles';
CREATE TABLE tiles('pk' INTEGER PRIMARY KEY, 'data' BLOB, 'x' INTEGER, 'y' INTEGER, 'z' INTEGER, 'importKey' INTEGER)
sqlite> select sql from sqlite_master where tbl_name = 'tiles_v2';
CREATE TABLE tiles_v2 (pk int primary key, x int, y int, z int, layer int, data blob, timestamp real)
It probably means your insert statement is violating a constraint in the new table. Could be a primary key constraint, a unique constraint, a foreign key constraint (if you're using PRAGMA foreign_keys = ON;), and so on.
You fix that either by dropping the constraint, correcting the data, or dropping the data. Dropping the constraint is usually a Bad Thing, but that depends on the application.
Is there a compelling reason to copy data one row at a time instead of as a set?
INSERT INTO new_table
SELECT column_list FROM old_table;
If you need help identifying the constraint, edit your original question, and post the output of these two SQLite queries.
select sql from sqlite_master where tbl_name = 'old_table_name';
select sql from sqlite_master where tbl_name = 'new_table_name';
Update: Based on the output of those two queries, I see only one constraint--the primary key constraint in each table. If you haven't built any triggers on these tables, the only constraint that can fail is the primary key constraint. And the only way that constraint can fail is if you try to insert two rows that have the same value for 'pk'.
I suppose that could happen in a few different ways.
The old table has duplicate values in
the 'pk' column.
The code that does your migration
alters or injects a duplicate value
before inserting data into your new
table.
Another process, possibly running on
a different computer, is inserting or
updating data without your knowledge.
Other reasons I haven't thought of yet. :-)
You can determine whether there are duplicate values of 'pk' in the old table by running this query.
select pk
from old_table_name
group by pk
having count() > 1;
You might consider trying to manually migrate the data using INSERT INTO . . . SELECT . . . If that fails, add a WHERE clause to reduce the size of the set until you isolate the bad data.
I have found that troubleshooting foreign key constraint errors with sqlite to be troublesome, particularly on large data sets. However the following approach helps identify the offending relation.
disable foreign key checking: PRAGMA foreign_keys = 0;
execute the statement that cause the error - in my case it was an INSERT of 70,000 rows with 3 different foreign key relations.
re-enable foreign key checking: PRAGMA foreign_keys = 1;
identify the foreign key errors: PRAGMA foreign_key_check(table-name);
In my case, it showed the 13 rows with invalid references.
Just in case anyone lands here looking for "Constraint failed" error message, make sure your Id column's type is INTEGER, not INTEGER (0, 15) or something.
Background
If your table has a column named Id, with type INTEGER and set as Primary Key, SQLite treats it as an alias for the built-in column RowId. This column works like an auto-increment column. In my case, this column was working fine till some table designer (probably the one created by SQLite guys for Visual Studio) changed column type from INTEGER to INTEGER (0, 15) and all of a sudden my application started throwing Constraint failed exception.
just for making it more clearer :
make sure that you use data type correct , i was using int instead of integer on creating tmy tables like this :
id int primary key not null
and i was stuck on the constrain problem over hours ...
just make sure that to enter data type correctly in creating database.
One of the reasons that this error occurs is inserting duplicated data for a unique row too. Check your all unique and keys for duplicated data.
Today i had similar error: CHECK constraint failed: profile
First i read here what is constraint.
The CONSTRAINTS are an integrity which defines some conditions that
restrict the column to contain the true data while inserting or
updating or deleting. We can use two types of constraints, that is
column level or table level constraint. The column level constraints
can be applied only on a specific column where as table level
constraints can be applied to the whole table.
Then figured out that i need to check how database was created, because i used sqlalchemy and sqlite3 dialect, i had to check tables schema. Schema will show actual database structure.
sqlite3 >>> .schema
CREATE TABLE profile (
id INTEGER NOT NULL,
...
forum_copy_exist BOOLEAN DEFAULT 'false',
forum_deleted BOOLEAN DEFAULT 'true',
PRIMARY KEY (id),
UNIQUE (id),
UNIQUE (login_name),
UNIQUE (forum_name),
CHECK (email_verified IN (0, 1)),
UNIQUE (uid),
CHECK (forum_copy_exist IN (0, 1)),
CHECK (forum_deleted IN (0, 1))
So here I found that boolean CHECK is always 0 or 1 and i used column default value as 'false', so each time i had no forum_copy_exist or forum_deleted value, false was inserted, and because false is invalid value it throws error, and didn't inserted row.
So changing database defaults to:
forum_copy_exist BOOLEAN DEFAULT '0',
forum_deleted BOOLEAN DEFAULT '0',
solved the issue.
In postgresql i think false is valid value. So it depends how database schema is created.
Hope this will help others in the future.