Oracle - Error at line 4: PL/SQL: SQL Statement - sql

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.

Related

MySQL - How do I make the HAVING query pick the lowest value listing AND also compare something else?

My function wants to check if the number of bedrooms match what the user inputs in to the function AND also check to make sure it is the listing with the lowest price. So for instance "cheapest_property_with_n_bathrooms (2)" would check all the listings where the property has 2 bedrooms, and then return the cheapest one. My code works, but doesnt return anything because HAVING needs to compare two values, and the part after the AND isn't comparing anything.
DELIMITER $$
DROP FUNCTION IF EXISTS cheapest_property_with_n_bedrooms $$
CREATE FUNCTION cheapest_property_with_n_bedrooms (numBedrooms INT)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE PropertyID INT;
DECLARE bedrooms INT;
SET PropertyID = (SELECT Property.property_id
FROM Property
JOIN Listing ON Listing.property_FK = Property.property_id
JOIN Amenities ON Amenities.property_FK = Property.property_id
GROUP BY Amenities.num_bedrooms
HAVING Amenities.num_bedrooms = numBedrooms AND MIN(Listing.price)
ORDER BY Property.property_id DESC
LIMIT 1
);
RETURN PropertyID;
END$$
DELIMITER ;
When I am trying with your SQL query it gives me an syntax error.
Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'db_160534543.Property.property_id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
I have resolved by that adding the Amenities.property_FK and Listing.price to the GROUP BY.
Instead of getting the least price using MIN function you can ORDER BY the price.
And When group by these columns, We need to change the ORDER BY to ASC to get the least price one.
Can you try with this SQL query
SELECT Property.property_id
FROM Property
JOIN Listing ON Listing.property_fK = Property.property_id
JOIN Amenities ON Amenities.property_FK =Property.property_id
GROUP BY
Amenities.property_FK,Amenities.num_bedrooms,Listing.price
HAVING Amenities.num_bedrooms = numBedrooms
ORDER BY Listing.price ASC LIMIT 1;
try with dbfiddle
You can refer this to get an example about GROUP BY using with JOIN tables.

Function is SQL

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

Multiple Columns and different Join Conditions for Oracle update

I have 2 tables , 1 is location and the Other one is Look up table. I have to look into the look up table for the location values and if they are present mark them as 'Y' and 'N' along with their corresponding values
I have written individual update Statements as below:
**Location1,L1value**
Update Location
set (Location1,L1value) =
(select UPPER(VAlue),'Y' from Location_lookup where trim(Location1)=Location
where exists (select 1 from Location_lookup where trim(Location1)=Location);
commit;
**Location2,value**
Update Location
set (Location2,L2value) =
(select UPPER(VAlue),'Y' from Location_lookup where trim(Location2)=Location
where exists (select 1 from Location_lookup where trim(Location2)=Location);
commit;
Similarly for 3rd flag and value.
Is there a way to write single update for all the three conditions? Reason why I am looking for single update is that I have 10+ million records and I do not want to scan the records three different times. The lookup table has > 32 million records.
Here is a solution which uses Oracle's bulk FORALL ... UPDATE capability. This is not quite as performative as a pure SQL solution but it is simpler to code and the efficiency difference probably won't matter much for 10 million rows on a modern enterprise server, especially if this is a one-off exercise.
Points to note:
You don't say whether LOCATION has a primary key. For this answer I have assumed it has an ID column. The solution won't work if there isn't a primary key, but if your table doesn't have a primary key you've likely got bigger problems.
Your question mentions setting the FLAG columns "as 'Y' and 'N'" but the required output only shows 'Y' setting. I have included processing for 'N' but see the coda underneath.
declare
cursor get_locations is
with lkup as (
select *
from location_lookup
)
select locn.id
,locn.location1
,upper(lup1.value) as l1value
,nvl2(lup1.value, 'Y', 'N') as l1flag
,locn.location2
,upper(lup2.value) as l2value
,nvl2(lup2.value, 'Y', 'N') as l2flag
,locn.location3
,upper(lup3.value) as l3value
,nvl2(lup3.value, 'Y', 'N') as l3flag
from location locn
left outer join lkup lup1 on trim(locn.location1) = lup1.location
left outer join lkup lup2 on trim(locn.location2) = lup2.location
left outer join lkup lup3 on trim(locn.location3) = lup3.location
where lup1.location is not null
or lup2.location is not null
or lup3.location is not null;
type t_locations_type is table of get_locations%rowtype index by binary_integer;
t_locations t_locations_type;
begin
open get_locations;
loop
fetch get_locations bulk collect into t_locations limit 10000;
exit when t_locations.count() = 0;
forall idx in t_locations.first() .. t_locations.last()
update location
set l1value = t_locations(idx).l1value
,l1flag = t_locations(idx).l1flag
,l2value = t_locations(idx).l2value
,l2flag = t_locations(idx).l2flag
,l3value = t_locations(idx).l3value
,l3flag = t_locations(idx).l3flag
where id = t_locations(idx).id;
end loop;
close get_locations;
end;
/
There is a working demo on db<>fiddle here. The demo output doesn't exactly match the sample output posted in the query, because that doesn't the given input data.
Setting flags to 'Y' or 'N'?
The code above uses left outer joins on the lookup table. If a row is found the NVL2() function will return 'Y' otherwise it returns 'N'. This means the flag columns are always populated, regardless of whether the value columns are. The exception is for rows which have no matches in LOCATION_LOOKUP for any location (ID=4000 in my demo). In this case the flag columns will be null. This inconsistency follows from the inconsistencies in the question.
To resolve it:
if you want all flag columns to be populated with 'N' remove the WHERE clause from the get_locations cursor query.
if you don't want to set flags to 'N' change the NVL2() function calls accordingly: nvl2(lup1.value, 'Y', null) as l1flag

Delete rows depending on values from another table

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

PL/SQL Anonymous block 'Exact fetch returns more than requested number of rows'

I've been working on a PL/SQL script that calculates the similarity between two recipes.
Recipe is the table containing all the titles, prep etc. Ingredient is where all the unique ingredients are stored, and recipeing is where the many to many problem is solved, and the two are linked.
CREATE OR REPLACE FUNCTION func_similarity
(idS1 IN recipes.recipe.recipeID%type , idS2 IN recipes.recipe.recipeID%type )
RETURN NUMBER
AS
sim_value NUMBER := 0;
BEGIN
SELECT 2*
(select count(*) from recipes.ingredient i where i.ingredientID in (
Select distinct ingredientID from recipes.recipeing where recipeID = s1.recipeID
intersect
select distinct ingredientID from recipes.recipeing where recipeID = s2.recipeID))
/ ((select distinct count(ingredientID) from RECIPES.recipeing where recipeID = s1.recipeID) +
(select distinct count(ingredientID) from recipes.recipeing where recipeID = s2.recipeID) ) INTO sim_value
from recipes.recipe s1, recipes.recipe s2
where s1.recipeID = idS1
and s2.recipeID = idS2;
RETURN (sim_value);
END func_similarity;
/
However, when I go to test it with an anonymous block, I get the error:
"exact fetch returns more than requested number of rows"
declare
v_sim number;
begin
v_sim:=func_similarity(1,4);
dbms_output.put_line(v_sim);
end;
/
Now, I'm pretty sure the function makes sense and should work (took me all weekend). Does anyone have any ideas as to why this might not be working?
Thanks in advance.
There is no link between the two tables:
from recipes.recipe s1, recipes.recipe s2
where s1.recipeID = idS1
and s2.recipeID = idS2;
Consequently the query is a cross join. This doesn't matter if recipeID is a primary key. But if you have duplicate numbers in that column your query will return more than one row. If your query returns more than one row your function will hurl a TOO_MANY_ROWS exception, because we can only select one row INTO a variable (unless we use BULK COLLECT INTO a collection variable
.
Only 1 line should be returned into sim_value. To see what is going on, run SQL without INTO and using values for IDS1 and IDS2.