I have a problem with triggers i sql Oracle - sql

(zaposlenik means emplpoyee, godisnji_odmor means vacation)
So I want to make a trigger that triggers if an employee(here is called zaposlenik) has more than 21 days of vacation after an insert in year 2020. An employee can have multiple vacations(like 2 days, 10 days...) one to many relation between tables zaposlenik(employee) and godisnji_odmor(vacation). My problem here is that I don't know how to get that zaposlenik_id from the insert on which the trigger tiggers so I can sum up the vacation days of that employee on the ID = zaposlenik_id.
Here are my tables and trigger.
CREATE TABLE zaposlenik
(
zaposlenik_id INTEGER CONSTRAINT zaposlenik_pk PRIMARY KEY,
posao VARCHAR(30) NOT NULL,
ime VARCHAR(30) NOT NULL,
prezime VARCHAR(30) NOT NULL,
broj_tel INTEGER NOT NULL,
email VARCHAR(50) NOT NULL,
adresa VARCHAR(100) NOT NULL,
mjesecni_iznos_place FLOAT NOT NULL,
IBAN VARCHAR(34) NOT NULL,
budzet FLOAT,
parking_mjesto_id VARCHAR(5) CONSTRAINT zaposlenik_parking_mjesto_fk REFERENCES
parking_mjesto(etaza_i_br_mjesta),
zaposlenik_id_2 INTEGER CONSTRAINT zaposlenik_zaposlenik_fk REFERENCES
zaposlenik(zaposlenik_id)
);
CREATE TABLE godisnji_odmor
(
godisnji_odmor_id INTEGER CONSTRAINT godisnji_odmor_pk PRIMARY KEY,
pocetak DATE NOT NULL,
kraj DATE NOT NULL,
zaposlenik_id INTEGER NOT NULL CONSTRAINT godisnji_odmor_zaposlenik_fk REFERENCES
zaposlenik(zaposlenik_id)
);
CREATE OR REPLACE TRIGGER t_godisnji
AFTER INSERT
ON godisnji_odmor
DECLARE
v_br NUMBER; --sum of the vacation days
v_id NUMBER; -- id of the employee that is inserted (his id is zaposlenik_id)
BEGIN
SELECT zaposlenik_id INTO v_id FROM INSERTED;
SELECT SUM( g.kraj - g.pocetak ) INTO v_br
FROM zaposlenik z INNER JOIN godisnji_odmor g USING(zaposlenik_id)
WHERE g.pocetak > '01-JANUARY-2020' AND zaposlenik_id = v_id;
IF v_br > 21 THEN
ROLLBACK;
raise_application_error(-20100,'Godisnji prekoracuje 21 dan u sumi');
END IF;
END t_godisnji;
/

You need to use the :new for this as follows:
replace
SELECT zaposlenik_id INTO v_id FROM INSERTED;
with
v_id := :new.zaposlenik_id;
In short, You can have your code without v_id variable as follows:
CREATE OR REPLACE TRIGGER t_godisnji
AFTER INSERT
ON godisnji_odmor
DECLARE
v_br NUMBER; --sum of the vacation days
--v_id NUMBER; -- id of the employee that is inserted (his id is zaposlenik_id)
BEGIN
--SELECT zaposlenik_id INTO v_id FROM INSERTED;
SELECT SUM( g.kraj - g.pocetak ) INTO v_br
FROM zaposlenik z INNER JOIN godisnji_odmor g USING(zaposlenik_id)
WHERE g.pocetak > '01-JANUARY-2020'
AND zaposlenik_id = :new.zaposlenik_id; -- see the usage of :new here
IF v_br > 21 THEN
ROLLBACK;
raise_application_error(-20100,'Godisnji prekoracuje 21 dan u sumi');
END IF;
END t_godisnji;
/

Related

Function not giving a response

I have created a database for an airline that currently has 3 tables: Table1: FLIGHTS, Table2: Clients, Table3: Reservation, and I want to create a function that returns the time left in the month from the reservation made till the flights date inside a function. After I run it, nothing is returning. Any tips?
DROP DATABASE if exists Chartered_Airlines;
CREATE DATABASE Chartered_Airlines;
USE Chartered_Airlines;
CREATE TABLE FLIGHTS(FLIGHT_NO INT(5) NOT NULL, DEPARTURE VARCHAR(15), ARRIVAL VARCHAR(15), TYPEFL VARCHAR(15), SEATS INT(4) NOT NULL, FREE_SEATS INT(4), FLIGHT_DATE DATE,
PRIMARY KEY(FLIGHT_NO));
CREATE TABLE CUSTOMERS(CL_NO INT(5) NOT NULL, LAST_NAME VARCHAR(15) NOT NULL, FIRST_NAME VARCHAR(15) NOT NULL, CITIZENSHIP VARCHAR(15) NOT NULL, B_DATE DATE,
PRIMARY KEY(CL_NO));
CREATE TABLE RESERVATIONS(RES_NO INT(5) NOT NULL, CL_NO INT(5) NOT NULL, FLIGHT_NO INT(5), COST FLOAT(15), RES_DATE DATE,
PRIMARY KEY(RES_NO),
FOREIGN KEY(FLIGHT_NO) REFERENCES FLIGHTS(FLIGHT_NO),
FOREIGN KEY(CL_NO) REFERENCES CUSTOMERS(CL_NO));
SHOW TABLES;
INSERT INTO FLIGHTS VALUES(10,'ATHENS','LONDON','EXTERNAL',310,34, '2023/10/04');
INSERT INTO FLIGHTS VALUES(20,'ATHENS','MYKONOS','INTERNAL',100,5, '2022/12/12');
INSERT INTO FLIGHTS VALUES(30,'ATHENS','PARIS','EXTERNAL',256,16, '2022/09/07');
INSERT INTO CUSTOMERS VALUES(5472,'ALEKSANDRAKIS','GEORGIOS','GREECE','1989/01/01');
INSERT INTO CUSTOMERS VALUES(1354,'STAVROU','ANTREAS','GREECE','1977/07/07');
INSERT INTO CUSTOMERS VALUES(6598,'AMALFIO','ROBERTO','ITALY','1995/05/02');
INSERT INTO CUSTOMERS VALUES(1953,'EUSTATHIOU','NIKOS','GREECE','2001/06/03');
INSERT INTO CUSTOMERS VALUES(1387,'STAKAS','VASILLIS','GREECE','1990/01/07');
INSERT INTO RESERVATIONS VALUES(3174,5472,10,6482, '2020/03/02');
INSERT INTO RESERVATIONS VALUES(3143,1354,10,6482, '2021/05/09');
INSERT INTO RESERVATIONS VALUES(1286,6598,20,662, '2022/10/12');
INSERT INTO RESERVATIONS VALUES(3275,1953,20,662, '2022/09/08');
INSERT INTO RESERVATIONS VALUES(7654,1387,30,264, '2022/09/06');
SELECT * FROM FLIGHTS;
SELECT * FROM CUSTOMERS;
SELECT * FROM RESERVATIONS;
DESC FLIGHTS;
DESC RESERVATIONS;
DESC CUSTOMERS;
DELIMITER //
CREATE FUNCTION TIME_LEFT(RES_NO INT) RETURNS INT
DETERMINISTIC
BEGIN
DECLARE TIME_LEFT INT DEFAULT 0;
SELECT TIMESTAMPDIFF(MONTH,RESERVATIONS.RES_DATE,FLIGHTS.FLIGHT_DATE) INTO TIME_LEFT FROM RESERVATIONS
WHERE RES_NO = RES_NO AND RESERVATIONS.FLIGHT_NO = FLIGHTS.FLIGHT_NO ;
RETURN TIME_LEFT;
END;
//
SELECT TIME_LEFT(3143);
I was expecting that it returns the date difference between reservation made date and flight date
Your forgot to use join and also use a variable name for holding the value passed in function:
DELIMITER //
CREATE FUNCTION TIME_LEFT(VAR_RES_NO INT) RETURNS INT
DETERMINISTIC
BEGIN
DECLARE TIME_LEFT INT DEFAULT 0;
SELECT TIMESTAMPDIFF(MONTH,RESERVATIONS.RES_DATE,FLIGHTS.FLIGHT_DATE) INTO TIME_LEFT FROM RESERVATIONS join FLIGHTS
WHERE RES_NO = VAR_RES_NO AND RESERVATIONS.FLIGHT_NO = FLIGHTS.FLIGHT_NO ;
RETURN TIME_LEFT;
END;
//
I think you need to include the table Flights in the SELECT statement, as well as specify the difference between RES_NO in the input vs reservations e.g.,
CREATE FUNCTION TIME_LEFT(RES_NO_in INT) RETURNS INT
DETERMINISTIC
BEGIN
DECLARE TIME_LEFT INT DEFAULT 0;
SELECT TIMESTAMPDIFF(MONTH,RESERVATIONS.RES_DATE,FLIGHTS.FLIGHT_DATE) INTO TIME_LEFT
FROM RESERVATIONS, FLIGHTS
WHERE RESERVATIONS.RES_NO = RES_NO_in AND RESERVATIONS.FLIGHT_NO = FLIGHTS.FLIGHT_NO;
RETURN TIME_LEFT;
END;
or
CREATE FUNCTION TIME_LEFT(RES_NO_in INT) RETURNS INT
DETERMINISTIC
BEGIN
DECLARE TIME_LEFT INT DEFAULT 0;
SELECT TIMESTAMPDIFF(MONTH,RESERVATIONS.RES_DATE,FLIGHTS.FLIGHT_DATE) INTO TIME_LEFT
FROM RESERVATIONS
INNER JOIN FLIGHTS ON RESERVATIONS.FLIGHT_NO = FLIGHTS.FLIGHT_NO
WHERE RESERVATIONS.RES_NO = RES_NO_in;
RETURN TIME_LEFT;
END;

Create trigger to update balance after transactions from an account and to an account - SQL Oracle

Could someone help me here? I'm trying to create a trigger that updates balance in ACCOUNT table after transactions from the TRANSFERS table. The whole table model is bigger, but here are two of them:
--ACCOUNT--
CREATE TABLE account(
account_num NUMBER(8) NOT NULL PRIMARY KEY,
ktnr REFERENCES account_type(ktnr),
regdate DATE NOT NULL,
balance NUMBER(10,2));
--TRANSFERS--
CREATE TABLE transfers(
row_nr NUMBER(9) NOT NULL PRIMARY KEY,
pers_nr REFERENCES customer(pers_nr),
from_account_num REFERENCES account(account_num),
to_account_num REFERENCES account(account_num),
amount NUMBER(10,2),
date DATE NOT NULL);
And the code I think is quite wrong but that's the idea of solution. Not sure how to write it in a better way.
create or replace trigger aifer_transfers
after insert
on transfers
for each row
when (new.amount is not null)
begin
if :new.to_account_num != :new.pers_nr then
update account a
set a.balance = a.balance - :new.amount
where :old.account_num = :new:account_num;
elsif
:new.to_account_num = :new.pers_nr then
update account a
set a.balance = a.balance + :new.amount
where :old.account_num = :new:account_num;
end if;
end;
Many different errors, depending on chosen variables:
Line/Col: 3/9 PL/SQL: SQL Statement ignored
Line/Col: 5/26 PLS-00049: bad bind variable 'NEW'
Your table declarations are missing some data types (since we don't have your other tables) and, from Oracle 12, you can use IDENTITY columns for the primary keys (and have some CHECK constraints and DEFAULT values for DATE columns):
CREATE TABLE account(
account_num NUMBER(8,0)
GENERATED ALWAYS AS IDENTITY
NOT NULL
CONSTRAINT account__account_num__pk PRIMARY KEY,
ktnr NUMBER(8,0)
-- CONSTRAINT account__ktnr__fk REFERENCES account_type(ktnr)
,
regdate DATE
DEFAULT SYSDATE
NOT NULL,
balance NUMBER(10,2)
NOT NULL
);
CREATE TABLE transfers(
row_nr NUMBER(9)
GENERATED ALWAYS AS IDENTITY
NOT NULL
CONSTRAINT transfers__row_nr__pk PRIMARY KEY,
pers_nr NUMBER(8,0)
NOT NULL
-- CONSTRAINT transfers__pers_nr__fk REFERENCES customer(pers_nr)
,
from_account_num NOT NULL
CONSTRAINT transfers_from__fk REFERENCES account(account_num),
to_account_num NOT NULL
CONSTRAINT transfers__to__fk REFERENCES account(account_num),
amount NUMBER(10,2)
NOT NULL
CONSTRAINT transfers__amount_chk CHECK ( amount > 0 ),
datetime DATE
DEFAULT SYSDATE
NOT NULL,
CONSTRAINT transfers__from_ne_to__chk CHECK ( from_account_num != to_account_num )
);
Then you can create the trigger:
CREATE TRIGGER aifer_transfers
AFTER INSERT ON TRANSFERS
FOR EACH ROW
BEGIN
UPDATE account
SET balance = CASE account_num
WHEN :NEW.from_account_num
THEN balance - :NEW.amount
WHEN :NEW.to_account_num
THEN balance + :NEW.amount
ELSE balance
END
WHERE account_num IN ( :new.from_account_num, :new.to_account_num );
END;
/
If you create 3 accounts:
INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );
INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );
INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );
Then after:
INSERT INTO transfers (
pers_nr, from_account_num, to_account_num, amount
) VALUES (
0, 1, 2, 20
);
Then:
SELECT * FROM account;
Outputs:
ACCOUNT_NUM
KTNR
REGDATE
BALANCE
1
0
18-MAY-21
80
2
0
18-MAY-21
120
3
0
18-MAY-21
100
Then after:
INSERT INTO transfers (
pers_nr, from_account_num, to_account_num, amount
) VALUES (
0, 1, 3, 15
);
It would be:
ACCOUNT_NUM
KTNR
REGDATE
BALANCE
1
0
18-MAY-21
65
2
0
18-MAY-21
120
3
0
18-MAY-21
115
Then after:
INSERT INTO transfers ( pers_nr, from_account_num, to_account_num, amount )
SELECT 0, 3, 2, 10 FROM DUAL UNION ALL
SELECT 0, 2, 3, 20 FROM DUAL UNION ALL
SELECT 0, 1, 2, 10 FROM DUAL;
It would be:
ACCOUNT_NUM
KTNR
REGDATE
BALANCE
1
0
18-MAY-21
55
2
0
18-MAY-21
120
3
0
18-MAY-21
125
db<>fiddle here
Update
For your tables, without any additional CHECK constraints, you may want:
CREATE TRIGGER aifer_transfers
AFTER INSERT ON TRANSFERS
FOR EACH ROW
BEGIN
IF :NEW.amount <= 0 THEN
RAISE_APPLICATION_ERROR( -20000, 'Amount must be above zero.' );
END IF;
IF :NEW.from_account_num IS NULL THEN
RAISE_APPLICATION_ERROR( -20000, 'From account must be non-null.' );
END IF;
IF :NEW.to_account_num IS NULL THEN
RAISE_APPLICATION_ERROR( -20000, 'To account must be non-null.' );
END IF;
IF :NEW.from_account_num = :NEW.to_account_num THEN
RAISE_APPLICATION_ERROR( -20000, 'Accounts must be different.' );
END IF;
UPDATE account
SET balance = CASE account_num
WHEN :NEW.from_account_num
THEN balance - :NEW.amount
WHEN :NEW.to_account_num
THEN balance + :NEW.amount
ELSE balance
END
WHERE account_num IN ( :new.from_account_num, :new.to_account_num );
END;
/
(But that validation should all be implemented as CHECK constraints on the transfers table rather than in a trigger.)
db<>fiddle here

Oracle SQL Trigger Syntax

I am trying to create a trigger that checks if the faculty member to be added to the assigned table will is already on the qualified table for a specified class. Perhaps this is a tedious method. Nevertheless, I'd still like to know what I'm doing wrong. The following is my code with created the created tables and the trigger being the last part of the code:
CODE:
CREATE TABLE Faculty (
FId varchar(10),
FName varchar(20),
CONSTRAINT Faculty_ID_pk PRIMARY KEY(FId)
);
CREATE TABLE Course (
CId varchar(10),
CName varchar(20),
CONSTRAINT Course_ID_pk PRIMARY KEY(CId)
);
CREATE TABLE Qualify (
QDate DATE,
FId varchar(10),
CId varchar(10),
CONSTRAINT Qualifying_date CHECK(QDate >= TO_DATE('2020-08-24', 'YYYY-MM-DD')),
CONSTRAINT Qualify_FID_fk FOREIGN KEY(FId) REFERENCES Faculty(FId),
CONSTRAINT Qualify_CID_fk FOREIGN KEY(CId) REFERENCES Course(CId)
);
CREATE TABLE Assign (
ADate DATE,
FId varchar(10),
CId varchar(10),
CONSTRAINT Qualifying_check CHECK(ADate > TO_DATE('2020-08-24', 'YYYY-MM-DD')),
CONSTRAINT Assign_FID_fk FOREIGN KEY(FId) REFERENCES Faculty(FId),
CONSTRAINT Assign_CID_fk FOREIGN KEY(CId) REFERENCES Course(CId)
);
CREATE OR REPLACE TRIGGER Check_If_Qualified
BEFORE INSERT ON Assign
FOR EACH ROW
DECLARE
v_facNum number;
BEGIN
SELECT f.FId
into v_facNum
from Faculty f
where f.facnum = :new.fid;
END;
However, I keep receiving an error saying:
Error at line 7: PLS-00225: subprogram or cursor 'F' reference is out of scope
v_facNum number;
BEGIN
SELECT f.FId
into v_facNum
from Faculty f
Does anyone know what could be wrong?
There are lots of issues in your code. You can check the count of records into FACULTY table and use that count for whatever logic you want.
CREATE OR REPLACE TRIGGER CHECK_IF_QUALIFIED
BEFORE INSERT ON ASSIGN
FOR EACH ROW
DECLARE
CNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO CNT
FROM FACULTY
WHERE FACNUM = :NEW.FID;
END;
/
That would be something like this (sample tables are really just a sample; they are here to make the trigger compile):
SQL> create table assign (fid number);
Table created.
SQL> create table faculty (facnum number, fid number);
Table created.
SQL> CREATE OR REPLACE TRIGGER Check_If_Qualified
2 BEFORE INSERT ON Assign
3 FOR EACH ROW
4 DECLARE
5 v_facNum number;
6 BEGIN
7 SELECT f.FId
8 into v_facNum
9 from Faculty f
10 where f.facnum = :new.fid;
11
12 -- now do something with v_facNum
13 END;
14 /
Trigger created.
SQL>

SQL Server - Limit maximum of 8 records per week to be inserted for a staff_schedule table for a single staff

I have a staff table and then staff_schedule table which references staff id. I want to limit the number of schedules created for a single staff to 8 records per week. How can I do that?
Thanks in advance.
What I have so far..
CREATE TABLE Staff
(
staffID int,
fullName varchar(100) NOT NULL,
s_category varchar(25),
s_email varchar(50),
s_contactNo int,
speciality varchar(100),
qualifications varchar(250),
pre_employment varchar(200),
salary numeric(8,2),
staff_gender char(1),
CONSTRAINT PK_Staff PRIMARY KEY (staffID),
CONSTRAINT CHK_StaffGender CHECK (staff_gender='M' OR staff_gender='F'),
CONSTRAINT CHK_FullName CHECK (fullName NOT LIKE '%0%' AND fullName NOT LIKE '%1%' AND fullName NOT LIKE '%2%' AND fullName NOT LIKE '%3%' AND fullName NOT LIKE '%4%' AND fullName NOT LIKE '%5%' AND fullName NOT LIKE '%6%' AND fullName NOT LIKE '%7%' AND fullName NOT LIKE '%8%' AND fullName NOT LIKE '%9%'),
CONSTRAINT CHK_SALARY CHECK (salary>0 AND salary<=150000)
);
CREATE TABLE Staff_Allocation
(
allocationId int,
staff_Id int,
branch_Id int,
staff_start_date DateTime,
staff_end_date DateTime,
CONSTRAINT PK_Staff_Allocation PRIMARY KEY (allocationId),
CONSTRAINT FK_Staff_Allocation_Staff FOREIGN KEY (staff_Id) REFERENCES Staff(staffID),
CONSTRAINT FK_Staff_Allocation_Branch FOREIGN KEY (branch_Id) REFERENCES Branch(branchID),
CONSTRAINT CHK_StaffAllocationRotaDaily CHECK (DATEDIFF(hh, staff_start_date, staff_end_date) <=6)
);
A trigger is definitely the way to go for enforcing this constraint. TABLE constraints cannot enforce requirements that span multiple rows.
There are a number of things to test for your requirements
Single row inserted
Multiple rows attempted to be inserted across different staff_ids
Multiple rows attempted to be inserted for same staff_id
Below trigger will satisfy these. I left the date range check as empty as I'm not sure the exact criteria. The inner query collects all the rows from rows being inserted along with all rows already present for staff_ids in the current insert. The outer query aggregates by staff_id after applying the date range criteria. Finally the IF EXISTS check is used to throw an error.
CREATE TRIGGER insert_checkScheduleCount
ON Staff_Allocation AS
BEGIN
IF EXISTS (
SELECT * FROM
(
SELECT staff_id, staff_start_date, staff_end_date
FROM
inserted // relevant columns from the current set of rows
// attempting to be inserted
UNION ALL
SELECT staff_id, staff_start_date, staff_end_date
FROM
Staff_Allocation
WHERE
staff_id IN (SELECT staff_id FROM inserted)
// rows already in the table for staff being touched in this insert
) AS staffRows
WHERE
<apply you criteria on staff_start_date and staff_end_date>
GROUP BY
staff_id
HAVING COUNT(*) > 8
)
BEGIN
RAISERROR(N'More than 8 rows', 16, -1)
END
END
You can create an instead of trigger:
Sample Query for your reference:
CREATE TRIGGER INSTEADOF_INS
ON Staff_Allocation
INSTEAD OF INSERT AS
BEGIN
DECLARE #CNT_ALLOC TINYINT
DECLARE #CNT_ALLOC_TAB TINYINT
DECLARE #ST_DATE DATE
DECLARE #END_DATE DATE
SELECT #ST_DATE=DATEADD(dd, -(DATEPART(dw, GETDATE())-1), GETDATE())
SELECT #END_DATE=DATEADD(dd, 7-(DATEPART(dw, GETDATE())), GETDATE())
SELECT #CNT_ALLOC=COUNT(*)
FROM Staff_Allocation S INNER JOIN INSERTED I
ON S.staff_Id = I.staff_Id AND S.staff_start_date>#ST_DATE AND S.staff_end_date<#END_DATE
IF (#CNT_ALLOC >8 )
BEGIN
RAISERROR (N'8 rows Per Week',
16, 1)
RETURN
END
INSERT INTO Staff_Allocation (allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date)
SELECT allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date
FROM inserted
END
GO
If you do not want to use a trigger you could just use an IF statement
DECLARE #TableCount INT
SELECT #TableCount = COUNT(*)
FROM YourTable
WHERE StartDate BETWEEN GETDATE()-7 AND GETDATE()
AND EndDate BETWEEN GETDATE()-7 AND GETDATE()
IF #TableCount < 8
INSERT...
ELSE
RAISERROR...
RETURN

List the 5 most expensive toys ordened by price(Postgres)

I want a list of the 5 toys that have cost more money, in descending order if we have delivering each toy every single time.
For example, I have an Ipad that cost 600€ and is requested by child 1, 2 and 3. Now, I have a Nintendo that cost 300€ and is requested 3 times (1 time by child 1 and 2 times by the child 3). I have another toy (Laptop) that cost 360€ and is requested 8 times (2 times by child 1 and 6 times by child 3). Then, I must see:
Laptop because is requested 8 times and cost 360€ (in total 8 * 360€ = 2880€)
Ipad because is requested 3 times and cost 600€ (in total 3 * 600€ = 1800€)
Nintendo
When I have this, I want to see the child's data that more times request the same toy, for example, in case of Nintendo I would like see information about child 2. In case of laptop I would like see information about child 3.
I create this type:
CREATE TYPE ToysList AS (
t_Toy_name VARCHAR(255),
t_Price REAL,
t_Times_requested INTEGER,
t_Total_amount_money REAL,
t_Child_name VARCHAR(255),
t_Child_times_request SMALLINT,
t_Child_address VARCHAR(255),
t_Number_Siblings SMALLINT);
The tables are these:
CREATE TABLE CHILD(
child_id SMALLINT,
child_name VARCHAR(255) NOT NULL,
birth_date DATE NOT NULL,
gender VARCHAR(255) NOT NULL,
address VARCHAR(255),
city VARCHAR(255),
CONSTRAINT PK_CHILD PRIMARY KEY(child_id),
CONSTRAINT VALID_GENDER CHECK (gender IN ('m', 'f')),
CONSTRAINT VALID_DATE CHECK (birth_date <= now())
);
CREATE TABLE letter (
letter_id SMALLINT NOT NULL,
arrival_date DATE DEFAULT now() NOT NULL,
number_toys INTEGER NOT NULL,
child_id SMALLINT,
CONSTRAINT valid_child_id CHECK ((child_id IS NOT NULL)),
CONSTRAINT PK_LETTER PRIMARY KEY(letter_id),
CONSTRAINT CHILD_FK FOREIGN KEY (child_id) REFERENCES CHILD(child_id)
);
CREATE TABLE SIBLING(
child_id1 SMALLINT,
child_id2 SMALLINT,
CONSTRAINT PK_SIBLING PRIMARY KEY(child_id1, child_id2),
CONSTRAINT CHILD1_FK FOREIGN KEY (child_id1) REFERENCES CHILD(child_id),
CONSTRAINT CHILD2_FK FOREIGN KEY (child_id2) REFERENCES CHILD(child_id)
);
CREATE TABLE TOY(
toy_id SMALLINT,
toy_name VARCHAR(255) NOT NULL,
price REAL NOT NULL,
toy_type VARCHAR(255) NOT NULL,
manufacturer VARCHAR(255),
CONSTRAINT PK_TOY PRIMARY KEY(toy_id),
CONSTRAINT POSITIVE_PRICE CHECK (price > 0),
CONSTRAINT VALID_TYPE CHECK(toy_type IN ('symbolic', 'rule', 'educational', 'cooperative', 'other'))
);
CREATE TABLE WISHED_TOY(
letter_id SMALLINT,
toy_id SMALLINT,
CONSTRAINT PK_WISHED_TOY PRIMARY KEY(letter_id, toy_id),
CONSTRAINT LETTER_FK FOREIGN KEY (letter_id) REFERENCES LETTER(letter_id),
CONSTRAINT TOY_FK FOREIGN KEY (toy_id) REFERENCES TOY(toy_id)
);
At this moment I did this:
CREATE OR REPLACE FUNCTION list_top_Toys() RETURNS SETOF ToysList AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id, t.toy_name
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM child c
INNER JOIN letter L ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE c.child_id = l_child_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
COMMIT;
Can anyone say me what I am doing wrong?
Thank you,
Your function returns setof type of data. So, just select from it`s result, like this:
select * from list_top_Toys();
After that You can manipulate with results as it is table.
But, as I can see, this function needs much more changes.
Second query gives same results in every LOOP iteration, so I changed it to reflect result of first SELECT, and to make Letters table as leading in query.
At first, why group by toy_name - there need to be only group by toy_id.
Also, group by child_name (in first inner query) is redundant.
I would include toy_id in result set, it may be useful in later computations.
Also, You did not set toy price as you says in your post, so first SELECT have to have toy`s price.
So, my version of your function will be:
CREATE OR REPLACE FUNCTION list_top_Toys()
RETURNS SETOF ToysList
AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Price, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, t.price, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t
INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Price = l_price;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM letter L
INNER JOIN child c ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE wis.toy_id = l_toy_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
I did not touch siblings query.