Foreign key reference with ON UPDATE CASCADE inside array - arrays

I've a natural primary key in table A.
In table B, I want to have an array of foreign key references to A.
Is it possible to specify ON UPDATE CASCADE on the elements of the array, such that when the value of a primary key in table A changes, arrays in B get modified.
Or should I just normalise the array out into a separate table?

Normalizing this would allow you to use standard ON UPDATE CASCADE in a foreign key constraint. That would be much faster, because the system can use plain indexes. That should give you three tables. Needs somewhat more disk space, but worth every bit:
table a
table b
table a_b -- to implement n:m relationship
See:
How to implement a many-to-many relationship in PostgreSQL?
Can PostgreSQL array be optimized for join?
Else you will have to write a trigger function to find and replace all references in B to values of master A.

Is it possible to specify ON UPDATE CASCADE on the elements of the
array, such that when the value of a primary key in table A changes,
arrays in B get modified.
Only if
both the referenced column and the referencing column are arrays of the same type, and
the values have the same number of elements.
If you want to insert valid values for array elements in one table, and in another table store an array of those valid values, it won't work.
OTOH, this does work, but only in part.
create table a (
str varchar[2] primary key
);
create table b (
-- Room for two values from table a . . .
str varchar[4] primary key references a (str) on update cascade
);
insert into a values
('{''A'', ''B''}'),
('{''C'', ''D''}'),
('{''E'', ''F''}');
insert into b values
('{''A'', ''B''}');
update a set str = '{''A'',''C''}'
where str = '{''A'',''B''}';
select * from b;
{'A','C'}
That much works. But if you try to store two arrays in table b, you'll get an error.
insert into b values
('{{"C", "D"}, {"E", "F"}}');
ERROR: insert or update on table "b" violates foreign key constraint "b_str_fkey"
DETAIL: Key (str)=({{C,D},{E,F}}) is not present in table "a".
And, when you squint and tilt your head just right, that makes sense. In the relational model, the intersection of every row and column contains just one value. So you shouldn't be able to update half a value by ON UPDATE CASCADE.

Related

Postgres INSERT INTO... SELECT violates foreign key constraint

I'm having a really, really strange issue with postgres. I'm trying to generate GUIDs for business objects in my database, and I'm using a new schema for this. I've done this with several business objects already; the code I'm using here has been tested and has worked in other scenarios.
Here's the definition for the new table:
CREATE TABLE guid.public_obj
(
guid uuid NOT NULL DEFAULT uuid_generate_v4(),
id integer NOT NULL,
CONSTRAINT obj_guid_pkey PRIMARY KEY (guid),
CONSTRAINT obj_id_fkey FOREIGN KEY (id)
REFERENCES obj (obj_id)
ON UPDATE CASCADE ON DELETE CASCADE
)
However when I try to backfill this using the following code, I get a SQL state 23503 claiming that I'm violating the foreign key constraint.
INSERT INTO guid.public_obj (guid, id)
SELECT uuid_generate_v4(), o.obj_id
FROM obj o;
ERROR: insert or update on table "public_obj" violates foreign key constraint "obj_id_fkey"
SQL state: 23503
Detail: Key (id)=(-2) is not present in table "obj".
However, if I do a SELECT on the source table, the value is definitely present:
SELECT uuid_generate_v4(), o.obj_id
FROM obj o
WHERE obj_id = -2;
"0f218286-5b55-4836-8d70-54cfb117d836";-2
I'm baffled as to why postgres might think I'm violating the fkey constraint when I'm pulling the value directly out of the corresponding table. The only constraint on obj_id in the source table definition is that it's the primary key. It's defined as a serial; the select returns it as an integer. Please help!
Okay, apparently the reason this is failing is because unbeknownst to me the table (which, I stress, does not contain many elements) is partitioned. If I do a SELECT COUNT(*) FROM obj; it returns 348, but if I do a SELECT COUNT(*) FROM ONLY obj; it returns 44. Thus, there are two problems: first, some of the data in the table has not been partitioned correctly (there exists unpartitioned data in the parent table), and second, the data I'm interested in is split out across multiple child tables and the fkey constraint on the parent table fails because the data isn't actually in the parent table. (As a note, this is not my architecture; I'm having to work with something that's been around for quite some time.)
The partitioning is by implicit type (there are three partitions, each of which contains rows relating to a specific subtype of obj) and I think the eventual solution is going to be creating GUID tables for each of the subtypes. I'm going to have to handle the stuff that's actually in the obj table probably by selecting it into a temp table, dropping the rows from the obj table, then reinserting them so that they can be partitioned properly.

ORA-00001: Unique Constraint: Setting Primary Keys Manually

we have an Oracle Database and we have a table where we store a lot of data in.
This table has a primary key and usually those primary keys are just created upon insertion of a new row.
But now we need to manually insert data into this table with certain fixed primary keys. There is no way to change those primary keys.
So for example:
Our table has already 20 entries with the primary keys 1 to 20.
Now we need to add data manually with the primary keys 21 to 23.
When someone wants to enter a row using our standard approach, the insert process will fail because of:
Caused by: java.sql.BatchUpdateException: ORA-00001: Unique Constraint (VDMA.SYS_C0013552) verletzt
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10500)
at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:230)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
I totally understand this: The database routine (sequence) that is creating the next primary key fails because the next primary key is already taken.
But: How do I tell my sequence to look at the table again and to realize that the next primary key is 24 and not 21 ?
UPDATE
The reason why the IDs need to stay the same is because is accessing the records using a Web Interface using links that contain the ID.
So either we change the implementation mapping the old IDs to new IDs or we keep the IDs in the database.
UPDATE2
Found a solution: Since we are using hibernate, only one sequence is populating all the tables. Thus the primary keys in those 4 days where I was looking for an answer went so high that I can savely import all the data.
How do I tell my sequence to look at the table again and to realize that the next primary key is 24 and not 21 ?
In Oracle, a sequence doesn't know that you intend to use it for any particular table. All the sequence knows is its current value, its increment, its maxval and so on. So, you can't tell the sequence to look at a table, but you can tell your stored procedure to check the table and then increment the sequence beyond the maximum val of the primary key. In other words, if you really insist on manually updating the primary key with non sequence values, then your code needs to check for non sequence values in the PK and get the sequence up to speed before it uses the sequence to generate a new PK.
Here is something simple you can use to bring the sequence up to where it needs to be:
select testseq.nextval from dual;
Each time you run it the sequence increments by 1. Stick it in a for loop and run it until testseq.currval is where you need it to be.
Having said that, I agree with #a_horse_with_no_name and #EdStevens. If you have to insert rows manually, at least use sequence_name.nextval in the insert instead of a literal like '21'. Like this:
create table testtab (testpk number primary key, testval number);
create sequence testseq start with 1 increment by 1;
insert into testtab values (testseq.nextval, '12');
insert into testtab values (testseq.nextval, '123');
insert into testtab values (testseq.nextval, '1234');
insert into testtab values (testseq.nextval, '12345');
insert into testtab values (testseq.nextval, '123456');
select * from testtab;
testpk testval
2 12
3 123
4 1234
5 12345
6 123456

Store array in SQLite that is referenced in another table

I'm trying to store arrays (or vectors) of data in a SQLite database but I'm having problems trying to find a decent way to do so. I found some other post on StackOverflow, that I can't seem to find anymore, which mentioned storing the data in a table like the following:
CREATE TABLE array_of_points
(
id integer NOT NULL,
position integer NOT NULL,
x integer NOT NULL,
y integer NOT NULL,
PRIMARY KEY (id, position)
);
So to store all the data for a single array you would insert each item under the same ID and just increment the position. So for example to insert an array with three values it would be something like:
INSERT INTO array_of_points VALUES (0, 0, 1, 1);
INSERT INTO array_of_points VALUES (0, 1, 2, 2);
INSERT INTO array_of_points VALUES (0, 2, 3, 3);
And then to retrieve the values you would select everything with the same ID and order by the position:
SELECT x,y FROM array_of_points WHERE id = 0 ORDER BY position;
This is all great and works wonderfully, but I'm now running into a problem where I don't know how to reference an array in a different table. For example I want to do something like the following:
CREATE TABLE foo
(
id integer NOT NULL,
array_id integer NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (array_id) REFERENCES array_of_points (id)
);
This will create the table just fine but once you try to execute a query on it the foreign key constraint throws an error since it must reference both the id and position of the array_of_points table since they are part of a composite primary key.
The only solution I currently have is to just remove the foreign key from the foo table, but that is not a good solution since it means it can now hold any value even if it doesn't actually map to an array in the array_of_points table.
Is there any way to work around this problem? Or maybe there's some other way to store the data so that this is possible?
Just as an FYI, please do not suggest I store the data in some sort of comma/semi-colon/whatever delimited list because that is an even worse option that I am not going to consider. It is also not possible to do with some of the more complex objects that are going to be stored in the database.
There is one special case that this schema cannot handle: it is not possible to store an array of size zero.
This might not be a concern in practice, but it shows that the database is not fully normalized.
A foreign key always references a single parent record.
Therefore, what is missing is a table that has a single record for each array.
Implementing this would result in a schema like this:
CREATE TABLE array
(
id integer PRIMARY KEY
-- no other properties
);
CREATE TABLE array_points
(
array_id integer REFERENCES array(id),
position integer,
x, y, [...],
PRIMARY KEY (array_id, position)
) WITHOUT ROWID; -- see http://www.sqlite.org/withoutrowid.html
CREATE TABLE foo
(
[...],
array_id integer REFERENCES array(id)
);
The additional table requires more effort to manage, but now you have the ability to generate array IDs through autoincrementing.

SQL Server Conditional Foreign Key Constraints

I'm having trouble figuring out how to create a foreign key constraint. My data model is fixed and out of my control, it looks like this:
CREATE TABLE Enquiry
(Enquiry_Ref INTEGER PRIMARY KEY CLUSTERED, Join_Ref INTEGER, EnquiryDate, EnquiryType...)
CREATE TABLE Contact
(Contact_Ref INTEGER PRIMARY KEY CLUSTERED, Surname, Forenames ....)
CREATE TABLE UniversalJoin
(Join_Ref INTEGER, Contact_Ref INTEGER, Rel_Type INTEGER)
Each Enquiry has exactly one Contact. The link between the two is the UniversalJoin table where
Enquiry.Join_Ref = UniversalJoin.Join_Ref AND
Rel_Type = 1 AND
UniversalJoin.Contact_Ref = Contact.Contact_Ref
The Rel_Type differs depending on what the source table is, so in the case of Enquiry, Rel_Type is 1 but for another table it would set to N.
My question is how do I create a foreign key constraint to enforce the integrity of this relationship? What I want to say, but can't, is:
CREATE TABLE Enquiry
...
CONSTRAINT FK_Foo
FOREIGN KEY (Join_Ref)
REFERENCES UniversalJoin (JoinRef WHERE Rel_Type=1)
You can't use conditional or filtered foreign keys in SQL Server
In these cases, you could have a multiple column FK between (JoinRef, Rel_Type) and set a check constraint on Rel_Type in UniversalJoin to make it 1.
However, I think you are trying to have a row with multiple parents which can't be done.
You might rather want to have a look at CHECK Constraints
CHECK constraints enforce domain
integrity by limiting the values that
are accepted by a column. They are
similar to FOREIGN KEY constraints in
that they control the values that are
put in a column. The difference is in
how they determine which values are
valid: FOREIGN KEY constraints obtain
the list of valid values from another
table, and CHECK constraints determine
the valid values from a logical
expression that is not based on data
in another column.
You could use a table trigger with INSERT and Update to layer the equivalent as a FK.
This way you are able to apply conditions i.e. if column value =1 check exists in table a if column value = 2 then check another table.

Are foreign keys indexed automatically in SQL Server?

Would the following SQL statement automatically create an index on Table1.Table1Column, or must one be explicitly created?
Database engine is SQL Server 2000
CREATE TABLE [Table1] (
. . .
CONSTRAINT [FK_Table1_Table2] FOREIGN KEY
(
[Table1Column]
) REFERENCES [Table2] (
[Table2ID]
)
)
SQL Server will not automatically create an index on a foreign key. Also from MSDN:
A FOREIGN KEY constraint does not have
to be linked only to a PRIMARY KEY
constraint in another table; it can
also be defined to reference the
columns of a UNIQUE constraint in
another table. A FOREIGN KEY
constraint can contain null values;
however, if any column of a composite
FOREIGN KEY constraint contains null
values, verification of all values
that make up the FOREIGN KEY
constraint is skipped. To make sure
that all values of a composite FOREIGN
KEY constraint are verified, specify
NOT NULL on all the participating
columns.
As I read Mike's question, He is asking whether the FK Constraint will create an index on the FK column in the Table the FK is in (Table1). The answer is no, and generally. (for the purposes of the constraint), there is no need to do this The column(s) defined as the "TARGET" of the constraint, on the other hand, must be a unique index in the referenced table, either a Primary Key or an alternate key. (unique index) or the Create Constraint statment will fail.
(EDIT: Added to explicitly deal with comment below -)
Specifically, when providing the data consistency that a Foreign Key Constraint is there for. an index can affect performance of a DRI Constraint only for deletes of a Row or rows on the FK side. When using the constraint, during a insert or update the processor knows the FK value, and must check for the existence of a row in the referenced table on the PK Side. There is already an index there. When deleting a row on the PK side, it must verify that there are no rows on the FK side. An index can be marginally helpful in this case. But this is not a common scenario.
Other than that, in certain types of queries, however, where the query processor needs to find the records on the many side of a join which uses that foreign key column. join performance is increased when an index exists on that foreign key. But this condition is peculiar to the use of the FK column in a join query, not to existence of the foreign Key constraint... It doesn't matter whether the other side of the join is a PK or just some other arbitrary column. Also, if you need to filter, or order the results of a query based on that FK column, an index will help... Again, this has nothing to do with the Foreign Key constraint on that column.
No, creating a foreign key on a column does not automatically create an index on that column. Failing to index a foreign key column will cause a table scan in each of the following situations:
Each time a record is deleted from the referenced (parent) table.
Each time the two tables are joined on the foreign key.
Each time the FK column is updated.
In this example schema:
CREATE TABLE MasterOrder (
MasterOrderID INT PRIMARY KEY)
CREATE TABLE OrderDetail(
OrderDetailID INT,
MasterOrderID INT FOREIGN KEY REFERENCES MasterOrder(MasterOrderID)
)
OrderDetail will be scanned each time a record is deleted in the MasterOrder table. The entire OrderDetail table will also be scanned each time you join OrderMaster and OrderDetail.
SELECT ..
FROM
MasterOrder ord
LEFT JOIN OrderDetail det
ON det.MasterOrderID = ord.MasterOrderID
WHERE ord.OrderMasterID = #OrderMasterID
In general not indexing a foreign key is much more the exception than the rule.
A case for not indexing a foreign key is where it would never be utilized. This would make the server's overhead of maintaining it unnecessary. Type tables may fall into this category from time to time, an example might be:
CREATE TABLE CarType (
CarTypeID INT PRIMARY KEY,
CarTypeName VARCHAR(25)
)
INSERT CarType .. VALUES(1,'SEDAN')
INSERT CarType .. VALUES(2,'COUP')
INSERT CarType .. VALUES(3,'CONVERTABLE')
CREATE TABLE CarInventory (
CarInventoryID INT,
CarTypeID INT FOREIGN KEY REFERENCES CarType(CarTypeID)
)
Making the general assumption that the CarType.CarTypeID field is never going to be updated and deleting records would be almost never, the server overhead of maintaing an index on CarInventory.CarTypeID would be unnecessary if CarInventory was never searched by CarTypeID.
According to: https://learn.microsoft.com/en-us/sql/relational-databases/tables/primary-and-foreign-key-constraints?view=sql-server-ver16#indexes-on-foreign-key-constraints
Unlike primary key constraints, creating a foreign key constraint does not automatically create a corresponding index

Resources