How to find if a function exists in PostgreSQL? - function

Unlike tables or sequences, user-defined functions cannot be found through pg_class. There are questions on how find a list of all functions to delete or grant them, but how to find an individual function (with known name and argument types) is not self-evident from them. So how to find whether a function exists or not?
EDIT: I want to use it in a function, in automated manner. Which solution is the best performance-wise? Trapping errors is quite expensive, so I guess the best solution for me would be something without the extra step of translating error to false, but I might be wrong in this assumption.

Yes, you cannot to find functions in pg_class because functions are stored on system table pg_proc
postgres-# \df
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+--------------------+------------------+----------------------+--------
public | foo | integer | a integer, b integer | normal
public | function_arguments | text | oid | normal
(2 rows)
Query for list of custom functions based on pg_proc is simply
postgres=# select p.oid::regprocedure
from pg_proc p
join pg_namespace n
on p.pronamespace = n.oid
where n.nspname not in ('pg_catalog', 'information_schema');
oid
-------------------------
foo(integer,integer)
function_arguments(oid)
(2 rows)
Most simply and fastest tests on functions existence are casting (without parameters) to regproc or regprocedure (with parameters):
postgres=# select 'foo'::regproc;
regproc
---------
foo
(1 row)
postgres=# select 'foox'::regproc;
ERROR: function "foox" does not exist
LINE 1: select 'foox'::regproc;
^
postgres=# select 'foo(int, int)'::regprocedure;
regprocedure
----------------------
foo(integer,integer)
(1 row)
postgres=# select 'foo(int, text)'::regprocedure;
ERROR: function "foo(int, text)" does not exist
LINE 1: select 'foo(int, text)'::regprocedure;
^
or you can do some similar with test against pg_proc
postgres=# select exists(select * from pg_proc where proname = 'foo');
exists
--------
t
(1 row)
postgres=# select exists(select *
from pg_proc
where proname = 'foo'
and function_arguments(oid) = 'integer, integer');
exists
--------
t
(1 row)
where:
CREATE OR REPLACE FUNCTION public.function_arguments(oid)
RETURNS text LANGUAGE sql AS $function$
select string_agg(par, ', ')
from (select format_type(unnest(proargtypes), null) par
from pg_proc where oid = $1) x
$function$
or you can use buildin functions:pg_get_function_arguments
p.s. trick for simply orientation in system catalog. Use a psql option -E:
[pavel#localhost ~]$ psql -E postgres
psql (9.2.8, server 9.5devel)
Type "help" for help.
postgres=# \df
********* QUERY **********
SELECT n.nspname as "Schema",
p.proname as "Name",
pg_catalog.pg_get_function_result(p.oid) as "Result data type",
pg_catalog.pg_get_function_arguments(p.oid) as "Argument data types",
CASE
WHEN p.proisagg THEN 'agg'
WHEN p.proiswindow THEN 'window'
WHEN p.prorettype = 'pg_catalog.trigger'::pg_catalog.regtype THEN 'trigger'
ELSE 'normal'
END as "Type"
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE pg_catalog.pg_function_is_visible(p.oid)
AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
ORDER BY 1, 2, 4;
**************************
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+--------------------+------------------+----------------------+--------
public | foo | integer | a integer, b integer | normal
public | function_arguments | text | oid | normal
(2 rows)

I think the easiest way would be to use pg_get_functiondef().
If it returns something, the function is there, otherwise the function does not exist:
select pg_get_functiondef('some_function()'::regprocedure);
select pg_get_functiondef('some_function(integer)'::regprocedure);
The drawback is that it will produce an error if the function isn't there instead of simply returning an empty result. But this could e.g. be overcome by writing a PL/pgSQL function that catches the exception and returns false instead.

Based on #PavelStehule answer this is how I am checking this in my scripts (using postgres exceptions and available exception codes)
DO $_$
BEGIN
BEGIN
SELECT 'some_schema.some_function(text)'::regprocedure;
EXCEPTION WHEN undefined_function THEN
-- do something here, i.e. create function
END;
END $_$;

Late to the party,
but it could be something like this (don't use select instead of perform if you are not using the result or you would get an error complaining about it :
ERROR: query has no destination for result data
So the following code will work :
DO $$
BEGIN
BEGIN
perform pg_get_functiondef('some_function()'::regprocedure);
raise notice 'it exists!';
EXCEPTION WHEN undefined_function THEN
raise notice 'Does not exist';
END;
END $$;

Related

How can I iterate a JSON in SQL?

So I have a JSON variable with several values like this:
"["1", "2", "3", "4"]"
What i need to do is pass that value to an SQL procedure to mount a query in which the WHERE clause adds all the values in the JSON, so something like parsing the JSON, interate it and concat it in order to get an #where similar to:
AND id=1 AND id=2 AND id=3 AND id=4
I tried something like this, as something really similar is taking place in an already existing procedure, but doesn't work:
SET #idWhere="";
IF id IS NOT NULL AND JSON_EXTRACT(id, '$[0]') IS NOT NULL THEN
SET #idWhere = CONCAT(#idWhere," AND JSON_SEARCH('",id,"','one',id) IS NOT NULL ");
END IF;
Where id is both the name of the JSON and the column name.
Thanks in advance!
If you are running MySQL 8.0, you can use json_table() to turn the array to a recordset, and then aggregate the results with group_concat():
select group_concat(concat('id = ', i) separator ' and ') id_where
from json_table(
'["1", "2", "3", "4"]',
"$[*]" columns(i int path '$')
) as t
In this demo on DB Fiddle, this yields:
| id_where |
| --------------------------------------- |
| id = 1 and id = 2 and id = 3 and id = 4 |
NB: you probably want ors, not ands.

Retrieving json elements with a specific key name from a complex nested structure in postgres

I have a complex nested json structure in a postgres json field. I want to list all element values with key '$type' no matter where in the nested structure they appear. The structure contains arrays nested within arrays to several levels deep. What is the sql query I should use?
The table structure is:
create table if not exists documents
(
id text not null
constraint documents_pkey primary key,
value json not null
)
This recursive function extracts all attributes from a complex jsonb object:
create or replace function jsonb_extract_all(jsonb_data jsonb, curr_path text[] default '{}')
returns table(path text[], value text)
language plpgsql as $$
begin
if jsonb_typeof(jsonb_data) = 'object' then
return query
select (jsonb_extract_all(val, curr_path || key)).*
from jsonb_each(jsonb_data) e(key, val);
elseif jsonb_typeof(jsonb_data) = 'array' then
return query
select (jsonb_extract_all(val, curr_path || ord::text)).*
from jsonb_array_elements(jsonb_data) with ordinality e(val, ord);
else
return query
select curr_path, jsonb_data::text;
end if;
end $$;
Example usage:
with my_table(data) as (
select
'{
"$type": "a",
"other": "x",
"nested_object": {"$type": "b"},
"array_1": [{"other": "y"}, {"$type": "c"}],
"array_2": [{"$type": "d"}, {"other": "z"}]
}'::jsonb
)
select f.*
from my_table
cross join jsonb_extract_all(data) f
where path[cardinality(path)] = '$type';
path | value
-----------------------+-------
{$type} | "a"
{array_1,2,$type} | "c"
{array_2,1,$type} | "d"
{nested_object,$type} | "b"
(4 rows)
You can use a resursive query. I have done most of the work here:
with recursive dived(jkey, jval, jtype) as (
select t.key, t.value,
json_typeof(t.value) jtype
from json_each('{"id":"243769","name":"domains","type":"TABLE","adata":{"sfield":"name"},"fields":{"id":{"ind":1,"enum":null,"refs":[null,null],"reqd":true,"type":"int4","constr":["p",null],"default":null},"name":{"ind":2,"enum":null,"refs":[null,null],"reqd":true,"type":"text","constr":["u",null],"default":null},"appid":{"ind":5,"enum":null,"refs":["apps","id"],"reqd":true,"type":"int4","constr":[null,null],"default":null},"userid":{"ind":8,"enum":null,"refs":["users","id"],"reqd":true,"type":"int8","constr":[null,null],"default":null},"createdat":{"ind":6,"enum":null,"refs":[null,null],"reqd":true,"type":"timestamptz","constr":[null,null],"default":null},"updatedat":{"ind":7,"enum":null,"refs":[null,null],"reqd":true,"type":"timestamptz","constr":[null,null],"default":null},"subdomainforward":{"ind":4,"enum":null,"refs":[null,null],"reqd":false,"type":"text","constr":[null,null],"default":null},"wilcardsubdomain":{"ind":3,"enum":null,"refs":[null,null],"reqd":false,"type":"bool","constr":[null,null],"default":null}},"schema":"web","relchecks":0,"relhasrules":false,"relhastriggers":true,"relrowsecurity":false,"relforcerowsecurity":false}'::json) t
union all
select t.key, t.value,
json_typeof(t.value) jtype
from dived, json_each(dived.jval) as t
where dived.jtype in ('object' /*, 'array'*/)
)
select * From dived where jkey = 'yourkey' limit 100
You will simply need to add in an case when or some logic when it comes to arrays and json_array_elements.
Iterating through nested arrays with json is not too difficult with a recursive query but I find it tedious.
Place the CASE WHEN in front of the json_each as something like:
CASE WHEN dived.jtype = 'array' then
json_array_elements(dived.jval) t
It may be possible to handle the situation with the case when scenario, otherwise you may need a separate recursive query specifically for arrays and then do a union with the object keys/values.
You also may find more info here:
Collect Recursive JSON Keys In Postgres
I hope this helps!

How do I check if a json key exists in Postgres?

Let's say I have a json that looks like this:
some_json = {'key_a': {'nested_key': 'a'},
'key_b': {'nested_key': 'b'}}
Note that key_a and key_b are optional keys mapped to dictionaries and may or may not exist.
I have a function that checks if an outer key exists in some_json and returns a boolean.
CREATE FUNCTION key_exists(some_json json, outer_key text)
RETURNS boolean AS $$
BEGIN
RETURN (some_json->outer_key IS NULL);
END;
$$ LANGUAGE plpgsql;
I get the following error:
ProgrammingError: operator does not exist: json -> boolean
Why is outer_key equating to a boolean? What's the proper syntax to perform this check?
You can also use the '?' operator like that:
SELECT '{"key_a":1}'::jsonb ? 'key_a'
And if you need to query by nested key, use like this:
SELECT '{"key_a": {"nested_key": "a"}}'::jsonb -> 'key_a' ? 'nested_key'
See http://www.postgresql.org/docs/9.5/static/functions-json.html
NOTE: Only for jsonb type.
Your function does the exact opposite of what the name is, but the way to fix your function is to add ( and ) around the some_json->outer_key.
Here is it fully functioning, and matching the name of your function (notice the NOT in front of the NULL).
CREATE FUNCTION key_exists(some_json json, outer_key text)
RETURNS boolean AS $$
BEGIN
RETURN (some_json->outer_key) IS NOT NULL;
END;
$$ LANGUAGE plpgsql;
Some tests:
select key_exists('{"key_a": {"nested_key": "a"}, "key_b": {"nested_key": "b"}}'::json, 'key_a');
key_exists
------------
t
(1 row)
And here when a key doesn't exist:
select key_exists('{"key_a": {"nested_key": "a"}, "key_b": {"nested_key": "b"}}'::json, 'test');
key_exists
------------
f
(1 row)
To check key exists or not you can use the operator -> this is used to get Get JSON object field by key
For example:
actual json data in column(attribute): {
"active": "t",
"email_address": "kris.ann.augdahl#hp.com",
"pin": "2233"
}
SELECT attributes::json->'email_address'
FROM entity
WHERE entity_id = 55;
You can also search key via operator #> and #>>
Get JSON object field as text: '{"a":1,"b":2}'::json->>'b' by using the operator ->>
PostgreSQL 14 add subscripting feature for json/jsonb.
SELECT ('{"key_a":1}'::jsonb) ['key_a'] is not null;
return t.
In Postgres, if you select a key that does not exist it will return null.
so u can check the existence of a key by checking the null value of that key.
select '{"key_a": {"nested_key": "a"},
"key_b": {"nested_key": "b"}}'::jsonb->>'a'
------------
null
(1 row)

SQL - The used select statement have a different number of colums

I'm trying to make my first function, it creates without any error, but, when I try to use it it gives me error.
Here's the function -
CREATE FUNCTION isie_kontakti (condition CHAR(3))
RETURNS CHAR(100)
BEGIN
DECLARE returnthis CHAR(100);
SELECT DISTINCT Person.name, Person.lastName, Contacts.mobile, Contacts.email
FROM Person JOIN Contacts on Contacts.Person_ID = Person.ID
JOIN ParentChild on ParentChild.parentID = Person.ID
JOIN ChildGroup ON ChildGroup.Person_ID = ParentChild.childID
WHERE ChildGroup.Group_ID = 'condition' INTO returnthis;
RETURN returnthis;
END//
Table schema - http://www.imagesup.net/dm-713886347846.png
You create your function to return a single column of type char(100) yet the returnthis item contains quite a few columns.
You need to match up your query and return type.
How you do that depends on what you're trying to achieve. It's possibly as simple as just concatenating the columns from the select into a single variable, something along the lines of (untested since I don't have my DBMS available at the moment):
SELECT Person.name | ' '
| Person.lastName | ' '
| Contacts.mobile | ' '
| Contacts.email
FROM ...

How do I select noncontiguous characters from a string of text in MySQL?

I have a table with millions of rows and a single column of text that is exactly 11,159 characters long. It looks like this:
1202012101...(to 11,159 characters)
1202020120...
0121210212...
...
(to millions of rows)
I realize that I can use
SELECT SUBSTR(column,2,4) FROM table;
...if I wanted to pull out characters 2, 3, 4, and 5:
1202012101...
1202020120...
0121210212...
^^^^
But I need to extract noncontiguous characters, e.g. characters 1,5,7:
1202012101...
1202020120...
0121210212...
^ ^ ^
I realize this can be done with a query like:
SELECT CONCAT(SUBSTR(colm,1,1),SUBSTR(colm,5,1),SUBSTR(colm,7,1)) FROM table;
But this query gets very unwieldy to build for thousands of characters that I need to select. So for the first part of the question - how do I build a query that does something like this:
SELECT CHARACTERS(string,1,5,7) FROM table;
Furthermore, the indices of the characters I want to select are from a different table that looks something like this:
char_index keep_or_discard
1 keep
2 discard
3 discard
4 discard
5 keep
7 discard
8 keep
9 discard
10 discard
So for the second part of the question, how could I build a query to select specific characters from the first table based on whether keep_or_discard="keep" for that character's index in the second table?
this function does what you want:
CREATE DEFINER = `root`#`localhost` FUNCTION `test`.`getsubset`(selection mediumtext, longstring mediumtext)
RETURNS varchar(200)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT 'This function returns a subset of characters.'
BEGIN
SET #res:='';
SET #selection:=selection;
WHILE #selection<>'' DO
set #pos:=CONVERT(#selection, signed);
set #res := concat_ws('',#res,SUBSTRING(longstring,#pos,1));
IF LOCATE(',',#selection)=0 THEN
SET #selection:='';
END IF;
set #selection:=SUBSTRING(#selection,LOCATE(',',#selection)+1);
END WHILE;
RETURN #res;
END
Note: the CONVERT('1,2,3,4',signed) will yield 1, but it will give a warning.
I have it defined to be available in the database test.
The function takes two parameters; a string(!) with a list of positions, and a long string from where you want the characters taken.
An example of using this:
mysql> select * from keepdiscard;
+---------+------------+
| charind | keepordisc |
+---------+------------+
| 1 | keep |
| 2 | discard |
| 3 | keep |
| 4 | discard |
| 5 | keep |
| 6 | keep |
+---------+------------+
6 rows in set (0.00 sec)
mysql> select * from test;
+-------------------+
| longstring |
+-------------------+
| abcdefghijklmnopq |
| 123456789 |
+-------------------+
2 rows in set (0.00 sec)
mysql> select getsubset(group_concat(charind ORDER BY charind),longstring) as result from keepdiscard, test where keepordisc='keep' group by longstring;
+--------+
| result |
+--------+
| 1356 |
| acef |
+--------+
2 rows in set, 6 warnings (0.00 sec)
The warnings stem from the fast conversion to integer that is done in the function. (See comment above)
How about dynamic sql? (You will need to build the select part of the query)
CREATE PROCEDURE example_procedure()
BEGIN
--
--build the concat values here
--
SET #ids := '';
SET #S = 'SELECT #ids := built_concat_of_values FROM table';
PREPARE n_StrSQL FROM #S;
EXECUTE n_StrSQL;
DEALLOCATE PREPARE n_StrSQL;
END
You can write a php script to do this for you:
<?php
//mysql connect
$conn = mysql_connect('localhost', 'mysql_user', 'mysql_password');
if (!$conn) {
echo 'Unable to connect to DB: ' . mysql_error();
exit;
}
//database connect
$db = mysql_select_db('mydb');
if (!$db) {
echo 'Unable to select mydb: ' . mysql_error();
exit;
}
//get the keep numbers you’re going to use.
//and change the number into string so, for example, instead of 5 you get 'SUBSTR(colm,5,1)'
$result = mysql_query("SELECT number FROM number_table WHERE keep_or_discard='keep'");
$numbers = array();
while ($row = mysql_fetch_assoc($result)) {
$row = 'SUBSTR(colm,' . $row . ',1)';
$numbers = $row;
}
//implode the array so you get one long string with all the substrings
//eg. 'SUBSTR(colm,1,1),SUBSTR(colm,5,1),SUBSTR(colm,12,1)'
$numbers = implode(",", $numbers);
//pull the numbers you need and save them to an array.
$result = mysql_query("SELECT " . $numbers . " FROM table");
$concat = array();
while ($row = mysql_fetch_assoc($result)) {
$concat= $row;
}
And there you have an array with the correct numbers.
I'm sorry if you can't/don't want to use PHP for this, I just don't really know how to do this without PHP, Perl, Python or some other similar language. Hopefully this solution will help somehow...
The source of your difficulty is that your schema does not represent the true relationships between the data elements. If you wanted to achieve this with "pure" SQL, you would need a schema more like:
table
ID Index Char
1 0 1
1 1 2
1 2 0
charsToKeep
ID Index Keep
1 0 false
1 1 true
1 2 true
Then, you could perform a query like:
SELECT Char FROM table t JOIN charsToKeep c ON t.ID = c.ID WHERE c.Keep = true
However, you probably have good reasons for structuring your data the way you have (my schema requires much more storage space per character and the processing time is also probably much longer from what I am about to suggest).
Since SQL does not have the tools to understand the schema you have embedded into your table, you will need to add them with a user-defined function. Kevin's example of dynamic SQL may also work, but in my experience this is not as fast as a user-defined function.
I have done this in MS SQL many times, but never in MySql. You basically need a function, written in C or C++, that takes a comma-delimited list of the indexes you want to extract, and the string from which you want to extract them from. Then, the function will return a comma-delimited list of those extracted values. See these links for a good starting point:
http://dev.mysql.com/doc/refman/5.1/en/adding-functions.html
http://dev.mysql.com/doc/refman/5.1/en/adding-udf.html
To build the concatenated list of indexes you want to extract from the char_index table, try the group_concat function:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
Hope this helps!