I have some trouble in using jsonb's like_regex statement.
I usually use the statement for my jsonb's query statement, that is:
jsonb #? jsonpath
The query condition is transfered from the front as a json statement, just like:
{
"fname=":"Tiger",
"flocation~*":"Shenzhen, China, (86)"
}
I write a function to parse the json statement to the jsonpath statement, like:
jfilter:='{"fname=":"Tiger","flocation~*":"Shenzhen, China, (86)"}'::jsonb;
cur refcursor;
vkey text;
vval text;
vrule text;
open cur for select * from jsonb_each(jfilter);
loop
fetch cur into vkey,vval;
if found then
if strpos(vkey,'=')>0 then
...
vrule:=concat('#.',vkey,vval::text);
...
elseif strpos(vkey,'~*')>0 then
...
vrule:=concat('#.',vkey,' like_regex ',vval::text,' flag "i"');
...
end if;
else
exit;
end if;
end loop;
close cur;
And then I get the jsonpath like:
'$[*] ? (#.fname=="Tiger" && #.flocation like_regex "Shenzhen, China, (86)" flag "i")'
When the statement contains '(' or ')', the regulation fails. In fact, for me, '(' or ')' is just a normal part of the condition. I tried to replace the '(' to '\(', but it doesn't work. I know why the statement is failed, but I don't know how to handle this kind of problem.
Pls give me some advice, thanks very much.
Related
I am writing 1 PostgreSQL function for some operation.
Writing SQL migration for that function but facing formatting error as liquibase is not able to recognize some portion.
Function Liquibase Migration:
CREATE OR REPLACE FUNCTION schema.fncn(trId integer, sts integer, stIds character varying)
RETURNS double precision
LANGUAGE plpgsql
AS '
DECLARE
abc integer;
query CHAR(1500);
xyz integer;
BEGIN
query := ''select sum(t.a)
FROM schema.tbl t
where t.id in(1,2)
and t.status ='' || sts ||
'' and t.status <> 2
and t.tr_id ='' || trId ||
'' and t.sw in('''', ''N'')'';
IF stIds is not null then
query := query || '' AND t.st_id IN ('' || stIds || '')'';
ELSE
END IF;
EXECUTE query INTO abc;
SELECT abc INTO xyz;
RETURN xyz;
END;
'
;
Following error it throwing:
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "N"
Reason: liquibase.exception.DatabaseException: ERROR: syntax error at or near "N"
Any suggestion what I am missing?
The immediate problem is the nesting of ' of single quotes. To make that easier, use dollar quoting for the function body. You can nest dollar quoted string by choosing different delimiters.
To avoid any problems with concatenation of parameters, use parameter place holders in the query and pass the values with the USING clause. That will however require two different execute calls.
I assume stIds is a comma separated string of values. To use that as a (single) placeholder, convert it to an array using string_to_array() - or even better: change the type of the input parameter to text[] and pass an array directly.
The query variable is better defined as text, don't use char. There is also no need to copy the result of the query into a different variable (which by the way would be more efficient using xyz := abc; rather than a select into)
CREATE OR REPLACE FUNCTION schema.fncn(trId integer, sts integer, stIds character varying)
RETURNS double precision
LANGUAGE plpgsql
AS
$body$
DECLARE
abc integer;
query text;
BEGIN
query := $q$ select sum(t.a)
FROM schema.tbl t
where t.id in (1,2)
and t.status = $1
and t.status <> 2
and t.tr_id = $2
and t.sw in ('''', 'N') $q$;
IF stIds is not null then
query := query || $sql$ AND t.st_id = ANY (string_to_array($4, ',') $sql$;
EXECUTE query INTO abc
using trid, sts, stids;
ELSE
EXECUTE query INTO abc
using trid, sts;
END IF;
RETURN abc;
END;
$body$
;
Note that in the Liquibase change, you must use splitStatements=false in order to run this without errors.
I am trying to return the movie name and the number of cast and crew when given a text. When I input the string and am using ilike, my query returns no matching titles. I created a view previously that has the movie titles and the number of crew to be input in the function.
My code is:
create or replace view movies_crew as
select movies.id, movies.title, principals.role
from movies
join principals on principals.movie_id=movies.id
where principals.role <> 'producer'
;
create or replace view movie_makers as
select movies_crew.title, count(movies_crew.title) as ncrew
from movies_crew
where movies_crew.title = 'Fight Club'
group by movies_crew.title;
CREATE or REPLACE function Q11(partial_title text)
RETURNS SETOF text
AS $$
DECLARE
title text;
BEGIN
for title in
select movie_makers.title, movie_makers.ncrew
from movie_makers
where movie_makers.title ilike '%$1%'
loop
return next movie_makers.title||'has'||movie_makers.ncrew||'cast and crew';
end loop;
if(not found) then
return next 'No matching titles';
end if;
END;
$$ LANGUAGE plpgsql;
select * from q11('Fight Club')
My database is: https://drive.google.com/file/d/1NVRLiYBVbKuiazynx9Egav7c4_VHFEzP/view?usp=sharing
Your immediate quoting issue aside (has been addressed properly by Jeff), the function can be much simpler and faster like this:
CREATE or REPLACE FUNCTION q11(partial_title text)
RETURNS SETOF text
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY
SELECT m.title || ' has ' || m.ncrew || ' cast and crew'
FROM movie_makers m
WHERE m.title ~* $1;
IF NOT FOUND THEN
RETURN NEXT 'No matching titles';
END IF;
END
$func$;
Major points:
Your function was still broken. References to movie_makers.title and movie_makers.ncrew wouldn't work that way. I fixed it.
Use RETURN QUERY instead of the loop. This way we also do not need to use or even declare any variables at all. See:
How to return result of a SELECT inside a function in PostgreSQL?
Optionally use the case insensitive regular expression match operator ~*. (Simpler, not faster.)
Difference between LIKE and ~ in Postgres
Either way, you may want to escape special characters. See:
Escape function for regular expression or LIKE patterns
Aside: hardly makes sense to filter on a view that already selects 'Fight Club' as its only row. For a meaningful search, you wouldn't use these views ...
ilike '%$1%'
$1 is not interpolated when inside single quotes, so you are searching for the literal characters $ and 1.
You could instead do:
ilike '%'||$1||'%'
I have a statement here that runs fine from what I can tell. If evaluates the condition and sticks the result into a variable. All I need to know is how to read the value out of the variable and display it. Thanks
DO
$do$
DECLARE result text;
BEGIN
IF EXISTS (select 1 from siteName where SiteNameID=9) THEN
SELECT 'Yes' into result;
ELSE
SELECT 'No' into result;
END IF;
END
$do$
In the event that by display, you meant output to STDOUT:
RAISE NOTICE 'result: %', result;
http://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html
You cannot return data from a DO command. For that, you would need a function. With DO commands you are restricted to messages from RAISE like Denis provided, or you can write data to tables or temp tables, and select from them.
DO
$do$
BEGIN
CREATE TEMP TABLE site9_exists AS
SELECT EXISTS (SELECT 1 FROM sitename WHERE sitenameid=9) AS result;
END
$do$;
SELECT result FROM site9_exists;
Of course, you wouldn't need the DO command at all for the trivial example ...
A DO-statement can't return anything. You could use a notice, like Denis already showed.
But why do you need a function? You could use a normal query for this, something like this:
SELECT CASE
WHEN input.SiteNameID = siteName.SiteNameID THEN 'Yes'
ELSE 'No'
END AS result
FROM (SELECT 9 AS SiteNameID) input
LEFT JOIN siteName ON input.SiteNameID = siteName.SiteNameID;
(not tested....)
here is another algorithm using cursor but i'm having a hard time fixing its error ...
CREATE OR REPLACE FUNCTION extractstudent()
RETURNS VOID AS
$BODY$
DECLARE
studcur SCROLL cursor FOR SELECT fname, lname, mname, address FROM student;
BEGIN
open studcur;
Loop
--fetching 1 row at a time
FETCH First FROM studcur;
--every row fetched is being inserted to another database on the local site
--myconT is the name of the connection to the other database in the local site
execute 'SELECT * from dblink_exec(''myconT'', ''insert into temp_student values(studcur)'')';
--move to the next row and execute again
move next from studcur;
--exit when the row content is already empty
exit when studcur is null;
end loop;
close studcur;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION extractstudent() OWNER TO postgres;
You rarely need to explicitly use cursors in postgresql or pl/pgsql. What you've written looks suspiciously like a SQL Server cursor loop construct, and you don't need to do that. Also, you can use "PERFORM" instead of "EXECUTE" to run a query and discard the results: this will avoid re-parsing the query each time (although it can't avoid dblink parsing the query each time).
You can do something more like this:
DECLARE
rec student%rowtype;
BEGIN
FOR rec IN SELECT * FROM student
LOOP
PERFORM dblink_exec('myconT',
'insert into temp_student values ('
|| quote_nullable(rec.fname) || ','
|| quote_nullable(rec.lname) || ','
|| quote_nullable(rec.mname) || ','
|| quote_nullable(rec.address) || ')');
END LOOP;
END;
Why not try it by yourself , according the error, you can try to solve them step by step !
I'm trying to write a function in PL/PgSQL that have to work with a table it receives as a parameter.
I use EXECUTE..INTO..USING statements within the function definition to build dynamic queries (it's the only way I know to do this) but ... I encountered a problem with RECORD data types.
Let's consider the follow (extremely simplified) example.
-- A table with some values.
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (
code INT,
descr TEXT
);
INSERT INTO table1 VALUES ('1','a');
INSERT INTO table1 VALUES ('2','b');
-- The function code.
DROP FUNCTION IF EXISTS foo (TEXT);
CREATE FUNCTION foo (tbl_name TEXT) RETURNS VOID AS $$
DECLARE
r RECORD;
d TEXT;
BEGIN
FOR r IN
EXECUTE 'SELECT * FROM ' || tbl_name
LOOP
--SELECT r.descr INTO d; --IT WORK
EXECUTE 'SELECT ($1)' || '.descr' INTO d USING r; --IT DOES NOT WORK
RAISE NOTICE '%', d;
END LOOP;
END;
$$ LANGUAGE plpgsql STRICT;
-- Call foo function on table1
SELECT foo('table1');
It output the following error:
ERROR: could not identify column "descr" in record data type
although the syntax I used seems valid to me. I can't use the static select (commented in the example) because I want to dinamically refer the columns names.
So..someone know what's wrong with the above code?
It's true. You cannot to use type record outside PL/pgSQL space.
RECORD value is valid only in plpgsql.
you can do
EXECUTE 'SELECT $1.descr' INTO d USING r::text::xx;
$1 should be inside the || ,like || $1 || and give spaces properly then it will work.
BEGIN
EXECUTE ' delete from ' || quote_ident($1) || ' where condition ';
END;