Trigger keeps failing to compile. Claims table not found when table exists - sql

All the tables that are called definitely exist. I double checked the spelling as well.
The errors sqldeveloper gives are not very helpful and I can't seem to figure out why this is failing. I am attempting to make a trigger which updates the top5restaurants table when a new review comes in. I have a procedure which parses the review and adds them to the rating table. The top5restaurant table maintains the restaurants with the 5 highest ratings.
drop view bestratings_rest;
create view bestratings_rest(rid, rate) as
(select distinct rid, max(stars) from rating
group by rid);
drop table top5restaurants;
create table top5restaurants(rid int);
insert into top5restaurants rid
select rid from
(select rid, rate from bestratings_rest r
order by r.rate asc)
where rownum <=5;
drop view top5rest;
create view top5rest as
select rid, best from(
select t.rid, best from top5restaurants t
inner join (select distinct rid, max(stars) as best from rating
group by rid) b
on t.rid = b.rid);
create or replace trigger top5_trigger
after insert on rating
for each row
declare
minrow top5rest%rowtype;
restrid restaurant.rid%type;
restname restaurant.name%type;
begin
insert into minrow
select rid, min(best) from top5rest;
insert into restname
select rid from restaurant
where :new.rid = restaurant.rid;
if :new.stars > minrow.best then
DELETE FROM top5restaurants
where top5restaurants.rid = :new.rid;
insert into top5restaurants values(rid);
end if;
end;
/
--
--
begin
update_reviews('Jade Court','Sarah M.', 4, '08/17/2017');
update_reviews('Shanghai Terrace','Cameron J.', 5, '08/17/2017');
update_reviews('Rangoli','Vivek T.',5,'09/17/2017');
update_reviews('Shanghai Inn','Audrey M.',2,'07/08/2017');
update_reviews('Cumin','Cameron J.', 2, '09/17/2017');
end;
/
select * from top5restaurants;
sqldeveloper outputs:
View BESTRATINGS_REST dropped.
View BESTRATINGS_REST created.
Table TOP5RESTAURANTS dropped.
Table TOP5RESTAURANTS created.
5 rows inserted.
View TOP5REST dropped.
View TOP5REST created.
Trigger TOP5_TRIGGER compiled
LINE/COL ERROR
7/5 PL/SQL: SQL Statement ignored
7/17 PL/SQL: ORA-00942: table or view does not exist
11/5 PL/SQL: SQL Statement ignored
11/17 PL/SQL: ORA-00942: table or view does not exist
18/5 PL/SQL: SQL Statement ignored
18/40 PL/SQL: ORA-00984: column not allowed here
Errors: check compiler log

You don't insert into a scalar variable. You can do a select into. The statement
select rid
into restname
from restaurant
where :new.rid = restaurant.rid;
is syntactically valid. But it is almost certainly incorrect. Presumably, you want to select the restaurant name not the rid
select name
into restname
from restaurant
where :new.rid = restaurant.rid;
If you declare a %rowtype variable, then you want to do a select * into that variable. Otherwise you'll get a compilation error in the future if anyone adds or removes a column. You'd have to add a predicate that specified which row you wanted to select. My guess is that you want something like this to give the row with the lowest best score (or the row with the lowest rid if there are two with the same low score
select *
into minrow
from top5rest low
where not exists( select 1
from top5rest high
where low.best > high.best )
and not exists( select 1
from top5rest same
where low.best = same.best
and low.rid > same.rid )
More fundamentally, though, even if you get your syntax errors corrected, you're going to get a runtime mutating table error if you try to query the rating table (which your view does) from inside a row-level trigger on the rating table. That's not allowed. It almost guarantees that you have a problem in your logic. In this case, there seems to be no need for a separate table to store the top 5 restaurants. Realistically, your view should just do that calculation if you need the results in real-time or you should create a materialized view that refreshes periodically if you don't need to see a restaurant's overall rating change instantly when new ratings are added. So you probably don't want a trigger at all.

Related

"Object not found" error when using multiple table expressions in WITH...AS inside of CREATE VIEW

I am trying to create a view based on complex query in HSQLDB (version 2.5.1).
The query looks like this (simplified for clarity), also includes DDL for the tables:
DROP VIEW TEST_VIEW IF EXISTS;
DROP TABLE TEST_1 IF EXISTS;
CREATE TABLE TEST_1 (
contentid VARCHAR(10),
contenttype VARCHAR(10),
downloaddate TIMESTAMP
);
DROP TABLE TEST_2 IF EXISTS;
CREATE TABLE TEST_2 (
dbid INTEGER,
contentid VARCHAR(10),
version VARCHAR(10)
);
CREATE VIEW TEST_VIEW AS
WITH a AS (
SELECT CONTENTID, count(*) AS amount
FROM TEST_2
GROUP BY CONTENTID
),
b AS (
SELECT CONTENTID, amount
FROM a
)
SELECT b.CONTENTID, b.amount, i.DOWNLOADDATE
FROM b /* error here */
JOIN TEST_1 i ON i.CONTENTID = b.CONTENTID
ORDER BY b.CONTENTID;
However, it fails with the following error:
[42501][-5501] user lacks privilege or object not found: JOIN in statement [CREATE VIEW TEST_VIEW AS......
The same query runs fine when used as a SELECT (without CREATE VIEW...AS).
Also, the view is created successfully if there is only one table expression in WITH...AS statement, like below:
CREATE VIEW TEST_VIEW AS
WITH a AS (
SELECT CONTENTID, count(*) AS amount
FROM TEST_2
GROUP BY CONTENTID
)
SELECT a.CONTENTID, a.amount, i.DOWNLOADDATE
FROM a
JOIN TEST_1 i ON i.CONTENTID = a.CONTENTID
ORDER BY a.CONTENTID;
It looks like in the first statement the DB engine tries to parse "JOIN" as a table alias for table "b".
Is there a syntax error I have not noticed, or does HSQLDB not support multiple table expressions in WITH...AS inside of CREATE VIEW?
Edit: Updated example query to include table DDL for completeness.
HSQLDB supports creating this type of view.
As you haven't provided table definitions, I tried with a similar query with the test tables that are generated by DatabaseManager and it was successful. Please report the tables.
CREATE VIEW REPORT_LINKED_IDS AS
WITH a AS (
SELECT PRODUCTID, count(*) AS amount
FROM ITEM
GROUP BY PRODUCTID
),
b AS (
SELECT PRODUCTID, amount
FROM a
)
SELECT b.PRODUCTID, b.amount, i.NAME, i.PRICE
FROM b
JOIN PRODUCT i ON i.ID = b.PRODUCTID
ORDER BY b.PRODUCTID;
Thanks to a suggestion by #fredt, I have confirmed that the issue is with trying to use this query in IntelliJ IDEA (2020.1). The query worked fine and the view was created successfully in the same DB when another tool was used (DbVisualizer in my case). Furthermore, after having created the view in DB, IntelliJ IDEA throws an exception on the same word "JOIN" when trying to connect to this DB - the error is similar to this: The specified database user/password combination is rejected: org.hsqldb.HsqlException: unexpected token: NOT. Similarly to a comment in the above question, I have recovered from the error by manually editing the .script file.
There are at least 2 possible options to resolve the issue:
1st solution: Refactor SQL query to only have one table in WITH clause. In my case I just moved the first table to a select expression in FROM clause, like below:
CREATE VIEW TEST_VIEW AS
WITH b AS (
SELECT CONTENTID, amount
FROM (
SELECT CONTENTID, count(*) AS amount
FROM TEST_2
GROUP BY CONTENTID
)
)
SELECT b.CONTENTID, b.amount, i.DOWNLOADDATE
FROM b
JOIN TEST_1 i ON i.CONTENTID = b.CONTENTID
ORDER BY b.CONTENTID;
2nd solution: Use a different tool to work with the DB, or have the issue fixed in IDEA.

How should I temporarily store data within a PL/SQL procedure?

I am very new to PL/SQL. I have data in an initial table, named 'FLEX_PANEL_INSPECTIONS' that I am attempting to summarise in a second table, named 'PANEL_STATUS_2' using a PL/SQL procedure. However, due to the nature of the data, I have had to write a case statement in order to correctly summarise the data from FLEX_PANEL_INSPECTIONS. I have therefore created a third, intermediate table to bridge the two (named 'PANEL_STATUS_1') since the case statement will not allow columns in the group by clause which specifically order the data (to the extent of my knowledge - I get an error when I try and do this). I do not want to be storing data in the intermediate table - is there any way that I can either make it temporary (i.e. exist only while the procedure runs so that data from 'PANEL_STATUS_1' is not retained); create a view within the procedure, or remove the need for the intermediate table altogether?
Any help or criticism of my mistakes / misunderstanding of PL/SQL would be greatly appreciated. Here is the code I have written:
create or replace procedure PANEL_STATUS_PROCEDURE (panel_lot_id in number) as
begin
--Populate intermediate table with information about the status of the panels.
insert into PANEL_STATUS_1 (FLEX_LOT_ID, FLEX_PANEL_DMX, FLEX_PANEL_STATUS)
select FLEX_LOT_ID, FLEX_PANEL_DMX,
--Sum the status values of the 4 panel inspections. A panel passes if and only if this sum = 4.
case sum (FLEX_PANEL_STATUS)
when 4 then 1
else 0
end as new_panel_status
from FLEX_PANEL_INSPECTIONS
where FLEX_LOT_ID = panel_lot_id
group by FLEX_LOT_ID, FLEX_PANEL_DMX;
--Add information about the machine ID and the upload time to this table.
insert into PANEL_STATUS_2 (FLEX_LOT_ID, FLEX_PANEL_DMX, FLEX_PANEL_STATUS, MACHINE_ID, UPLOAD_TIME)
select distinct PANEL_STATUS_1.*, MACHINE_ID, UPLOAD_TIME
from PANEL_STATUS_1, FLEX_PANEL_INSPECTIONS
where (FLEX_PANEL_INSPECTIONS.FLEX_LOT_ID = PANEL_STATUS_1.FLEX_LOT_ID
and FLEX_PANEL_INSPECTIONS.FLEX_PANEL_DMX = PANEL_STATUS_1.FLEX_PANEL_DMX)
and FLEX_PANEL_INSPECTIONS.FLEX_LOT_ID = panel_lot_id;
end PANEL_STATUS_PROCEDURE;
/
You can create your temp table as
create global temporary table gtt_panel_status
( column datatype ... )
on commit [delete|preserve] rows;
(specifying either delete or preserve in the on commit clause).
However you usually don't need a temp table. You might try a with clause (CTE), or else an inline view along lines of select x, y, z from (select your subquery here).
Edit: actually looking at your query some more, I think what you a actually need is an analytic sum, i.e. a total without aggregating. For example, something like this:
create or replace procedure panel_status_procedure
( panel_lot_id in number )
as
begin
-- Add information about the machine ID and the upload time to this table.
insert into panel_status_2
( flex_lot_id
, flex_panel_dmx
, flex_panel_status
, machine_id
, upload_time )
select distinct
flex_lot_id
, flex_panel_dmx
, case sum(flex_panel_status) over (partition by flex_lot_id, flex_panel_dmx)
when 4 then 1
else 0
end
, machine_id
, upload_time
from flex_panel_inspections pi
where pi.flex_lot_id = panel_lot_id;
end panel_status_procedure;

Oracle SQL - select from view more rows than running select in the view

When I run this SQL, I get 116,463 rows.
select * from appsdisc.appsdisc_phones_gen_v
When I run the select that is in the view definition script, I get 11,702 rows.
I can't figure out why the result set is different.
The view script is as follows.
CREATE OR REPLACE FORCE VIEW APPSDISC.APPSDISC_PHONES_GEN_V
(PARTY_ID, CUSTOMER_ID, CUSTOMER_NUMBER, PHONE_NUMBER, PHONE_TYPE)
AS
SELECT party_id,
customer_id,
customer_number,
phone_number,
phone_type
FROM appsdisc_phones_v pv1
WHERE pv1.phone_type LIKE
DECODE (TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')
AND pv1.contact_point_id =
(SELECT MIN (pv2.contact_point_id)
FROM appsdisc_phones_v pv2
WHERE pv2.customer_id = pv1.customer_id
AND pv2.phone_type LIKE
DECODE (
TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE'));
If you're running the view query exactly as it is, and you are not logged in as APPSDISC, you might be querying your own table (or view), since appsdisc_phones_v isn't prefixed by the schema in the view script. Hopefully this is a development environment and you have an old copy for a valid reason.
Here's a demo of the effect I think you're seeing. As one user (SOUSER1) I can create and populate a table with a view on top of it, and grant access to that view to a different user. Notice I don't need to grant access to the underlying table directly.
create table my_table (id number);
insert into my_table
select level as id from dual connect by level <= 1000;
commit;
create view souser1.my_view as select * from my_table;
grant select on souser1.my_view to souser2;
select count(*) from my_view;
COUNT(*)
----------
1000
select count(*) from my_table;
COUNT(*)
----------
1000
I didn't specify the schema in the select inside the view statement, so it's going to be the same as the view owner, which is SOUSER1 in this case.
Then as a second user (SOUSER2) I can create my own version of the table, with fewer rows. Querying the view still shows the row count from the SOUSER1 table, not my own.
create table my_table (id number);
insert into my_table
select level as id from dual connect by level <= 100;
commit;
select count(*) from souser1.my_view;
COUNT(*)
----------
1000
If I run the query from the original view I'm seeing my own copy of the table, which is smaller, because the table name isn't qualified with the schema name - hence it defaults to my own:
select count(*) from my_table;
COUNT(*)
----------
100
So seeing a different number of rows makes sense as long as there are two versions of the table and you haven't specified which you want to query.
And in my case, if I try to query the other schema's table directly I get an error, since I didn't grant any privileges on that:
select count(*) from souser1.my_table;
SQL Error: ORA-00942: table or view does not exist
00942. 00000 - "table or view does not exist"
But you'd see the same error querying my_table if you don't have your own copy, you don't set your current schema on login, and you don't have a synonym pointing to a table in some schema.
Gordon is right, the select should return the same results as the view. Issues such as running the query in different schema, databases, or over database links could explain what you are observing. You can see this by running the two SQL commands in the same database and schema below and comparing the values returned by both.
First, confirm the number of rows the view returns with the SQL:
SELECT COUNT(*) FROM APPSDISC.APPSDISC_PHONES_GEN_V;
Then confirm the number of rows the query for the view returns with the SQL:
WITH RESULTS AS (
SELECT party_id,
customer_id,
customer_number,
phone_number,
phone_type
FROM appsdisc_phones_v pv1
WHERE pv1.phone_type LIKE
DECODE (TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')
AND pv1.contact_point_id =
(SELECT MIN (pv2.contact_point_id)
FROM appsdisc_phones_v pv2
WHERE pv2.customer_id = pv1.customer_id
AND pv2.phone_type LIKE
DECODE (
TRIM (SUBSTR (pv1.attribute14, 1, 4)),
'FR', 'FR T%',
'PHONE')))
SELECT COUNT(*)
FROM RESULTS
/
Both queries should be returning the same values. If not then there is more to this issue than a query returning a different number of rows.

SQL Insert/Update Issue

I am trying to update one table from another, im able to update fine as long as the customer record exists, but there are some entries that dont.
To solve this i've tried running the following insert
SELECT *
INTO SalBudgetCust
FROM SalBudgetCust_temp
WHERE NOT EXISTS (
SELECT Customer
FROM SalBudgetCust
WHERE Customer = SalBudgetCust_temp.Customer
)
but im prompted with
There is already an object named 'SalBudgetCust' in the database.
Im stuck at this point... could anyone offer a little guideance?
SELECT INTO implicitly creates the table you name. You should instead use INSERT INTO ... SELECT * FROM ..., so that the existing table is used.
It should be INSERT INTO instead of SELECT * INTO ... like
INSERT INTO SalBudgetCust SELECT * FROM SalBudgetCust_temp
WHERE NOT EXISTS
(
SELECT Customer FROM SalBudgetCust WHERE Customer = SalBudgetCust_temp.Customer
)
The general syntax to insert data of one table into another is :
INSERT INTO new_table
SELECT * FROM old_table
WHERE some_condition;
Where, new_table is the table where you want to insert data, old_table is table from where you are fetching data and some_condition is the expression / condition based upon which you want to fetch data from old table.
You may use other clauses like order by, group by, and even sub queries after where clause.
May refer this SQL INSERT INTO and it's subsequent pages.

Looking for SQL constraint: SELECT COUNT(*) from tBoss < 2

I'd like to limit the entries in a table. Let's say in table tBoss. Is there a SQL constraint that checks how many tuples are currently in the table? Like
SELECT COUNT(*) from tBoss < 2
Firebird says:
Invalid token.
Dynamic SQL Error.
SQL error code = -104.
Token unknown - line 3, column 8.
SELECT.
You could do this with a check constraint and a scalar function. Here's how I built a sample.
First, create a table:
CREATE TABLE MyTable
(
MyTableId int not null identity(1,1)
,MyName varchar(100) not null
)
Then create a function for that table. (You could maybe add the row count limit as a parameters if you want more flexibility.)
CREATE FUNCTION dbo.MyTableRowCount()
RETURNS int
AS
BEGIN
DECLARE #HowMany int
SELECT #HowMany = count(*)
from MyTable
RETURN #HowMany
END
Now add a check constraint using this function to the table
ALTER TABLE MyTable
add constraint CK_MyTable__TwoRowsMax
check (dbo.MyTableRowCount() < 3)
And test it:
INSERT MyTable (MyName) values ('Row one')
INSERT MyTable (MyName) values ('Row two')
INSERT MyTable (MyName) values ('Row three')
INSERT MyTable (MyName) values ('Row four')
A disadvantage is that every time you insert to the table, you have to run the function and perform a table scan... but so what, the table (with clustered index) occupies two pages max. The real disadvantage is that it looks kind of goofy... but everything looks goofy when you don't understand why it has to be that way.
(The trigger solution would work, but I like to avoid triggers whenever possible.)
Does your database have triggers? If so, Add a trigger that rolls back any insert that would add more than 2 rows...
Create Trigger MyTrigName
For Insert On tBoss
As
If (Select Count(*) From tBoss) > 2
RollBack Transaction
but to answer your question directly, the predicate you want is to just put the select subquery inside parentheses. like this ...
[First part of sql statement ]
Where (SELECT COUNT(*) from tBoss) < 2
To find multiples in a database your best bet is a sub-query for example: (Note I am assuming you are looking to find duplicated rows of some sort)
SELECT id FROM tBoss WHERE id IN ( SELECT id FROM tBoss GROUP BY id HAVING count(*) > 1 )
where id is the possibly duplicated column
SELECT COUNT(*) FROM tBoss WHERE someField < 2 GROUP BY someUniqueField