Allow my to elaborate on the question with an example. I am writing from the
perspective that
(1) bytecodes should not be used to implement the logic already
implemented (hopefully more efficiently) in the database engine (e.g., if we
need to filter out 20 lines out of 500 coming out as the result of an SQL query,
we should be writing a better where clause), and that
(2) I have only a conceptual
understanding of foreign keys (e.g., they automatically create, manage and enforce
the constraints required to maintain data integrity across different tables).
Now, lets consider a simple schema with 4 tables and 15 columns in them
as follows (assume all columns are not null):
people
pid bigint autoinc PK
fname varchar(32)
lname varchar(32)
dob date
sex char(1)
addyID bigint FK to address(aid)
relationship
relof bigint FK to people(pid)
relto bigint FK to people(pid)
relis tinyint
address
aid bigint autoinc PK
street varchar(128)
zipcode int FK to z2cs(zip)
z2cs
zip int PK
city varchar(64)
state char(2)
Expectations (Please answer True/False to the following 6)
It is possible to create the table relationship as above with foreign keys
pointing to different rows of the same table
Adding a row to relationship throws SQLException if either relof or relto
is missing in people
If we add a row to address, we do not need to check if the zipcode is there in z2cs -
missing zip throws an SQLException
Attempt to delete a row from address throws an exception if aid is used in people
Attempt to delete a row from people causes error if pid is used in relationship in
either of the columns relto or relof
An SQL statement exists that lets you find which columns in a table are foreign keys
and which table(column) do they refer to (I know "describe tablename" doesn't do it,
although I feel that it should)
If the answer to any of the above is false, can you please guide me to finding
what exactly is the contract for foreign keys in MySql. Thank you.
PS: If you answer True to [6], would you please share what is that statement.
Bonus question (unrelated to FK):
How would you write the create statement for the relationship table that satisfies the following constraints:
relof and relto cannot be the same
Combination of relof and relto is unique, i.e.,
if their is a row with [relof=584,relto=7823], you cannot insert another row with
either [relof=584,relto=7823] or [relof=7823,relto=584]
1. It is possible to create the table relationship as above with foreign keys pointing to different rows of the same table
NO, a FK can only point to one row. But a row can be referenced by many other rows, like, an adress can be referenced by many people.
2. Adding a row to relationship throws SQLException if either relof or relto is missing in people
YES.
3. If we add a row to address, we do not need to check if the zipcode is there in z2cs - missing zip throws an SQLException
YES.
4. Attempt to delete a row from address throws an exception if aid is used in people
YES, if there is no Cascade Delete in the FK, which then automatically would delete the
entry in people instead of failing.
5. Attempt to delete a row from people causes error if pid is used in relationship in either of the columns relto or relof
YES, see 4.
6. An SQL statement exists that lets you find which columns in a table are foreign keys and which table(column) do they refer to (I know "describe tablename" doesn't do it, although I feel that it should)
AFAIK. There should be an information schema in MySQL where you can query the information from.
B1. relof and relto cannot be the same
Use a Trigger on update or insert to validate this. PostgreSQL knows CHECK constraints, which might ba also available in MySQL, but else a trigger is your choice.
B2. Combination of relof and relto is unique
A combined UNIQUE INDEX on both columns is your friend here.
Related
I have created a table User with following details
{
id , //autoincrment
name
}
and Table Account which has a User with foreign key relation
Account {
id , //autoincrement
userid //foreign key reference to user
}
when i insert in to account with value userId which doesnot exist in user table
i get Constraint voilation Exception
Cannot add or update a child row: a foreign key constraint fails (`schema`.`account`, CONSTRAINT `constraint_name` FOREIGN KEY (`user_id`) REFERENCES `User` (`id`))
but my AUTO increment Count for Account got increased.
what i meant to say is before exception account table has max id of 100.
when i insert new record it fails with above described exception.i corrected query and insert with correct userid then i see accountid value as 102.
is it a correct behaviour from mysql.should it increment autoincrement value even though query execution fails?
It's intended and correct, that auto_increment works outside the scope of transactions (like sequences in other database systems like oracle do as well). The reason is that two different transactions must not draw the same number, and a locking on such a neuralgic point as the key column of a table would have enormous performance impact in concurrent situations otherwise.
Cf. also this SO answer.
I'm developing a java application that saves information in DB using procedures. I will give an example to show my doubt cause i'm kinda lost!
Lets pretend that i have this 2 different classes
public class Seg{
//variables
....
public class Dur{
//variables
private List list<Seg> //Lets pretend that Dur1 has 3 seg, and Dur1's PK = 1
....
And i want to save the information in DB. As the Dur1 has 3 seg and code PK=1 , so i will have 3 insert in seg that has a FK = 1 = Dur's PK
And my question is how can i automically, using a procedure, put a FK in the three seg inserts, assuming that (in java) i know all the matches between Seg and Dur(i have the list that connect them)
//Note: The pk is a attribute defined in the procedures with a sequence
I fear that some may not understand the question but in fact im a little bit confuse
Thanks all
Your example (and the focus on the FK) makes it not clear if you try define a plain PL/SQL layer to handle elementary CRUD (in PL/SQL called also TAPI) or if you intend to encapsulate some kind of business logic.
In the former case you may rethink your approach and have a look on some kind of ORM.
Don't understand me incorrect, I'm not trying to answer your question with "do something else". My point is, there are tons of experience with your situation (database assigned keys) in ORM, so simple search links similar to the above and adapt it to your PL/SQL solution.
In my opinion, you will need to provide an output parameter in the procedure storing the parent class returning the sequence assigned PK and pass this value in the procedure for storing the child classes.
Hey i tried to illustrate your scenario with an example. Hope it
helps. Please pardon any syntax error since i dont have workspace
currently.
--Drop any existing object with same name
DROP TABLE A1PK;
DROP SEQUENCE A1PK_seq;
-- Seq creation
CREATE SEQUENCE A1PK_seq START WITH 1 INCREMENT BY 1;
-- Provding req privileges
GRANT SELECT ON INFRA_OWNER.A1PK_seq TO PUBLIC;
--Root table creation
CREATE TABLE AIK
(PK_ID NUMBER PRIMARY KEY,
PK_NAME VARCHAR2(100));
--Drop existing object
DROP TABLE FK1;
--Create Child table
CREATE TABLE FK1
(
PK_ID NUMBER,
PK_ADD1 VARCHAR2(100),
PK_ADD2 VARCHAR2(100)
);
--Drop any existing constraints if any with same name
ALTER TABLE FK1
DROP CONSTRAINT FK_PK;
--Adding foreign key for child table
ALTER TABLE FK1
ADD CONSTRAINT FK_PK FOREIGN KEY(PK_ID) REFERENCES AIK(PK_ID);
CREATE OR REPLACE PROCEDURE insert_into_child_tables
(p_seg1 IN VARCHAR2,
p_seg2 IN VARCHAR2,
p_seg3 IN VARCHAR2,
p_root_val IN VARCHAR2)
AS
lv_long LONG;
lv_seq PLS_INTEGER;
BEGIN
SELECT INFRA_OWNER.A1PK_SEQ.NEXTVAL
INTO lv_seq
FROM DUAL;
INSERT
INTO INFRA_OWNER.AIK VALUES
(
lv_seq,
p_root_val
);
FOR I IN
(SELECT a1.OWNER,
a1.CONSTRAINT_NAME,
a1.TABLE_NAME
FROM ALL_CONSTRAINTS a1
WHERE A1.R_CONSTRAINT_NAME IN
(SELECT a2.CONSTRAINT_NAME
FROM ALL_CONSTRAINTS a2
WHERE a2.TABLE_NAME = 'AIK'
AND a2.constraint_type = 'P'
)
ORDER BY A1.TABLE_NAME
)
LOOP
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
END LOOP;
END;
I have 3-4 tables in my database which I want to track the changes for.
I am mainly concerned about updates.
Whenever updates happen, I want to store previous entry (value or complete row) in audit table.
Basic columns I was thinking of are as following:
AuditId, TableName, PK1, PK2, PK3, PKVal1, PKVal2, PKVal3, UpdateType, PrevEntryJSON
JSON will be of format: Key:Value and I preferred to go with it as columns keep on changing and I want to keep all values even if they don't change.
Other option is to remove JSON with 100's of columns which will have names same as different columns (cumulative of all tables).
I wanted to hear people's views on this. How could I improve on it and what issues could I face?
Going through triggers might not be preferable way but I am open to it.
Thanks,
I have seen a very effective implementation of this which goes as follows:
TABLE audit_entry (
audit_entry_id INTEGER PRIMARY KEY,
audit_entry_type VARCHAR2(10) NOT NULL,
-- ^^ stores 'INSERT' / 'UPDATE' -- / 'DELETE'
table_name VARCHAR2(30) NOT NULL,
-- ^^ stores the name of the table that is changed
column_name VARCHAR2(30) NOT NULL,
-- ^^ stores the name of the column that is changed
primary_key_id INTEGER NOT NULL,
-- ^^ Primary key ID to identify the row that is changed
-- Below are the actual values that are changed.
-- If the changed column is a foreign key ID then
-- below columns tell you which is new and which is old
old_id INTEGER,
new_id INTEGER,
-- If the changed column is of any other numeric type,
-- store the old and new values here.
-- Modify the precision and scale of NUMBER as per your
-- choice.
old_number NUMBER(18,2),
new_number NUMBER(18,2),
-- If the changed column is of date type, with or without
-- time information, store it here.
old_ts TIMESTAMP,
new_ts TIMESTAMP,
-- If the changed column is of VARCHAR2 type,
-- store it here.
old_varchar VARCHAR2(2000),
new_varchar VARCHAR2(2000),
...
... -- Any other columns to store data of other types,
... -- e.g., blob, xmldata, etc.
...
)
And we create a simple sequence to give us new incremental integer value for audit_entry_id:
CREATE SEQUENCE audit_entry_id_seq;
The beauty of a table like audit_entry is that you can store information about all types of DMLs- INSERT, UPDATE and DELETE in the same place.
For e.g., for insert, keep the old_* columns null and populate the new_* with your values.
For updates, populate both old_* and new_* columns whenever they are changed.
For delete, just populate the old_* columns and keep the new_* null.
And of course, enter the appropriate value for audit_entry_type. ;0)
Then, for example, you have a table like follows:
TABLE emp (
empno INTEGER,
ename VARCHAR2(100) NOT NULL,
date_of_birth DATE,
salary NUMBER(18,2) NOT NULL,
deptno INTEGER -- FOREIGN KEY to, say, department
...
... -- Any other columns that you may fancy.
...
)
Just create a trigger on this table as follows:
CREATE OR REPLACE TRIGGER emp_rbiud
-- rbiud means Row level, Before Insert, Update, Delete
BEFORE INSERT OR UPDATE OR DELETE
ON emp
REFERENCING NEW AS NEW OLD AS OLD
DECLARE
-- any variable declarations that deem fit.
BEGIN
WHEN INSERTING THEN
-- Of course, you will insert empno.
-- Let's populate other columns.
-- As emp.ename is a not null column,
-- let's insert the audit entry value directly.
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
new_varchar)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'ENAME',
:new.empno,
:new.ename);
-- Now, as date_of_birth may contain null, we do:
IF :new.date_of_birth IS NOT NULL THEN
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
new_ts)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'DATE_OF_BIRTH',
:new.empno,
:new.date_of_birth);
END IF;
-- Similarly, code DML statements for auditing other values
-- as per your requirements.
WHEN UPDATING THEN
-- This is a tricky one.
-- You must check which columns have been updated before you
-- hurry into auditing their information.
IF :old.ename != :new.ename THEN
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
old_varchar,
new_varchar)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'ENAME',
:new.empno,
:old.ename,
:new.ename);
END IF;
-- Code further DML statements in similar fashion for other
-- columns as per your requirement.
WHEN DELETING THEN
-- By now you must have got the idea about how to go about this.
-- ;0)
END;
/
Just one word of caution: be selective with what tables and columns you choose to audit, because anyways, you this table will have a huge number of rows. SELECT statements on this table will be slower than you may expect.
I would really love to see any other sort of implementation here, as it would be a good learning experience. Hope your question gets more answers, as this is the best implementation of an audit table that I have seen and I'm still looking for ways to make it better.
I have 2 tables user and userinfo. userinfo table contains user_id(id of user table) column which has UNIQUE constraint.
now i have 2users(primaryUser and secondaryUser) which has records in user and userInfo tables.
The primaryInfo object contains primaryUserId and secondaryInfo object contains secondaryUserId
I want to swap the userinfo data of primaryUser to secondaryUser and viceversa. I am doing like this
primaryInfo.setUserId(secondaryUser.getId());
secondaryInfo.setUserId(primaryUser.getId());
session.update(primaryInfo);
session.update(secondaryInfo);
but when commiting the transaction it is giving error like
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper:147 ERROR: duplicate key value violates unique constraint "user_infos_unique_user"
Detail: Key (ui_user_id)=(52560087) already exists.
can you please tell how to do this.. Thanks
You can use the DEFERRABLE and INITIALLY DEFERRED properties on the constraint and update both records in a single transaction. DEFERRED means the constraint will not be evaluated until the transaction is commited -- at which time it should be valid again.
However: I have not figured out how to use Hibernate annotations to specify the DEFERRED properties, so you will have to use LiquiBase to maintain the database schema (not a bad idea anyway.) (Or use "raw" SQL which is not so good an idea.)
See this question for more about the annotations (alas I cannot use LiquiBase on the project I ask about there.)
For Oracle database you can create next unique constraint with special attributes 'DEFERRABLE INITIALLY DEFERRED':
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE (table_field) DEFERRABLE INITIALLY DEFERRED
A possible trick to work around the unique constraint is to do 3 updates:
update row A with a value for the column that no other row can use. NULL may be used if not forbidden by a not-NULL constraint, otherwise 0 if not forbidden and assuming it's an integer, otherwise a negative value.
then update row B with its final value (the previous value from row A)
then update row A with its real final value (the previous value from row B)
As error Shows:
there is a unique constraint on userInfo table. that means user must be unique. So If you wnat to swipe the two user Id. you have to perform following steps
1. Remove the constraint
2. Swap two id's(same code as you currently have)
3. Add Constaint.
I'm kinda stuck in a tricky situation with the mySQL DB design for my webservice.The DB had initially this Structure:
CREATE TABLE IF NOT EXISTS `Disease` (
`Name` varchar(20) NOT NULL,
`Age` int(10) unsigned NOT NULL,
`Descriptin` text NOT NULL,
`Sex` varchar(10) NOT NULL,
`Ethnicity` varchar(20) NOT NULL,
PRIMARY KEY (`Name`,`Sex`,`Ethnicity`),
KEY `Sex` (`Sex`),
KEY `Ethnicity` (`Ethnicity`)
)
ALTER TABLE `Disease`
ADD CONSTRAINT `Disease_ibfk_1` FOREIGN KEY (`Sex`) REFERENCES `Sex` (`Sex`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `Disease_ibfk_2` FOREIGN KEY (`Ethnicity`) REFERENCES `Ethnicity` (`Ethnicity`) ON DELETE CASCADE ON UPDATE CASCADE;
So basically Disease(Name,Age,Description,Sex, Ethnicity ) Where Sex and Ethnicity are foreign keys to two tables named Sex and Ethnicity because they can have more than one value.
Now to the question I need to add another Column called Symptoms which will be multivalued but I cant declare it as a foreign key, what i need is this:
example of a row
Disease(Name="DiseaseName",Age="40",Description="is caused by...",Sex="male",Ethnicity="Caucasian",Symptoms"Headache,sorethroat,fever")
So basically i need Symptoms to contain a String of Array but apparently I cant do it since its an RDBMS
Thanks all for you time and efforts!
Don't do that. Instead, normalize your data model: Make a new table "Symptoms", constrained with foreign key "Disease", and make one record for each symptom.
Whenever you start thinking about putting collections of data into a single field, you're effectively trying to build your own mini database inside the database. Thinking that you can outperform and outwit your RDBMS is optimistic at best and most likely leads to unmaintainable code later on.
By the way, does Sex really have to be looked up in a separate table? For micro-categories like that you might like to consider some sort of enum type.
You can accomplish the "array of strings" you're looking for by normalizing your data. Add a new key column to your 'Disease' table. Then create a child table called 'Symptom'. Insert a record for each string with a foreign key back to the 'Disease' table parent record.
In case you didn't notice you misspelled Description in your table creation query.
You need a m:n relation:
Table: Disease
Name
Sex
Ethnicity
Table: Symptoms
ID
Name
Table: Disease_has_Symptoms
Name (FK to Disease)
Sex (FK to Disease)
Ethnicity (FK to Disease)
ID (FK to Symptoms)
(Maybe it's easier to add an ID-column to Disease and reference that inside Disease_has_Symptoms)