Is it bad practice to update part of primary key? - oracle

I have the pretty bad headache about primary key based on the three rows:
ID DATE_START DATE_END
They describes the version of object. Version of object is the combination of ID, D_S, D_E and it has no any breaks inside of timeline.
There is a few states of object:
State 1 (latest version):
ID DATE_START DATE_END
1 01-01-2022 00:00:00 31-12-9999 00:00:00
State 2 (insert new version)
ID DATE_START DATE_END
1 01-01-2022 00:00:00 01-02-2022 23:59:59
1 02-02-2022 00:00:00 31-12-9999 00:00:00
So you can see each time user creates new version, we should update DATE_END on previous, like this:
DATE_END = DATE_START - 1 Second
I'm almost sure that updating part of PK is the bad practice. I'm on to drop DATE_END from PK at all, in case of there's no any breaks inside timeline.
But my colleagues try to convince me that we should have DATE_END - it's pretty slow to search version on some date cuz we should compare DATE_START of each row against to comparing DATE_START and DATE_END in one row.
Could someone explain me who's wrong and what is the best solution in that case?

You could only store the start date and calculate the end-date; or you could store the end date and have to UPDATE the last record.
The first option incurs greater relative cost when you SELECT the output and a lower cost on INSERT;
The second option incurs a greater relative cost when you INSERT (and UPDATE) and a lower cost on SELECT.
So if all you are doing is INSERTing and SELECTing then you can look at which option you do more and prioritise the lower-cost option for that.
However, if the primary key is also the target of referential constraints then your costs are exponentially higher for having to modify an existing primary key are you will also have to modify all the columns referenced through the constraints and that will quickly become a nightmare.
What you should do is divorce the primary key used in referential constraints from the date columns that are going to be updated and use a surrogate primary key:
CREATE TABLE table_name (
key NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
id NUMBER
NOT NULL,
start_date DATE
NOT NULL,
end_date DATE
NOT NULL,
CONSTRAINT table_name__id__sd__ed__unique UNIQUE (id, start_date, end_date)
);
or:
CREATE TABLE table_name (
key NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
id NUMBER
NOT NULL,
start_date DATE
NOT NULL,
CONSTRAINT table_name__id__sd__ed__unique UNIQUE (id, start_date)
);
Then you do not need to modify any referential constraints and you can evaluate whether you want to have greater costs during INSERTion or SELECTion.
Whichever of those two you prioritise, that decision is a business decision and there is no "best" choice as what might work in one situation will not work for another situation.

I'd say that modifying primary key really is bad practice. Mind you, primary keys often are referenced by foreign keys; even if you tried to update such a primary key (referenced by other foreign key(s)), you won't be able to do it because Oracle will complain that - most probably - parent key doesn't exist. What will you do then? Disable foreign key constraint, update primary key value, update foreign key(s), enable foreign key constraints and hope that nobody/nothing did something that will violate referential integrity during that time (with disabled foreign keys).
So, why wouldn't you rather use a surrogate primary key? In modern Oracle database versions, use an identity column. If your Oracle database doesn't support it, use a sequence. Never modify its value, reference it from other tables and maintain referential integrity. Then, if you want, update those end dates, if you must, as it won't break anything.

Related

Oracle Reference Partition - How to make sure I'm getting the benefits when I select

I'm trying to make sure I'm getting the benefit of selecting from a partition when using reference partitions.
In normal partitions, I know you have to include the column(s) on which the partition is defined in order for Oracle to know it can just search one specific partition.
My question is, when I'm selecting from a reference-partitioned table, do I just need to include the column on which the reference foreign key is defined? Or do I need to join and include the parent table's column on which the partition is actually defined?
create table alpha (
name varchar2(240) not null,
partition_no number(14) not null,
constraint alpha_pk
primary key (name),
constraint alpha_c01
check (partition_no > 0)
)
partition by range(partition_no)
interval (1)
(partition empty values less than (1))
;
create table beta (
name varchar2(240) not null,
alpha_name varchar2(240) not null,
some_data number not null,
constraint beta_pk
primary key (name),
constraint beta_f01
foreign key (alpha_name)
references alpha (name)
)
partition by reference (beta_f01)
;
Assume the tables in production will have much more data in them, with hundreds of millions of rows in the beta table, but merely thousands per partition.
Is this all I need?
select b.some_data
from beta b
where b.alpha_name = 'Blah'
;
Thanks if anyone can verify this for me. Or can explain anything else I'm missing with regard to properly creating indexes in reference-partitioned tables.
[Edit] Removed part of the example where clause that shouldn't have been there. The example is meant to represent reading the reference-partitioned with just the reference partition foreign key in the where clause.

Do I need a primary key with an Oracle identity column?

I am using Oracle 12c and I have an IDENTITY column set as GENERATED ALWAYS.
CREATE TABLE Customers
(
id NUMBER GENERATED ALWAYS AS IDENTITY,
customerName VARCHAR2(30) NULL,
CONSTRAINT "CUSTOMER_ID_PK" PRIMARY KEY ("ID")
);
Since the ID is automatically from a sequence it will be always unique.
Do I need a PK on the ID column, and if yes, will it impact the performance?
Would an index produce the same result with a better performance on INSERT?
No, you don't need a primary key necessarily, but you should always provide the optimiser as much information about your data as possible - including a unique constraint whenever possible.
In the case of a surrogate key (like your ID), it's almost always appropriate to declare it as a Primary Key, since it's the most likely candidate for referential constraints.
You could use an ordinary index on ID, and performance of lookups will be comparable depending on data volume - but there is virtually no good reason in this case to use a non-unique index instead of a unique index - and there is no good reason in this case to avoid the constraint which will require an index anyway.
Yes, you always should have a Uniqueness constraint (with rare exceptions) on an ID column if indeed it is (and should be) unique, regardless of the method by which it is populated - whether the value is provided by your application code or via an IDENTITY column.

Oracle Partial Foreign Key that is not unique

I have some problems with Oracle Foreign Composite keys. I have an application that is somewhat big (you know, 5000+ tables, that kind of thing) and we store some 'static' (it actually can change) data into some set of tables. This data is referenced by a lot of the tables through the database, so it worked like this:
TABLE StaticData
ID(PK) Data
1 StaticData1
2 StaticData2
...
n StaticDataN
TABLE TypicalTable
ID(PK) StaticDataID(FK to StaticData)
1 1
2 1
3 7
4 2
...
n n
And all was well in Wonderland.
But some changes of spec, and some meetings with the client after, we were tasked with having different 'versions' of the data ready to replace the static data when some time arrives. Last part was easy, we can create jobs that will check every day/week for a date and change the data, but we will have to maintain older and newer versions of the data... in the same table. So now StaticData looks like:
TABLE StaticData
ID(PK) Data KickInDate(Also PK)
1 StaticData1.1 01/01/1900
1 StaticData1.2 10/07/2014
1 StaticData1.3 12/12/2015
2 StaticData2.1 01/01/1900
...
n StaticDataN.1 01/01/1900
And of course all integrity reference has gone off the board. And of course, since I cannot put a UNIQUE constraint in the ID, I cannot keep the foreign keys.
I have searched the net for a solution for this (less restrictive kind of foreign keys) and most of the time the solution is to use triggers checking BEFORE INSERT|UPDATE|DELETE
But that will be kind of a very, very, very big job.
So I ask, Do I have other solutions?
Is there any way to tell Oracle to reference another column of another table even thought is not UNIQUE? (it will definitely be NOT NULL).
Primary keys in Oracle can have duplicates. Primary keys can be built with non-unique indexes and existing values can be excluded by creating the constraint with NOVALIDATE. It is a rarely used feature, will confuse people, and is not a clean solution. But in the real world sometimes data isn't clean and there's no time for the perfect solution.
Sample schema and data.
create table staticData
(
id number not null,
data varchar2(100),
constraint staticData_pk primary key (id)
);
create table typicalTable
(
id number not null,
staticDataID number,
constraint typicalTable_pk primary key (id),
constraint typicalTable_fk foreign key (staticDataID)
references staticData(id)
);
insert into staticData values (1, 'StaticData1');
insert into staticData values (2, 'StaticData2');
insert into typicalTable values(1, 1);
insert into typicalTable values(2, 1);
Process to drop constraints, add duplicate data, and re-enable constraints.
--Drop constraints.
alter table typicalTable drop constraint typicalTable_fk;
alter table staticData drop constraint staticData_pk;
--Add semi-duplicate data.
insert into staticData values (1, 'StaticData1.2');
--Use a non-unique index to build a NOVALIDATE primary key.
create index staticData_pk on staticData(id);
alter table staticData add constraint staticData_pk primary key (id) novalidate;
alter table typicalTable add constraint typicalTable_fk foreign key(staticDataID)
references staticData(id);
No, the target column(s) must be unique, that's the whole idea. However, you can propagate an additional version column from StaticData to TypicalTable:
CREATE TABLE StaticData (
id NUMBER,
version NUMBER,
col1 ... coln,
PRIMARY KEY (id,version)
);
CREATE TABLE TypicalTable (
StaticDataID NUMBER,
version NUMBER,
colx ... coly,
FOREIGN KEY (StaticDataID, version) REFERENCES StaticData(id, version)
);

Oracle create table to handle valid_from and valid_to data

I am in the middle of designing a table which include two columns valid_from and valid_to to track historical changes. For example, my table structure is like below:
create table currency_data
(
currency_code varchar(16) not null,
currency_desc varchar(16) not null,
valid_from date not null,
valid_to date,
d_insert_date date,
d_last_update date,
constraint pk_currency_data primary key (currency_code, valid_from)
)
The idea is to leave the valid_to as blank to start with, and if the currency_desc changes in the future, I will need to set a valid_to to the date that the old description is not valid any more, and create a new rows with a new valid_from. But how can I ensure that there will be never a overlap between these 2 rows. For example the query below should only yield one row.
select currency_desc
from currency_data
where currency_code = 'USD'
and trunc(sysdate) between valid_from and nvl(valid_to, sysdate)
Is there a better way to achieve this please other than make sure all developers/end users aware of this rule. Many thanks.
There is a set of implementation approaches known as slowly changing dimensions (SCD) for handling this kind of storage.
What you are currently implementing is SCD II, however, there are more.
Regarding your possible interval overlap issue - there is no simple way to enforce table-level (instead of row-level) consistency with standard constraints, so I guess a robust approach would be to restrict direct DML to this table and wrap it into some standartized pl/sql API which will enforce your riles prior to insert/update and which every developer will use.

Trigger to prevent inserting 2 same values in one table

I have little problem with programming trigger for my dtb. I need to control 2 values in one 1 table. I have table called Concert and it has 2 foreign keys: 1 is the id of table Place. Second is not important for this I think.
Concert: id_concert, id_place<fk>, id_organizer<fk>, date, name, sponsor
Place: id_place, name, capacity, adress, town
What I want to eliminate is, that 2 concerts organized at same day cannot be on one place. So, I need to somehow control that user cannot insert the same date and same place for concert if there already concert with this values exists.
Thank you very much for your suggestions and sorry for bad english.
You need to add a unique constraint on your Concert table that consists of the (id_place, date) pair. This would instruct the database engine to not allow more than one Concert in the same place at the same time.
For Oracle, information can be found here: http://www.techonthenet.com/oracle/unique.php
CREATE TABLE Concert
(
... (filled in with your existing table definition)
CONSTRAINT concert_place_unique UNIQUE (id_place, date)
);
or to alter an existing table:
ALTER TABLE Concert
add CONSTRAINT concert_place_unique UNIQUE (id_place, date);
Constraints are the proper way to handle this condition, not triggers. Constraints are database intrinsic and have no race conditions and prevent the data from being added in the first place.

Resources