I want to create a function with a trigger on a relation. The trigger is supposed to not let more than 3 datasets inside of a relation. Somehow the function I created gives an exception regardless of how many datasets are inside of the Relation.
Here is my Code for the Function. so it gives back Null which apparently is enough to raise the exception. How do I make it so that when there are less than 3 it does not raise the exception?
create function myFunction() returns trigger as $$
Begin
if exists(
select case when count(*)>3 then count(*) end
from person
)
then raise exception 'Just 3 Datasets allowed';
End if;
return null;
End;
$$ language plpgsql
This construct:
if exists (select case when count(*)>3 then count(*) end
from person
)
always evaluates to true. Why? exists counts the number of rows returned by a subquery. A query that is an aggregation query with no group by always returns one row. If there are no rows in the underlying table, then the returned values would typically be NULL.
Note that exists doesn't care about the column values on the returned row. So a row with NULL values "exists" just as much as a row with other values. In fact, Postgres let's you return no columns at all! (Although I personally find that syntax a bit awkward.)
I suspect that you intend:
if (select count(*)
from person
) > 3
Related
How to delete rows from my customer table depending on values from another table, let's say orders?
If the customer has no active orders they should be able to be deleted from the DB along with their rows (done using CASCADE). However, if they have any active orders at all, they can't be deleted.
I thought about a PLPGSQL function, then using it in a trigger to check, but I'm lost. I have a basic block of SQL shown below of my first idea of deleting the record accordingly. But it doesn't work properly as it deletes the customer regardless of status, it just needs one cancelled order in this function and not all cancelled.
CREATE OR REPLACE FUNCTION DelCust(int)
RETURNS void AS $body$
DELETE FROM Customer
WHERE CustID IN (
SELECT CustID
FROM Order
WHERE Status = 'C'
AND CustID = $1
);
$body$
LANGUAGE SQL;
SELECT * FROM Customer;
I have also tried to use a PLPGSQL function returning a trigger then using a trigger to help with the deletion and checks, but I'm lost on it. Any thoughts on how to fix this? Any good sources for further reading?
You were very close. I suggest you use NOT IN instead of IN:
DELETE FROM Customer
WHERE CustID = $1 AND
CustID NOT IN (SELECT DISTINCT CustID
FROM Order
WHERE Status = 'A');
I'm guessing here that Status = 'A' means "active order". If it's something else change the code appropriately.
Your basic function can work like this:
CREATE OR REPLACE FUNCTION del_cust(_custid int)
RETURNS int --③
LANGUAGE sql AS
$func$
DELETE FROM customer c
WHERE c.custid = $1
AND NOT EXISTS ( -- ①
SELECT FROM "order" o -- ②
WHERE o.status = 'C' -- meaning "active"?
AND o.custid = $1
);
RETURNING c.custid; -- ③
$func$;
① Avoid NOT IN if you can. It's inefficient and potentially treacherous. Related:
Select rows which are not present in other table
② "order" is a reserved word in SQL. Better chose a legal identifier, or you have to always double-quote.
③ I made it RETURNS int to signify whether the row was actually deleted. The function returns NULL if the DELETE does not go through. Optional addition.
For a trigger implementation, consider this closely related answer:
Foreign key contraints in many-to-many relationships
Currently, inside a loop, I have:
IF(R1.CURRENT_STATE_CODE not in (select stvstat_code from stvstat))
THEN
v_cur_state := 'FR';
ELSE
v_cur_state := R1.CURRENT_STATE_CODE;
END IF;
This fails because you cannot perform a subquery in a plsql conditional. "subquery not allowed in this context"
How could I accomplish this instead
Why not just set the value in a select?
select coalesce(max(s.stvstat_code, 'FR'))
into v_cur_state
from stvstat s
where R1.CURRENT_STATE_CODE = s.stvstat_code;
This is an aggregation query with no group by, so it always returns exactly one row. If the where filters out all rows, then the MAX() returns NULL. The COALESCE() then fills in a value in that case.
I am trying to build a trigger for my database and I am getting an error and I suspect it is because of my into clause but I am not getting the reason. I was reading the documentation of it (https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/selectinto_statement.htm) and there is saying:
By default, a SELECT INTO statement must return only one row.
Otherwise, PL/SQL raises the predefined exception TOO_MANY_ROWS and
the values of the variables in the INTO clause are undefined. Make
sure your WHERE clause is specific enough to only match one row
Well, at least I am trying to be sure that my where clause is returning one and just one row. Can you give me some advice about it?
CREATE OR REPLACE TRIGGER t_ticket
INSTEAD OF INSERT ON V_buyTicket FOR EACH ROW
declare ticketID number; busy number; seatRoom number;
BEGIN
select count(a.id_ticket) into busy , s.freeSeats into seatRoom
from assigned a
inner join show e on (a.id_movie= e.id_movie)
inner join rooms s on (e.id_room = s.id_room)
where a.id_session = 1 AND a.id_movie = 1
group by s.freeSeats;
if(busy < seatRoom ) then
ticketID := seq_ticket.NEXTVAL;
insert into tickets(id_ticket, type, number, cc, store) values(ticketID, :new.type, :new.number, :new.cc, :new.store);
insert into assigned (id_ticket, id_movie, id_session) values(ticketID, :new.id_movie, :new.id_session);
else
DBMS_OUTPUT.PUT_LINE('No available seats');
end if;
END;
Don't use group by if you want exactly one row returned:
select count(a.id_ticket), sum(s.freeSeats)
into busy, seatRoom
from assigned a inner join
show e
on a.id_movie = e.id_movie inner join
rooms s
on e.id_room = s.id_room
where a.id_session = 1 and a.id_movie = 1;
An aggregation query without a group by always returns exactly one row. The alternative would be to include an explicit rownum = 1 in the WHERE clause.
My guess is that you conditions are not choosing exactly one room. You might want to check on the logic.
I have a trigger in SQL Server that needs to check on an update the number of rows with a value between a certain amount and do something accordingly. My current code is something like this:
IF EXISTS(SELECT COUNT(id) as NumberOfRows
FROM database
WHERE id = 3 AND value <= 20 and value > 2
GROUP BY id
HAVING COUNT(id) > 18)
-- if true, do something
From what I can tell, the select statement should find the number of rows with a value between 2 and 20 and if there are more than 18 rows, the EXISTS function should return 1 and the query will execute the code within the IF statement.
However, what is happening is that it is always executing the code within the IF statement regardless of the number of rows with a value between 2 and 20.
Any ideas on why this might be? I can post more complete code if it might help.
The reason is that the Exists function is checking the result of the sub-query for existing - are there any rows or not. And, as you return the COUNT, it'll never be not-existing - COUNT returns 0 if there are no rows presented in database.
Try to store the resulting count in a local variable, like in this question:
Using IF ELSE statement based on Count to execute different Insert statements
DECLARE #retVal int
SELECT #retVal = COUNT(*)
FROM TABLE
WHERE COLUMN = 'Some Value'
IF (#retVal > 0)
BEGIN
--INSERT SOMETHING
END
ELSE
BEGIN
--INSERT SOMETHING ELSE
END
I would do it like so (single line):
IF ((SELECT COUNT(id) FROM table WHERE ....)>18) BEGIN
...do something
You can even do between in a single line
IF ((SELECT COUNT(id) FROM table WHERE ....)between 2 and 20) BEGIN
...do something
END
Your subquery is looking for matches in the entire table. It does not limit the results only to those that are related to the rows affected by the update. Therefore, if the table already has rows matching your condition, the condition will be true on any update that affects other rows.
In order to count only the relevant rows, you should either join the database table to the inserted pseudo-table or use just the inserted table (there is not enough information in your question to be sure which is better).
Suppose I have the following data:
Table some_table:
some_table_id | value | other_table_id
--------------------------------------
1 | foo | 1
2 | bar | 2
Table other_table:
other_table_id | value
----------------------
1 | foo
2 | bar
Here, some_table has a foreign key to column other_table_id from other_table into the column of some name.
With the following query in PostgreSQL:
SELECT *
FROM some_table
WHERE other_table_id = 3;
As you see, 3 does not exists in other_table This query obviously will return 0 results.
Without doing a second query, is there a way to know if the foreign key that I am using as a filter effectively does not exist in the other_table?
Ideally as an error that later could be parsed (as it happends when doing an INSERT or an UPDATE with a wrong foreign key, for example).
You can exploit a feature of PL/pgSQL to implement this very cheaply:
CREATE OR REPLACE FUNCTION f_select_from_some_tbl(int)
RETURNS SETOF some_table AS
$func$
BEGIN
RETURN QUERY
SELECT *
FROM some_table
WHERE other_table_id = $1;
IF NOT FOUND THEN
RAISE WARNING 'Call with non-existing other_table_id >>%<<', $1;
END IF;
END
$func$ LANGUAGE plpgsql;
A final RETURN; is optional in this case.
The WARNINGis only raised if your query doesn't return any rows. I am not raising an ERROR in the example, since this would roll back the whole transaction (but you can do that if it fits your needs).
We've added a code example to the manual with Postgres 9.3 to demonstrate this.
If you perform an INSERT or UPDATE on some_table, specifying an other_table_id value that does not in fact exist in other_table, then you will get an error arising from violation of the foreign key constraint. SELECT queries are therefore your primary concern.
One way you could address the issue with SELECT queries would be to transform your queries to perform an outer join with other_table, like so:
SELECT st.*
FROM
other_table ot
LEFT JOIN some_table st ON st.other_table_id = ot.other_table_id
WHERE st.other_table_id = 3;
That query will always return at least one row if any other_table row has other_table_id = 3. In that case, if there is no matching some_table row, then it will return exactly one row, with that row having all columns NULL (given that it selects only columns from some_table), even columns that are declared not null.
If you want such queries to raise an error then you'll probably need to write a custom function to assist, but it can be done. I'd probably implement it in PL/pgSQL, using that language's RAISE statement.