BigQuery- Mystery "unclosed string literal" error from execute immediate statement - google-bigquery

I'm working on a script that creates a table while setting the table description metadata. Eventually this will take variable table names and table descriptions as input, hence the need for the execute immediate statement. For now to keep things cleaner I'm just setting the variables as static strings. I would like the table description to have multiple new lines, as I will be including a few different pieces of information there that I'd like not to be all on one line for legibility reasons.
My code (below) looks correct to me, however when I run it, I'm getting an error:
Invalid EXECUTE IMMEDIATE sql string ` CREATE OR REPLACE TABLE `project.dataset.example_table` OPTIONS ( description = ' This is a table description. It uses multiple lines. ' ) AS ( Select 1 ) `, Syntax error: Unclosed string literal at [12:19]`
I'm really not seeing where this unclosed string literal is. If I copy the SQL string from the error message and run it, it works just fine. The only thing I can think of is if it might have something to do with a newline character in the table_description variable?
Declare table_name string;
Declare table_description string;
Set table_name = 'project.dataset.example_table';
Set table_description =
"""
This is a table description.
It uses multiple lines.
""";
EXECUTE IMMEDIATE """
CREATE OR REPLACE TABLE `""" || table_name || """`
OPTIONS (
description = '""" || table_description || """'
) AS (
Select 1
)
""";

Below is fixed version - just compare to see few "fixes"
Declare table_name string;
Declare table_description string;
Set table_name = 'project.dataset.example_table';
Set table_description =
"""
This is a table description.
It uses multiple lines.
""";
EXECUTE IMMEDIATE """
CREATE OR REPLACE TABLE `""" || table_name || """`
OPTIONS (
description = '''""" || table_description || """'''
) AS (
Select 1 as col_name
)
""";

Related

INSERT with dynamic column names

I have column names stored in variable colls, next I execute code:
DO $$
DECLARE
v_name text := quote_ident('colls');
BEGIN
EXECUTE 'insert into table1 select '|| colls ||' from table2 ';
-- EXECUTE 'insert into table1 select '|| v_name ||' from table2 ';
END$$;
I have got error: column "colls" does not exist. Program used colls as name not as variable. What am I doing wrong?
I have found similar example in documentation:
https://www.postgresql.org/docs/8.1/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
I have column names stored in variable colls
No, you don't. You have a variable v_name - which holds a single word: 'colls'. About variables in SQL:
User defined variables in PostgreSQL
Read the chapters Identifiers and Key Words and Constants in the manual.
And if you had multiple column names in a single variable, you could not use quote_ident() like that. It would escape the whole string as a single identifier.
I guess the basic misunderstanding is this: 'colls' is a string constant, not a variable. There are no other variables in a DO statement than the ones you declare in the DECLARE section. You might be looking for a function that takes a variable number of column names as parameter(s) ...
CREATE OR REPLACE FUNCTION f_insert_these_columns(VARIADIC _cols text[])
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'INSERT INTO table1 SELECT '
|| string_agg(quote_ident(col), ', ')
|| ' FROM table2'
FROM unnest(_cols) col
);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT f_insert_these_columns('abd', 'NeW Deal'); -- column names case sensitive!
SELECT f_insert_these_columns(VARIADIC '{abd, NeW Deal}'); -- column names case sensitive!
Note how I unnest the array of column names and escape them one by one.
A VARIADIC parameter should be perfect for your use case. You can either pass a list of column names or an array.
Either way, be vary of SQL injection.
Related, with more explanation:
Pass multiple values in single parameter
Table name as a PostgreSQL function parameter

How to use variable as table name?

BEGIN
_table_name := 'mytable';
CREATE TEMPORARY TABLE _table_name (
id integer NOT NULL,
name character varying,
active boolean
) ON COMMIT DROP';
-- logic here
RETURN QUERY SELECT table1.id, table1.name FROM _table_name AS table1;
END;
I have simplified my problem. I'm trying to use a variable as the table name.
I know you can do things like SELECT * FROM table1 WHERE id = _id where _id is a declared varaible.
I know I can do this EXECUTE, but then I have a query like:
INSERT INTO table2 (id) SELECT id FROM unnest(_ids) as id where _ids is an array.
Anyway to solve problem 1 with using a variable as table name? and problem 2, using unnest inside EXECUTE?
So the problem is that the queries take _table_name as a literal table name, it doesn't look like it's using 'mytable' as the table name instead.
If you're dynamically changing the table name (i.e. via a variable) then you will need to use EXECUTE. You can use this with arrays and unnest, as long as you cast the arrays to/from a TEXT representation.
DECLARE
_ids INT[] = ARRAY[ 1, 2, 3, 4, 5 ];
_table_name TEXT = 'mytable';
BEGIN
EXECUTE
'INSERT INTO ' || QUOTE_IDENT( _table_name ) || ' (id)
SELECT id
FROM unnest( ' || QUOTE_LITERAL( _ids::TEXT ) || '::INT[] ) AS id';
END;
You have been told about dynamic SQL with EXECUTE in plpgsql. You build the query string dynamically including invariable code and identifiers.
But do not concatenate values. Use the USING clause instead:
DECLARE
_ids INT[] = ARRAY[ 1, 2, 3, 4, 5 ];
_table_name TEXT = 'mytable';
BEGIN
EXECUTE
'INSERT INTO ' || quote_ident(_table_name) || ' (id)
SELECT * FROM unnest($1)'
USING ids;
END;
Avoids error-prone casting back and forth.
Table name as a PostgreSQL function parameter

How to convert this select statement into a stored procedure?

I have this select statement that displays the text (content) of a pl/sql script :
select text
from DBA_source
where type like '%PROCEDURE%' and name like '%JOB_HISTORY%'
order by line;
I want to convert this into a stored procedure in such a way that the 'name' condition of the select statement should be taken as an input and should not be pre defined as show like '%job_history%.
How can this be done ?
This is a function, not a stored procedure:
create type proc_tab is table of DBA_source.text%type;
/
create function select_procedure(p_name in film.title%type) return proc_tab
is
l_proc_tab proc_tab := proc_tab();
n integer := 0;
begin
select text
bulk collect into l_proc_tab
from DBA_source
where type like '%PROCEDURE%' and name like '%' || p_name || '%'
order by line;
return l_proc_tab;
end;
/
I haven't tested this code, but it should at least be a good start.

Update substrings using lookup table and replace function

Here's my setup:
Table 1 (table_with_info): Contains a list of varchars with substrings that I'd like to replace.
Table 2 (sub_info): Contains two columns: the substring in table_with_info that I'd like to replace and the string I'd like to replace it with.
What I'd like to do is replace all the substrings in table_with_info with their substitutions in sub_info.
This works to a point but the issue is that select replace(...) returns a new row for each one of the substituted words replaced and doesn't replace all of the ones in an individual row.
I'm explaining the best I can but I don't know if it's too clear. Here's the code an example of what's happening/what I'd like to happen.
Here's my code:
create table table_with_info
(
val varchar
);
insert into table_with_info values
('this this is test data');
create table sub_info
(
word_from varchar,
word_to varchar
);
insert into sub_info values
('this','replace1')
, ('test', 'replace2');
update table_with_info set val = (select replace("val", "word_from", "word_to")
from "table_with_info", "sub_info"
the update() function doesn't work as select() returns two rows:
Row 1: replace1 replace1 is test data
Row 2: this this is replace2 data
so what I'd like for it for the select statement to return is:
Row 1: replace1 replace1 is test data
Any thoughts? I can't create UDFs on the system I'm running.
Your UPDATE statement is incorrect in multiple ways. Consult the manual before you try to run anything like this again. You introduce two cross joins that would make this statement extremely expensive, besides yielding nonsense.
To do this properly, you need to administer each UPDATE sequentially. In a single statement, one row version eliminates the other, while each replace would use the same original row version. You can use a DO statement for this or wrap it in a plpgsql function for instance:
DO
$do$
DECLARE
r sub_info;
BEGIN
FOR r IN
TABLE sub_info
-- SELECT * FROM sub_info ORDER BY ??? -- order is relevant
LOOP
UPDATE table_with_info
SET val = replace(val, r.word_from, r.word_to)
WHERE val LIKE ('%' || r.word_from || '%'); -- avoid empty updates
END LOOP;
END
$do$;
Be aware, that the order in which updates are applied can make a difference! If the first update creates a string where the second matches (but not otherwise) ..
So, order your columns in sub_info if that can be relevant.
Avoid empty updates. Without the additional WHERE clause, you would write many new row versions without changing anything. Expensive and useless.
double-quotes are optional for legal, lower-case names.
->SQLfiddle
Expanding on Erwin's answer, a do block with dynamic SQL can do the trick as well:
do $$
declare
rec record;
repl text;
begin
repl := 'val'; -- quote_ident() this if needed
for rec in select word_from, word_to from sub_info
loop
repl := 'replace(' || repl || ', '
|| quote_literal(rec.word_from) || ', '
|| quote_literal(rec.word_to) || ')';
end loop;
-- now do them all in a single query
execute 'update ' || 'table_with_info'::regclass || ' set val = ' || repl;
end;
$$ language plpgsql;
Optionally, build a like parameter in a similar way to avoid updating rows needlessly.

Check a whole table for a single value

Background: I'm converting a database table to a format that doesn't support null values. I want to replace the null values with an arbitrary number so my application can support null values.
Question: I'd like to search my whole table for a value ("999999", for example) to make sure that it doesn't appear in the table. I could write a script to test each column individually, but I wanted to know if there is a way I could do this in pure sql without enumerating each field. Is that possible?
You can use a special feature of the PostgreSQL type system:
SELECT *
FROM tbl t
WHERE t::text LIKE '%999999%';
There is a composite type of the same name for every table that you create in PostgreSQL. And there is a text representation for every type in PostgreSQL (to input / output values).
Therefore you can just cast the whole row to text and if the string '999999' is contained in any column (its text representation, to be precise) it is guaranteed to show in the query above.
You cannot rule out false positives completely, though, if separators and / or decorators used by Postgres for the row representation can be part of the search term. It's just very unlikely. And positively not the case for your search term '999999'.
There was a very similar question on codereview.SE recently. I added some more explanation in my answer there.
create or replace function test_values( real ) returns setof record as
$$
declare
query text;
output record;
begin
for query in select 'select distinct ''' || table_name || '''::text table_name, ''' || column_name || '''::text column_name from '|| quote_ident(table_name)||' where ' || quote_ident(column_name) || ' = ''' || $1::text ||'''::' || data_type from information_schema.columns where table_schema='public' and numeric_precision is not null
loop
raise notice '%1 qqqq', query;
execute query::text into output;
return next output;
end loop;
return;
end;$$ language plpgsql;
select distinct * from test_values( 999999 ) as t(table_name text ,column_name text)