Perl-Postgres column array and quotes - arrays

I want to populate in Perl a postgres column defined as a character array (Tag_List[])
Values may contains single-quote, so when constructing the SQL string, I cannot get the right syntax to enquote these kind of values, since I already must double-enquote string values and single-enquote array type (because of the curly brackets).
I've tried to escape the single-quote (with a $VAL =~ s/\x27/\x5c\x27/g;) but no way :
INSERT INTO "table-name" ("Date","Time"...,"Tag_List"[1],"Tag_List"[2],"Tag_List"[3],"Tag_List"[4],"Tag_List"[5])
VALUES ('2019/10/30', '14:17:59', .... ,'{"LastSuccessfulBackup-com.dellemc.avamar","2019-10-29 20:00:22 UTC"}','{"TAG1","tag value\'with quote"}',...
ERROR: syntax error at or near "with"
LIGNE 1 : ...20:00:22 UTC"}','{"TAG1","tag value\'with quote"}','{...
Any clue ?
Thanks

You can insert the whole array as a string:
CREATE TABLE arr (
id integer PRIMARY KEY,
tag_list character varying(128)[]
);
INSERT INTO arr (id, tag_list) VALUES
(1,
'{normal_string,string with spaces,"string,with,comma","string\"with\"quote","string''with''apostrophe"}'
);
This will result in entries like:
SELECT u.* FROM arr CROSS JOIN unnest(tag_list) AS u;
u
------------------------
normal_string
string with spaces
string,with,comma
string"with"quote
string'with'apostrophe
(5 rows)
An alternative is to insert each array entry individually:
INSERT INTO arr (
id,
tag_list[1],
tag_list[2],
tag_list[3],
tag_list[4],
tag_list[5]
) VALUES (
2,
'normal_string',
'string with spaces',
'string,with,comma',
'string"with"quote',
'string''with''apostrophe'
);

Use bound parameters. (Always use parameter binding to send data into a database.) DBD::Pg supports array parameters by default.
use strict;
use warnings;
my $query = 'INSERT INTO "table-name" ("Date","Time"...,"Tag_List"[1],"Tag_List"[2],"Tag_List"[3],"Tag_List"[4],"Tag_List"[5]) (?,?,...,?,?,?,?,?)';
$dbh->insert($query, undef, $date, $time, ... ['TAG1',"tag value'with quote"], ...);

Related

Replacing elements in an array column in snowflake

I have sample data as follows;
team_id
mode
123
[1,2]
Here mode is an array.The goal is to replace the values in column mode by literal values, such as 1 stands for Ocean, and 2 stands for Air
Expected Output
team_id
mode
123
[Ocean,Air]
Present Approach
As an attempt, I tried to first flatten the data into multiple rows;
team_id
mode
123
1
123
2
Then we can define a new column assigning literal values to mode column using a case statement, followed by aggregating the values into an array to get desired output.
Can I get some help here to do the replacement directly in the array? Thanks in advance.
Using FLATTEN and ARRAY_AGG:
CREATE OR REPLACE TABLE tab(team_id INT, mode ARRAY) AS SELECT 123, [1,2];
SELECT TEAM_ID,
ARRAY_AGG(CASE f.value::TEXT
WHEN 1 THEN 'Ocean'
WHEN 2 THEN 'Air'
ELSE 'Unknown'
END) WITHIN GROUP(ORDER BY f.index) AS new_mode
FROM tab
,LATERAL FLATTEN(tab.mode) AS f
GROUP BY TEAM_ID;
Output:
TEAM_ID
NEW_MODE
123
[ "Ocean", "Air" ]
For an alternative solution with easy array manipulation. you could create a JS UDF:
create or replace function replace_vals_in_array(A variant)
returns variant
language javascript
as $$
dict = {1:'a', 2:'b', 3:'c', 4:'d'};
return A.map(x => dict[x]);
$$;
Then to update your table:
update arrs
set arr = replace_vals_in_array(arr);
Example setup:
create or replace temp table arrs as (
select 1 id, [1,2,3] arr
union all select 2, [2,4]
);
select *, replace_vals_in_array(arr)
from arrs;

Postgres String Array Comparison

I have an array of the form :
myArray = ["1234-56", "1234-567"]
My table has a column which is constructed exactly like the array and consists of a string array, we call the column : myColumn.
I want to output the rows where , one or more values of the arrays match.
My current attempt was the following :
SELECT *
FROM myTable
WHERE myColumn && myArray;
But this ends with the following error message:
ERROR: Column "56" does not exist.
A string in double quotes is an “identifier” in SQL, that is the name of a table, column, function or other object.
So when you write
SELECT * FROM mytable
WHERE mycolumn && ARRAY["56","95"];
PostgreSQL will identify "56" as a column name (it couldn't be a table in that context), and it complaint that table mytable has no column called 56.
The solution is to mark 56 as a string literal, that is, surround it with single quotes:
SELECT * FROM mytable
WHERE mycolumn && ARRAY['56','95'];

How do I turn an array into a record / tuple / row type?

I need to execute a dynamic insert into a table with a variable number of columns.
Right now I'm quoting both the column names, with quote_ident, and the actual values, with quote_nullable, and then joining them with array_to_string:
for ... loop
...
cols := array_append(cols, quote_ident(column_name));
vals := array_append(vals, quote_nullable(column_value));
end loop;
execute format('insert into %s (%s) values (%s)',
target_table,
array_to_string(cols, ', ')
array_to_string(vals, ', ')
);
It's a pattern found all over the place, including on the official documentation. But it feels a bit unclean. I'd rather pass the array of values a parameter in the using clause:
execute format('insert into %s (%s) ... $1 ...',
target_table,
array_to_string(cols, ', ')
)
using vals;
Notice the using vals, which is what I'd like to achieve. But I cannot seem to be able to fill in the dots in the insert statement. Maybe some kind of select ... from ...?
More generally, how do I turn an array into a record / tuple / row type?
I'm using code very like this in production:
EXECUTE ( SELECT 'insert into sometable '
'('||string_agg(quote_ident(p.key),',') ||
') values ('|| string_agg(quote_nullable(p.value),',') || ');'
FROM each(payload) as p
);
but here payload is a hstore, not a pair of arrays.

Return rows matching elements of input array in plpgsql function

I would like to create a PostgreSQL function that does something like the following:
CREATE FUNCTION avg_purchases( IN last_names text[] DEFAULT '{}' )
RETURNS TABLE(last_name text[], avg_purchase_size double precision)
AS
$BODY$
DECLARE
qry text;
BEGIN
qry := 'SELECT last_name, AVG(purchase_size)
FROM purchases
WHERE last_name = ANY($1)
GROUP BY last_name'
RETURN QUERY EXECUTE qry USING last_names;
END;
$BODY$
But I see two problems here:
It is not clear to me that array type is the most useful type of input.
This is currently returning zero rows when I do:
SELECT avg_purchases($${'Brown','Smith','Jones'}$$);
What am I missing?
This works:
CREATE OR REPLACE FUNCTION avg_purchases(last_names text[] = '{}')
RETURNS TABLE(last_name text, avg_purchase_size float8)
LANGUAGE sql AS
$func$
SELECT last_name, avg(purchase_size)::float8
FROM purchases
WHERE last_name = ANY($1)
GROUP BY last_name
$func$;
Call:
SELECT * FROM avg_purchases('{foo,Bar,baz,"}weird_name''$$"}');
Or (example with dollar-quoting):
SELECT * FROM avg_purchases($x${foo,Bar,baz,"}weird_name'$$"}$x$);
How to quote string literals:
Insert text with single quotes in PostgreSQL
You don't need dynamic SQL here.
While you can wrap it into a plpgsql function (which may be useful), a simple SQL function is doing the basic job just fine.
You had type mismatches:
The result of avg() may be numeric to hold a precise result. A cast to float8 (alias for double precision) makes it work. For perfect precision, use numeric instead.
The OUT parameter last_name must be text instead of text[].
VARIADIC
An array is a useful type of input. If it's easier for your client you can also use a VARIADIC input parameter that allows to pass the array as a list of elements:
CREATE OR REPLACE FUNCTION avg_purchases(VARIADIC last_names text[] = '{}')
RETURNS TABLE(last_name text, avg_purchase_size float8)
LANGUAGE sql AS
$func$
SELECT last_name, avg(purchase_size)::float8
FROM purchases
JOIN (SELECT unnest($1)) t(last_name) USING (last_name)
GROUP BY 1
$func$;
Call:
SELECT * FROM avg_purchases('foo', 'Bar', 'baz', '"}weird_name''$$"}');
Or (with dollar-quoting):
SELECT * FROM avg_purchases('foo', 'Bar', 'baz', $y$'"}weird_name'$$"}$y$);
Stock Postgres only allows a maximum of 100 elements. This is determined at compile time by the preset option:
max_function_args (integer)
Reports the maximum number of function arguments. It is determined by the value of FUNC_MAX_ARGS when building the server. The default value is 100 arguments.
You can still call it with array notation when prefixed with the keyword VARIADIC:
SELECT * FROM avg_purchases(VARIADIC '{1,2,3, ... 99,100,101}');
For bigger arrays (100+), consider unnest() in a subquery and JOIN to it, tends to scale better:
Optimizing a Postgres query with a large IN

How to formulate an array literal of a composite type containing arrays?

I have a composite type like
CREATE TYPE example AS (id integer, some_stuff integer[]);
Thought I can use an array of this type as an argument of a function. The only problem is I couldn't find a way to build an array literal for that... If I try obtain it from PostgreSQL:
WITH elements AS (
SELECT (12, '{1,2}')::example AS e UNION
SELECT (3, '{3,1}')::example
)
SELECT array_agg(e) FROM elements;
I get the following:
{"(3,\"{3,1}\")","(12,\"{1,2}\")"}
But look:
SELECT E'{"(3,\"{3,1}\")","(12,\"{1,2}\")"}'::example[];
ERROR: malformed array literal: "{"(3,"{3,1}")","(12,"{1,2}")"}"
LINE 1: select E'{"(3,\"{3,1}\")","(12,\"{1,2}\")"}'::example[]
Is there a way to do this?
Try using ARRAY and ROW constructors:
Select array[row(3, array[3,1]), row(12, array[1,2])]::example[];
array
------------------------------------
{"(3,\"{3,1}\")","(12,\"{1,2}\")"}
(1 row)
If you want solution without using constructors, then use following example:
Select E'{"(3,\\"{3,1}\\")","(12,\\"{1,2}\\")"}'::example[];
example
------------------------------------
{"(3,\"{3,1}\")","(12,\"{1,2}\")"}
(1 row)
As you see main issue here is that you need to write \\", because this effectively means \" (using "escape" string syntax) that you saw as output of your first select.

Resources