SQL trigger not working - sql

CREATE TABLE lab7.standings
(
team_name VARCHAR(100) NOT NULL PRIMARY KEY,
wins INTEGER,
losses INTEGER,
winPct NUMERIC,
CHECK(wins > 0),
CHECK(losses >0)
);
CREATE OR REPLACE FUNCTION
calc_winning_percentage()
RETURNS trigger AS $$
BEGIN
New.winPct := New.wins /(New.wins + New.losses);
RETURN NEW;
END;
$$LANGUAGE plpgsql;
CREATE TRIGGER
update_winning_percentage
AFTER INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE calc_winning_percentage();
This is accurately updating the wins in my standings table, but doesn't seem to send my new calculated winning percentage.

Try this:
CREATE TRIGGER update_winning_percentage
BEFORE INSERT OR UPDATE ON standings
FOR EACH ROW EXECUTE PROCEDURE calc_winning_percentage();

In addition to changing the trigger to BEFORE like pointed out by #Grijesh:
I notice three things in your table definition:
1.integer vs. numeric
wins and losses are of type integer, but winPct is numeric.
Try the following:
SELECT 1 / 4, 2 / 4
Gives you 0 both times. The result is of type integer, fractional digits are truncated towards zero. This happens in your trigger function before the integer result is coerced to numeric in the assignment. Therefore, changes in wins and losses that only affect fractional digits are lost to the result. Fix this by:
.. either changing the column definition to numeric for all involved columns in the base table.
.. or changing the trigger function:
NEW.winPct := NEW.wins::numeric / (NEW.wins + NEW.losses);
Casting one of the numbers in the calculation to numeric (::numeric) forces the result to be numeric and preserves fractional digits.
I strongly suggest the second variant, since integer is obviously the right type for wins and losses. If your percentage doesn't have to be super-exact, I would also consider using a plain floating point type (real or double precision) for the percentage. Your trigger could then use:
NEW.winPct := NEW.wins::float8 / (NEW.wins + NEW.losses);
2.The unquoted column name winPct
It's cast to lower case and effectively just winpct. Be sure to read about identifiers in PostgreSQL.
3. Schema
Your table obviously lives in a non-standard schema: lab7.standings. Unless that is included in your search_path, the trigger creation has to use a schema-qualified name:
...
BEFORE INSERT OR UPDATE ON lab7.standings
...
P.S.
Goes to show the importance of posting your table definition with this kind of question.

Related

How to create auto incrementing table based on a column name?

The title might be a little confusing but I will try explaining here better.
What I want to achieve is:
Create a table with 3 columns (so far so good):
CREATE TABLE StatisticCounter
(
statisticID NUMBER GENERATED ALWAYS as IDENTITY (START with 1000 INCREMENT by 1),
statistikname varchar(255),
counter integer
);
Create a way to call (function, procedure, trigger or something. I will call it function for now) that will increment counter with 1 based on statistikname or statisticid.
The restriction here being: said statistic is an SQL script ran through a cockpit file with a little bit of a different syntax than regular sql (in: out: select, WHERE with variables). I want to put a function or something in this cockpit file, that will run each time the script is run. Which will auto increment the number of times the statistic has been used.
I have no idea what I need (function, procedure, trigger or anything else) and this is why I am writing it a bit vague.
EDIT: I tried with merge but I always get the WHEN NOT MATCHED result executed. Without CAST its the same.
merge into StatisticCounter stc using
CAST((select 1000 id from dual)AS INTEGER) val on (stc.statisticid=val.id)
when matched then
UPDATE StatisticCounter SET counter = counter + 1;
when not matched then
select * from dual;
I found an answer on my own after going through a lot of functions and procedures.
Create table;
Create function;
Call function in file.
The important thing here is that the outside _cockpit file that executes the SQL goes through all OUT parameters for each line the statistic returns as result. This makes the counter go wild - no way to catch END of statistic or beginning. So I made a second _cockpit with only 1 result and attach it to the statistic I want to count.
CREATE TABLE UDX_Table_StatisticCounter(statisticID INTEGER,
StatistikName varchar2(256 char), counter integer);
CREATE OR REPLACE FUNCTION UDX_FUNC_StatisticCounter(datainput IN
VARCHAR2) RETURN VARCHAR2 IS PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
UPDATE UDX_Table_StatisticCounter SET counter = counter +1 WHERE statisticID=datainput;
COMMIT;
RETURN 'done'; END UDX_FUNC_StatisticCounter;

Postgres and math on columns during insert or update

When inserting or updating data I would like to be able to perform some math on two columns and have that entered as a value for a third column.
Table schema:
CREATE TABLE "public"."subscriptions" (
"id" int4 NOT NULL DEFAULT nextval('subscriptions_id_seq'::regclass),
"item" varchar,
"amount" float4,
"yearly_recurrance" int2,
"annual_cost" float4,
"rank" int2,
PRIMARY KEY ("id")
);
Insert Statement:
INSERT INTO "public"."subscriptions" ("item", "amount", "yearly_recurrance", "rank") VALUES ('test', '19', '7', '0');
I have created a function and trigger that in my mind should take the amount and multiply it by the yearly_recurrance and enter the result in the annual_cost field.
Function:
CREATE OR REPLACE FUNCTION public.calc_cost()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.annual_cost = NEW.amount * NEW.yearly_recurrance;
RETURN NEW;
END;
$function$
Trigger:
CREATE TRIGGER calc_cost BEFORE INSERT or UPDATE ON subscriptions
FOR EACH STATEMENT
EXECUTE PROCEDURE calc_cost();
It is not working out how I expect. Instead the values from the insert statement are put where they belong, but it is as though the function doesn't run. Not getting any errors that I can see.
I should mention that I believe the trigger is working and calling the function. If I put garbage in the function I get errors. I think the problem is in the function. Given this is my first function however, I am not sure how to work with uncommitted data. Perhaps it's not possible.
Use a generated column instead:
alter table subscriptions
add annual_cost float4 generated always as (amount * yearly_recurrance) stored;
No trigger overhead and it is always accurate.
Note: I don't recommend floats for monetary amounts; rounding errors can be problematic. Use numeric.

How can i do an insert into a table of my DB that has one attribute of ADT type? with Oracle Live SQL

I have created a table for seeing how many people could die from COVID-19 in Latin country's for that i created an ADT structure which have two attributes probabilidad_fallecidos that means probability to death and cantidad_infectados that is the quantity of infected per country, The part i'm having problems is when i try to do an insert says ORA-00947: not enough values
I'm very new at this, this is my first try
Below i will let my ADT structure,my function, my table and my try of insert
ADT
CREATE OR REPLACE TYPE infectados AS OBJECT(
cantidad_infectados number,
probabilidad_fallecidos number,
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
);
Function cantidad_fallecidos
CREATE OR REPLACE TYPE BODY infectados IS
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
IS numero1 number(1);
BEGIN
IF cantidad_infectados > probabilidad_fallecidos*cantidad_infectados THEN
RETURN (probabilidad_fallecidos*cantidad_infectados);
ELSE
RAISE_APLICATION_ERROR(-2000,'Error: cantidad_infectados es menor a la probabilidad de fallecidos');
END IF;
END;
END;
Creation of my table
CREATE TABLE Vnzla_infectado(
vnzlaInf_id NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
num_infectados infectados
);
Try of insert
INSERT INTO Vnzla_infectado
VALUES (infectados(100,0.1,infectados.cantidad_fallecidos(100,0.1)));
You are getting the error ORA-00947: not enough values because you are supplying one value to insert into a table with two columns, and you are not specifying which column you are trying to insert into so Oracle thinks you are inserting into all columns.
Your vnzlaInf_id column may be generated by an IDENTITY, but it looked to the database as if you were attempting to insert a value into that column and nothing into the num_infectados column, hence the error about not enough values.
So the first thing you need to do is to modify the INSERT statement to tell the database which column you want to insert into:
INSERT INTO Vnzla_infectado (num_infectados)
VALUES ...
I wrote "first thing" because there is another problem with your INSERT statement. If you add that column name, you get another error, ORA-02315: incorrect number of arguments for default constructor. This is because your type constructor has two arguments, but you are specifying three. One way to fix it is to get rid of the third argument:
INSERT INTO Vnzla_infectado (num_infectados)
VALUES (infectados(100,0.1));
This INSERT statement runs successfully.
Alternatively, you may want to add another field to your type:
CREATE OR REPLACE TYPE infectados AS OBJECT(
cantidad_infectados number,
probabilidad_fallecidos number,
your_new_field_name_here number,
STATIC FUNCTION cantidad_fallecidos(cantidad_infectados number,probabilidad_fallecidos number) RETURN number
);
If you are going to change the type, you will have to drop the table first and recreate it afterwards. After doing this, your original INSERT statement runs fine.
While I'm here, there are some other problems I noticed with your static function cantidad_fallecidos. Firstly, there is a typo in RAISE_APLICATION_ERROR, it should be RAISE_APPLICATION_ERROR - you're missing one of the Ps. Secondly, the argument -2000 will get rejected by Oracle: it will complain with ORA-21000: error number argument to raise_application_error of -2000 is out of range if you attempt to raise your custom error. I guess you meant to use -20000 for the error number instead. Thirdly, the condition
cantidad_infectados > probabilidad_fallecidos*cantidad_infectados
looks a bit odd to me. Provided cantidad_fallecidos is greater than zero, then it is equivalent to
1 > probabilidad_fallecidos
Also, are you sure you need to use > rather than >=? This leads to some odd behaviour in unusual cases: if cantidad_infectados is zero, your condition will never be true and your custom error will be raised whatever probabilidad_fallecidos is. To me it makes more sense to validate that probabilidad_fallecidos is between 0 and 1.

PostgreSQL Varchar UID to Int UID while preserving uniqueness

Say I have a unique column of VarChar(32).
ex. 13bfa574e23848b68f1b7b5ff6d794e1.
I want to preserve the uniqueness of this while converting the column to int. I figure I can convert all of the letters to their ascii equivalent, while retaining the numbers and character position. To do this, I will use the translate function.
psuedo code: select translate(uid, '[^0-9]', ascii('[^0-9]'))
My issue is finding all of the letters in the VarChar column originally.
I've tried
select uid, substring(uid from '[^0-9]') from test_table;
But it only returns the first letter it encounters. Using the above example, I would be looking for bfaebfbbffde
Any help is appreciated!
First off, I agree with the two commenters who said you should use a UID datatype.
That aside...
Your UID looks like a traditional one, in that it's not alphanumeric, it's hex. If this is the case, you can convert the hex to the numeric value using this solution:
PostgreSQL: convert hex string of a very large number to a NUMERIC
Notice the accepted solution (mine, shame) is not as good as the other solution listed, as mine will not work for hex values this large.
That said, yikes, what a huge number. Holy smokes.
Depending on how many records are in your table and the frequency of insert/update, I would consider a radically different approach. In a nutshell, I would create another column to store your numeric ID whose value would be determined by a sequence.
If you really want to make it bulletproof, you can also create a cross-reference table to store the relationships that would
Reuse an ID if it ever repeated (I know UIDs don't, but this would cover cases where a record is deleted by mistake, re-appears, and you want to retain the original id)
If UIDs repeat (like this is a child table with multiple records per UID), it would cover that case as well
If neither of these apply, you could dumb it down quite a bit.
The solution would look something like this:
Add an ID column that will be your numeric equivalent to the UID:
alter table test_table
add column id bigint
Create a sequence:
CREATE SEQUENCE test_id
create a cross-reference table (again, not necessary for the dumbed down version):
create table test_id_xref (
uid varchar(32) not null,
id bigint not null,
constraint test_id_xref_pk primary key (uid)
)
Then do a one-time update to assign a surrogate ID to each UID for both the cross-reference and actual tables:
insert into test_id_xref
with uids as (
select distinct uid
from test_table
)
select uid, nextval ('test_id')
from uids;
update test_table tt
set id = x.id
from test_id_xref x
where tt.uid = x.uid;
And finally, for all future inserts, create a trigger to assign the next value:
CREATE OR REPLACE FUNCTION test_table_insert_trigger()
RETURNS trigger AS
$BODY$
BEGIN
select t.id
from test_id_xref t
into NEW.id
where t.uid = NEW.uid;
if NEW.id is null then
NEW.id := nextval('test_id');
insert into test_id_xref values (NEW.uid, NEW.id);
end if;
return NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER insert_test_table_trigger
BEFORE INSERT
ON test_table
FOR EACH ROW
EXECUTE PROCEDURE test_table_insert_trigger();
create one function which replace charter with blank which you not need in string,
CREATE FUNCTION replace_char(v_string VARCHAR(32) CHARSET utf8) RETURNS VARCHAR(32)
DETERMINISTIC
BEGIN
DECLARE v_return_string VARCHAR(32) DEFAULT '';
DECLARE v_remove_char VARCHAR(200) DEFAULT '1,2,3,4,5,6,7,8,9,0';
DECLARE v_length, j INT(3) DEFAULT 0;
SET v_length = LENGTH(v_string);
WHILE(j < v_length) DO
IF ( FIND_IN_SET( SUBSTR(v_string, (j+1), 1), v_remove_char ) = 0) THEN
SET v_return_string = CONCAT(v_return_string, SUBSTR(v_string, (j+1), 1) );
END IF;
SET j = j+1;
END WHILE;
RETURN v_return_string;
END$$
DELIMITER ;
Now you just nee to call this function in query
select uid, replace_char(uid) from test_table;
It will give you string what you need (bfaebfbbffde)
If you want to int number only i.e 13574238486817567941 then change value of variable, and also column datatype in decimal(50,0), decimal can stored large number and there is 0 decimal point so it will store int value as decimal.
v_remove_char = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z';

How to round each array element in an array column in Postgres?

Say I have a table results that contains a score column that is an array full of scores:
CREATE TABLE results (
id serial PRIMARY KEY,
scores numeric[]
);
I would like to update the table so that I round each score to 4 decimal places.
I have created a rounding function round_numeric_array that works for a single array value:
CREATE OR REPLACE FUNCTION round_numeric_array (numeric[]) RETURNS numeric[]
LANGUAGE SQL
AS $$
SELECT array_agg(round(unnest($1), 4))
$$;
But how do I apply it to every value in the table? I've been trying
UPDATE results SET scores = round_numeric_array(scores)
But I get a set-valued function called in context that cannot accept a set error. Any ideas?
Place the unnest() function in the FROM clause:
CREATE OR REPLACE FUNCTION round_numeric_array (numeric[])
RETURNS numeric[]
IMMUTABLE
LANGUAGE SQL
AS $$
SELECT array_agg(round(elem, 4))
FROM unnest($1) as arr(elem);
$$;
Note, that the function is immutable, read more in the documentation.