Oracle - Trigger [closed] - sql

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I am new to PL SQL. I have the following two tables: UserGame and Game.
CREATE TABLE Game (
GameID INT NOT NULL,
Name CHAR(100) NOT NULL,
Description CHAR(100) NOT NULL,
Publisher CHAR(100) NOT NULL,
AgeRating INT NOT NULL,
ImageLink CHAR(100),
WebsiteUrl CHAR(100),
AverageRating FLOAT,
OverallRanking INT,
CONSTRAINT pkGameId
PRIMARY KEY (GameID),
CONSTRAINT AgeRating CHECK (AgeRating >= 0),
CONSTRAINT OverallRankingMin CHECK (OverallRanking >= 0)
);
CREATE TABLE UserGame (
PlayerID INT NOT NULL,
GameID INT NOT NULL,
Rating INT,
RatingComment CHAR(100),
LastPlayed DATE,
HighestScore INT,
InProgress CHAR(1),
CONSTRAINT pkUserGame
PRIMARY KEY (PlayerID, GameID),
CONSTRAINT fkPlayerID
FOREIGN KEY (PlayerID)
REFERENCES Player (PlayerID),
CONSTRAINT fkGameIdTer
FOREIGN KEY (GameID)
REFERENCES Game (GameID),
CONSTRAINT RatingMin CHECK (Rating >= 0),
CONSTRAINT RatingMax CHECK (Rating <= 5),
CONSTRAINT HighestScore CHECK (HighestScore >= 0),
CONSTRAINT InProgress CHECK (InProgress IN (0,1))
);
I would like to update the average rating of a game, every time a player updates a rating in UserGame.
This is what I came up with.
CREATE OR REPLACE TRIGGER averageUpdate
AFTER UPDATE OF Rating ON UserGame
BEGIN
FOR r1 in (SELECT DISTINCT GameID FROM UserGame)
LOOP
UPDATE Game
SET Game.AverageRating = (SELECT AVG(Rating) FROM UserGame WHERE GameID = r1.GameID GROUP BY GameID)
WHERE Game.GameID = r1.GameID;
END LOOP;
END averageUpdate;
But it does not work and I get this error:
Error: ORA-00900: invalid SQL statement
SQLState: 42000
ErrorCode: 900
Error occured in:
END LOOP
Could anyone explain to me what I am doing wrong?
It seems the code I had posted was correct, as confirmed by Justin Cave. There must be something wrong with my set-up then.
To make sure of this, I ran the queries using SQL Fiddle, with success.

Are you sure that what you posted is actually what you're running? It works fine for me (once I remove the foreign keys to tables that you haven't provided).
SQL> CREATE TABLE Game (
2 GameID INT NOT NULL,
3 Name CHAR(100) NOT NULL,
4 Description CHAR(100) NOT NULL,
5 Publisher CHAR(100) NOT NULL,
6 AgeRating INT NOT NULL,
7 ImageLink CHAR(100),
8 WebsiteUrl CHAR(100),
9 AverageRating FLOAT,
10 OverallRanking INT,
11 CONSTRAINT pkGameId
12 PRIMARY KEY (GameID),
13 CONSTRAINT AgeRating CHECK (AgeRating >= 0),
14 CONSTRAINT OverallRankingMin CHECK (OverallRanking >= 0)
15 );
Table created.
SQL> ed
Wrote file afiedt.buf
1 CREATE TABLE UserGame (
2 PlayerID INT NOT NULL,
3 GameID INT NOT NULL,
4 Rating INT,
5 RatingComment CHAR(100),
6 LastPlayed DATE,
7 HighestScore INT,
8 InProgress CHAR(1),
9 CONSTRAINT fkGameIdTer
10 FOREIGN KEY (GameID)
11 REFERENCES Game (GameID),
12 CONSTRAINT RatingMin CHECK (Rating >= 0),
13 CONSTRAINT RatingMax CHECK (Rating <= 5),
14 CONSTRAINT HighestScore CHECK (HighestScore >= 0),
15 CONSTRAINT InProgress CHECK (InProgress IN (0,1))
16* )
SQL> /
Table created.
SQL> CREATE OR REPLACE TRIGGER averageUpdate
2 AFTER UPDATE OF Rating ON UserGame
3 BEGIN
4 FOR r1 in (SELECT DISTINCT GameID FROM UserGame)
5 LOOP
6 UPDATE Game
7 SET Game.AverageRating = (SELECT AVG(Rating) FROM UserGame WHERE GameID = r1.GameID GROUP BY GameID)
8 WHERE Game.GameID = r1.GameID;
9 END LOOP;
10 END averageUpdate;
11 /
Trigger created.
Can you cut and paste from a SQL*Plus session just as I did here showing exactly what you are doing?
This doesn't have any impact on your current question. But I would strongly suggest that you not use char(100) or float data types in this data model. All these strings are variable length so you should be using varchar2. char is a fixed-width data type. A char(100) will always store exactly 100 bytes of data. If your actual data is less than that, Oracle will add spaces at the end. If you try to search for a particular value in the table and you end up with char comparison semantics, you'll need to ensure that the search string is space-padded to 100 bytes. A varchar2 is a variable-width data type. It uses only as much space as is required for the actual data. It doesn't do pointless and wasteful space-padding of data. And you never need to worry about space-padding search strings.
I can also all but guarantee that you want your ratings to be number data types of some length and precision, not float. Floating point numbers are inherently imprecise so an game that might average a score of 4.4 might be represented in a float as 4.3999999999865 or 4.4000000000107 (making the numbers up). It's very unlikely that is the sort of score that your users want to see. If you use a number(4,3), you'll get 3 decimal digits of precision and you won't have to deal with errors (or imprecision if you prefer) in the least significant bits of the data. A game that averages a score of 4.4 will have a value of 4.4 not something very very close to 4.4.
From a performance standpoint, I would strongly suggest that you not use a trigger to meet this requirement. Particularly not a trigger that recomputes the score for every game every time any game gets rated. That will not scale well and you will be spending gobs of time constantly recalculating scores. Assuming you need to store the computed score, you probably want it to be refreshed periodically not immediately when a rating is entered. If you do want to recompute the score every time a game is rated, only recompute the score for the game that was rated not for every game in the system.

Related

PostgreSQL Trigger takes 5 seconds

i have a MediaStore Database on Postgres where tried to make a trigger which Updates the average Rating of a Product if a new review is inserted.
The Problem is: If I insert a review now, it takes more than 5 seconds.
Im not really into Databases so i thought of asking you people here :)
The DDL of the two relevant tables are:
create table review
(
review_id bigint generated by default as identity primary key,
rating integer not null CHECK (rating BETWEEN 1 AND 5),
helpful integer not null CHECK (helpful >= 0),
reviewDate date,
benutzer varchar(255),
summary varchar(255),
comment text,
produkt_id bigint NOT NULL references produkt ON DELETE CASCADE
);
create table produkt
(
produkt_id bigint generated by default as identity primary key,
asin varchar(255) unique NOT NULL,
titel varchar(1000) NOT NULL,
rating double precision,
bild varchar(1000),
verkaufsrang integer
);
And the Trigger:
CREATE OR REPLACE FUNCTION update_rating()
RETURNS TRIGGER AS $$
BEGIN
UPDATE produkt
SET rating =
(SELECT AVG(rating) AS rating
FROM review
GROUP BY produkt_id
Having review.produkt_id = new.produkt_id)
WHERE produkt_id = new.produkt_id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE TRIGGER update_rating
AFTER INSERT ON review
FOR EACH ROW
EXECUTE PROCEDURE update_rating();
Does somebody have a solution which reduces the Time of the Insert?
You don't describe your indexes. An index on review (produkt_id, rating) could help a lot if you don't have one already. If you had columns in produkt for sum and count, then you could just compute the new average without needing to traverse the entire set in review for that produkt_id. You might have a problem with concurrency, but that could be a problem with your current one too.

Oracle: stored procedure error

I'm really new to SQL and I must create a stored procedure.
My idea is that I want to type my PaperRoll_ID and to get the "Worker_ID". Since my PaperRoll_ID values in the table are from 1 to 500 and my Worker_id values are from 1500 to 2000, I want to make it so that PaperRoll_ID is equal to the Worker_id index, not value by index (I mean, index 1 is the first Worker_id I added, 2 is the second and so on till 500 (the number of workers)). PaperRoll_ID is located in the table invoice_Paper and Worker_id in the table machine_operator.
Sorry if it's hard to understand, but I lack a lot of knowledge in SQL, so it's a bit hard for me to express myself.
create or replace PROCEDURE name_worker(pi IN NUMBER, mi OUT NUMBER) IS
BEGIN
Select q.worker_ID2 INTO mi
from invoice_paper z,machine_operator o
where z.PaperRoll_ID=pi AND o.WORKER_ID2 = q.worker_ID2;
END;
The tables are
create Table invoice_paper(
PaperRoll_ID Number(10) constraint ppr_id not null,
Single_Layer Varchar(20) Default 'None in stock',
Double_Layer Varchar(20) Default 'None in stock',
Manufacturer_FactoryID Integer,
primary key(PaperRoll_ID),
Constraint pprid_invoice Foreign key (Manufacturer_FactoryID) References Paper_Factory(Factory_ID)
);
create table machine_operator(
Insurence_ID number(10) constraint in_numb not null,
Worker_ID2 number(10) constraint worka_id not null,
operator_name Varchar(20),
Email Varchar(30),
Primary key (Insurence_ID, Worker_ID2),
Constraint wka_id Foreign key(Worker_ID2) References worker(worker_id)
);
"I want to make it so that PaperRoll_ID is equal to the Worker_id index, not value by index (I mean, index 1 is the first Worker_id I added, 2 is the second and so on till 500 (the number of workers)"
That's really not how relational databases work. You should enforce such a relationship with a foreign key between invoice_paper and machine_operator, probably by adding Worker_ID2 column to invoice_paper (*).
Beyond that it's not clear what you are trying to achieve with your procedure, so it's hard to suggest anything better. However, let's assume what you want to do is get the next free worker for assignment to invoice_paper.
create or replace PROCEDURE name_worker(mi OUT NUMBER) IS
BEGIN
Select min(o.worker_ID2) INTO mi
from machine_operator o
where o.WORKER_ID2 not in (select p.worker_ID2
from invoice_paper p)
;
END;
Kept as a procedure for ease of comparison, although this sort of thing is usually written as a FUNCTION with a return value instead.
(*) Just noticed that machine_operator has a compound primary key. If this is correct the foreign key would have to be (Insurence_ID, Worker_ID2) which is ugly. It these situations it is better to have a single column surrogate primary key and enforce the compound key with an additional unique constraint.

Complex T-SQL Statement

I am not the best SQL programmer so I am basically asking someone for help writing a query that will get my desired result. Here is the table structure for the scope of the query.
CREATE TABLE [dbo].[tblOverNightPermissions] (
[DateAndTime] DATETIME NULL,
[Address] NVARCHAR (200) NULL,
[Direction] NVARCHAR (102) NULL,
[NoOfDays] INT NULL,
[UserID] INT NOT NULL,
[OverNightID] INT IDENTITY (1, 1) NOT NULL,
[Exempt] INT NULL,
[Beat] INT NULL,
CONSTRAINT [PrimaryKey_1fd244dd-bfd8-4998-8439-4d7d7893d387] PRIMARY KEY CLUSTERED ([OverNightID] ASC),
CONSTRAINT [FK_tblOverNightPermissions_0] FOREIGN KEY ([UserID]) REFERENCES [dbo].[tblUsers] ([UserID])
);
CREATE TABLE [dbo].[tblOverNightToVehicles] (
[OverNightID] INT NOT NULL,
[VehicleID] INT NOT NULL,
[ID] INT IDENTITY (1, 1) NOT NULL,
CONSTRAINT [PrimaryKey_b433eaad-fb12-493c-9302-3f3bd9bd74e3] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_tblOverNightToVehicles_0] FOREIGN KEY ([OverNightID]) REFERENCES [dbo].[tblOverNightPermissions] ([OverNightID]) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT [FK_tblOverNightToVehicles_1] FOREIGN KEY ([VehicleID]) REFERENCES [dbo].[tblVehicles] ([VehicleID]) ON DELETE CASCADE ON UPDATE CASCADE
);
What I want to do is select record from tblOverNightPermissions and group them by month. I also want to count the number of records for that month and grouped by the vehicle ID which is in tblOverNightToVehicles. The goal is to run a check to make sure there have not been more than 5 overnightpermissions per vehicle id per month. It gets kind of tricky because the database design is not sound. As you can see, the NoOfDays field in the tblOvernightPermissions makes things complicated. Instead of there always being a set number of records per month, users have the ability to select up to 5 consecutive days of parking. So if I park today and select 5 days my record entry will look like this
DateAndTime = 5/8/2014
Address = x
Direction = S
NoOfDays = 5
UserId = 1
OvernightId = 1
Exempt = 0
Beat = 2
That means, for the month I will only have one physical record in tblOverNightPermissions that represents 5 days of parking. I could just as easily create 5 records in the table to signify 5 days of parking and thats where the issue comes in. Writing conditionals in TSQL to take into account if a record has NoOfDays > 1 add that to the count of the physical records for the month.
Here are the scripts to populate your databases
Script to populate tblOvernightPermissions https://dl.dropboxusercontent.com/u/62170850/tblOvernightPermissions.txt
Script to populate tblOvernightToVehicles
https://dl.dropboxusercontent.com/u/62170850/tblOverNightToVehicles.txt
SELECT DATEADD(month, DATEDIFF(month, 0, p.DateAndTime), 0) AS [Month],
SUM(p.NoOfDays) As Days
FROM tblOverNightPermissions p
INNER JOIN tblOverNightToVehicles v ON p.OverNightID = v.OverNightID
GROUP BY DATEADD(month, DATEDIFF(month, 0, p.DateAndTime), 0), v.VehicleID
HAVING SUM(p.NoOfDays) >= 5

ORA-00918: column ambiguously defined [duplicate]

This question already has an answer here:
ORA 00918- Column ambiguosly defined error [duplicate]
(1 answer)
Closed 9 years ago.
I am trying to retrieve some data (coursename) from one of my tables but the following error is coming all the time
ORA-00918: column ambiguously defined
the command I am typing is:
select bookno,courno,coursename
from booking, course,coursename
where bookno = 6200
and booking.courno = course.courno
and coursename.coursenameno = course.coursenameno
I have some tables as described :
CREATE TABLE BOOKING
(BOOKNO NUMBER (4) NOT NULL,
COURNO NUMBER (4) NOT NULL,
BOOKDATE DATE,
BOOKCUSTPAYMENT VARCHAR (20),
CONSTRAINT PK_BOOK PRIMARY KEY (BOOKNO,COURNO),
CONSTRAINT FK_BOOK FOREIGN KEY (COURNO) REFERENCES COURSE(COURNO)
ON DELETE CASCADE);
CREATE TABLE CUSTOMER
(CUSTNO NUMBER (4) NOT NULL, -- creation of primary-key
PROFNO NUMBER (4) NOT NULL,
CUSTFNAME VARCHAR (15),
CUSTLNAME VARCHAR (15),
CUSTDOB DATE,
CUSTPHONEDAY NUMBER (15),
CUSTPHONEEVE NUMBER (15),
CONSTRAINT PK_CUST PRIMARY KEY (CUSTNO),
CONSTRAINT FK_PROF FOREIGN KEY (PROFNO) REFERENCES PROFICIENCY(PROFNO)
ON DELETE CASCADE);
CREATE TABLE COURSENAME
( COURSENAMENO NUMBER (4) NOT NULL,
COURSENAME VARCHAR (20),
COURSEDESC VARCHAR (120),
COURSEDAYCOST NUMBER (7,2),
CONSTRAINT PK_COURSENAME PRIMARY KEY (COURSENAMENO));
CREATE TABLE COURSE
(COURNO NUMBER (4) NOT NULL, -- creation of primary-key
COURSTART DATE,
COUREND DATE,
COURSENAMENO NUMBER (4) NOT NULL,
ACCDAYNO NUMBER (4) NOT NULL,
FOODNO NUMBER (4) NOT NULL,
TRANSNO NUMBER (4) NOT NULL,
CONSTRAINT PK_COURSE PRIMARY KEY (COURNO),
CONSTRAINT FK_COURSENAME FOREIGN KEY (COURSENAMENO) REFERENCES COURSENAME(COURSENAMENO));
I am researching but I cannot figure out what is happening !!!
when the same column appears in several tables you need to specify which table is the one to be used. As a general rulem its always a good idea to prefix the column with the table (or alias) as improves readability and speeds up parsing.
so, for your query try (changes in upper case)
select BOOKING.bookno,BOOKING.courno,COURSENAME.coursename
from booking, course,coursename
where BOOKING.bookno = 6200
and booking.courno = course.courno
and coursename.coursenameno = course.coursenameno
You need to specify from which table the columns in SELECT and WHERE statements should be retrieved:
select booking.bookno, booking.courno, course.coursename
from booking, course, coursename
where booking.bookno = 6200
and booking.courno = course.courno
and coursename.coursenameno = course.coursenameno
Also, consider using ANSI SQL-92+ JOIN syntax like so:
select booking.bookno, booking.courno, course.coursename
from booking
inner join course on booking.courno = course.courno
inner join coursename on coursename.coursenameno = course.coursenameno
where booking.bookno = 6200
See [Bad habits to kick : using old-style JOINs][1] for some reasoning about it.
[1]: https://sqlblog.org/2009/10/08/bad-habits-to-kick-using-old-style-joins
When a column is ambigious, this means the database doesnt know which column to use from 2 or more different tables.
You must define in the select like this
select tablename.bookno,tablename.courno,tablename.coursename
from booking, course,coursename
where tablename.bookno = 6200
and booking.courno = course.courno <-- Here its correct
and coursename.coursenameno = course.coursenameno <-- Here its correct
Change tablename. to the correct table where the column is.
field courno in your select: you haven't defined from which table: course or booking

PL/SQL function that returns a value from a table after a check

I am new to php and sql and I am building a little game to learn a little bit more of the latter.
This is my simple database of three tables:
-- *********** SIMPLE MONSTERS DATABASE
CREATE TABLE monsters (
monster_id VARCHAR(20),
haunt_spawn_point VARCHAR(5) NOT NULL,
monster_name VARCHAR(30) NOT NULL,
level_str VARCHAR(10) NOT NULL,
creation_date DATE NOT NULL,
CONSTRAINT monster_id_pk PRIMARY KEY (monster_id)
);
-- ****************************************
CREATE TABLE spawntypes (
spawn_point VARCHAR(5),
special_tresures VARCHAR (5) NOT NULL,
maximum_monsters NUMBER NOT NULL,
unitary_experience NUMBER NOT NULL,
CONSTRAINT spawn_point_pk PRIMARY KEY (spawn_point)
);
-- ****************************************
CREATE TABLE fights (
fight_id NUMBER,
my_monster_id VARCHAR(20),
foe_spawn_point VARCHAR(5),
foe_monster_id VARCHAR(20) NOT NULL,
fight_start TIMESTAMP NOT NULL,
fight_end TIMESTAMP NOT NULL,
total_experience NUMBER NOT NULL
loot_type NUMBER NOT NULL,
CONSTRAINT my_monster_id_fk FOREIGN KEY (my_monster_id)
REFERENCES monsters (monster_id),
CONSTRAINT foe_spawn_point_fk FOREIGN KEY (foe_spawn_point)
REFERENCES spawntypes (spawn_point),
CONSTRAINT fight_id_pk PRIMARY KEY (fight_id)
);
Given this data how can I easily carry out this two tasks:
1) I would like to create a pl/sql function that passing only a fight_id as a parameter and given the foe_spawn_point (inside the fight table) return the unitary_experience that is related to this spawn point referencing the spawntypes table, how can I do it? :-/ [f(x)]
In order to calculate the total experience earned from a fight (unitary_experience * fight_length) I have created a function that given a particular fight will subtract the fight_end with the fight_start so now I know how long the fight lasted. [f(y)]
2) is it possible to use this two functions (multiply the result that they returns) during the database population task?
INSERT INTO fights VALUES(.... , f(x) * f(y), 'loot A');
in order to populate all the total_experience entries inside the fights table?
thank you for your help
In SQL, you don't generally talk about building functions to do things. The building blocks of SQL are queries, views, and stored procedures (most SQL dialects do have functions, but that is not the place to start).
So, given a variable with $FIGHTID you would fetch the unitary experience with a simple query that uses the join operation:
select unitary_experience
from fight f join
spawnTypes st
on st.spawn_point = f.foe_spawn_point
where fightid = $FIGHTID
If you have a series of values to insert, along with a function, I would recommend using the select form of insert:
insert into fights(<list of columns, total_experience)
select <list of values>,
($FIGHT_END - $FIGHT_START) * (select unitary_experience from spawnTypes where spawnType ='$SPAWN_POINT)
One comment about the tables. It is a good idea for all the ids in the table to be integers that are auto-incremented. In Oracle you do this by creating a sequence (and it is simpler in most other databases).