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
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;
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'];
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.
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
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.