Use variable inside "from" in firebird - sql

I try use variable inside "from" SELECT COUNT(*) FROM :T_NAME, but is not work. How can i fix this? This my code:
SET TERM ^ ;
CREATE OR ALTER PROCEDURE Myfunction
RETURNS(
T_NAME varchar(100),
NUM_RECORDS integer
)
AS
BEGIN
FOR SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
WHERE (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND RDB$VIEW_BLR IS NULL
ORDER BY RDB$RELATION_NAME
INTO :T_NAME
DO
BEGIN
SELECT COUNT(*) FROM :T_NAME
INTO :NUM_RECORDS;
SUSPEND;
END
END^
SET TERM ; ^

You can't parametrize object names. You will need to dynamically build the query (which if you aren't careful may leave you open to SQL injection).
Specifically you need to use:
...
BEGIN
EXECUTE STATEMENT 'select count(*) from "' || T_NAME || '"'
INTO :NUM_RECORDS;
SUSPEND;
END
This should be safe except for table names that contain double quotes. I haven't explicitly checked the behavior with single quotes in an object name, which technically is possible. You may need to take extra steps to protect against these forms of SQL injection if you use this in real production code.

Related

How to skip/continue delete statement when the table does not exist?

I want to write execute block that I can use for multiple databases with similar structure, some don't have 1 or more tables. I use something like this
execute block as
begin
delete from table1;
delete from table2;
delete from table3;
delete from table4;
delete from table5;
end
and this works for base with all tables, but when table is missing the execute block stops. I want to use this execute block when there is missing table so I don't have to commit the missing one.
You can't do it like this. A table referenced in a delete PSQL statement must exist for the execute block to successfully compile before it can even run.
Instead you will need to use statement blocks containing a execute statement to execute the statements dynamically and a when any exception handler to continue on any error.
For example, something like:
execute block as
begin
begin
execute statement 'delete from table1';
when any do
begin
-- ignore error
end
end
begin
execute statement 'delete from table2';
when any do
begin
-- ignore error
end
end
-- etc
end
You could also check for existence in the system tables before executing the delete dynamically. For example, something like:
execute block
as
declare variable tablename varchar(64);
begin
for select trim(rdb$relation_name)
from rdb$relations
where rdb$relation_name in ('TABLE1', 'TABLE2', 'TABLE3', 'TABLE4', 'TABLE5')
into tablename do
begin
execute statement 'delete from "' || replace(tablename, '"', '""') || '"';
end
end
The table names in the IN-clause must match the name as stored (for unquoted object names like table1 that means uppercase TABLE1). The replace(tablename, '"', '""') is just for completeness to escape possible double quotes in table names.

Find tables, columns with specific value

I'm using Firebird 2.5.0. I know a value and need to find all tables, columns in which it occurs.
I created procedure:
CREATE OR ALTER PROCEDURE NEW_PROCEDURE (
searching_value varchar(30))
returns (
table_with_value varchar(100),
column_with_value varchar(100))
as
declare variable all_tables varchar(50);
declare variable all_columns varchar(50);
declare variable all_values varchar(50);
begin
FOR SELECT
r.rdb$relation_name, f.rdb$field_name
from rdb$relation_fields f
join rdb$relations r on f.rdb$relation_name = r.rdb$relation_name
and r.rdb$view_blr is null
and (r.rdb$system_flag is null or r.rdb$system_flag = 0)
order by 1, f.rdb$field_position INTO :all_tables, :all_columns
DO
BEGIN
FOR SELECT all_columns FROM all_tables
INTO :all_Values
DO
BEGIN
IF (SEARCHING_VALUE = all_Values) THEN
BEGIN
table_With_Value = all_Tables;
column_With_Value = all_Columns;
SUSPEND;
END
END
END
END^
When I run it I get error message:
Undefined name.
Dynamic SQL Error.
SQL error code = -204.
Table unknown.
ALL_TABLES.
At line 21, column 13.
So in this select statement "SELECT all_columns FROM all_tables" it is not taking values from previous for select statement but just trying to find table all_tables. How to fix it?
The problem is that all_columns is considered to be a colum name and all_tables a table name and not your variables in:
SELECT all_columns FROM all_tables
You can't parametrize objectnames in a query like this. Also note that if it had been possible to parametrize object names, you would have had to use :all_columns and :all_tables for disambiguation.
Instead you will need to create a dynamic SQL statement and execute that with EXECUTE STATEMENT (or more specifically: FOR EXECUTE STATEMENT).
In this case:
FOR EXECUTE STATEMENT 'SELECT "' || all_columns || '" FROM "' || all_tables || '"'
INTO :all_values
DO
BEGIN
/* .... */
END
I have quoted the object names to account for case sensitive column and table names (or identifiers that are invalid unquoted). Constructing a query like this might leave you open to SQL injection if the values are obtained from another source than the Firebird metadata tables.

Missing expression Oracle Query

I have a oracle trigger and it returns the error
Additional information: ORA-00936: missing expression
In my TOAD it shows me the below line.
EXECUTE IMMEDIATE 'UPDATE TBL_NEWS_TYPE SET FULLNAME='|| newsName ||' WHERE ID = SELECT MAX(ID) FROM TBL_NEWS_TYPE)';
In here newsName is varchar2 variable like newsName VARCHAR2(50) / and ID in INTEGER.
Try this:
EXECUTE IMMEDIATE 'UPDATE TBL_NEWS_TYPE
SET FULLNAME='''|| newsName
||''' WHERE ID = (SELECT MAX(ID) FROM TBL_NEWS_TYPE)'
I think you had two problems here:
1) You were missing '(' at the start of the select
2) I think this won't work without putting quote sign wraping the newsName because its a string.

Firebird pass input parameter to gen_id function

I want to create stored procedure to get the current id for specific tables i have many tables
so i don't want to create sp for each one,
I'm trying this way but i fail
create procedure
sp_get_id(mytable varchar(128)) returns(id integer)
as
begin
select gen_id(:mytable, 0) from rdb$database into :id;
suspend;
end
I wonder if there is a way to pass the input param to the gen_id or i must create different sp for each table..
thanks in advance
If you keep a naming convention so that the generator name can be derived from the table name you could use something similar to this:
SET TERM ^;
CREATE OR ALTER PROCEDURE SP_GET_ID (
TABLE_NAME VARCHAR(128))
RETURNS (
ID INTEGER)
AS
BEGIN
EXECUTE STATEMENT 'SELECT GEN_ID(GEN_' || :TABLE_NAME || ', 0) FROM RDB$DATABASE' INTO :ID;
END^
SET TERM ;^

Update multiple columns that start with a specific string

I am trying to update a bunch of columns in a DB for testing purposes of a feature. I have a table that is built with hibernate so all of the columns that are created for an embedded entity begin with the same name. I.e. contact_info_address_street1, contact_info_address_street2, etc.
I am trying to figure out if there is a way to do something to the affect of:
UPDATE table SET contact_info_address_* = null;
If not, I know I can do it the long way, just looking for a way to help myself out in the future if I need to do this all over again for a different set of columns.
You need dynamic SQL for this. So you must defend against possible SQL injection.
Basic query
The basic query to generate the DML command needed can look like this:
SELECT format('UPDATE tbl SET (%s) = (%s)'
,string_agg (quote_ident(attname), ', ')
,string_agg ('NULL', ', ')
)
FROM pg_attribute
WHERE attrelid = 'tbl'::regclass
AND NOT attisdropped
AND attnum > 0
AND attname ~~ 'foo_%';
Returns:
UPDATE tbl SET (foo_a, foo_b, foo_c) = (NULL, NULL, NULL);
Make use of the "column-list syntax" of UPDATE to shorten the code and simplify the task.
I query the system catalogs instead of information schema because the latter, while being standardized and guaranteed to be portable across major versions, is also notoriously slow and sometimes unwieldy. There are pros and cons, see:
Get column names and data types of a query, table or view
quote_ident() for the column names prevents SQL-injection - also necessary for identifiers.
string_agg() requires 9.0+.
Full automation with PL/pgSQL function
CREATE OR REPLACE FUNCTION f_update_cols(_tbl regclass, _col_pattern text
, OUT row_ct int, OUT col_ct bigint)
LANGUAGE plpgsql AS
$func$
DECLARE
_sql text;
BEGIN
SELECT INTO _sql, col_ct
format('UPDATE tbl SET (%s) = (%s)'
, string_agg (quote_ident(attname), ', ')
, string_agg ('NULL', ', ')
)
, count(*)
FROM pg_attribute
WHERE attrelid = _tbl
AND NOT attisdropped -- no dropped columns
AND attnum > 0 -- no system columns
AND attname LIKE _col_pattern; -- only columns matching pattern
-- RAISE NOTICE '%', _sql; -- output SQL for debugging
EXECUTE _sql;
GET DIAGNOSTICS row_ct = ROW_COUNT;
END
$func$;
COMMENT ON FUNCTION f_update_cols(regclass, text)
IS 'Updates all columns of table _tbl ($1)
that match _col_pattern ($2) in a LIKE expression.
Returns the count of columns (col_ct) and rows (row_ct) affected.';
Call:
SELECT * FROM f_update_cols('myschema.tbl', 'foo%');
To make the function more practical, it returns information as described in the comment. More about obtaining the result status in plpgsql in the manual.
I use the variable _sql to hold the query string, so I can collect the number of columns found (col_ct) in the same query.
The object identifier type regclass is the most efficient way to automatically avoid SQL injection (and sanitize non-standard names) for the table name, too. You can use schema-qualified table names to avoid ambiguities. I would advise to do so if you (can) have multiple schemas in your db! See:
Table name as a PostgreSQL function parameter
db<>fiddle here
Old sqlfiddle
There's no handy shortcut sorry. If you have to do this kind of thing a lot, you could create a function to dynamically execute sql and achieve your goal.
CREATE OR REPLACE FUNCTION reset_cols() RETURNS boolean AS $$ BEGIN
EXECUTE (select 'UPDATE table SET '
|| array_to_string(array(
select column_name::text
from information_schema.columns
where table_name = 'table'
and column_name::text like 'contact_info_address_%'
),' = NULL,')
|| ' = NULL');
RETURN true;
END; $$ LANGUAGE plpgsql;
-- run the function
SELECT reset_cols();
It's not very nice though. A better function would be one that accepts the tablename and column prefix as args. Which I'll leave as an exercise for the readers :)