Create FOREIGN KEY to versioned table - sql

I have version logic in tables. So, each table contains 'is_deleted' and 'version' fields. ('version' field is referenced to single table with all versions)
table1(id, field1_1, field1_2, is_deleted, version[fk to versions.id])
table2(id, field2_1, field2_2, table1_id, is_deleted, version[fk to versions.id])
versions(id, main_version)
When I get records from db I use where clause for each of referenced table and for table itself. Something like:
SELECT * FROM table1 AS t1
WHERE (
(
(t1.id, t1."version") IN
(SELECT sub_t1.id, max(sub_t1."version") FROM table AS sub_t1 GROUP by sub_t1.id)
)
AND t1.is_deleted IS FALSE
)
For MySQL you can change condition for 'is_deleted' to
is_deleted = 0
Now, I'm adding new tables and I want to use foreign keys, but foreign key can reference only unique column, but id is not unique (it is kept through versions) and I can't use complex key, because new version created only when smth changed and only for that record.
Should I omit FK or should I add additional table with only one column id for each of versioned tables? Will adding such tables offer some benefit except for data integrity? Adding such tables will make logic more complicated.
So, should I live it untouched or make smth like this:
table1_ids(id)
table1_versions(id, table1_id[fk to table1_ids.id], ...)
table2_versions(id, ..., table1_id[fk to table1_ids.id],...)
table2_ids(id)

Can you rather create schema for every version ?
It will be much more clean solution.

Related

Oracle: selfcopying data from Oracle tables

Application has different versions. Each version has it's own set of values in each table. I need to provide functionality to copy data from one version to another. Problem :
By inserting data I am trying to insert Ids which has already been in use in this table. So, I need to change ids of components which I want to insert but I must save relationship between those components. How cat I do that?
Create a master table which has a surrogate key as your primary key. A numeric value of type NUMBER(9) works well. You can create a sequence and trigger to automatically insert this.
The rest of the table is the column of your current table plus a column to indicate which version the row is for.
For simplicity you may wish to create views on top of the table along the lines of
select * from master_table where version_id = ####;
To copy the data from one version to another this will work:
Insert into master_table seq_master_table.nextval, new version_id,.....
from master_table
where version_id = ####;

Primary key violation while merging data from another table

I have two tables, TBTC03 and TBTC03Y, with TBTC03Y having two extra columns as EFFDTE and EXPDTE. I have to merge the data from TBTC03 to TBTC03Y with the following logic:
If no matching TC03 entry is found in TC03Y
a new TC03Y record is build with the TC03 data
the Effective Date will default to '01-01-1980'
the Expiration Date will default to '09-30-1995'
I wrote a query for the same as :
insert into TBTC03Y (LOB,MAJPERIL,LOSSCAUSE,NUMERICCL,EFFDTE,EXPDTE)
select LOB,MAJPERIL,LOSSCAUSE,NUMERICCL,'0800101' ,'0950930'
from TBTC03 where not EXISTS (select * from TBTC03Y where
TBTC03Y.LOB = TBTC03.LOB AND
TBTC03Y.MAJPERIL = TBTC03.MAJPERIL AND
TBTC03Y.LOSSCAUSE = TBTC03.LOSSCAUSE AND
TBTC03Y.NUMERICCL = TBTC03.NUMERICCL)
The primary key for both the tables is LOB, MAJPERIL and LOSSCAUSE.
However i have some TBTC03Y records, that already have the data with the primary key.
Firing the above query gives primary key constraints on some of the rows.
I am unable to figure out how i can acomplish it.
The issue with the primary key is that you're also including NUMERICCL in the WHERE clause. If you remove this you'll then be inserting unique data.
You may have to create a separate process as it appears you have some records in each table that have the same LOB, MAJPERIL and LOSSCAUSE but have a different NUMERICCL. I can think of three options here;
You have an issue with the data that needs fixing.
Maybe you want to update this value to match, in which case you're looking at an UPDATE rather than INSERT INTO.
You need to update your composite primary key to include the column NUMERICCL.
Removing NUMERICCL from the where clause would also correct this.
If the PK for both tables is {LOB, MAJPERIL, LOSSCAUSE}, you should remove TBTC03Y.NUMERICCL = TBTC03.NUMERICCL from your where clause.
Example:
t1{LOB, MAJPERIL, LOSSCAUSE, NUMERICCL}
1 1 1 1
t2{LOB, MAJPERIL, LOSSCAUSE, NUMERICCL}
1 1 1 2
In t2 there is no row where:
TBTC03Y.LOB = TBTC03.LOB AND
TBTC03Y.MAJPERIL = TBTC03.MAJPERIL AND
TBTC03Y.LOSSCAUSE = TBTC03.LOSSCAUSE AND
TBTC03Y.NUMERICCL = TBTC03.NUMERICCL
But inserting will obvioulsy violate PK constraint in t2:
t2{LOB, MAJPERIL, LOSSCAUSE}
1 1 1

Relating two tables

I have created tables T1 with columns( id as Primary key and name) and T2 with columns( id as primary key, name, t_id as foreign key references T1(id)) . I Inserted some values from inputs from a Windows form. After querying SELECT * FROM T2; using isql, all the values in the foreign key column are null instead of duplicating values in T1(id) because of the relationship created. Is they anything I have left out or need to add? The primary key of both tables are autoincremented.
You are confusing auto-incremented keys and relationship uses.
Auto-incremented keys (or generally talking, fields) just help you when you are inserting a new record on the table of the key. But when you are inserting a new record that makes a reference to a record in another table, then you must specify that record, using the foreign key field. Or in your case, the user that is inserting the "name" in T2 must say which one record on T1 that "name" in T2 is making a reference.
Your confusion on the relationship is that you are thinking that an established relationship will enforce the use of that values automatically. But the relationship just enforce the validation of the values. So, the field t_id in T2 will not use the value of the last record of T1 automatically. But if you try to insert a value that do not exist in T1 in the field t_id, the relationship will not let you do.
So, answering your question, what you left out and need to add?
You left out the part of the code that insert the value on the t_id field of T2 table.
Let me try to explain using an example that is more common.
The most common case of this is that the application insert first the T1 record and then when the user is inserting T2, the application provide a way to the user to choose which one T1 record his T2 record is referencing.
Suppose T1 is a publishers table and T2 is a book table. User insert a publisher, and when it is inserting a book it can choose which one publisher publish that book.
Field "ID" of Customers will be AUTOINCREMENT by default in table create using Event BeforeInsert on table CUSTOMERS. LOOK AT
CREATE TRIGGER nametrigger FOR nametable
ACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.ID IS NULL) THEN BEGIN
NEW.ID = GEN_ID(GEN_PK_ID, 1);
END
END
Now one new record in Customers
INSERT INTO Customers (CustomerName, ContactName, Address, City, PostalCode, Country)
VALUES ('Cardinal','Tom B. Erichsen','Skagen 21','Stavanger','4006','Norway');
Then ID will be automaticaly one sequencial number from 1 up to last integer or smallint or bigint as you defined in your create table (pay attencion that ID field is not include in FIELDS and VALUES) because TRIGGER
now you can use the dataset (obj) options to link the table MATER and DETAIL see in help delphi
or in SQL you can to use PARAMS FIELDS
later insert one new record in table MASTER try...
INSERT INTO xTable2 (IDcustomersField, ..., ..., ...., ....)
VALUES ( :IDcustomersField, ..., ..., ...., ....);
xTable2 may using one field ID (Primary Key) autoincrement too. this help when DELETING or UPDATING fileds in this table
Then you can say the value to :IDcustomersField in table detail using
xQuery.PARAM( 0 ).value or xQuery.PARAMBYNAME( IDcustomersField).value (here im using Query obj as example )
you can to use example with DATASOURCE in code to say the value for IDcustomersField
can to use
Events in SQL
can to use
PROCEDURE IN SQL
DONT FORGOT
you have to create Relationship between two table ( REFERENCIAL INTEGRITY and PRIMARY KEY in mater table ) NOT NULL FOR TWO FIELDS ON TABLES
I believe that understand me about my poor explanation (i dont speak english
You need to insert the values for t_id manually, after you get the ID's value from the main table T1.
Depending on your logic in the database you also can use a trigger or a stored procedure. Give us more information about what values you expect to have in NAME field in T2 after the insert? Are they duplicates from T1 or independent from T1?
If T1.NAME=T2.NAME, you can automate the process with a trigger
CREATE OR ALTER TRIGGER TR_T1_AI0 FOR T1
ACTIVE AFTER INSERT POSITION 0
AS
BEGIN
INSERT INTO T2(NAME, T_ID)
VALUES (NEW.NAME, NEW.ID);
END
If T2.NAME's value is different from T1.NAME you can use a stored procedure with parameters both names:
CREATE ORA ALTER PROCEDURE XXXX(
P_NAME_T1 TYPE OF T1.NAME,
P_NAME_T2 TYPE OF T2.NAME)
AS
DECLARE VARIABLE L_ID TYPE OF T1.ID;
BEGIN
INSERT INTO T1(NAME)
VALUES (:p_NAME_T1)
RETURNING ID INTO:L_ID;
INSERT INTO T2(NAME, T_ID)
VALUES (:P_NAME_T2, :l_ID);
END
You can use both statements from the stored procedure directly in your program if it supports the returning syntax. If not, you need an additional query with SELECT NEXT VALUE FOR GENERATOR_FOR_T1 FROM RDB$DATABASE; and use the value returned from it in both INSERT statements.

Use a column name to specify a predefined table

I'm trying to use set up a database for a school project, and I'm using triggers to set up referential integrity for one table. I have a table, Addresses, which stores the address for People, Studios, and Directors. Then I have a table called Address Reference. This table points to the Address table, and it has a two fields, the ReferenceID and the TableName to show which table and row this address is for. I have a Constraint so TableName will always be valid.
I'm trying to set up a trigger to make sure any rows inserted are valid, which I can do, I'm just trying to improve it. My code would look like this:
SELECT *
FROM inserted
WHERE ReferenceID IN
(SELECT PersonID
FROM inserted.TableName)
However I found I needed to use dynamic sql. So I was thinking something like this:
SELECT *
FROM inserted
WHERE ReferenceID IN
(EXEC('SELECT PersonID FROM' + inserted.TableName))
Which didn't work, even when I removed the exec.
I'm doing this in SQL Server Management Studio With SQL Server 11.0.3128
Let me know if you need any more information. I've looked around, and I haven't found any answers to this question that work.
This is a poor way to maintain referential integrity. There are a number of ways you could approach this.
The first would be to have an address table, then multiple tables to contain the links, e.g.
CREATE TABLE StudioAddress
( StudioID INT NOT NULL,
AddressID INT NOT NULL,
CONSTRAINT PK_StudioAddress__StudioID_AddressID PRIMARY KEY (StudioID, AddressID),
CONSTRAINT FK_StudioAddress__StudioID FOREIGN KEY (StudioID) REFERENCES Studio (StudioID),
CONSTRAINT FK_StudioAddress__AddressID FOREIGN KEY (AddressID) REFERENCES Address (AddressID)
);
This maintains your referenctial integrity without needing triggers, and still caters for a 1 to many relationship.
Another option would be to have 3 nullable columns in your address table (StudioID, PersonID, DirectorID), each with a foreign key to the relevant table, you can the add a check constraint to ensure only one of the 3 fields is populated (if this is required).
I much prefer the first option though, it is much cleaner, and also allows for the same address to be used for multiple things.
ADENDUM
If this has to be done using triggers, then I think you would need to use something like this:
IF EXISTS( SELECT 1
FROM inserted i
WHERE NOT EXISTS
( SELECT 1
FROM People p
WHERE p.PersonID = i.ReferenceID
AND i.TableName = 'People'
UNION ALL
SELECT 1
FROM Studios s
WHERE s.StudioID = i.ReferenceID
AND i.TableName = 'Studios'
UNION ALL
SELECT 1
FROM Directors d
WHERE d.DirectorID = i.ReferenceID
AND i.TableName = 'Directors'
)
)
BEGIN
ROLLBACK TRANSACTION;
RAISERROR('Referential integrity error', 16, 1);
END
This essentially checks that for all inserted/updated rows a record exists with the relevant ID in the relevant table.
I still stand by my earlier answer though, that this is a terrible approach, and I would question any syllabus this is on!

Create a unique primary key (hash) from database columns

I have this table which doesn't have a primary key.
I'm going to insert some records in a new table to analyze them and I'm thinking in creating a new primary key with the values from all the available columns.
If this were a programming language like Java I would:
int hash = column1 * 31 + column2 * 31 + column3*31
Or something like that. But this is SQL.
How can I create a primary key from the values of the available columns? It won't work for me to simply mark all the columns as PK, for what I need to do is to compare them with data from other DB table.
My table has 3 numbers and a date.
EDIT What my problem is
I think a bit more of background is needed. I'm sorry for not providing it before.
I have a database ( dm ) that is being updated everyday from another db ( original source ) . It has records form the past two years.
Last month ( july ) the update process got broken and for a month there was no data being updated into the dm.
I manually create a table with the same structure in my Oracle XE, and I copy the records from the original source into my db ( myxe ) I copied only records from July to create a report needed by the end of the month.
Finally on aug 8 the update process got fixed and the records which have been waiting to be migrated by this automatic process got copied into the database ( from originalsource to dm ).
This process does clean up from the original source the data once it is copied ( into dm ).
Everything look fine, but we have just realize that an amount of the records got lost ( about 25% of july )
So, what I want to do is to use my backup ( myxe ) and insert into the database ( dm ) all those records missing.
The problem here are:
They don't have a well defined PK.
They are in separate databases.
So I thought that If I could create a unique pk from both tables which gave the same number I could tell which were missing and insert them.
EDIT 2
So I did the following in my local environment:
select a.* from the_table#PRODUCTION a , the_table b where
a.idle = b.idle and
a.activity = b.activity and
a.finishdate = b.finishdate
Which returns all the rows that are present in both databases ( the .. union? ) I've got 2,000 records.
What I'm going to do next, is delete them all from the target db and then just insert them all s from my db into the target table
I hope I don't get in something worst : - S : -S
The danger of creating a hash value by combining the 3 numbers and the date is that it might not be unique and hence cannot be used safely as a primary key.
Instead I'd recommend using an autoincrementing ID for your primary key.
Just create a surrogate key:
ALTER TABLE mytable ADD pk_col INT
UPDATE mytable
SET pk_col = rownum
ALTER TABLE mytable MODIFY pk_col INT NOT NULL
ALTER TABLE mytable ADD CONSTRAINT pk_mytable_pk_col PRIMARY KEY (pk_col)
or this:
ALTER TABLE mytable ADD pk_col RAW(16)
UPDATE mytable
SET pk_col = SYS_GUID()
ALTER TABLE mytable MODIFY pk_col RAW(16) NOT NULL
ALTER TABLE mytable ADD CONSTRAINT pk_mytable_pk_col PRIMARY KEY (pk_col)
The latter uses GUID's which are unique across databases, but consume more spaces and are much slower to generate (your INSERT's will be slow)
Update:
If you need to create same PRIMARY KEYs on two tables with identical data, use this:
MERGE
INTO mytable v
USING (
SELECT rowid AS rid, rownum AS rn
FROM mytable
ORDER BY
co1l, col2, col3
)
ON (v.rowid = rid)
WHEN MATCHED THEN
UPDATE
SET pk_col = rn
Note that tables should be identical up to a single row (i. e. have same number of rows with same data in them).
Update 2:
For your very problem, you don't need a PK at all.
If you just want to select the records missing in dm, use this one (on dm side)
SELECT *
FROM mytable#myxe
MINUS
SELECT *
FROM mytable
This will return all records that exist in mytable#myxe but not in mytable#dm
Note that it will shrink all duplicates if any.
Assuming that you have ensured uniqueness...you can do almost the same thing in SQL. The only problem will be the conversion of the date to a numeric value so that you can hash it.
Select Table2.SomeFields
FROM Table1 LEFT OUTER JOIN Table2 ON
(Table1.col1 * 31) + (Table1.col2 * 31) + (Table1.col3 * 31) +
((DatePart(year,Table1.date) + DatePart(month,Table1.date) + DatePart(day,Table1.date) )* 31) = Table2.hashedPk
The above query would work for SQL Server, the only difference for Oracle would be in terms of how you handle the date conversion. Moreover, there are other functions for converting dates in SQL Server as well, so this is by no means the only solution.
And, you can combine this with Quassnoi's SET statement to populate the new field as well. Just use the left side of the Join condition logic for the value.
If you're loading your new table with values from the old table, and you then need to join the two tables, you can only "properly" do this if you can uniquely identify each row in the original table. Quassnoi's solution will allow you to do this, IF you can first alter the old table by adding a new column.
If you cannot alter the original table, generating some form of hash code based on the columns of the old table would work -- but, again, only if the hash codes uniquely identify each row. (Oracle has checksum functions, right? If so, use them.)
If hash code uniqueness cannot be guaranteed, you may have to settle for a primary key composed of as many columns are required to ensure uniqueness (e.g. the natural key). If there is no natural key, well, I heard once that Oracle provides a rownum for each row of data, could you use that?