Oracle SQL, conditional foreign key - sql

I have a problem with implementing DB. I have 2 tables:
CREATE TABLE TAB1
(
TAB1_ID INT NOT NULL PRIMARY KEY,
TAB1_BOOL VARCHAR(4) NOT NULL CHECK (TAB1_BOOL IN('OPT1', 'OPT2'))
);
CREATE TABLE TAB2
(
TAB2_ID INT NOT NULL
);
I would like to make TAB2_ID as foreign key of TAB1_ID, but TAB2_ID can be only these TAB1_IDs which rows in table TAB1 are marked with TAB1_BOOL == OPT1. For example:
TAB1 contains 2 rows:
1. 'TAB1_ID==1' 'TAB1_BOOL==OPT1',
2. 'TAB1_ID==2' 'TAB1_BOOL==OPT2'.
Now, statement like:
INSERT INTO TAB2 (TAB2_ID) VALUES (2);
should be rejected because row with TAB1_ID==2 is marked with OPT2
Is it possible to make this in Oracle SQL ?

This can be achieved by using a trigger:
CREATE TABLE TAB1 (
TAB1_ID INT NOT NULL PRIMARY KEY,
TAB1_BOOL VARCHAR(4) NOT NULL CHECK (TAB1_BOOL IN('OPT1', 'OPT2'))
);
CREATE TABLE TAB2 (
TAB2_ID INT NOT NULL,
FOREIGN KEY (TAB2_ID) REFERENCES TAB1 (TAB1_ID)
);
CREATE OR REPLACE TRIGGER CHECK_TAB2_ID
BEFORE UPDATE OR INSERT ON TAB2 FOR EACH ROW
DECLARE
CURSOR CHECK_CURSOR IS
SELECT TAB1_BOOL
FROM TAB1
WHERE TAB1_ID = :new.TAB2_id;
V_TAB1_BOOL VARCHAR(4);
BEGIN
OPEN CHECK_CURSOR;
FETCH CHECK_CURSOR INTO V_TAB1_BOOL;
IF (V_TAB1_BOOL <> 'OPT1') THEN
CLOSE CHECK_CURSOR;
raise_application_error(10000, 'Wrong value in TAB1');
END IF;
CLOSE CHECK_CURSOR;
END;
INSERT INTO TAB1 VALUES (1, 'OPT1');
INSERT INTO TAB1 VALUES (2, 'OPT2');
INSERT INTO TAB2 (TAB2_ID) VALUES (1);
There is still a small error in the trigger above. I have currently no access to an Oracle database, so I can hardy fix it. Here's my Fiddle.
p.s.: ok, just a ; missing in the Fiddle - here's an update.

Related

Transfer data from one table to another with both table having same id, in Postgres

I have 2 tables. TBL_A and TBL_B. I want to update TBL_A so that A_NAME = B_NAME, where A_LA = B_LA. How do I do this? This is just s sample data. In the real table, I have thousands of records on each table.
I tried connecting both tables using View, but it seems that the view can't be updated.
TBL_A:
CREATE TABLE public."TBL_A"
(
"A_LA" character varying,
"A_NAME" character varying,
"A_ID" bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 1000 CACHE 1 ),
CONSTRAINT "TBL_A_pkey" PRIMARY KEY ("A_ID")
);
INSERT INTO public."TBL_A"("A_LA", "A_NAME") VALUES ('8904001', '');
INSERT INTO public."TBL_A"("A_LA", "A_NAME") VALUES ('8904003', '');
INSERT INTO public."TBL_A"("A_LA", "A_NAME") VALUES ('8904005', '');
TBL_B:
CREATE TABLE public."TBL_B"
(
"B_LA" character varying,
"B_NAME" character varying,
"B_ID" bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 1000 CACHE 1 ),
CONSTRAINT "TBL_B_pkey" PRIMARY KEY ("B_ID")
);
INSERT INTO public."TBL_B"("B_LA", "B_NAME") VALUES ('8904001', 'John');
INSERT INTO public."TBL_B"("B_LA", "B_NAME") VALUES ('8904002', 'James');
INSERT INTO public."TBL_B"("B_LA", "B_NAME") VALUES ('8904003', 'Jacob')
INSERT INTO public."TBL_B"("B_LA", "B_NAME") VALUES ('8904004', 'Jared')
INSERT INTO public."TBL_B"("B_LA", "B_NAME") VALUES ('8904005', 'Josh');
View:
CREATE VIEW public."A_B_CONNECT_VIEW" AS
SELECT "TBL_A"."A_LA","TBL_A"."A_NAME","TBL_B"."B_LA","TBL_B"."B_NAME"
FROM "TBL_A" JOIN "TBL_B" ON "TBL_A"."A_LA"::text = "TBL_B"."B_LA"::text;
You can do it in this way:
UPDATE table_A AS A
SET A.A_NAME = B.B_NAME
FROM table_B AS B
WHERE A.A_LA = B.B_LA;
I update the query from #Farshid Shekari for it to work.
UPDATE "TBL_A" AS A
SET "A_NAME" = B."B_NAME"
FROM "TBL_B" AS B
WHERE A."A_LA" = B."B_LA";

query returned 3 columns in insert trigger for view

I have two tables (test_a and test_b), one (test_b) is basically an extension of the second table (test_a) for certain entries to prevent primarily NULL value columns. To keep logical consistency of stored data I wrapped test_b with a view to represent the full range of values that describe the stored entity and thus require on insert into that view the full range of required information.
The insert on table_b_view and table_a_view is required to be able to return the id of the inserted entity as the application working with this db might need to keep that id for internal reference.
My current (poc) solution is this:
DROP TABLE IF EXISTS TEST_A CASCADE;
CREATE TABLE TEST_A (
id SERIAL,
name text,
PRIMARY KEY(id)
);
CREATE OR REPLACE VIEW TEST_A_VIEW AS
SELECT id,
name
FROM TEST_A;
DROP TABLE IF EXISTS TEST_B CASCADE;
CREATE TABLE TEST_B (
id int,
second_name text,
PRIMARY KEY(id),
FOREIGN KEY(id)
REFERENCES TEST_A(id)
);
CREATE OR REPLACE VIEW TEST_B_VIEW AS
SELECT tb.id, name, second_name
FROM TEST_B as tb JOIN TEST_A as ta
ON tb.id = ta.id;
CREATE OR REPLACE FUNCTION TEST_B_INSERT_FUNC() RETURNS TRIGGER AS $$
DECLARE
ident int;
BEGIN
INSERT INTO TEST_A_VIEW (name) VALUES (NEW.name)
RETURNING id INTO ident;
INSERT INTO TEST_B (id, second_name) VALUES (ident, NEW.second_name);
RETURN ident, NEW.name, NEW.second_name;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER TEST_B_INSERT
INSTEAD OF INSERT ON TEST_B_VIEW
FOR EACH ROW EXECUTE PROCEDURE TEST_B_INSERT_FUNC();
Which gives me an
ERROR: query "SELECT ident, NEW.name, NEW.second_name" returned 3 columns
CONTEXT: PL/pgSQL function test_b_insert_func() line 8 at RETURN
when running the query
INSERT INTO TEST_B_VIEW(name, second_name) VALUES ('test', 'test') RETURNING id;
The query
INSERT INTO TEST_A_VIEW (name) VALUES ('test') RETURNING id;
works like a charm and does return me the corresponding id.
How can I fix my function or adjust my approach to be able to
keep the representation of the data as is (i.e., table_b as an extension of table_a which is not visible from a person just looking at the views)
inserts on either table have to be able to return the id used for the entity
The solution was easier than expected. You have to return NEW, but you can 'manipulate' the values of NEW within the trigger.
DROP TABLE IF EXISTS TEST_A CASCADE;
CREATE TABLE TEST_A (
id SERIAL,
name text,
PRIMARY KEY(id)
);
CREATE OR REPLACE VIEW TEST_A_VIEW AS
SELECT id,
name
FROM TEST_A;
DROP TABLE IF EXISTS TEST_B CASCADE;
CREATE TABLE TEST_B (
id int,
second_name text,
PRIMARY KEY(id),
FOREIGN KEY(id)
REFERENCES TEST_A(id)
);
CREATE OR REPLACE VIEW TEST_B_VIEW AS
SELECT tb.id, ta.name, tb.second_name
FROM TEST_B as tb JOIN TEST_A as ta
ON tb.id = ta.id;
CREATE OR REPLACE FUNCTION TEST_B_INSERT_FUNC() RETURNS TRIGGER AS $$
DECLARE
ident int;
BEGIN
INSERT INTO TEST_A_VIEW (name) VALUES (NEW.name)
RETURNING id INTO ident;
INSERT INTO TEST_B (id, second_name) VALUES (ident, NEW.second_name);
NEW.id = ident;
RETURN NEW;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER TEST_B_INSERT
INSTEAD OF INSERT ON TEST_B_VIEW
FOR EACH ROW EXECUTE PROCEDURE TEST_B_INSERT_FUNC();
Now the INSERT query works as intended:
INSERT INTO TEST_B_VIEW (name, second_name)
VALUES ('test', 'test')
RETURNING id;
id
----
1
(1 row)
INSERT INTO TEST_B_VIEW (name, second_name)
VALUES ('test', 'test')
RETURNING id;
id
----
2
(1 row)

I need to create 3 rows in Student_Fee table when 1 row is inserted in Student table using trigger

Following is the trigger
Create TRIGGER [dbo].[Student]
ON [dbo].[Student]
After INSERT
AS
BEGIN
Insert Into Student_Fee([StudentID],[InstID],[PersonID],[FeeSubmiteTime],[FeeMsg],[Type])
Select NULL,3,PersonID,getdate(),'Student submitted on','Student' from INSERTED
END
I need to create 3 rows in Student_Fee table when 1 row is inserted in Student table.First row in Student_fee must have StudentID Null and for other two rows student ID is filled obtained from previous table. Also, Feemsg should be different for the 3 rows. It is text and could be any value. And for type there are two types Student and Admin and the types are also not fixed. They can vary while inserting rows.
How can I do that by using trigger?
here you go :
CREATE TABLE Temp1abhari (
id INT identity(1, 1)
,number INT
);
CREATE TABLE Temp2abhari (
id INT
,number INT
);
CREATE TRIGGER TTemp1abhari ON Temp1abhari
FOR INSERT
AS
BEGIN
INSERT INTO Temp2abhari
VALUES (
NULL
,1
);
INSERT INTO Temp2abhari
SELECT ID
,2
FROM Inserted;
INSERT INTO Temp2abhari
SELECT ID
,3
FROM Inserted;
END

SQL check constraint on multiple tables

So If the "Type" is 0, i should be able to add my person in Table B, else not, but the "Type" column is not and shouldn't be in Table B.
You can do this with a foreign key constraint and some trickery.
First, set up a unique constraint on TableA for both type and person:
alter table TableA add constraint unq_TableA_type_person on TableA(type, person);
This allows you set to set up a foreign key constraint. However, you need a type column. For that, you can use a computed column:
alter table TableB add type_for_a as (0); -- it is always 0
Now, just use a foreign key constraint:
alter table TableB add constraint fk_tableA_type_person
foreign key (type_for_a, person) references tableA(type, person);
Voila! You have the constraint in place without having to write any code.
CREATE TABLE T1 (TypeID INT NOT NULL, people VARCHAR(50));
GO
CREATE TABLE T2 ( people VARCHAR(50));
GO
-- creating trigger to insert on the behalf when there is a particular type
CREATE TRIGGER dbo.AfterInsertTrigger
ON T1
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
declare #id int,
#someval char(1)
insert into dbo.T2
select i.people FROM Inserted i
where i.TypeID=0 -- checks only when the id is 0
END
GO
-- inserting people with different id s into Table1
INSERT T1 (TypeID, people) SELECT 1, 'A';
INSERT T1 (TypeID, people) SELECT 0, 'B';
GO
--selecting from tables see what got affected.
select * from T1
select *from T2
--Clean up
DROP TABLE T2;
DROP TABLE T1;
GO

Database auto-increment column

Is it possible to create a Database which has 1 column (but not the column of primary key) to be auto-increment? So that when I insert value to the database, i don't need to fill in the value myself, and DB will fill in that value for that column for me (and increment every time I do a new insert)?
Thank you.
Yes, of course it is possible. Just make this column a unique key (not a primary key) and it has to be declared with a special attribute: "IDENTITY" for SQL Server, and
"AUTO_INCREMENT" for MySQL (see the example below) . And another column can be a primary key.
On MySQL database the table could be declared like this:
CREATE TABLE `mytable` (
`Name` VARCHAR(50) NOT NULL,
`My_autoincrement_column` INTEGER(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`Name`),
UNIQUE KEY `My_autoincrement_column` (`My_autoincrement_column`)
);
Yes, you can do this. Here is a sample for SQL Server using IDENTITY:
CREATE TABLE MyTable (
PrimaryKey varchar(10) PRIMARY KEY,
IdentityColumn int IDENTITY(1,1) NOT NULL,
DefaultColumn CHAR(1) NOT NULL DEFAULT ('N')
)
INSERT INTO MyTable (PrimaryKey) VALUES ('A')
INSERT INTO MyTable (PrimaryKey) VALUES ('B')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('C', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('D', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('E', DEFAULT)
--INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('F', NULL) -- ERROR
--> Cannot insert the value NULL into column 'DefaultColumn', table 'tempdb.dbo.MyTable'; column does not allow nulls. INSERT fails.
SELECT * FROM MyTable
Here is an example using SQL Server using functions to roll-your-own incrementing column. This is by means not fault tolerant or the way I would do it. (I'd use the identity feature.) However, it is good to know that you can use functions to return default values.
DROP TABLE MyTable
GO
DROP FUNCTION get_default_for_mytable
GO
CREATE FUNCTION get_default_for_mytable
()
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #ResultVar int
-- Add the T-SQL statements to compute the return value here
SET #ResultVar = COALESCE((SELECT MAX(HomeBrewedIdentityColumn) FROM MyTable),0) + 1
-- Return the result of the function
RETURN #ResultVar
END
GO
CREATE TABLE MyTable (
PrimaryKey varchar(10) PRIMARY KEY,
IdentityColumn int IDENTITY(1,1) NOT NULL,
DefaultColumn CHAR(1) NOT NULL DEFAULT ('N'),
HomeBrewedIdentityColumn int NOT NULL DEFAULT(dbo.get_default_for_mytable())
)
GO
INSERT INTO MyTable (PrimaryKey) VALUES ('A')
INSERT INTO MyTable (PrimaryKey) VALUES ('B')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('C', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('D', 'Y')
INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('E', DEFAULT)
--INSERT INTO MyTable (PrimaryKey, DefaultColumn) VALUES ('F', NULL) -- ERRROR
--> Cannot insert the value NULL into column 'DefaultColumn', table 'tempdb.dbo.MyTable'; column does not allow nulls. INSERT fails.
SELECT * FROM MyTable
Results
PrimaryKey IdentityColumn DefaultColumn HomeBrewedIdentityColumn
---------- -------------- ------------- ------------------------
A 1 N 1
B 2 N 2
C 3 Y 3
D 4 Y 4
E 5 N 5
I think you can have only 1 identity autoincrement column per table, this columns doesn't have to be the primary key but it would mean you have to insert the primary key yourself.
If you already have a primary key which is auto increment then I would try and use this if possible.
If you are trying to get an row ID to range on for querying then I would look at creating a view which has the row ID in it (not SQL 2000 or below).
Could you add in what your primary key is and what you intend to use the auto increment column for and it might help come up with a solution
On sql server this is called an identity column
Oracle and DB2 have sequence but I think you are looking for identity and all major dbms (mysql, sql server, db2, oracle) support it.