Suppose B and C are both subclass and A is a superclass. B and C can not have same id (disjoint)
CREATE TABLE a(id integer primary key);
CREATE TABLE b(id integer references a(id));
CREATE TABLE c(id integer references a(id));
insert into a values('1');
insert into a values('2');
insert into b values('1');
insert into c values('2');
Could I use a trigger to prevent the same id appearing in tables B and C?
"b and c can not have same id"
So you want to enforce a mutually exclusive relationship. In data modelling this is called an arc. Find out more.
We can implement an arc between tables without triggers by using a type column to distinguish the sub-types like this:
create table a (
id integer primary key
, type varchar2(3) not null check (type in ( 'B', 'C'))
, constraint a_uk unique (id, type)
);
create table b (
id integer
, type varchar2(3) not null check (type = 'B')
, constraint b_a_fk foreign key (id, type) references a (id, type)
);
create table b (
id integer
, type varchar2(3) not null check (type = 'C')
, constraint c_a_fk foreign key (id, type) references a (id, type)
);
The super-type table has a unique key in addition to its primary key; this provides a reference point for foreign keys on the sub-type tables. We still keep the primary key to insure uniqueness of id.
The sub-type tables have a redundant instance of the type column, redundant because it contains a fixed value. But this is necessary to reference the two columns of the compound unique key (and not the primary key, as is more usual).
This combination of keys ensures that if the super-type table has a record id=1, type='B' there can be no record in sub-type table C where id=1.
Design wise this is not good but we can do it using the below snippet. You can create a similar trigger on table b
CREATE TABLE a(id integer primary key);
CREATE TABLE b(id integer references a(id));
CREATE TABLE c(id integer references a(id));
create or replace trigger table_c_trigger before insert on c for each row
declare
counter number:=0;
begin
select count(*) into counter from b where id=:new.id;
if counter<>0 then
raise_application_error(-20001, 'values cant overlap between c and b');
end if;
end;
insert into a values('1');
insert into a values('2');
insert into b values('1');
insert into b values('2');
insert into c values('2');
You can use Oracle Sequence:
CREATE SEQUENCE multi_table_seq;
INSERT INTO A VALUE(1);
INSERT INTO A VALUE(2);
INSERT INTO B VALUE(multi_table_seq.NEXTVAL()); -- Will insert 1 in table B
INSERT INTO C VALUE(multi_table_seq.NEXTVAL()); -- Will insert 2 in table C
...
With trigger:
-- Table B
CREATE TRIGGER TRG_BEFORE_INSERT_B -- Trigger name
BEFORE INSERT -- When trigger is fire
ON A -- Table name
DECLARE
v_id NUMBER;
BEGIN
v_id := multi_table_seq.NEXTVAL();
BEGIN
SELECT TRUE FROM C WHERE id = v_id;
RAISE_APPLICATION_ERROR(-20010, v_id || ' already exists in table C');
EXCEPTION WHEN NO_DATA_FOUND -- Do nothing if not found
END;
END;
And same trigger for table C who check if id exists in table B
Related
USE MASTER
GO
CREATE DATABASE db_movies;
GO
USE db_movies;
GO
CREATE TABLE tbl_movies
(
movie_id INT PRIMARY KEY NOT NULL IDENTITY (1,1),
movie_name VARCHAR(50) NOT NULL
);
INSERT INTO tbl_movies (movie_name)
VALUES ('Jurassic Park'), ('Star Wars'), ('Blade Runner');
CREATE TABLE tbl_genre
(
genre_id INT PRIMARY KEY NOT NULL IDENTITY (100,1),
genre_name VARCHAR(50) NOT NULL
);
INSERT INTO tbl_genre (genre_name)
VALUES ('Sci-Fi'), ('Thriller'), ('Horror');
CREATE TABLE tbl_movielist
(
MovieID INT PRIMARY KEY NOT NULL IDENTITY (1000,1),
MovieName VARCHAR (50) NOT NULL,
Movie_identification INT NOT NULL
CONSTRAINT fk_movie_id
FOREIGN KEY REFERENCES tbl_movies(movie_id)
ON UPDATE CASCADE ON DELETE CASCADE,
Genre_identification INT NOT NULL
CONSTRAINT fk_genre_id
FOREIGN KEY REFERENCES tbl_genre(genre_id)
ON UPDATE CASCADE ON DELETE CASCADE,
rating FLOAT(3) NOT NULL,
);
INSERT INTO tbl_movielist (MovieName, Movie_identification, Genre_identification, rating)
VALUES ('Sandlot', 10, 109, 7.80),
('Knives Out', 11, 110, 7.90),
('The Notebook', 12, 111, 7.80);
INSERT INTO tbl_genre (genre_name)
VALUES ('Comedy'), ('Mystery'), ('Drama');
INSERT INTO tbl_movies(movie_name)
VALUES ('Sandlot'), ('Knives Out'), ('The Notebook');
SELECT * FROM tbl_genre;
SELECT * FROM tbl_movies;
SELECT * FROM tbl_movielist;
SELECT *
FROM tbl_movies
INNER JOIN tbl_genre ON CONVERT(int, tbl_genre.genre_id) = tbl_movies.movie_name;
I have created a database and for some reason on my final two lines, where I am using the INNER JOIN statement it says "Conversion failed when converting the varchar value 'Jurassic Park' to data type int. Now I have tried using the CAST function and CONVERT function, as well as changing the tbl and attributes I wanted to INNER JOIN. It either says the error message above, or when I do get no error message and it prints a table, there is no data in the tables. Can not figure out why, I am pretty new to SQL.
I am attempting to write data into two tables, let's say:
Primary_Tbl(Id, Title)
Foreign_Tbl(Id, Primary_Tbl_id, Subtext)
I want to insert the data in both of these tables but also want to maintain primary keys and foreign keys relationship. The issue is that I won't have primary keys until data is inserted in Primary_Tbl that can be used to insert in Foreign_Tbl.Primary_Tbl_id
Is there any clean and neat approach to achieve this in Snowflake?
You can insert into 2 tables at the same time with insert all, while using sequences to generate ids:
create or replace sequence seq_01 start = 1 increment = 1;
create or replace sequence seq_02 start = 1000 increment = 1;
create or replace table table_a (id integer, title string);
create or replace table table_b (id integer, a_id integer, subtext string);
insert all
into table_a values(id_a, title)
into table_b values(id_b, id_a, subtext)
select seq_01.nextval id_a, seq_02.nextval id_b
, 'a title' title, 'the subtext' subtext
;
select *
from table_a a
join table_b b
on a.id=b.a_id;
I'm using database first approach with EF core and trying to figure out a clean solution to the below problem -
Consider a Student attendance table (irrelevant columns removed) below that stores date of class and allows the student to enter his class rating -
create table Student (
Id int Identity(1, 1) not null,
ClassDate smalldatetime not null,
ClassRatingByStudent varchar(250) not null
)
This is a webapp where school attendance system automatically populates the above table at EOD and then the student (let's say a few days later) is required to add class ratings. When the table is populated by the school attendance system, there is nothing in the ClassRatingByStudent column. Then when the student logs in, he must add the rating.
As you see, ClassRatingByStudent must be null when the school attendance system populates the table and must be not-null when the student saves his changes. One obvious solution is make ClassRatingByStudent column nullable ad handle it in the code but I'm wondering if there is a neater database (or maybe EF) level solution exists or some sort of pattern/architecture guidelines for this type of scenarios?
I don't know but maybe CHECK constraint could help you:
CREATE TABLE TestTable(
ID int NOT NULL IDENTITY,
RatingAllowed bit NOT NULL DEFAULT 0, -- switcher
RatingValue varchar(250),
CONSTRAINT PK_TestTable PRIMARY KEY(ID),
CONSTRAINT CK_TestTable_RatingValue CHECK( -- constraint
CASE
WHEN RatingAllowed=0 AND RatingValue IS NULL THEN 1
WHEN RatingAllowed=1 AND RatingValue IS NOT NULL THEN 1
ELSE 0
END=1
)
)
INSERT TestTable(RatingAllowed,RatingValue)VALUES(0,NULL)
INSERT TestTable(RatingAllowed,RatingValue)VALUES(1,'AAA')
-- The INSERT statement conflicted with the CHECK constraint "CK_TestTable_RatingValue"
INSERT TestTable(RatingAllowed,RatingValue)VALUES(0,'AAA')
INSERT TestTable(RatingAllowed,RatingValue)VALUES(1,NULL)
I found a variant how to check using another table as switcher
CREATE TABLE TableA(
ID int NOT NULL IDENTITY PRIMARY KEY,
StudentID int NOT NULL,
Grade int
)
CREATE TABLE TableB(
StudentID int NOT NULL PRIMARY KEY
)
GO
-- auxiliary function
CREATE FUNCTION GradeIsAllowed(#StudentID int)
RETURNS bit
BEGIN
DECLARE #Result bit=CASE WHEN EXISTS(SELECT * FROM TableB WHERE StudentID=#StudentID) THEN 1 ELSE 0 END
RETURN #Result
END
GO
-- constraint to check
ALTER TABLE TableA ADD CONSTRAINT CK_TableA_Grade CHECK(
CASE dbo.GradeIsAllowed(StudentID) -- then we can use the function here
WHEN 1 THEN CASE WHEN Grade IS NOT NULL THEN 1 ELSE 0 END
WHEN 0 THEN CASE WHEN Grade IS NULL THEN 1 ELSE 0 END
END=1)
GO
-- Tests
INSERT TableB(StudentID)VALUES(2) -- allowed student
INSERT TableA(StudentID,Grade)VALUES(1,NULL) -- OK
INSERT TableA(StudentID,Grade)VALUES(2,5) -- OK
INSERT TableA(StudentID,Grade)VALUES(1,4) -- Error
INSERT TableA(StudentID,Grade)VALUES(2,NULL) -- Error
INSERT TableB(StudentID)VALUES(1) -- add 1
UPDATE TableA SET Grade=4 WHERE StudentID=1 -- OK
UPDATE TableA SET Grade=NULL WHERE StudentID=1 -- Error
Given two tables:
TableA
(
id : primary key,
type : tinyint,
...
)
TableB
(
id : primary key,
tableAId : foreign key to TableA.id,
...
)
There is a check constraint on TableA.type with permitted values of (0, 1, 2, 3). All other values are forbidden.
Due to the known limitations, records in TableB can exist only when TableB.TableAId references the record in TableA with TableA.type=0, 1 or 2 but not 3. The latter case is forbidden and leads the system into an invalid state.
How can I guarantee that in such case the insert to TableB will fail?
Cross-table constraint using an empty indexed view:
Tables
CREATE TABLE dbo.TableA
(
id integer NOT NULL PRIMARY KEY,
[type] tinyint NOT NULL
CHECK ([type] IN (0, 1, 2, 3))
);
CREATE TABLE dbo.TableB
(
id integer NOT NULL PRIMARY KEY,
tableAId integer NOT NULL
FOREIGN KEY
REFERENCES dbo.TableA
);
The 'constraint view'
-- This view is always empty (limited to error rows)
CREATE VIEW dbo.TableATableBConstraint
WITH SCHEMABINDING AS
SELECT
Error =
CASE
-- Error condition: type = 3 and rows join
WHEN TA.[type] = 3 AND TB.id = TA.id
-- For a more informative error
THEN CONVERT(bit, 'TableB cannot reference type 3 rows in TableA.')
ELSE NULL
END
FROM dbo.TableA AS TA
JOIN dbo.TableB AS TB
ON TB.id = TA.id
WHERE
TA.[type] = 3;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.TableATableBConstraint (Error);
Online demo:
-- All succeed
INSERT dbo.TableA (id, [type]) VALUES (1, 1);
INSERT dbo.TableA (id, [type]) VALUES (2, 2);
INSERT dbo.TableA (id, [type]) VALUES (3, 3);
INSERT dbo.TableB
(id, tableAId)
VALUES
(1, 1),
(2, 2);
-- Fails
INSERT dbo.TableB (id, tableAId) VALUES (3, 3);
-- Fails
UPDATE dbo.TableA SET [type] = 3 WHERE id = 1;
This is similar in concept to the linked answer to Check constraints that ensures the values in a column of tableA is less the values in a column of tableB, but this solution is self-contained (does not require a separate table with more than one row at all times). It also produces a more informational error message, for example:
Msg 245, Level 16, State 1
Conversion failed when converting the varchar value 'TableB cannot reference type 3 rows in TableA.' to data type bit.
Important notes
The error condition must be completely specified in the CASE expression to ensure correct operation in all cases. Do not be tempted to omit conditions implied by the rest of the statement. In this example, it would be an error to omit TB.id = TA.id (implied by the join).
The SQL Server query optimizer is free to reorder predicates, and makes no general guarantees about the timing or number of evaluations of scalar expressions. In particular, scalar computations can be deferred.
Completely specifying the error condition(s) within a CASE expression ensures the complete set of tests is evaluated together, and no earlier than correctness requires. From an execution plan perspective, this means the Compute Scalar associated with the CASE tests will appear on the indexed view delta maintenance branch:
The light shaded area highlights the indexed view maintenance region; the Compute Scalar containing the CASE expression is dark-shaded.
I have two tables
threads table
_id recipient_id type
1 1
2 1
3 2
and addresses table
_id address
1 HELLO
2 BYE
recipient_id is mapped to addresses table's _id
I need to update type to a specific value if address is HELLO. How do i do it? I have already tried
UPDATE threads SET threads.type = 106 FROM threads
INNER JOIN
addresses ON addresses._id = threads.recipient_ids
AND
addresses.address LIKE '%HELLO%';
But i am getting an error near ".": syntax error .
What is the correct syntax to update a column?
You can use the IN operator:
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE threads (_id integer primary key, recipient_id integer, type text);
INSERT INTO "threads" VALUES(1,1,NULL);
INSERT INTO "threads" VALUES(2,1,NULL);
INSERT INTO "threads" VALUES(3,2,NULL);
CREATE TABLE addresses (_id integer primary key, address text);
INSERT INTO "addresses" VALUES(1,'HELLO');
INSERT INTO "addresses" VALUES(2,'BYE');
COMMIT;
sqlite> update threads set type = 106 where _id in
...> (select t._id from threads t, addresses a
...> where t.recipient_id = a._id and a.address like '%HELLO%');
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE threads (_id integer primary key, recipient_id integer, type text);
INSERT INTO "threads" VALUES(1,1,'106');
INSERT INTO "threads" VALUES(2,1,'106');
INSERT INTO "threads" VALUES(3,2,NULL);
CREATE TABLE addresses (_id integer primary key, address text);
INSERT INTO "addresses" VALUES(1,'HELLO');
INSERT INTO "addresses" VALUES(2,'BYE');
COMMIT;