PostgreSQL: migrating ms sql xml query to postgresql query - sql

I am trying to migrate the following MS SQL Server procedure to a PostgreSQL function.
CREATE PROCEDURE [dbo].[GMC]
AS
BEGIN
DECLARE #LID VARCHAR(3);
DECLARE #xml XML = '<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>';
SELECT #LID = Pay.b.value('.','Varchar(3)')
FROM #xml.nodes('/XMLProf/CID') as Pay(b)
SELECT 'Return Value' = #LID
END
I have tried to convert to the following but it doesn't work.
CREATE OR REPLACE FUNCTION dbo.GMC()
RETURNS void
AS
$BODY$
DECLARE
LID VARCHAR(3);
bxml XML = '<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>';
BEGIN
SELECT LID = Pay.b.value('.','Varchar(3)')
FROM XMLTABLE('/XMLProf/CID' PASSING bxml) as Pay(b)
SELECT 'Return Value' = LID
end;
$BODY$
LANGUAGE plpgsql;
Edit:
The result I am expecting is "840"
The error that I am getting is a syntax error:
ERROR: syntax error at or near ")"
LINE 12: FROM XMLTABLE('/XMLProf/CID' PASSING bxml) as Pay(b)
Can someone please tell me how can I accomplish this. Any help is really appreciated.

If you want to return something from a function, you can't use returns void. As XML is character data, returns text makes more sense.
As you only want to return a single value, xmltable() isn't really needed. And you don't need PL/pgSQL either:
CREATE OR REPLACE FUNCTION dbo.gmc()
RETURNS text
AS
$BODY$
select (xpath('/XMLProf/CID/text()',
'<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>'::xml))[1]::text;
$BODY$
LANGUAGE sql;
xpath() returns an array of all matches, that's why the [1] is needed to pick out the first match.
Assuming you actually want to pass the XML to the function, you can use this:
CREATE OR REPLACE FUNCTION dbo.gmc(p_xml xml)
RETURNS text
AS
$BODY$
select (xpath('/XMLProf/CID/text()', p_xml))[1]::text;
$BODY$
LANGUAGE sql;

If you're basing your response element on the string size of the inner nodes of XMLProf, you might wanna take a look at XPATH and UNNEST.
CREATE OR REPLACE FUNCTION gmc() RETURNS text
AS $BODY$
WITH j AS (
SELECT
UNNEST(XPATH('//XMLProf/node()',
'<XMLProf><CID>840</CID><MD>101113</MD></XMLProf>'::XML)) AS rawxml
) SELECT (XPATH('//text()',j.rawxml))[1]::TEXT FROM j
WHERE CHAR_LENGTH((XPATH('//text()',j.rawxml))[1]::TEXT) = 3
$BODY$
LANGUAGE sql;
Testing ..
db=# SELECT * FROM gmc();
gmc
-----
840
(1 Zeile)
If you know exactly where to look and the string length is irrelevant, just get rid of the UNNEST and use the XPATH /XMLProf/CID/text() as pointed out by #a_horse_with_no_name.

Related

Unable to run a function with a select statement

I want to build a sql function that returns array of rows based on a select statement, unfortunately I couldn't make it work.
create or replace function latestOilFee()
return array
IS
Begin
select * from oil_tracker order by ts desc fetch first 1 row only;
End;
console throws an error
syntax error at or near "return"
There are multiple mistakes
RETURNS instead of RETURN
For ARRAY you have to mentioned like text[]
CREATE OR REPLACE FUNCTION latestOilFee()
RETURNS text[] AS $$
select array[fieldname::text, fieldname::text]
FROM oil_tracker limit 1
$$
LANGUAGE SQL;
Please check above, you should mentioned fields type while creating array
Shouldn't it be RETURNS (not RETURN)?

"ERROR: query has no destination for result data"

I am trying to create a function like this:
I tried changing the return type to int or text etc. to see if the code works outside of that, but it doesn't. I am a beginner in PostgreSQL so please don't be harsh if I missed something obvious.
create or replace function date_select(i INT) returns void as
$$
begin
select * from dwh_stg.stg_dce_gift where gift_id = i;
end
$$ language plpgsql
select date_select(16940)
SQL Error [42601]:
ERROR: query has no destination for result data
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function date_select(integer) line 3 at SQL statement
If you want to return something, you need to define the function to return something (not void)
Apparently you want to return multiple rows from the table stg_dec_gift, for that you need to define the function as returns setof dwh_stg.stg_dce_gift. For a simple function encapsulating a query there is not need to use PL/pgSQL a plain SQL function will do just fine:
create or replace function date_select(i INT)
returns setof dwh_stg.stg_dce_gift --<< here
as
$$
select *
from dwh_stg.stg_dce_gift
where gift_id = i;
$$
stable
language sql;
Then use it in the FROM part:
select *
from date_select(16940);
Online example: https://rextester.com/WYDCE44062

pgadmin, sql, function inputday by table name

I made a function im pgadmin
create or replace function get_source2(a text)
returns integer as
$$
declare
a text;
geom geometry;
begin
select get_source(geom)
from a;
end
$$
language plpgsql;
I want input a by table name
How can I do?
I try to like this
select get_source2('postgis.center')
but
ERROR: relation "a" does not exist
LINE 2: from a
help me
You need to call it as part of a regular select statement:
select get_source(geom), name, place
from location;
But that will be very inefficient as you are running the select inside the function for each and every row in the location table.

Input table for PL/pgSQL function

I would like to use a plpgsql function with a table and several columns as input parameter. The idea is to split the table in chunks and do something with each part.
I tried the following function:
CREATE OR REPLACE FUNCTION my_func(Integer)
RETURNS SETOF my_part
AS $$
DECLARE
out my_part;
BEGIN
FOR i IN 0..$1 LOOP
FOR out IN
SELECT * FROM my_func2(SELECT * FROM table1 WHERE id = i)
LOOP
RETURN NEXT out;
END LOOP;
END LOOP;
RETURN;
END;
$$
LANGUAGE plpgsql;
my_func2() is the function that does some work on each smaller part.
CREATE or REPLACE FUNCTION my_func2(table1)
RETURNS SETOF my_part2 AS
$$
BEGIN
RETURN QUERY
SELECT * FROM table1;
END
$$
LANGUAGE plpgsql;
If I run:
SELECT * FROM my_func(99);
I guess I should receive the first 99 IDs processed for each id.
But it says there is an error for the following line:
SELECT * FROM my_func2(select * from table1 where id = i)
The error is:
The subquery is only allowed to return one column
Why does this happen? Is there an easy way to fix this?
There are multiple misconceptions here. Study the basics before you try advanced magic.
Postgres does not have "table variables". You can only pass 1 column or row at a time to a function. Use a temporary table or a refcursor (like commented by #Daniel) to pass a whole table. The syntax is invalid in multiple places, so it's unclear whether that's what you are actually trying.
Even if it is: it would probably be better to process one row at a time or rethink your approach and use a set-based operation (plain SQL) instead of passing cursors.
The data types my_part and my_part2 are undefined in your question. May be a shortcoming of the question or a problem in the test case.
You seem to expect that the table name table1 in the function body of my_func2() refers to the function parameter of the same (type!) name, but this is fundamentally wrong in at least two ways:
You can only pass values. A table name is an identifier, not a value. You would need to build a query string dynamically and execute it with EXECUTE in a plpgsql function. Try a search, many related answers her on SO. Then again, that may also not be what you wanted.
table1 in CREATE or REPLACE FUNCTION my_func2(table1) is a type name, not a parameter name. It means your function expects a value of the type table1. Obviously, you have a table of the same name, so it's supposed to be the associated row type.
The RETURN type of my_func2() must match what you actually return. Since you are returning SELECT * FROM table1, make that RETURNS SETOF table1.
It can just be a simple SQL function.
All of that put together:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Note the parentheses, which are essential for decomposing a row type. Per documentation:
The parentheses are required here to show that compositecol is a column name not a table name
But there is more ...
Don't use out as variable name, it's a keyword of the CREATE FUNCTION statement.
The syntax of your main query my_func() is more like psudo-code. Too much doesn't add up.
Proof of concept
Demo table:
CREATE TABLE table1(table1_id serial PRIMARY KEY, txt text);
INSERT INTO table1(txt) VALUES ('a'),('b'),('c'),('d'),('e'),('f'),('g');
Helper function:
CREATE or REPLACE FUNCTION my_func2(_row table1)
RETURNS SETOF table1 AS
'SELECT ($1).*' LANGUAGE sql;
Main function:
CREATE OR REPLACE FUNCTION my_func(int)
RETURNS SETOF table1 AS
$func$
DECLARE
rec table1;
BEGIN
FOR i IN 0..$1 LOOP
FOR rec IN
SELECT * FROM table1 WHERE table1_id = i
LOOP
RETURN QUERY
SELECT * FROM my_func2(rec);
END LOOP;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM my_func(99);
SQL Fiddle.
But it's really just a a proof of concept. Nothing useful, yet.
As the error log is telling you.. you can return only one column in a subquery, so you have to change it to
SELECT my_func2(SELECT Specific_column_you_need FROM hasval WHERE wid = i)
a possible solution can be that you pass to funct2 the primary key of the table your funct2 needs and then you can obtain the whole table by making the SELECT * inside the function

Passing array of a composite type to stored procedure

I'm probably doing something wrong with forming the literal. Suppose I have a simple stored procedure like this:
CREATE OR REPLACE FUNCTION do_something(input_array composite_type[])
RETURNS SETOF text AS
$BODY$
DECLARE
temp_var composite_type;
BEGIN
FOR temp_var IN SELECT unnest(input_array) LOOP
return next temp_var.message;
END LOOP;
END
$BODY$
LANGUAGE plpgsql;
The composite_type is defined as:
CREATE TYPE composite_type AS
(message text,
amount numeric(16,2));
Performing a query like this:
SELECT * FROM do_something('{"(test,11)","(test2,22)"}')
Produces this result set:
(test,11.00)
(test2,22.00)
Instead of:
test
test2
Is it something wrong with my literal or should I access the message field in a different way? Thanks for any suggestions.
How you specify your input appears fine, as the same behaviour is observed with row- and array-constructor syntax:
SELECT * FROM do_something( ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[] );
And:
SELECT ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[];
produces:
'{"(test,11.00)","(test2,22.00)"}'
If you add a:
RAISE NOTICE '!%!',temp_var;
inside the loop the output is:
NOTICE: !("(test,11.00)",)!
NOTICE: !("(test2,22.00)",)!
showing that you're actually getting a tuple with "message" as the tuple text you expected and a null "amount".
So. Why?
It's a bit of a subtle one. You're using:
SELECT unnest(input_array)
which seems to do what you want, right:
regress=> SELECT unnest( ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[] );
unnest
---------------
(test,11.00)
(test2,22.00)
(2 rows)
... but actually, it's returning a single column of type composite_type. PL/PgSQL composite type assignment expects one column per type column instead. So the single col is being shoved into 'message' and there is no second col.
Instead, write:
SELECT * FROM unnest(input_array)
to unpack the composite for assignment. Then it works as expected:
regress=> SELECT * FROM do_something( ARRAY[ ROW('test',11), ROW('test2',22) ]::composite_type[] );
do_something
--------------
test
test2
(2 rows)
If the first field of composite_type were of a non-text type, you'd get an error that was rather more informative about this.
Craig explained well a reason for this behave - Assignment variable=value inside FOR statement expects zero nesting. So you should to do:
CREATE OR REPLACE FUNCTION do_something(input_array composite_type[])
RETURNS SETOF text AS $BODY$
DECLARE
temp_var record;
BEGIN
-- unnesting
FOR temp_var IN SELECT (unnest(input_array)).*
LOOP
RETURN NEXT temp_var.message;
END LOOP;
RETURN;
END
$BODY$ LANGUAGE plpgsql;
or -- preferable - newer use SetReturnedFunction inside "column list"
CREATE OR REPLACE FUNCTION do_something(input_array composite_type[])
RETURNS SETOF text AS $BODY$
DECLARE
temp_var record;
BEGIN
-- SELECT FROM
FOR temp_var IN SELECT * FROM unnest(input_array)
LOOP
RETURN NEXT temp_var.message;
END LOOP;
RETURN;
END
$BODY$ LANGUAGE plpgsql;