I'm currently going through the growing pains of trying to learn about functions and triggers. I'm trying to do a problem from a book I'm reading , but i dont understand how to do certain parts.
using this table
create table movies (
id integer primary key,
title varchar(255) not null,
year integer
);
insert into movies values (1, 'The Croods', 2013);
insert into movies values (2, 'Now You See Me', 2013);
insert into movies values (3, 'Argo', 2012);
insert into movies values (4, 'Jurassic World', 2015);
create table discs (
id integer primary key,
movie_id integer not null references movies(id),
type_id integer references disc_types(id),
price decimal(10,2),
available boolean
);
insert into discs values (1, 1, 1, 1.59, 't');
insert into discs values (2, 1, 1, 1.59, 'f');
insert into discs values (3, 1, 2, 2.99, 'f');
insert into discs values (4, 2, 1, 1.29, 't');
insert into discs values (5, 2, 1, 1.29, 't');
insert into discs values (6, 2, 2, 2.99, 't');
insert into discs values (7, 3, 2, 2.59, 't');
insert into discs values (8, 3, 2, 2.59, 't');
create table customers (
id integer primary key,
name varchar(255),
email varchar(255)
);
insert into customers values (1, 'John', 'john#hotmail.com');
insert into customers values (2, 'Jane', 'jane#gmail.com');
create table rentals (
id integer primary key,
customer_id integer not null references customers(id),
disc_id integer not null references discs(id),
date_rented date,
date_returned date
);
insert into rentals values (1, 1, 7, '2013-10-01', '2013-10-03');
insert into rentals values (2, 2, 5, '2013-10-05', '2013-10-06');
insert into rentals values (3, 2, 2, '2013-11-02', null);
insert into rentals values (4, 2, 3, '2013-11-02', null);
create table ratings (
customer_id integer not null references customers(id),
movie_id integer not null references movies(id),
rating integer,
primary key (customer_id, movie_id)
);
insert into ratings values (1, 1, 1);
insert into ratings values (1, 2, 4);
insert into ratings values (1, 3, 5);
insert into ratings values (2, 1, 4);
my logic was that i would have the new values of the ratings table that were going to be inserted or updated and use those to compare to whats in the rentals table to see if that customer had rented that movie already, if they did then they could enter a rating. but i cant transfer that logic in this lol. unless there an easier way to do this.
The loop inside the function complicates matters a bit, let's see if we can get rid of it. Your ratings table has a reference to customer and movie so we need a join.
SELECT COUNT(*) INTO rented FROM rentals WHERE disc_id IN
(SELECT id from discs INNER JOIN
rentals ON disc_id = discs.id where movie_id = new.movie_id)
AND customer_id = new.customer_id
Right this should make the logic of your stored procedure a lot easier. I am now leaving you to finish it because this after all is a learning exercise.
You need this sort of a join because it's more efficient and simpler than the loop. The ratings table has a reference to the movie_id but the rentals table only has a disc_id thus to find out if the user has rented a particular movie, you need to join it through the disc table.
You will need to change the return values. ref: http://www.postgresql.org/docs/9.2/static/plpgsql-trigger.html
Row-level triggers fired BEFORE can return null to signal the trigger
manager to skip the rest of the operation for this row (i.e.,
subsequent triggers are not fired, and the INSERT/UPDATE/DELETE does
not occur for this row). If a nonnull value is returned then the
operation proceeds with that row value
And also note that you do not do an INSERT inside your trigger function. You just return a non null value for the insert to proceed.
This is the EXISTS() version. (BTW: the definition for movies is missing)
CREATE OR REPLACE FUNCTION rate_only_rented()
RETURNS TRIGGER AS $func$
BEGIN
IF ( NOT EXISTS (
SELECT *
FROM rentals r
JOIN discs d ON r.disc_id = d.id
WHERE d.movie_id = NEW.movie_id
AND r.customer_id = NEW.customer_id
) ) THEN
RAISE EXCEPTION 'you(%) have not rented this movie(%) before'
, NEW.customer_id ,NEW.movie_id;
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$func$ language plpgsql;
And the trigger:
CREATE TRIGGER rate_only_rented
AFTER INSERT OR UPDATE
ON ratings
FOR EACH ROW
EXECUTE PROCEDURE rate_only_rented()
;
Related
I have created the tables with following code, but the foreign key constraints does not allow data addition. What can I do to solve this problem?
CREATE TABLE Employee(
Ssn VARCHAR(10) PRIMARY KEY NOT NULL,
BDate DATE,
FName VARCHAR(25),
MInit VARCHAR(5),
LName VARCHAR(25),
Address VARCHAR(40),
Sex VARCHAR(6),
Salary INT,
SupervisorSsn VARCHAR(10),
DNumber INT
);
CREATE TABLE
CREATE TABLE Department(
DNumber INT PRIMARY KEY NOT NULL,
DName VARCHAR(15),
MgrSsn VARCHAR(10),
MgrStartDate DATE,
NumberofEmployees INT,
CONSTRAINT Department_MgrSsn_FK FOREIGN KEY(MgrSsn) REFERENCES Employee(Ssn) ON DELETE SET DEFAULT ON UPDATE CASCADE
);
ALTER TABLE Employee
ADD CONSTRAINT Employee_SupervisorSsn_FK FOREIGN KEY(SupervisorSsn) REFERENCES Employee(Ssn) ON DELETE SET DEFAULT ON UPDATE CASCADE,
ADD CONSTRAINT Employee_DNumber_FK FOREIGN KEY(DNumber) REFERENCES Department(DNumber) ON DELETE SET DEFAULT ON UPDATE CASCADE;
There are several ways to do that in Postgres.
Update later
The most obvious one: insert null values firs, then update it later:
insert into department
(dnumber, dname)
values
(1, 'One'),
(2, 'Two'),
(3, 'Three');
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3);
update department
set mgrssn = '456'
where dnumber in (1,2);
update department
set mgrssn = '789'
where dnumber = 3;
Online example
Deferred constraints
Make the constraints deferred, so that they will be checked at the end of the transaction, rather when running the INSERT:
ALTER TABLE department
add constraint fk_dempt2emp foreign key (mgrssn) references employee
deferrable initially deferred; --<<
Then you can insert the rows in any order you like as long as everything happens in a single transaction:
begin transaction; --<< important!
insert into department
(dnumber, dname, mgrssn)
values
(1, 'One', '456'),
(2, 'Two', '456'),
(3, 'Three', '789')
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3);
commit; -- the FKs will be checked here
Do everything in a single statement
You can use a data modifying CTE to insert rows into two tables. As this is evaluated as a single statement, the constraints do not need to be deferrable.
with new_depts as (
insert into department
(dnumber, dname, mgrssn)
values
(1, 'One', '456'),
(2, 'Two', '456'),
(3, 'Three', '789')
)
insert into employee (ssn, fname, lname, supervisorssn, dnumber)
values
('123', 'Arthur', 'Dent', '456', 1),
('456', 'Ford', 'Prefect', null, 2),
('789', 'Zaphod', 'Beeblebrox', null, 3)
;
Online example
Given 3 tables. I need to build SQL query to find two actors who CAST TOGETHER THE MOST and list the titles of those movies. Sort alphabetically
https://www.db-fiddle.com/f/r2Y9CpH8n7MHTeBaqEHe9S/0
The data for reproducing below:
create table film_actor
(
actor_id integer,
film_id integer
)
;
create table film
(
film_id integer,
title varchar
)
;
create table actor
(
actor_id integer,
first_name varchar,
last_name varchar
)
;
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (1, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 1);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 2);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (2, 3);
INSERT INTO public.film_actor (actor_id, film_id) VALUES (3, 1);
INSERT INTO public.film (film_id, title) VALUES (1, 'First');
INSERT INTO public.film (film_id, title) VALUES (2, 'Second');
INSERT INTO public.film (film_id, title) VALUES (3, 'Third');
INSERT INTO public.film (film_id, title) VALUES (4, 'Fourth');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (1, 'John', 'Snow');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (2, 'Spider', 'Man');
INSERT INTO public.actor (actor_id, first_name, last_name) VALUES (3, 'Mike', 'Kameron');
Is this what you are looking for?
with acting_pairs as (
select a1.actor_id as a1_id, a2.actor_id as a2_id
from film_actor a1
join film_actor a2 on a1.film_id = a2.film_id
where a1.actor_id < a2.actor_id
)
select a1_id, a2_id, count(*) as total
from acting_pairs
group by (a1_id, a2_id)
order by total desc
limit 1
Giving us expected output for the example input would be nice.
For the information, the general purpose of this question is to be able to work in QGIS with an updatable view system.
Let's say I have a table "building" and a table "apartment". Let's say the tables are defined as such :
CREATE TABLE building (
id_building INTEGER PRIMARY KEY,
adress VARCHAR(255)
);
CREATE TABLE apartment (
id_apartment INTEGER PRIMARY KEY,
floor INTEGER,
id_building INTEGER,
CONSTRAINT fk1 FOREIGN KEY (id_building) REFERENCES building (id_building)
);
INSERT INTO building VALUES (1, 'adress1');
INSERT INTO building VALUES (2, 'adress2');
INSERT INTO apartment VALUES (1, 0, 1);
INSERT INTO apartment VALUES (2, 0, 1);
INSERT INTO apartment VALUES (3, 0, 1);
INSERT INTO apartment VALUES (4, 1, 1);
INSERT INTO apartment VALUES (5, 1, 1);
INSERT INTO apartment VALUES (6, 1, 1);
INSERT INTO apartment VALUES (7, 2, 1);
INSERT INTO apartment VALUES (8, 2, 1);
INSERT INTO apartment VALUES (9, 0, 2);
INSERT INTO apartment VALUES (10, 1, 2);
INSERT INTO apartment VALUES (11, 1, 2);
INSERT INTO apartment VALUES (12, 2, 2);
INSERT INTO apartment VALUES (13, 2, 2);
I would like, in a view of the table building, to display the number of apartment grouped by floor. If it is possible, what would be the way to do so ? And since the number of floors can vary, it would be even better to be able to automatically generate a somehow flexible view relatively to the number of floors of each building.
There is a version of this code here: http://rextester.com/MTIJ52125
Thanks for your time.
Maybe its just that easy:
CREATE VIEW apartments AS (
SELECT
building.id_building,
apartment.floor,
count(apartment.id_apartment) as apartments_per_floor
FROM
apartment,building
WHERE
building.id_building = apartment.id_building
GROUP BY apartment.floor, building.id_building, apartment.floor
ORDER BY 1,2
);
SELECT * FROM apartments;
I have the following DB
CREATE TABLE IF NOT EXISTS users (
user_uid INTEGER PRIMARY KEY,
user_name CHAR(64) NOT NULL,
token_id INTEGER
);
CREATE TABLE IF NOT EXISTS unique_thing (
id SERIAL PRIMARY KEY,
unique_thing_id INTEGER NOT NULL,
option_id INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS example (
id SERIAL PRIMARY KEY,
variable INTEGER NOT NULL,
variable_2 INTEGER NOT NULL,
char_var CHAR(64) NOT NULL,
char_var2 CHAR(512),
char_var3 CHAR(256),
file_path CHAR(256) NOT NULL
);
CREATE TABLE IF NOT EXISTS different_option_of_things (
id SERIAL PRIMARY KEY,
name CHAR(64)
);
CREATE TABLE IF NOT EXISTS commits (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
unique_thing_id INTEGER NOT NULL,
value REAL NOT NULL,
var CHAR(512) NOT NULL,
example_id INTEGER NOT NULL,
boolean_var boolean NOT NULL
);
The tables unique_thing, different_option_of_things and examples will be static (the data will be added rarely and manually).
The table commits will be rather large. It will be the table for insert only (I will delete very rarely).
The user will be the table with user idnetification. It will be not so large as unique_thing, but will have quite a few users.
The Data of the table will be as follows:
INSERT INTO users VALUES(1, 'pacefist', 2);
INSERT INTO users VALUES(3, 'motherfucker', 4);
INSERT INTO users VALUES(4, 'cheater', 5);
INSERT INTO different_option_of_things VALUES(1, 'blablab');
INSERT INTO different_option_of_things VALUES(2, 'smth different');
INSERT INTO different_option_of_things VALUES(3, 'unique_thing');
INSERT INTO different_option_of_things VALUES(4 ,'unique_thing2');
INSERT INTO unique_thing VALUES(DEFAULT, 1, 1);
INSERT INTO unique_thing VALUES(DEFAULT, 1, 3);
INSERT INTO unique_thing VALUES(DEFAULT, 2, 3);
INSERT INTO unique_thing VALUES(DEFAULT, 2, 2);
INSERT INTO example VALUES(1, 20, 20, 'fsdfsdf', 'fgdfgdfg', 'url', '/home/user/file.txt');
INSERT INTO example VALUES(2, 24, 40, 'sfadfadf', 'dfgdfg', 'url', '/home/user/file2.txt');
INSERT INTO commits VALUES(DEFAULT, 1, 1, 55.43, '1234567', 1, TRUE);
INSERT INTO commits VALUES(DEFAULT, 2, 1, 97.85, '1234573', 2, TRUE);
INSERT INTO commits VALUES(DEFAULT, 3, 1, 0.001, '98766543', 1, TRUE);
INSERT INTO commits VALUES(DEFAULT, 4, 2, 100500.00, 'xxxxxxxx', 1, TRUE);
So, the data will be inserted ther following way:
1) I have input data of different_option_of_things, e.g., [ blablab, unique_thing], the REAL value (like 8.9999) and the number of example like `fsdfsdf`
2) It's necessary to find this record in the table `unique_thing`
a) if we've found 2 or more values or haven't found anything
results false -> the search is over
b) if we've found 1 result then
3) we are searching all values (record from unique_thing) in the 'commits' table.
a) if it has been found
a.1 search of the given example name
a.1.1 if found -> get first 25 values and check whether the current value is bigger
a.1.1.1 if yes, we make a commit
a.1.1.2 if no, do nothing (do not duplicate the value)
a.1.2 no -> no results
a.2 if no -> no results
The second function will be almost the same but without insertion, we will just make a selection without insertion (only to get data) and will find for all existing values in the table 'examples' (not only one).
The question: is it better to create 3 functions instead of one big query?
SELECT count(1) AS counter FROM different_option_of_things
WHERE name IN (SELECT * FROM unnest(different_option_of_things));
SELECT * FROM example where id=fsdfsdf;
SELECT TOP 25
FROM commits
JOIN unique_thing
ON commits.id=unique_thing.unique_thing_id where value > 8.9999;
if results-> 0 do a commit
Or is it better to write one enormous query? I am using Postgresql, Tornado and momoko.
I would prefer two stored procedures each to get and insert data.
Pros:
all required data is in db, so it seems like a job for db,
each call on execute needs to:
get/wait for available connection in pool (depending on your app)
run query
fetch data
Release connection
x. And between all of this operation on IOLoop
Although momoko is non-blocking, it is not for free
db can be api, not only sack of data
Cons:
logic in db means you depend on it - change db engine (for example to cassandra) will be harder
often logic in db means there is no tests. Of course you can and you should test it (e.g. pgTap)
for simple tasks it seems like a overkill
It is matter of db and app load, performance and time constraints - in other words run tests and choose solution that meets your expectations/requirements.
I want to write trigger for insert update and Delete. O have one table named (tbl_rank) which have primary key (ID).
ID Name Rank
1 A 1
2 B 2
3 C 3
4 D 4
5 E 5
Now I want to insert new rank but conditions are
1) if I enter 6 it will be 6
2) if I enter 7 it also should be 6 (I mean in sequence)
3) if I enter 2 than than entered rank will be 2 and 2 will be 3 and so on
For delete trigger
1) if I delete 5 the rank should be 1 to 4
2) if I delete 2 the rank would be rearranged and 3 should be 2 and 4 would be 3 and so on
for update trigger
1) if I update 3 to 5 than 4 would be 3 and 5 would be 4
2) if I update 5 to 3 than 3 would be 4 and 4 would be 5
I wrote insert and delete trigger its working fine but in update I am getting uneven result.
Can you not just have tbl_rank as a view then you don't need any triggers? To rank them in the view you can use a windowed function row_number() over (order by Id)
How is the initial update performed? If you know it is an update then you need to do a delete and insert just for the effected range. Eg changing 3 to 5. You delete records for 3 to 5 then insert those 3 records again with the different ids. An update statement essentially does this anyway
There is an assumption that id is not an auto-identity column.
CREATE TRIGGER trg_tbl_rank
ON tbl_rank
INSTEAD OF INSERT,DELETE,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #v_deleted_rank INT;
DECLARE #v_inserted_rank INT;
DECLARE #v_max_rank INT;
SELECT #v_deleted_rank = COALESCE(rank, 0) FROM deleted;
SELECT #v_inserted_rank = COALESCE(rank, 0) FROM inserted;
SELECT #v_max_rank = COALESCE(MAX(rank), 0) FROM tbl_rank;
IF #v_deleted_rank > 0
BEGIN
DELETE FROM tbl_rank
WHERE id = (SELECT id FROM deleted);
UPDATE tbl_rank
SET rank = rank - 1
WHERE rank > #v_deleted_rank;
END
IF #v_inserted_rank > 0
BEGIN
IF #v_inserted_rank <= #v_max_rank
BEGIN
UPDATE tbl_rank
SET rank = rank + 1
WHERE rank >= #v_inserted_rank;
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_inserted_rank FROM inserted;
END
ELSE
INSERT INTO tbl_rank (id, name, rank)
SELECT id, name, #v_max_rank + 1 FROM inserted;
END
END
GO
Here are queries to test:
INSERT INTO tbl_rank (id, name, rank) VALUES (1, 'A', 1);
INSERT INTO tbl_rank (id, name, rank) VALUES (2, 'B', 2);
INSERT INTO tbl_rank (id, name, rank) VALUES (3, 'C', 3);
INSERT INTO tbl_rank (id, name, rank) VALUES (4, 'D', 4);
INSERT INTO tbl_rank (id, name, rank) VALUES (5, 'E', 5);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (6, 'F', 7);
SELECT * FROM tbl_rank;
INSERT INTO tbl_rank (id, name, rank) VALUES (7, 'G', 2);
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 7;
SELECT * FROM tbl_rank;
DELETE FROM tbl_rank WHERE rank = 2;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 5 WHERE rank = 3;
SELECT * FROM tbl_rank;
UPDATE tbl_rank SET rank = 3 WHERE rank = 5;
SELECT * FROM tbl_rank;
TRUNCATE TABLE tbl_rank;
I want to write trigger for insert update and Delete I have one table named(tbl_rank)which have primary key(ID)
Please post DDL, so that people do not have to guess what the keys, constraints, Declarative Referential Integrity, data types, etc. in your schema are. Learn how to follow ISO-11179 data element naming conventions and formatting rules. Temporal data should use ISO-8601 formats. Code should be in Standard SQL as much as possible and not local dialect.
This is minimal behavior on SQL forums. Putting “tbl_” on table name is a classic design flaw called “tbling” and the column names are violations of ISO-11179 rules, too. Now we have to guess at keys, data types, etc. Here is my guess and clean up.
CREATE TABLE Prizes
(prize_id INTEGER NOT NULL PRIMARY KEY,
prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL);
INSERT INTO Prizes
VALUES
(1, 'A', 1),
(2, 'B', 2),
(3, 'C', 3),
(4, 'D', 4),
(5, 'E', 5);
Why triggers? RDBMS has virtual tables and columns. This not a deck of punch cards or a magnetic tape file. A VIEW is always current and correct.
CREATE VIEW Prize_List
AS
SELECT prize_id, prize_name,
ROW_NUMBER() OVER (ORDER BY prize_id)
AS prize_rank
FROM Prizes;
But it might be better to drop the prize_id column completely and re-arrange the display order based on the prize_rank column:
CREATE TABLE Prizes
(prize_name CHAR(1) NOT NULL,
prize_rank INTEGER NOT NULL PRIMARY KEY);
Now use procedures to manipulate the table as needed.
CREATE PROCEDURE Swap_Prize_Ranks (#old_prize_rank INTEGER, #new_prize_rank INTEGER)
AS
UPDATE Prizes
SET prize_rank
= CASE prize_rank
WHEN #old_prize_rank
THEN #new_prize_rank
ELSE prize_rank + SIGN(#old_prize_rank - #new_prize_rank)
END
WHERE prize_rank BETWEEN #old_prize_rank AND #new_prize_rank
OR prize_rank BETWEEN #new_prize_rank AND #old_prize_rank;
When you want to drop a few rows, remember to close the gaps with this:
CREATE PROCEDURE Close_Prize_Gaps()
AS
UPDATE Prizes
SET prize_rank
= (SELECT COUNT (P1.prize_rank)
FROM Prizes AS P1
WHERE P1.prize_rank <= Prizes.prize_rank);