CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _missing_id TEXT)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
from logs
where json_col_a::text like '%$1%' or -- <---- faulty
json_col_b::text like '%$1%' --- <----- faulty
order by timestamp desc'
, pg_typeof(_tbl_type))
USING _missing_id;
END
$func$ LANGUAGE plpgsql;
CREATE TABLE new_table AS TABLE logs WITH NO DATA;
SELECT * FROM data_of(NULL::new_table, '00000000-0000-0000-0000-000000000001');
Adapted from the answer here, I want to pass a GUID as a text into the SELECT query.
I convert the json_col_a, json_col_b from json to text.
Then, I check whether the _missing_id text is found in both columns above.
When I run, I got an error message saying:
ERROR: operator does not exist: unknown % new_table
LINE 4: where diagnostics_context::text like '%$1%' or
What have I missed?
Thanks
I'm learning how to write functions in SQL and this might be simple but can't seem to find what I'm looking for. I have a function as follows:
CREATE OR REPLACE FUNCTION A.aggregate(r_id text)
RETURNS VARCHAR
LANGUAGE plpgsql
AS $function$
begin
EXECUTE('CREATE UNLOGGED TABLE F.counts AS
SELECT name,
unique_cats,
unique_dogs,
total_cats,
total_dogs
FROM A.tb1
WHERE date in (select date from A.tb1) BETWEEN''' || start_date || '''AND''' || end_date ||
''';''');
END
$function$
;
I want to capture the minimum date and maximum date and use it in my execute function to filter out from my tb1 to get rows of data where the date is between the start and end date.
Modifications to your code:
-- Add to below
DECLARE
min_date date;
max_date date;
-- Add to below
BEGIN
SELECT INTO min_date, max_date min(some_date_fld), max(some_date_fld) FROM BA.activity_dates;
... BETWEEN $1 AND $2 ...
) USING min_date, max_date;
For SELECT INTO see SELECT INTO. For USING see Dynamic.
So I am working to create a function that will delete the 1 month worth records from a table. The table is in postgres. As postgres does not have stored procedures I am trying to declare a function with the logic that will insert the 1 month records into a history table and then delete the records from the live table. I have the following code :
CREATE FUNCTION DeleteAndInsertTransaction(Integer)
RETURNS Void
AS $Body$
SELECT now() into saveTime;
SELECT * INTO public.hist_table
FROM (select * from public.live_table
WHERE update < ((SELECT * FROM saveTime) - ($1::text || ' months')::interval)) as sub;
delete from public.live_table
where update < ((SELECT * FROM saveTime) - ($1::text || ' months')::interval);
DROP TABLE saveTime;
$Body$
Language 'sql';
So the above code compiles fine but when I try to run it by invoking it :- DeleteAndInsertTransaction(27) it gives me an
Error: relation "savetime" does not exist and I have no clue what is going on here.
If I take out the SELECT now() into saveTime; out of the function bloc and declare it before invoking the function then it runs fine but I need to store the current date into a variable and use that as a constant for the insert and delete and this is going against a huge table and there could be significant time difference between the insert and deletes. Any pointers as to what is going on here ?
select .. into .. is the deprecated syntax for create table ... as select ... which creates a new table.
So, SELECT now() into saveTime; actually creates a new table (named savetime), and is equivalent to: create table savetime as select now(); - it's not storing something in a variable.
To store a value in a variable, you need to first declare the variable, then you can assign the value. But you can only do that in PL/pgSQL, not SQL
CREATE FUNCTION DeleteAndInsertTransaction(p_num_months integer)
returns void
as
$Body$
declare
l_now timestamp;
begin
l_now := now();
...
end;
$body$
language plpgsql;
To insert into an existing table you need
insert into public.hist_table
select *
from public.live_table.
To select the rows from the last x month, there is no need to store the current date and time in a variable to begin with. It's also easier to use make_interval() to generate an interval based on a specified unit.
You can simply use
select *
from live_table
where updated_at <= current_date - make_interval(mons => p_pum_months);
And as you don't need a variable, you can actually do all that with a language sql function.
So the function would look something like this:
CREATE FUNCTION DeleteAndInsertTransaction(p_num_months integer)
RETURNS Void
AS
$Body$
insert into public.hist_table
select *
from live_table
where updated_at < current_date - make_interval(months => p_pum_months);
delete from public.live_table
where updated_at < current_date - make_interval(months => p_pum_months);
$Body$
Language sql;
Note that the language name is an identifier and should not be quoted.
You can actually do the DELETE and INSERT in a single statement:
with deleted as (
delete from public.live_table
where updated_at <= current_date - make_interval(months => p_pum_months)
returning *
)
insert into hist_table
select *
from deleted;
I am trying to make a function that creates a table, inserts data into this table and then calculates the average value of this table.
Currently, I can do this by making a table beforehand and then run the function. But I would like to have a single function that does all of this. When I run the function I get the following response:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function loop_time_v(bigint) line 16 at SQL statement
And here is the function I am taliking about:
CREATE OR REPLACE FUNCTION loop_time (n bigint)
RETURNS VOID AS $$
DECLARE
a RECORD;
start_time timestamp with time zone := '2019-07-26 15:05:00+02';
end_time timestamp with time zone := (SELECT line_current_data.valid_for_timestamp FROM line_current_data ORDER BY valid_for_timestamp DESC LIMIT 1);
interval_num bigint := (select extract(epoch from (end_time::timestamp with time zone - '2019-07-26 15:05:00+02'::timestamp with time zone))/ 300);
BEGIN
CREATE UNLOGGED TABLE interval_avg (valid_for_timestamp timestamp with time zone, device_inst_id bigint, i_n double precision);
FOR a IN 1..interval_num LOOP
INSERT INTO interval_avg
SELECT
valid_for_timestamp,
device_inst_id,
value
FROM line_current_data
WHERE line_current_data.valid_for_timestamp > (start_time + ((a - 1) * interval '5 min'))
AND line_current_data.valid_for_timestamp <= (start_time + (a * interval '5 min'))
AND line_current_data.device_inst_id >= 1097
AND line_current_data.device_inst_id <= 1201;
INSERT INTO avg_line_current
SELECT
(start_time + (a * interval '5 min')),
device_inst_id,
avg(i_n)
FROM interval_avg
GROUP BY device_inst_id;
DELETE FROM interval_avg;
RAISE NOTICE '%', a;
END LOOP;
RAISE NOTICE 'Fertik';
END;
$$ LANGUAGE plpgsql;
I have function script that I converted from SQL Server to Postgres, now when I'm running the function I get an error
ERROR: structure of query does not match function result type
My function gets 3 parameters (siteid bigint, datefrom timestamp, dateto timestamp) and should return a table which I included in code. I used "Return Query".
I'm executing my function like this:
getrtbactivesiteplaces(1475, '2016-02-01', '2016-08-01')
How I can get this result as a table from my function?
This is screenshot of my function
{
CREATE OR REPLACE FUNCTION "whis2011"."getrtbactivesiteplaces"(IN siteid int8, IN datefrom timestamp, IN dateto timestamp) RETURNS SETOF "varchar"
AS $BODY$
DECLARE
siteid BIGINT;
datefrom timestamp without time zone;
dateto timestamp without time zone;
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
/* SQLWays Notice: SET TRANSACTION ISOLATION LEVEL READ COMMITTED must be called before procedure call */
-- SET TRANSACTION ISOLATION LEVEL READ COMMITTED
-- Insert statements for procedure here
RETURN QUERY SELECT pl."Id",
pl."RtbActiveSiteId",
pl."AdPlaceId",
pl."AdPosition",
pl."Ctr",
pl."State",
pl."BidPrice",
pl."MinBidFloor",
pl."MinBidFloorCurrency",
pl."AverageCpm",
pl."AverageClickCost",
coalesce(SUM(ss."BidsCount"),0) AS BidsCount,
coalesce(SUM(ss."ShowsCount"),0) AS ShowsCount,
coalesce(SUM(ss."RealShowsCount"),0) AS RealShowsCount,
coalesce(SUM(ss."ClicksCount"),0) AS ClicksCount,
coalesce(SUM(ss."ClickLayerClicksCount"),0) as ClickLayerClicksCount,
coalesce(SUM(ss."ShowsCost"),0::money) AS ShowsCost,
coalesce(SUM(ss."ClicksCost"),0::money) AS ClicksCost,
coalesce(SUM(ss."BidsCost"),0::money) AS BidsCost,
coalesce(SUM(ss."SliderMovesCount"),0) AS SliderMovesCount
FROM "whis2011"."RtbActiveSitePlaces" pl
LEFT OUTER JOIN "whis2011"."RtbActiveSitePlaceStatistics" ss ON ss."RtbActiveSitePlaceId" = pl."Id"
WHERE ss."Date" >= datefrom AND ss."Date" < dateto AND pl."RtbActiveSiteId" = siteid
GROUP BY pl."Id", pl."RtbActiveSiteId", pl."AdPlaceId", pl."AdPosition", pl."Ctr", pl."State", pl."BidPrice",
pl."MinBidFloor", pl."MinBidFloorCurrency", pl."AverageCpm", pl."AverageClickCost";
END;
$BODY$
LANGUAGE plpgsql
COST 100
CALLED ON NULL INPUT
SECURITY INVOKER
VOLATILE;
}
You have two problems with your code and I have two recommendations for improvements.
First of all, instead of RETURNS SETOF varchar you should do RETURNS TABLE ... specifying all columns that the function returns. Secondly, you have three function parameters, whose names you redeclare in the function block, masking out the parameter values. This is why the function returns nothing because the parameter values are not used.
Thirdly, I put all the sums in a sub-query, which reads easier and is probably more efficient too. Lastly, since the function body is a single SQL statement, you should make this a SQL function, which is more efficient than a PL/pgSQL language function.
See below.
CREATE OR REPLACE FUNCTION "whis2011"."getrtbactivesiteplaces"
(IN siteid int8, IN datefrom timestamp, IN dateto timestamp)
RETURNS SETOF "varchar" TABLE (id int, RtbActiveSiteId int, ...) -- add all fields
AS $BODY$
DECLARE
siteid BIGINT;
datefrom timestamp without time zone;
dateto timestamp without time zone; -- don't redeclare parameters!!!
BEGIN -- not needed for a SQL function
-- Insert statements for procedure here
RETURN QUERY SELECT pl."Id", -- SQL function uses simple SELECT
pl."RtbActiveSiteId",
pl."AdPlaceId",
pl."AdPosition",
pl."Ctr",
pl."State",
pl."BidPrice",
pl."MinBidFloor",
pl."MinBidFloorCurrency",
pl."AverageCpm",
pl."AverageClickCost",
ss.*
FROM "whis2011"."RtbActiveSitePlaces" pl
LEFT JOIN (
SELECT "RtbActiveSitePlaceId" AS "Id"
coalesce(SUM("BidsCount"),0) AS BidsCount,
coalesce(SUM("ShowsCount"),0) AS ShowsCount,
coalesce(SUM("RealShowsCount"),0) AS RealShowsCount,
coalesce(SUM("ClicksCount"),0) AS ClicksCount,
coalesce(SUM("ClickLayerClicksCount"),0) as ClickLayerClicksCount,
coalesce(SUM("ShowsCost"),0::money) AS ShowsCost,
coalesce(SUM("ClicksCost"),0::money) AS ClicksCost,
coalesce(SUM("BidsCost"),0::money) AS BidsCost,
coalesce(SUM("SliderMovesCount"),0) AS SliderMovesCount
FROM "whis2011"."RtbActiveSitePlaceStatistics"
WHERE "Date" >= datefrom AND "Date" < dateto
GROUP BY "RtbActiveSitePlaceId") ss USING ("Id")
WHERE pl."RtbActiveSiteId" = siteid;
END; -- not needed for a SQL function
$BODY$ LANGUAGE sql STRICT STABLE; -- don't call on NULL input and use STABLE
You then call this function like so:
SELECT * FROM getrtbactivesiteplaces(1475, '2016-02-01', '2016-08-01');
Your function's return type is setof varchar, but it should be something like:
RETURNS TABLE (Id integer,
RtbActiveSiteId integer,
... etc ...
)