Postgresql function doesn't return anything when UUID argument is used in WHERE clause - postgresql

I'm pretty new to Postgresql. The issue I'm having is that I have a function that returns a table, but when I pass an UUID which is used in the where clause, it returns nothing. The funny thing is that if I take the SQL statement inside the function and run it by itself in PgAdmin, it gives me the right result.
The function looks like the following:
CREATE OR REPLACE FUNCTION get_service (
service_id uuid ) RETURNS TABLE(id uuid,title text,description text,category text,photo_url text,address text,
created_by uuid,created_on timestamp,service_rating float,rating_count bigint) AS $func$
Select
service.id,
service.title,
service.description,
service.category,
service.photo_url,
service.address,
service.created_by,
service.created_on,
CAST(AVG(rating.rating) AS float) as service_rating,
Count(rating.rating) as rating_count
from service
left join rating_service_map map
on service.id = map.service_id
left join rating
on rating.id = map.rating_id
where service.id = service_id
group by service.id,service.title,service.description,service.category,service.photo_url,service.address,service.created_by,service.created_on;
$func$ LANGUAGE SQL;
I have two records in my service table. The ID is of the type uuid and has a default value of uuid_generate_v4(). One of the records has an id of '2af3f03e-b2e5-44fd-89e8-3dc5fb641732'
If I run this I get no result:
select * from get_service('2af3f03e-b2e5-44fd-89e8-3dc5fb641732')
But if I run the following statement (the SQL portion of the function), then I get my right result:
Select
service.id,
service.title,
service.description,
service.category,
service.photo_url,
service.address,
service.created_by,
service.created_on,
CAST(AVG(rating.rating) AS float) as service_rating,
Count(rating.rating) as rating_count
from service
left join rating_service_map map
on service.id = map.service_id
left join rating
on rating.id = map.rating_id
where service.id = '2af3f03e-b2e5-44fd-89e8-3dc5fb641732'
group by service.id,service.title,service.description,service.category,service.photo_url,service.address,service.created_by,service.created_on;
I've also tried to cast the service_id (I've tried "where service.id = sevice_id::uuid" and "where service.id = CAST(service_id AS uuid)") but none of them worked.
I really appreciate it if you can tell me what I'm doing wrong. I've been at this for a couple of hours now.
Thank you.

I suspect that it's because the identifier service_id is ambiguous, being present as both a function parameter and a column in the map table.
Unlike a plain query, where such ambiguity would result in an error, conflicts in SQL functions are resolved by giving precedence to the column, so service_id in your case is actually referring to map.service_id.
You can either qualify it in your function body using the name of your function (i.e. get_service.service_id), or simply choose another name for the parameter.

Related

How to convert an jsonb array and use stats moment

how are you?
I needed to store an array of numbers as JSONB in PostgreSQL.
Now I'm trying to calculate stats moments from this JSON, I'm facing some issues.
Sample of my data:
I already was able to convert a JSON into a float array.
I used a function to convert jsonb to float array.
CREATE OR REPLACE FUNCTION jsonb_array_castdouble(jsonb) RETURNS float[] AS $f$
SELECT array_agg(x)::float[] || ARRAY[]::float[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;
Using this SQL:
with data as (
select
s.id as id,
jsonb_array_castdouble(s.snx_normalized) as serie
FROM
spectra s
)
select * from data;
I found a function that can do these calculations and I need to pass an array for that: https://github.com/ellisonch/PostgreSQL-Stats-Aggregate/
But this function requires an array in another way: unnested
I already tried to use unnest, but it will get only one value, not the entire array :(.
My goal is:
Be able to apply stats moment (kurtosis, skewness) for each row.
like:
index
skewness
1
21.2131
2
1.123
Bonus: There is a way to not use this 'with data', use the transformation in the select statement?
snx_wavelengths is JSON, right? And also you provided it as a picture and not text :( the data looks like (id, snx_wavelengths) - I believe you meant id saying index (not a good idea to use a keyword, would require identifier doublequotes):
1,[1,2,3,4]
2,[373,232,435,84]
If that is right:
select id, (stats_agg(v::float)).skewness
from myMeasures,
lateral json_array_elements_text(snx_wavelengths) v
group by id;
DBFiddle demo
BTW, you don't need "with data" in the original sample if you don't want to use and could replace with a subquery. ie:
select (stats_agg(n)).* from (select unnest(array[16,22,33,24,15])) data(n)
union all
select (stats_agg(n)).* from (select unnest(array[416,622,833,224,215])) data(n);
EDIT: And if you needed other stats too:
select id, "count","min","max","mean","variance","skewness","kurtosis"
from myMeasures,
lateral (select (stats_agg(v::float)).* from json_array_elements_text(snx_wavelengths) v) foo
group by id,"count","min","max","mean","variance","skewness","kurtosis";
DBFiddle demo

With PostgREST, convert a column to and from an external encoding in the API

We are using PostgREST to automatically generate a REST API for a Postgres database. Our primary keys have an external representation that's different from how we store them internally. For simplicity's sake lets pretend the ids are stored as integers but we represent them as hexadecimal strings outwardly.
It's simple enough to get PostgREST to convert to the external representation for read operations:
CREATE DOMAIN hexid AS bigint;
CREATE TABLE fruits (
fruit_id hexid PRIMARY KEY,
name text
);
CREATE OR REPLACE VIEW api_fruits AS
SELECT to_hex(fruit_id) as fruit_id, name FROM fruits;
INSERT INTO fruits(fruit_id, name) VALUES('51955', 'avocado');
PostgREST generates the expected representation when we GET api_fruits:
[
{
"fruit_id": "caf3",
"name": "avocado"
}
]
But that's about as far as we get with this solution. It's a one way transformation so we won't be able to POST/PATCH records this way. The way PostgREST works is to transform such requests into equivalent INSERT and UPDATE statements. But this view with its custom formatting is not updatable. This is what would happen if we tried:
ERROR: cannot insert into column "fruit_id" of view "api_fruits"
DETAIL: View columns that are not columns of their base relation are not updatable.
STATEMENT: WITH pgrst_source AS (WITH pgrst_payload AS (SELECT $1::json AS json_data), pgrst_body AS ( SELECT CASE WHEN json_typeof(json_data) = 'array' THEN json_data ELSE json_build_array(json_data) END AS val FROM pgrst_payload) INSERT INTO "api_x"."api_fruits"("fruit_id", "name") SELECT "fruit_id", "name" FROM json_populate_recordset (null::"api_x"."api_fruits", (SELECT val FROM pgrst_body)) _ RETURNING "api_x"."api_fruits".*) SELECT '' AS total_result_set, pg_catalog.count(_postgrest_t) AS page_total, CASE WHEN pg_catalog.count(_postgrest_t) = 1 THEN coalesce((
WITH data AS (SELECT row_to_json(_) AS row FROM pgrst_source AS _ LIMIT 1)
SELECT array_agg(json_data.key || '=eq.' || json_data.value)
FROM data CROSS JOIN json_each_text(data.row) AS json_data
WHERE json_data.key IN ('')
), array[]::text[]) ELSE array[]::text[] END AS header, '' AS body, nullif(current_setting('response.headers', true), '') AS response_headers, nullif(current_setting('response.status', true), '') AS response_status FROM (SELECT * FROM pgrst_source) _postgrest_t
We can't INSERT into "View columns that are not columns of their base relation".
The obvious workaround is to serve fruit_id as a straight column, just an integer. With some post and preprocessing at the nginx level we can hex encode it there (and hex decode incoming ids). I'm wondering if we can do better than that though. For large API operations, re-encoding the JSON will use a lot of memory and CPU time and it seems so unnecessary.
It would have been great to be able to use a custom CREATE CAST to take the incoming hexadecimal strings and turn them back into integers, something like this:
CREATE CAST (json AS hexid) WITH FUNCTION json_to_hexid AS ASSIGNMENT;
But alas custom casts are ignored on CREATE DOMAIN types. And we can't make a true custom column type because our cloud Postgres host (Google Cloud SQL) doesn't allow custom extensions.
It feels like some combination of INSTEAD OF triggers or rules could work. But when using query parameters to filter results using query parameters (e.g. select a fruit by id), I don't think there's an appropriate trigger to use. INSTEAD OF doesn't work for straight SELECT does it?
For example I've tested doing something like this to take care of INSERT and allow POST with PostgREST. It works:
CREATE OR REPLACE FUNCTION api_fruits_insert()
RETURNS trigger AS
$$
BEGIN
INSERT INTO fruits(fruit_id, name) VALUES (('x' || lpad(NEW.fruit_id, 16, '0'))::bit(64)::bigint::hexid, NEW.name);
RETURN NEW;
END
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER api_fruits_insert
INSTEAD OF INSERT
ON api_fruits
FOR EACH ROW
EXECUTE PROCEDURE api_fruits_insert();
The trouble is in the WHERE clause. Let's PATCH api_fruits?fruit_id=in.(7b,caf3) with {"name": "pear"}. This works out of the box since the name column is updatable but look at the query:
WITH pgrst_source AS (WITH pgrst_payload AS (SELECT $1::json AS json_data), pgrst_body AS ( SELECT CASE WHEN json_typeof(json_data) = 'array' THEN json_data ELSE json_build_array(json_data) END AS val FROM pgrst_payload) UPDATE "api_x"."api_fruits" SET "name" = _."name" FROM (SELECT * FROM json_populate_recordset (null::"api_x"."api_fruits" , (SELECT val FROM pgrst_body) )) _ WHERE "api_x"."api_fruits"."fruit_id" = ANY ($2) RETURNING 1) SELECT '' AS total_result_set, pg_catalog.count(_postgrest_t) AS page_total, array[]::text[] AS header, '' AS body, nullif(current_setting('response.headers', true), '') AS response_headers, nullif(current_setting('response.status', true), '') AS response_status FROM (SELECT * FROM pgrst_source) _postgrest_t
DETAIL: parameters: $1 = '{
"name": "pear"
}', $2 = '{7b,caf3}'
So we have essentially UPDATE api_fruits SET name='berry' WHERE fruit_id IN ('7b', 'caf3');. Surprisingly this works but it's a full table scan so Postgres can evaluate to_hex(fruit_id) for each row looking for matches. The same happens if we try to GET a record by fruit_id. How would we rewrite the WHERE clauses?
It really feels like some combination of just the right Postgres and PostgREST features should be able to get us to a point where it's all happening in Postgres without nginx's help and without excessive complexity. Any ideas?

How to create a user-defined function in Postgresql?

I have the following query in Postgres. I want to have a function where the user can define the value of record_year and record_month using the function call, dynamically without having to specify in the query statement. That way the same query can be reused for different user input values for record_year and record_month (in this case). Can anybody help me out?
SELECT x.sid, x.record_year, x.record_month, y.addr
FROM x
FULL OUTER JOIN y
ON x.sid = y.sid
WHERE x.record_time='23:50'
and x.record_year='2020'
and x.record_month='1'
group by x.station_id, x.record_year, x.record_month, y.addr;
The function call could be something like, select function_name(record_year, record_month);
As you are returning a result the function should be defined as returns table(). PL/pgSQL is not required if all you want to do is to return a result. A SQL function will be enough.
create function get_data(p_time time, p_year int, p_month int)
returns table (sid int, record_year int, record_month int, addr text)
as
$$
SELECT x.sid, x.record_year, x.record_month, y.addr
FROM x
FULL OUTER JOIN y ON x.sid = y.sid
WHERE x.record_time p_time
and x.record_year = p_year
and x.record_month = p_month
group by x.station_id, x.record_year, x.record_month, y.addr
$$
language sql
stable;
You have to adjust the data types of the returned columns - I have only guessed them based on name.
Note that your full outer join is really a left join because of the conditions on table x
As it is a set returning function, use it like a table in the FROM clause:
select *
from get_data(time '23:50', 2020, 1);

postgresql function error ERROR: query has no destination for result data

I have created one function in postgresql. but when i try to return data i am getting below error
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 "fn_GetAllCountData"() line 27 at SQL statement
SQL state: 42601
Below is the my postgresql function. In this function I am getting task status count in one query
CREATE OR REPLACE FUNCTION public."fn_GetAllCountData"() RETURNS setof "AssignDetails" AS $BODY$
DECLARE
total_draft text;
total_pending text;
total_rejected text;
total_approved text;
total_prev_pending text;
"AssignDetails" text;
BEGIN
--Total pending application no by the user
Select k."UserCode" as "UserCode",count(S."taskAssignTo") as "TotalPending" into total_pending
from user
left Outer Join public."tbl_task" S
on k."UserCode"=S."taskAssignTo" and s.Status='P'
And to_char(S."assignDate"::date, 'dd-mm-yyyy') = to_char(current_Date, 'dd-mm-yyyy')
group by k."UserCode";
--Previous Pending
Select k."UserCode" as "UserCode",count(S."taskAssignTo") as "TotalPrevPending" into total_prev_pending
from kyc k
left Outer Join public."tbl_task" S
on k."UserCode"=S."taskAssignTo" and s.Status='P'
And S."assignDate" < CONCAT(current_Date, ' 00:00:00'):: timestamp
group by k."UserCode";
-- Total Objection raised by the user
Select k."UserCode" as "UserCode",count(S."taskAssignTo") as "TotalRejected" into total_rejected
from kyc k
left Outer Join tbl_task S
on k."UserCode"=S."taskAssignTo" and s.Status='R'
And to_char(S."objectionDate"::date, 'dd-mm-yyyy') = to_char(current_Date, 'dd-mm-yyyy')
group by k."UserCode";
-- Total Approved application no by the user
Select k."UserCode" as "UserCode",count(S."taskAssignTo") as "TotalApproved" into total_approved
from kyc k
left Outer Join public."tbl_task" S
on k."UserCode"=S."taskAssignTo" and s.Status='A'
And S."assignDate" < CONCAT(current_Date, ' 00:00:00'):: timestamp
group by k."UserCode";
--Application no with start Time and total time
Select K."UserCode",K."Status", K."AppType",ST."taskNo" as "TaskId", ST."startTime" as "StartTime",
case
when COALESCE(ST."endTime",'')=''
then (SELECT DATEDIFF('second', ST."startTime":: timestamp, current_timestamp::timestamp))
else (SELECT DATEDIFF('second', ST."startTime":: timestamp, ST."endTime"::timestamp))
end as "Totaltime"
into "Final"
from kyc K
left outer join public."tbl_task_details" ST
On K."UserCode"=ST."empCode";
--Total Checked In Draft application no through by the user
Select k."UserCode" as "UserCode",count(S."taskAssignTo") as "Status_Count" into total_draft
from kyc k
left Outer Join public."tbl_task" S
on k."UserCode"=S."taskAssignTo" and s.Status='D'
And S."assignDate" < CONCAT(current_Date, ' 00:00:00'):: timestamp
group by k."UserCode";
Select distinct K."UserCode",K."Status",K."AppType",K."LoginTime",K."LogoutTime",
F."TaskId",F."StartTime",F."Totaltime",
TP."TotalPending" as "Pending",
TR."TotalRejected" as "Objection",
TA."TotalApproved" as "Approved",
TS."TotalAssign" as "Total_Assigned",
TD."Status_Count" as "Draft_Count",
TPP."TotalPrevPending" As "Prev_Pending"
into "AssignDetails"
From "Final" F
Right outer join kyc K On K."UserCode"=F."UserCode"
left outer join total_scrutiny TS On K."UserCode"=Ts."UserCode"
left outer join total_draft TD On TD."UserCode"=K."UserCode"
left outer join total_pending TP On TP."UserCode"=K."UserCode"
left outer join total_rejected TR On TR."UserCode"=K."UserCode"
left outer join total_approved TA On TA."UserCode"=K."UserCode"
Left Outer Join total_prev_pending TPP On TPP."UserCode"=K."UserCode"
order by TS."TotalAssign" desc;
Select * From "AssignDetails";
END
$BODY$ LANGUAGE plpgsql;
I tried to return table with return query but still not working. I don't know what i am doing wrong. Please help me with this.
Please note that postgreSQL only reports one error at a time. In fact there is a very great deal wrong with your function, so much so that it would take too long to correct everything here.
I have therefore given you a cut-down version here, which should point you in the right direction. I will give the code first, and then explain the points.
CREATE OR REPLACE FUNCTION public.fn_getallcountdata() RETURNS TABLE (usercode text, totalpending integer) AS $BODY$
BEGIN
CREATE TEMP TABLE total_pending
(
usercode text,
totalpending int
) ON COMMIT DROP;
--Total pending application no by the user
INSERT INTO total_pending
Select k.usercode, count(s.taskassignto)::integer
from public.user k
left Outer Join public.tbl_task s
on k.usercode=s.taskassignto and s.status='P'
And s.assigndate::date = current_date
group by k.usercode;
RETURN QUERY
select t.usercode, t.totalpending From total_pending t;
END;
$BODY$ LANGUAGE plpgsql;
Points to note:
Firstly please avoid using mixed case names in postgreSQL. It means that you have to double quote everything which is a real pain!
Secondly, you were declaring variables as text, when in fact they were holding table data. This you cannot do (you can only put a single value in any variable). Instead you need to create temporary tables in the way I have done. Note in particular the use of ON COMMIT DROP. This is a useful way in postgreSQL to avoid having to remember to drop temporary tables when you are finished with them!
Thirdly your alias k is not referring to anything in your first select. Note also that user is a reserved word. If you insist on having user as a name for a table, then you will need to access it through public.user (assuming it is in the public schema).
(As an aside it is generally considered to be a security risk to use the public schema, because of guest access).
Fourthly there is no need to convert a date to string form in order to compare it. Casting a timestamp to a date and directly comparing to another date is in fact far faster, than converting both dates to a string representation and comparing the strings.
Fifthly COUNT in postgreSQL returns a bigint, which is why I generally cast it as integer, because an integer usually suffices!
I have defined the function to return a table containing named columns. You can use setof, but if you do it has to be a known table type.
For the final SELECT I have supplied the required RETURN QUERY first. Note also that I am using a table alias. This is because the column names in the returning table match those in the temporary table, so you need to be explicit as to what you are doing.
I strongly recommend that you experiment with a shorter function first, (as in my cutdown version) and then increase the complexity once you have it compiling (and running). To this end please also note that in postgreSQL, if a function compiles, it does not mean that it contains no runtime errors. Also if you change the return columns between different compilations, you will need to delete the previous version.
Hope this points you in the right direction, but please feel free to get back with any further issues.

What does a column assignment using an aggregate in the columns area of a select do?

I'm trying to decipher another programmer's code who is long-gone, and I came across a select statement in a stored procedure that looks like this (simplified) example:
SELECT #Table2.Col1, Table2.Col2, Table2.Col3, MysteryColumn = CASE WHEN y.Col3 IS NOT NULL THEN #Table2.MysteryColumn - y.Col3 ELSE #Table2.MysteryColumn END
INTO #Table1
FROM #Table2
LEFT OUTER JOIN (
SELECT Table3.Col1, Table3.Col2, Col3 = SUM(#Table3.Col3)
FROM Table3
INNER JOIN #Table4 ON Table4.Col1 = Table3.Col1 AND Table4.Col2 = Table3.Col2
GROUP BY Table3.Col1, Table3.Col2
) AS y ON #Table2.Col1 = y.Col1 AND #Table2.Col2 = y.Col2
WHERE #Table2.Col2 < #EnteredValue
My question, what does the fourth column of the primary selection do? does it produce a boolean value checking to see if the values are equal? or does it set the #Table2.MysteryColumn equal to some value and then inserts it into #Table1? Or does it just update the #Table2.MysteryColumn and not output a value into #Table1?
This same thing seems to happen inside of the sub-query on the third column, and I am equally at a loss as to what that does as well.
MysteryColumn = gives the expression a name also called a column alias. The fact that a column in the table#2 also has the same name is besides the point.
Since it uses INTO syntax it also gives the column its name in the resulting temporary table. See the SELECT CLAUSE and note | column_alias = expression and the INTO CLAUSE