I have a dynamic table with an indertermined number of columns [statement_nn] with strings. I need to replace string parts by the correspondent item
The table looks like this: Original table
How could I do the operation having in mind that “statement” columns are variable and cannot specifically use them in a standard REPLACE statement?
This is the end result Im looking to get: Desired result
I tried to make an array of "statement" columns and replace items there, but I need to be able to keep column names to get the desired result
You can use EXECUTE IMMEDIATE to dynamically select and replace your desired items. The way I solved it was replacing each placeholder manually with the following query:
execute immediate (
select 'select * replace(' ||
string_agg('regexp_replace(' ||
'regexp_replace(' ||
'regexp_replace(' || column_name || ',' || ' r"<name>", name)'
|| ',' || ' r"<surname>", surname)'
|| ',' || ' r"<registration_year>", CAST(registration_year as STRING))'
|| ' as ' || column_name, ', ') ||
') from project_name.dataset_name.table_name'
from `project_name.dataset_name.INFORMATION_SCHEMA.COLUMNS`
where table_name = 'table_name'
and STARTS_WITH(column_name, "statement_")
)
Remember to replace project_name, dataset_name, and table_name.
Related
Idea is I am trying to query information schema metadata and build columns based on datatypes if date then I create min date max date ,if number then check count of distinct or count rows
I have simplified now I will pass dbname , schema name and table name as parameters,
My output should return with below columns
SCHEMA_NM,TBL_NAME,_COUNT,_DISTINCT_COUNT,_MIN_DATE,_MAX_DATE.
For testing purpose at least if two measure is syntactically correct remaining I will take care
CREATE OR REPLACE PROCEDURE PROFILING( DB_NAME VARCHAR(16777216),TBL_SCHEMA VARCHAR(16777216), TBL_NAME VARCHAR(16777216))
RETURNS VARCHAR(16777216)
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
DECLARE
BEGIN
create or replace temporary table tbl_name as (select case when data_type=NUMBER then (execute immediate 'select count(col_val) from ' || :1 || '.' || :2 || '.' || :3) else null end as col_value_num,case when data_type=DATE then (execute immediate 'select count(col_val) from ' || :1 || '.' || :2 || '.' || :3) else null end as col_min_date from ':1.information_schema.columns where table_catalog=:1 and table_schema=:2 and table_name=:3 ' )) ;
insert into some_base_table as select * from tbl_name;
truncate tbl_name
RETURN 'SUCCESS';
END;
$$;
With above query I am getting below error
error : SQL compilation error: Invalid expression value (?SqlExecuteImmediateDynamic?) for assignment.
Any help here please
There are multiple errors in your code, but the error you mentioned is about this line:
num := (execute immediate "select count(col_val) from tab_cat.tab_schema.tab_name") ;
NUM is declared as integer, you can't directly assign a value from execute immediate. Execute immediate statement returns a resultset. To access the value you need to define a cursor and fetch the data.
The SQL should be surrounded by single quotes (not double quotes), and you should also build the string correctly. Something like this:
result := (execute immediate 'select count(col_val) from ' || tab_cat || '.' || tab_schema || '.' || tab_name ) ;
I am trying to execute a procedure into which i send the table name and 2 column names as parameters:
EXECUTE IMMEDIATE 'select avg(#column1) from #Table1 where REF_D = #column2' into ATTR_AVG;
I have tried using the variables in combiations of '#', ':', '||' but nothing seems to work.
Has anyone used table names as a parameter. there are a few solutions here but for SQL Server
You can only use bind variables (denoted by colons) for values, not for parts of the structure. You will have to concatenate the table and column names into the query:
EXECUTE IMMEDIATE 'select avg(' || column1 | ') from ' || Table1
|| ' where REF_D = ' || column2 into ATTR_AVG;
Which implies REF_D is a fixed column name that can appear in any table you'll call this for; in a previous question that seems to be a variable. If it is actually a string variable then you'd need to bind and set that:
EXECUTE IMMEDIATE 'select avg(' || column1 | ') from ' || Table1
|| ' where ' || column2 || ' = :REF_D' into ATTR_AVG using REF_D;
If it's supposed to be a date you should make sure the local variable is the right type, or explicitly convert it.
You need to construct the executable statement using || (or else define it as one string containing placeholders that you can then manipulate with replace). Something like:
create or replace procedure demo
( p_table user_tab_columns.table_name%type
, p_column1 user_tab_columns.column_name%type
, p_column2 user_tab_columns.column_name%type )
is
attr_avg number;
begin
execute immediate
'select avg(' || p_column1 || ') from ' || p_table ||
' where ref_d = ' || p_column2
into attr_avg;
dbms_output.put_line('Result: ' || attr_avg);
end demo;
It's generally a good idea to build the string in a debugger-friendly variable first, i.e. something like:
create or replace procedure demo
( p_table user_tab_columns.table_name%type
, p_column1 user_tab_columns.column_name%type
, p_column2 user_tab_columns.column_name%type )
is
attr_avg number;
sql_statement varchar2(100);
begin
sql_statement :=
'select avg(' || p_column1 || ') from ' || p_table ||
' where ref_d = ' || p_column2;
execute immediate sql_statement into attr_avg;
dbms_output.put_line('Result: ' || attr_avg);
end demo;
Depending on what ref_d is, you may have to be careful with what you compare it to, so the above could require some more work, but hopefully it gives you the idea.
Edit: however see Alex Poole's answer for a note about the use of bind variables. If ref_d is a variable that may need to become:
sql_statement :=
'select avg(' || p_column1 || ') from ' || p_table ||
' where ' || p_column2 || ' = :b1';
execute immediate sql_statement into attr_avg using ref_d;
(The convention is to put the search expression on the right e.g. where name = 'SMITH' rather than where 'SMITH' = name, though they are the same thing to SQL.)
In Oracle PL/SQL, I have run a query and am trying to read through each column for each row one by one so I can concatenate them together with a delimiter (hard format requirement). The script is used on multiple tables of varying sizes, so the number of columns is not known in advance. I used
SELECT COUNT(column_name) INTO NumColumns FROM all_tabs_cols
WHERE table_name = Table_Array(i);
where Table_Array has already been defined. This is in the middle of a for loop and has successfully gotten me a total number of columns. Table_Cursor is a SELECT * statement. After this I am trying to do something like
FOR j IN 0..NumColumns-1 LOOP
FETCH TABLE_CURSOR.column(j) INTO DataValue;
DBMS_OUTPUT.PUT(DataValue || '/');
END LOOP
The above is pseudo code. It illustrates the concept I am after. I do not know PL/SQL well enough to know how to get a value like this out of a row. I am also worried about accidentally advancing the cursor while doing this. How can I accomplish this task?
You must use some form of dynamic SQL. Here is a quick example:
It builds the SQL statement that will select the '/' separated columns from the table you want. Then it uses dynamic SQL to run that SQL statement.
DECLARE
p_table_name VARCHAR2(30) := 'DBA_OBJECTS';
l_sql VARCHAR2(32000);
TYPE varchar2tab IS TABLE OF VARCHAR2(32000);
l_array varchar2tab;
BEGIN
SELECT 'SELECT ' || listagg(column_name,' ||''/''||') within group ( order by column_id ) || ' FROM ' || owner || '.' || table_name || ' WHERE ROWNUM <= 100'
INTO l_sql
FROM dba_tab_columns
where table_Name = 'DBA_OBJECTS'
group by owner, table_Name;
EXECUTE IMMEDIATE l_sql BULK COLLECT INTO l_array;
FOR i in l_array.first .. l_array.last LOOP
dbms_output.put_line(l_array(i));
END LOOP;
END;
This is how your code should look:
SELECT F1 || ', ' || F2 || ', ' || ... || ', ' || FN
FROM TABLE
NO LOOPS
Here is how you can generate code that does not use loops.
Note, if you want you can take out the where statement and generate the code for the whole database.
Test with just one table first.
SELECT 'SELECT '|| LISTAGG(COLUMN_NAME, ' || '', '' || ') || ' FROM '||TABLE_NAME as sql_stm
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME='tablename'
GROUP BY TABLE_NAME;
I am searching for a sql query(I am using Postgres database) that can give all values from a single record in comma separated.
e.g.
I have a table named master_table and having column no,name,phone
SELECT * FROM master_table WHERE id = 1
From above query I want result as
1,piyush,1111111
2,john,2222222
Above table is example only tables are dynamic so column numbers and names cannot be fixed.
Thanks in advance
SELECT (column_no::text || ',' || name || ',' || phone::text) AS comma_separated
FROM master_table WHERE id = 1
EDIT :
After your comment, I think you'd need a function. Even if I don't agree with the whole idea (it's much better to do that in the application layer), here is a function which makes what you are asking. It is ugly.
CREATE OR REPLACE FUNCTION GetCommaSeparatedValues(PAR_table text, PAR_where_clause text DEFAULT '') RETURNS TABLE (
comma_separated_values text
) AS $$
DECLARE
REC_columns record;
VAR_query text;
BEGIN
VAR_query := '';
FOR REC_columns IN SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema AND table_name = PAR_table LOOP
IF VAR_query <> '' THEN
VAR_query := VAR_query || ' || '','' || ';
END IF;
VAR_query := VAR_query || ' CASE WHEN ' || REC_columns.column_name || ' IS NULL THEN ''null'' ELSE ' || REC_columns.column_name || '::text END';
END LOOP;
VAR_query := 'SELECT ' || VAR_query || ' FROM ' || PAR_table::regclass || ' ' || PAR_where_clause;
RETURN QUERY EXECUTE VAR_query;
END;
$$ LANGUAGE plpgsql;
Usage:
SELECT * FROM GetCommaSeparatedValues('table1');
or
SELECT * FROM GetCommaSeparatedValues('table2', 'WHERE id = 1');
you can use string_agg to achieve above, for ex :
SELECT string_agg(id, ',') FROM table
Ref : http://www.craigkerstiens.com/2013/04/17/array-agg/
The problem about doing this directly on a SELECT query is that it lead to some problems, the principal is about escapes. For instance, if you have a comma inside one of the record strings, how would you treat that?
Of course you could parse the strings and escape it, but the most common (and recommended) approach is to use the well-known CSV format. And PostgreSQL already gives you a result that way using the COPY command, so you don't have to reinvent the wheel:
COPY (SELECT * FROM master_table WHERE id = 1) TO stdout WITH CSV;
If you like to get csv formatted data from psql shell, just modify some psql option
You can do it using below command
\a: toggle between unaligned and aligned output mode
\f [STRING]: show or set field separator for unaligned query output
also you can save query results by below command
\o [FILE]: send all query results to file or |pipe
Example
postgres=# select * from master_table;
id | string | number
----+--------+---------
1 | piyush | 1111111
2 | john | 2222222
(2 rows)
postgres=# \a
Output format is unaligned.
postgres=# \f ,
Field separator is ",".
postgres=# select * from master_table;
no,name,phone
1,piyush,1111111
2,john,2222222
(2 rows)
postgres=# \o result.csv
postgres=# select * from master_table; => query result is saved in 'result.csv'
postgres=#
I want to write a PLSQL stored procedure that accepts a table name as argument. This table is source table. Now inside my procedure i want to manipulate the fields of that table.
EX: I want to insert the records of this source table into another target table whose name is XYZ_<source table name>. The column names for source and target tables are the same. But there may be some extra fields in target table. How do i do it? The order of column names is not same.
You will have to build the INSERT statement dynamically.
create or replace procedure gen_insert
(p_src_table in user_tables.table_name%type
, p_no_of_rows out pls_integer)
is
col_str varchar2(16000);
begin
for rec in ( select column_name
, column_id
from user_tab_columns
where table_name = p_src_table
order by column_id )
loop
if rec.column_id != 1 then
col_str := col_str || ',' || rec.column_name;
else
col_str := rec.column_name;
end if:
end loop;
execute immediate 'insert into xyz_' || p_src_table || '('
|| col_str || ')'
|| ' select ' || col_str
|| ' from ' || p_src_table;
p_no_of_rows := sql%rowcount;
end;
/
Obviously you may want to include some error handling and other improvements.
edit
Having edited your question I see you have a special requirement for naming the target table which was obscured by the SO formatting.
You can do this using Dynamic SQL. Here's a link with basic info on Oracle Dynamic SQL