SQL CREATE FUNCTION generates 'no such function' error - sql

I am trying to generate a column which is not stored in the database. The column obtains its value from a function. The problem is there is an error of 'no such function:'
What should I do to get the value from this function and assign it to the column?
My SQL script so far:
CREATE FUNCTION GETAVERAGE (#locationID AS INTEGER)
RETURNS NUMERIC
AS
BEGIN
DECLARE #avg AS NUMERIC
SET #avg = SELECT AVG(value) FROM properties WHERE location_id = #locationID
END;
CREATE TABLE IF NOT EXISTS properties
(
property_id INTEGER PRIMARY KEY,
location_id INTEGER,
address VARCHAR (100),
value NUMERIC,
average_prop_value NUMERIC GENERATED ALWAYS AS (GETAVERAGE(location_id)) VIRTUAL,
FOREIGN KEY (location_id) REFERENCES locations (location_id)
);
CREATE TABLE IF NOT EXISTS locations
(
location_id INTEGER PRIMARY KEY,
name VARCHAR (50) UNIQUE
);

Not sure which database you are using. If you add a specific tag (and version detail) to the question, that could help. It could be MySQL. Here's standard SQL that would work with most databases (of a recent version):
SELECT t.*
, AVG(value) OVER (PARTITION BY location_id) AS avg_value
FROM properties t
;
Use this in a VIEW, if you wish.

Related

postgresql trigger to compare the third to last character with an attribute

I have recently started studying PostgreSQL and am having trouble creating triggers.
In the specific case I should check that a male athlete cannot participate in a competition for women and vice versa; in the match_code attribute an 'M' or an 'F' is inserted as the third to last character to identify that the race is for males or females (for example: 'Q100x4M06'); only one character, 'M' or 'F', is stored in the gender attribute.
I would therefore need to understand how to compare them and activate the trigger when they are not correctly entered in the participation table.
This is what i have assumed but i know it is wrong, it is just an idea, can someone help me?
CREATE FUNCTION check()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $BODY$
DECLARE
x VARCHAR;
y VARCHAR;
BEGIN
SELECT match_code INTO x FROM race;
SELECT gender INTO y FROM athlete;
IF $x LIKE '&$y__' == athlete.gender
THEN
RETURN new;
ELSE
RAISE EXCEPTION $$It is not possible to add an athlete of a gender that is not compatible with the competition$$;
RETURN NULL;
END IF;
END;
$BODY$
CREAT TRIGGER triggerCheck
BEFORE INSERT OR UPDATE ON participation
FOR EACH ROW
EXECUTE PROCEDURE check();
below are the definitions of the tables:
CREATE TABLE race (
ID_g SERIAL NOT NULL,
code_race VARCHAR (20) PRIMARY KEY,
r_date DATE,
discipline VARCHAR (20) NOT NULL
);
CREATE TABLE athlete (
ID_a SERIAL NOT NULL,
code_athlete INT CHECK (codice_atleta >= 0 AND codice_atleta <= 15000) PRIMARY KEY,
name VARCHAR (30),
surname VARCHAR (30) NOT NULL,
nation VARCHAR (3) NOT NULL,
gender CHAR CHECK (gender = 'M' OR gender = 'F'),
b_date DATE,
sponsor VARCHAR (20)
);
CREATE TABLE participation (
ID_p SERIAL NOT NULL,
codR VARCHAR (20) REFERENCES race (code_race) ON DELETE CASCADE ON UPDATE CASCADE,
codA INT REFERENCES athlete (code_athlete) ON DELETE CASCADE ON UPDATE CASCADE,
arrival_order INT CHECK (arrival_order > 0),
r_time TIME DEFAULT '00:00:00.00',
PRIMARY KEY (codG, codA)
);
According to the tables definition, your trigger function should be someting like :
CREATE FUNCTION check()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $BODY$
BEGIN
IF EXISTS ( SELECT 1
FROM race AS r
INNER JOIN athlete AS a
ON r.code_race ~ (a.gender || '..$')
WHERE r.code_race = NEW.codR
AND a.code_athlete = NEW.codA
)
THEN
RETURN NEW ;
ELSE
RAISE EXCEPTION 'It is not possible to add an athlete of a gender that is not compatible with the competition';
RETURN NULL;
END IF;
END;
$BODY$
In a function called by a trigger, you can (have to) use the variable NEW (resp. OLD) so that to refer to the new row (resp. the old row) to be inserted or updated (resp. only to be updated) in the targeted table ("participation" in your case) see the manual.
The proposed function doesn't need to declare any variable, as the test can be performed directly through the proposed sql query.
The proposed sql query :
First search for the rows in table "race" whose code_race equals the inserted/updated value of participation.codR = NEW.codR
Then search for the rows in table "athlete" whose code_athlete equals
the inserted/updated value of participation.codA = NEW.codA
Finally compare the selected rows from both tables "race" and
"athlete" using a regular expression to compare the code_race with
the gender values, see the manual
By the way,
(a) I don't see the added value of the columns ID of type serial in the tables definition, especially as they are not used in any primary key nor foreign key.
(b) The significant codification defined for the code_race attribute (for instance : 3rd last character defining the gender, an other character defining the competition level, ...) is a quite old-fashion practice that was used 30 years ago when the computer sciences had limited capacities and performances. In a more up-to-date approach, I would suggest you to manage these meaningful information in dedicated columns of the table "race", and then, if you really need a significant composite code_race, to implement it as a generated column :
CREATE TABLE race
( ID_g SERIAL NOT NULL PRIMARY KEY
, gender_race CHAR(1) NOT NULL CHECK (gender_race IN ('M', 'F'))
, competition_level VARCHAR(12) NOT NULL CHECK (competition_level IN ('Q-Qualifiers', 'H-Heats', 'S-Semifinals', 'F=Finals'))
, code_race VARCHAR (20) GENERATED ALWAYS AS (ID_g :: text || '-' || discipline || '-' || gender_race || '-' || left(competition_level, 1)) STORED
, r_date DATE NOT NULL
, discipline VARCHAR (20) NOT NULL
);
see the result in db<>fiddle.

How to insert data from one table into another as PostgreSQL array?

I have the following tables:
CREATE TABLE "User" (
id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
name text NOT NULL DEFAULT ''::text,
coinflips boolean[]
);
CREATE TABLE "User_coinflips_COPY" (
"nodeId" integer,
position integer,
value boolean,
id integer DEFAULT nextval('"User_coinflips_COPY_id_seq"'::regclass) PRIMARY KEY
);
I'm no looking for the SQL statement that grabs the value entry from each row in User_coinflips and inserts it as an array into the coinflips column on User.
Any help would be appreciated!
Update
Not sure if it's important but I just realized a minor mistake in my table definitions above, I replace User_coinflips with User_coinflips_COPY since that accurately describes my schema. Just for context, before it looked like this:
CREATE TABLE "User_coinflips" (
"nodeId" integer REFERENCES "User"(id) ON DELETE CASCADE,
position integer,
value boolean NOT NULL,
CONSTRAINT "User_coinflips_pkey" PRIMARY KEY ("nodeId", position)
);
You are looking for an UPDATE, rather then insert.
Use a derived table with the aggregated values to join against in the UPDATE statement:
update "User"
set conflips = t.flips
from (
select "nodeId", array_agg(value order by position) as flips
from "User_coinflips"
group by "nodeId"
) t
where t."nodeId" = "User"."nodeId";

Oracle SQL Check

I'm trying to implement an Oracle SQL database, in one of my tables I must introduce a restriction which does not allow to have more than 4 people in the same group:
I've tried this:
CREATE TABLE PERSON (name VARCHAR (20) PRIMARY KEY, group VARCHAR (3), CHECK (COUNT (*) group FROM PERSON) <=4);
also this (among others):
CREATE TABLE PERSON (name VARCHAR (20) PRIMARY KEY, group VARCHAR (3), CHECK NOT EXISTS (Select COUNT(*) FROM PERSON GROUP BY group HAVING COUNT(*) > 4);
But I'm getting errors every time (ORA-00934: group function is not allowed here or ORA-02251: subquery not allowed here.
What is the correct way to do it?
You have multiple issues with this
CREATE TABLE PERSON (
name VARCHAR(20) PRIMARY KEY,
group VARCHAR(3),
CHECK (COUNT (*) group FROM PERSON) <=4);
);
Oracle explicitly prefers VARCHAR2() to VARCHAR().
GROUP is a really bad name for a column, because it is a keyword. Surely you can find something like group_name or whatever for the name.
CHECK constraints only work within a single row.
Probably the best way to handle this is:
Create a new table called groups -- or whatever. It should have a group_id as well as group_name and num_persons.
Add triggers to person to keep the counter up-to-date for inserts, deletes, and updates to person.
Add a check constraint to groups, say check (num_persons <= 4).
You need to create the table as following:
CREATE TABLE PERSON (
name VARCHAR2(20) PRIMARY KEY,
group_ VARCHAR2(3) -- added _ after column name
); -- used varchar2 as data type of column
Then create before insert trigger as following:
create trigger person_trg
before insert on person
for each row
declare
group_cnt number;
begin
select count(distinct name)
into group_cnt
from person
where group_ = :new.group_;
if group_cnt = 4 then
raise_application_error(-20001, 'more than 4 persons are not allowed in the group');
end if;
end;
/
I have used distinct person name as more than 4 distinct persons are not allowed in the group as per your requirement.
db<>fiddle demo
Cheers!!

PostgreSQL: Select dynamic column in correlated subquery

I'm using the Entity-Attribute-Value (EAV) pattern to store 'overrides' for target objects. That is, there are three tables:
Entity, contains the target records
Attribute, contains the column names of 'overridable' columns in the Entity table
Override, contains the EAV records
What I'd like to do is select Overrides along with the value of the 'overridden' column from the Entity table. Thus, requiring dynamic use of the Attribute name in the SQL.
My naive attempt in (PostgreSQL) SQL:
SELECT
OV.entity_id as entity,
AT.name as attribute,
OV.value as value,
ENT.base_value as base_value
FROM "override" AS OV
LEFT JOIN "attribute" as AT
ON (OV.attribute_id = AT.id)
LEFT JOIN LATERAL (
SELECT
id,
AT.name as base_value -- AT.name doesn't resolve to a SQL identifier
FROM "entity"
) AS ENT
ON ENT.id = OV.entity_id;
This doesn't work as AT.name doesn't resolve to a SQL identifier and simply returns column names such as 'col1', 'col2', etc. rather than querying Entity with the column name.
I'm aware this is dynamic SQL but I'm pretty new to PL/pgSQL and couldn't figure out as it is correlated/lateral joined. Plus, is this even possible since the column types are not homogeneously typed? Note all the 'values' in the Override table are stored as strings to get round this problem.
Any help would be most appreciated!
You can use PL/pgSQL to dynamically request the columns. I'm assuming the following simplified database structure (all original and overide values are "character varying" in this example as I didn't find any further type information):
CREATE TABLE public.entity (
id integer NOT NULL DEFAULT nextval('entity_id_seq'::regclass),
attr1 character varying,
attr2 character varying,
<...>
CONSTRAINT entity_pkey PRIMARY KEY (id)
)
CREATE TABLE public.attribute (
id integer NOT NULL DEFAULT nextval('attribute_id_seq'::regclass),
name character varying,
CONSTRAINT attribute_pkey PRIMARY KEY (id)
)
CREATE TABLE public.override (
entity_id integer NOT NULL,
attribute_id integer NOT NULL,
value character varying,
CONSTRAINT override_pkey PRIMARY KEY (entity_id, attribute_id),
CONSTRAINT override_attribute_id_fkey FOREIGN KEY (attribute_id)
REFERENCES public.attribute (id),
CONSTRAINT override_entity_id_fkey FOREIGN KEY (entity_id)
REFERENCES public.entity (id))
With the PL/pgSQL function
create or replace function get_base_value(
entity_id integer,
column_identifier character varying
)
returns setof character varying
language plpgsql as $$
declare
begin
return query execute 'SELECT "' || column_identifier || '" FROM "entity" WHERE "id" = ' || entity_id || ';';
end $$;
you can use almost exactly your query:
SELECT
OV.entity_id as entity,
AT.name as attribute,
OV.value as value,
ENT.get_base_value as base_value
FROM "override" AS OV
LEFT JOIN "attribute" as AT
ON (OV.attribute_id = AT.id)
LEFT JOIN LATERAL (
SELECT id, get_base_value FROM get_base_value(OV.entity_id, AT.name)
) AS ENT
ON ENT.id = OV.entity_id;

How to combine particular rows in a pl/pgsql function that returns set of a view row type?

I have a view, and I have a function that returns records from this view.
Here is the view definition:
CREATE VIEW ctags(id, name, descr, freq) AS
SELECT tags.conc_id, expressions.name, concepts.descr, tags.freq
FROM tags, concepts, expressions
WHERE concepts.id = tags.conc_id
AND expressions.id = concepts.expr_id;
The column id references to the table tags, that, references to another table concepts, which, in turn, references to the table expressions.
Here are the table definitions:
CREATE TABLE expressions(
id serial PRIMARY KEY,
name text,
is_dropped bool DEFAULT FALSE,
rank float(53) DEFAULT 0,
state text DEFAULT 'never edited',
UNIQUE(name)
);
CREATE TABLE concepts(
id serial PRIMARY KEY,
expr_id int NOT NULL,
descr text NOT NULL,
source_id int,
equiv_p_id int,
equiv_r_id int,
equiv_len int,
weight int,
is_dropped bool DEFAULT FALSE,
FOREIGN KEY(expr_id) REFERENCES expressions,
FOREIGN KEY(source_id),
FOREIGN KEY(equiv_p_id) REFERENCES concepts,
FOREIGN KEY(equiv_r_id) REFERENCES concepts,
UNIQUE(id,equiv_p_id),
UNIQUE(id,equiv_r_id)
);
CREATE TABLE tags(
conc_id int NOT NULL,
freq int NOT NULL default 0,
UNIQUE(conc_id, freq)
);
The table expressions is also referenced from my view (ctags).
I want my function to combine rows of my view, that have equal values in the column name and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once, the combined row has one (doesn't matter which) of the ids, the value of the column descr is concatenated from the values of the rows being combined, and the row freq contains the sum of the values from the rows being combined. I have no idea how to do it, any help would be appreciated.
Basically, what you describe looks like this:
CREATE FUNCTION f_test()
RETURNS TABLE(min_id int, name text, all_descr text, sum_freq int) AS
$x$
SELECT min(t.conc_id) -- AS min_id
,e.name
,string_agg(c.descr, ', ') -- AS all_descr
,sum(t.freq) -- AS sum_freq
FROM tags t
JOIN concepts c USING (id)
JOIN expressions e ON e.id = c.expr_id;
-- WHERE e.name IS DISTINCT FROM
$x$
LANGUAGE sql;
Major points:
I ignored the view ctags altogether as it is not needed.
You could also write this as View so far, the function wrapper is not necessary.
You need PostgreSQL 9.0+ for string_agg(). Else you have to substitute with
array_to_string(array_agg(c.descr), ', ')
The only unclear part is this:
and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once
Waht column exactly refers to what column in table concepts?
concepts.equiv_r_id equals what exactly?
If you can clarify that part, I might be able to incorporate it into the solution.