PostgreSQL Function: Return Multiple Rows as JSON - json

I have a PostgreSQL function which successfully returns its data as
table(key character varying, value integer)
but I want to return JSON as it's more convenitent. I have looked at some other answers, and documentation, but it gets complicated when aliases are used (I'd like to return 'key' and 'value' as the column names).
Can someone please recommend the most simple and concise way of expressing this function, in a way that does not require extra complexity to call:
DROP FUNCTION get_document_metadata_integer_records_by_document_id(integer);
CREATE OR REPLACE FUNCTION get_document_metadata_integer_records_by_document_id(document_id integer)
RETURNS table(key character varying, value integer) AS
$BODY$
SELECT document_metadata_records.key, document_metadata_integer_records.value FROM document_metadata_integer_records
LEFT OUTER JOIN document_metadata_records
ON document_metadata_integer_records.document_metadata_record_id = document_metadata_records.id
WHERE document_metadata_records.document_id = $1
$BODY$
LANGUAGE sql VOLATILE
COST 100;
ALTER FUNCTION get_document_metadata_integer_records_by_document_id(integer)
OWNER TO postgres;

Use cross join lateral as a clean and easy path to create a composite type that can be used by row_to_json
create or replace function get_document_metadata_integer_records_by_document_id(
_document_id integer
) returns setof json as $body$
select row_to_json(s)
from
document_metadata_integer_records dmir
left outer join
document_metadata_records dmr on dmir.document_metadata_record_id = dmr.id
cross join lateral
(select dmr.key, dmir.value) s
where dmr.document_id = _document_id
;
$body$ language sql stable;
then use it as
select get_document_metadata_integer_records_by_document_id(1) as pair;
or
select pair from get_document_metadata_integer_records_by_document_id(1) j(pair);
or make it return a table
) returns table (pair json) as $body$
and use as
select pair from get_document_metadata_integer_records_by_document_id(1);
With older versions of Postgresql without lateral it is possible to do it in a subquery
select row_to_json(s)
from
(
select dmr.key, dmir.value
from
document_metadata_integer_records dmir
left outer join
document_metadata_records dmr on dmir.document_metadata_record_id = dmr.id
where dmr.document_id = _document_id
) s

Related

How to use declared variable to create a json element

I have a stored procedure, in this, I have declared a variable which holds a value from a select function. I need to use this value to create a json element, but it throws an exception
function jsonb_set(jsonb, unknown, character varying, boolean) does not exist
This is the function:
CREATE OR REPLACE FUNCTION test ( ) RETURNS
INTEGER AS $$
DECLARE
intent varchar;
BEGIN
select id into intent from customer;
UPDATE orders
SET data = jsonb_set(
data,
'{Items}', -- the array in which we operate
to_jsonb(
(WITH ar AS(
WITH temp AS(
SELECT data->'Items' AS items -- the array in which we operate
FROM orders
WHERE id = 1 -- the filtered order we are updating
)
SELECT jsonb_set(
jsonb_array_elements(items),
'{Quantity}', -- the new field we are adding
intent, -- this is where i need to replace the variable
true)
FROM temp)
SELECT (array_agg(ar.jsonb_set))
FROM ar)),
false)
WHERE id = 1;
return 0;
EXCEPTION WHEN others THEN
return 1;
END;
$$ LANGUAGE plpgsql;
Copying the snippet where I need to replace the variable:
SELECT jsonb_set(
jsonb_array_elements(items),
'{Quantity}', -- the new field we are adding
intent, -- this is where i need to replace the variable
true)
You have to explicitly cast intent to jsonb using CAST(intent AS jsonb) or intent::jsonb.
The reason it works with a string literal is that such literals are of the (internal) type unknown which can be transformed to most other types, but there is no implicit cast between character varying and jsonb, so you have to use an explicit one.

mysql where columnname in (function(a value)) not working

I have a strange situation here with the mysql query:
When the WHERE unterkategorie IN (children_csv(1)) is used there is no result.
Second "WHERE unterkategorie IN (11,12,13,28,29,32,14,15,16,30,31,33,34,35)" is fetching records when I substitute function name with the results of the function when executed separately
the full query is:
SELECT k.name category_name,
p.unterkategorie,
p.artikelnummer,
p.hauptkategorie,
p.id,
p.name product_name,
p.preis,
p.sortierung,
p.verpackungseinheit
FROM produkte p, kategorie k
WHERE unterkategorie IN (children_csv(1))
WHERE unterkategorie IN (11,12,13,28,29,32,14,15,16,30,31,33,34,35)
AND p.unterkategorie = k.id
ORDER BY unterkategorie, p.sortierung
Following is the function definition
delimiter //
CREATE DEFINER=`root`#`localhost` FUNCTION `children_csv`(child int)RETURNS varchar(1000) CHARSET utf8
BEGIN
declare return_value varchar(1000);
SELECT GROUP_CONCAT(Level SEPARATOR ',')childrens into return_value FROM (
SELECT #Ids := (
SELECT GROUP_CONCAT(`id` SEPARATOR ',')
FROM `kategorie`
WHERE FIND_IN_SET(`parent`, #Ids)
ORDER BY parent, sortierung
) Level
FROM `kategorie`
JOIN (SELECT #Ids := child) temp1
WHERE FIND_IN_SET(`parent`, #Ids)
) temp2;
RETURN return_value;
END;
//
delimiter ;
Your function is returning a single value, a string. It is not returning a a list of values (because MySQL functions do not do that). If you want to use the function directly, you can use find_in_set():
WHERE find_in_set(unterkategorie, children_csv(1))
I will caution you that MySQL cannot use an index on unterkategorie, so this might be slower.
If you want a faster query, then you can construct a query as a string (called dynamic SQL) and use prepare and exec to run it.
If you are coming from another programming language, you need to learn that functions are not the route to better performance in SQL. Moving the logic into a function generally does not help performance.

PostgreSQL: Getting result of a function in a single column in PostgreSQL

Trying to display the result in the form of table with different columns, but getting all result in a single column.
--My Function
create or replace function test1()
returns table ( "Fname" varchar(20),"Lname" varchar(20),"A-B" bigint,"C-D" bigint,
"E-F" bigint ) as
$body$
begin
return query
SELECT tb."Fname",tb."Lname",count(tb."City"='A-B' OR NULL) AS "A-B",
count(tb."City"='C-D' OR NULL) AS "C-D",
count(tb."City"='E-F' OR NULL) AS "E-F"
FROM "Table1" tb
WHERE tb."City" in ('A-B','C-D','E-F')
GROUP BY 1,2
ORDER BY 1,2;
end
$body$
language plpgsql;
In instead of
select test1()
do
select * from test1()
You need no plgpsql for this. This is just a plain query.
But supposing you just want to test it: How do you call the function?
For table returning functions you do: select * from f1();.
For functions returning one value you do select f1();.

how to combine two sql statements into a function

I am trying to combine the following two sql statements in my application code into a function in postgresql, but I'm having some trouble.
Here are the two sql queries I'd like to combine:
UPDATE userrange f
SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE f.uservalue IN(
SELECT a.uservalue FROM userrange a WHERE UsedYesNo=false Order By id ASC Limit 1)
RETURNING a.uservalue;
The results from the above statement are used in this query:
INSERT INTO widget
VALUES(DEFAULT, uservalue, 'test','123456778',1,"testservername", now(), Null)
So far, I've built function that just does the first update statement, like so:
CREATE or REPlACE FUNCTION create_widget(IN user_id integer, IN password character varying DEFAULT NULL::character varying)
RETURNS TABLE(uservalue integer) AS
$BODY$
BEGIN
RETURN QUERY
UPDATE userrange f SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE f.uservalue IN(
SELECT a.uservalue FROM userrange a WHERE UsedYesNo=false Order By id ASC Limit 1)
RETURNING a.uservalue;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
It compiles but when I execute it, it fails with the error:
ERROR: missing FROM-clause entry for table "a" LINE 4: RETURNING a.uservalue
I'm just googling this error to see how I can fix it... but could I just create a variable
called uservalue in a DECLARE section and use it in the secondary query? Or can i combine the sql into one
thanks.
If you can forego the function and you have PostgreSQL 9.2+, you can do the update and insert in a single query. It should be straightforward to port this to a function if necessary.
WITH f AS (
UPDATE userrange f
SET UsedYesNo = true, user_id=user_id, updateddatetime=now()
WHERE UsedYesNo IS FALSE
RETURNING f.uservalue)
INSERT INTO widget (<column list>)
SELECT f.uservalue, 'test','123456778',1,"testservername", now(), NULL
FROM f;
-- [edit: added function]
-- Note: the following function is untested
CREATE OR REPLACE FUNCTION create_widget(IN p_user_id INTEGER, IN p_password VARCHAR DEFAULT NULL::VARCHAR)
RETURNS TABLE(uservalue integer) AS
$BODY$
BEGIN
RETURN QUERY
WITH f AS (
UPDATE userrange f
SET UsedYesNo = true,
user_id = p_user_id,
updateddatetime = now()
WHERE UsedYesNo IS FALSE
RETURNING f.uservalue)
INSERT INTO widget (<column_list>)
/* Omit the DEFAULT, by not including it in the column list above,
* the DEFAULT already defined on the column will be used.
*/
SELECT f.uservalue, 'test','123456778',1,"testservername", now(), NULL
FROM f;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

How to use values returned from mysql stored function with in() function?

I have a field named 'dealBusinessLocations' (in a table 'dp_deals') which contain some ids of another table(dp_business_locations) in comma separated format.
dealBusinessLocations
----------------------
0,20,21,22,23,24,25,26
I need to use this values within an in() function of a query.
like
select * from dp_deals as d left join dp_business_locations as b on(b.businessLocID IN (d.dealBusinessLocations) ;
Sine mysql doesn't support any string explode function, I have created a stored function
delimiter //
DROP FUNCTION IF EXISTS BusinessList;
create function BusinessList(BusinessIds text) returns text deterministic
BEGIN
declare i int default 0;
declare TmpBid text;
declare result text default '';
set TmpBid = BusinessIds;
WHILE LENGTH(TmpBid) > 0 DO
SET i = LOCATE(',', TmpBid);
IF (i = 0)
THEN SET i = LENGTH(TmpBid) + 1;
END IF;
set result = CONCAT(result,CONCAT('\'',SUBSTRING(TmpBid, 1, i - 1),'\'\,'));
SET TmpBid = SUBSTRING(TmpBid, i + 1, LENGTH(TmpBid));
END WHILE;
IF(LENGTH(result) > 0)
THEN SET result = SUBSTRING(result,1,LENGTH(result)-1);
END IF;
return result;
END//
delimiter ;
The function is working perfectly.
mysql> BusinessList( '21,22' )
BusinessList( '21,22' )
-----------------------
'21','22'
But the query using the function does not worked either. here is the query.
select * from dp_deals as d left join dp_business_locations as b on(b.businessLocID IN (BusinessList(d.dealBusinessLocations)));
I have also tried using static value for function argumet, But no use
select * from dp_deals as d left join dp_business_locations as b on(b.businessLocID IN (BusinessList('21,22')));
It seems that there is some problem with using value returned from the function.
First, read this:
Is storing a comma separated list in a database column really that bad?
Yes, it is
Then, go and normalize your tables.
Now, if you really can't do otherwise, or until you normalize, use the FIND_IN_SET() function:
select *
from dp_deals as d
left join dp_business_locations as b
on FIND_IN_SET(b.businessLocID, d.dealBusinessLocations)
Then, read that article again. If the query is slow or if you have other problems with this table, then you'll know why:
Is storing a comma separated list in a database column really that bad?
Yes, it is
Simple, use find_in_set() instead.
SELECT *
FROM dp_deals as d
LEFT JOIN dp_business_locations as b
ON (FIND_IN_SET(b.businessLocID,d.dealBusinessLocations) > 0);
See: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_find-in-set
Note that if you drop CSV and get out off hell you can use a simple join like:
SELECT d.*, GROUP_CONCAT(b.dealBusinessLocation) as locations
FROM dp_deals as d
LEFT JOIN dp_business_location as b
ON (d.dealBusinessLocation = b.businessLocID);
Which will be much much faster and normalized as a bonus.
I think your problem is that IN() doesn't expect to get one string with lots of fields in it, but lots of fields.
With your function you are sending it this:
WHERE something IN ('\'21\',\'22\''); /* i.e. a single text containing "'21','22'" */
And not the expected
WHERE something IN ('21','22');