Dropping multiple columns: PLSQL and user_tab_cols - oracle

I have a table TABLE_X and it has multiple columns beginning with M_ characters which needs to be dropped. I decided to use the following PLSQL code to drop almost 100 columns beginning with M_ characters. Is it a good employment of dynamic sql and cursors? Can it be better? I didn't know more simple way since ALTER TABLE ... DROP COLUMN doesn't allow subquery to specify multiple column names.
declare
rcur sys_refcursor;
cn user_tab_cols.column_name%type;
begin
open rcur for select column_name from user_tab_cols where table_name='TABLE_X' and column_name LIKE 'M_%';
loop
fetch rcur into cn;
exit when rcur%NOTFOUND;
execute immediate 'alter table TABLE_X drop column '||cn;--works great
execute immediate 'alter table TABLE_X drop column :col'using cn;--error
end loop;
close rcur;
end;
Also. Why is it impossible to use 'using cn'?

This is a reasonable use of dynamic SQL. I would seriously question an underlying data model that has hundreds of columns in a single table that start with the same prefix and all need to be dropped. That implies to me that the data model itself is likely to be highly problematic.
Even using dynamic SQL, you cannot use bind variables for column names, table names, schema names, etc. Oracle needs to know at parse time what objects and columns are involved in a SQL statement. Since bind variables are supplied after the parse phase, however, you cannot specify a bind variable that changes what objects and/or columns a SQL statement is affecting.

The syntax for dropping multiple columns in a single alter statement is this:
SQL> desc t42
Name Null? Type
----------------------------------------- -------- ----------------------
COL1 NUMBER
COL2 DATE
COL3 VARCHAR2(30)
COL4 NUMBER
SQL> alter table t42 drop (col2, col3)
2 /
Table altered.
SQL> desc t42
Name Null? Type
----------------------------------------- -------- ----------------------
COL1 NUMBER
COL4 NUMBER
SQL>
So, if you really need to optimize the operation, you'll need to build up the statement incrementally - or use a string aggregation technique.
However, I would question whether you ought to be running a statement like this often enough to need to optimize it.

Related

How to rename multiple columns in oracle using one Alter table statement?

The only thing I found is renaming one column at a time:
ALTER TABLE table_name
RENAME COLUMN old_name TO new_name;
I read Oracle documentations, and couldn't get the answer for many columns at a time .
Ref: https://docs.oracle.com/javadb/10.6.2.1/ref/rrefsqljrenamecolumnstatement.html
It is not possible to rename multiple table columns in a single command, as of Oracle 18c.
The Oracle 18c SQL Language Reference includes the below diagram to illustrate how the RENAME_COLUMN_CLAUSE of the ALTER TABLE command works. Unfortunately, almost every column property can be modified in groups, except for renaming.
You can use user_tab_columns dictionary view as a data source within a cursor for a loop statement
declare
v_table_name varchar2(40):='mytable';
begin
for c in ( select from user_tab_columns where table_name = upper(v_table_name) )
loop
execute immediate ('ALTER TABLE '||c.table_name||' RENAME COLUMN '||c.column_name
||' TO new_'||c.column_name);
end loop;
end;

How to merge two or more unknown tables into one table in Oracle

I'm trying to merge x number of identical tables into one table. The reason we did this is because we want to have, for example 50 columns per table in the database. Tables are created externally via SCADA software called Ignition.
Every time table is created in the database, we want to view the data as one regardless of how many tables the data came from provided that all tables will have the same first three letters for example, Table_1, Table_2, Table_3....so on.
The query/procedure we want to have is like:
step 1: since the tables are unknown we can't do it by simple union, merge insert etc., so we must find all table_name with 'Table' prefix.
SELECT table_name FROM all_tables where table_name like 'Table%'
step 2: this is where the magic begins, it should query one by one each listed table_name in first step, then collect all the data and merge into one table or view.
I tried many ways using PL/SQL but don't know how to proceed with step 2. Is there any way to get what we want to achieve? any possible solutions would be great! :)
Thanks!
Assuming that you are selecting only the common columns from all the tables, you could create a dynamic view, which does a UNION ALL of all the tables starting with "Table" prefix.
DECLARE
v_select CLOB;
BEGIN
SELECT
LISTAGG('SELECT col1,col2,col3 FROM ' || table_name,
' UNION ALL ' || CHR(10) ) WITHIN GROUP
(
ORDER BY table_name
)
INTO v_select
FROM user_tables WHERE table_name LIKE 'TABLE_%';
IF
v_select IS NOT NULL
THEN
EXECUTE IMMEDIATE ' CREATE OR REPLACE VIEW v_all_tabs as ' || v_select;
END IF;
END;
/
Then select from the view by executing the above block(or put it into a procedure ) each time there's a new table added.
select * from v_all_tabs;
If there's a chance of your SQL string exceeding 4000 characters, instead of a single LISTAGG, you could append each select through a simple assignment in PL/SQL in a cursor loop.

How to find the column used in the dynamic query without executing whole query

Problem Statement
I have a dynamic SQL which i need to store in a table ,but before
storing the sql i need to validate the sql with the list of columns
stored in another table.
Without executing the query , is it possible to find name of columns in the select ?
Approach1
Only option i can think of is ,try to use explain plan of the query and read the meta data in the data dictionaries table .But unfortunately i am not able to find any table with such data.Please let me know if you know such views?
Approach2
Use DBMS_SQL.DESCRIBE_COLUMNS package to find the column name ,but i believe this will execute the whole query.
You don't need to execute the query to get the column names, you just need to parse it; e.g. as a simple example:
set serveroutput on
declare
l_statement varchar2(4000) := 'select * from employees';
l_c pls_integer;
l_col_cnt pls_integer;
l_desc_t dbms_sql.desc_tab;
begin
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c=>l_c, statement=>l_statement, language_flag=>dbms_sql.native);
dbms_sql.describe_columns(c=>l_c, col_cnt=>l_col_cnt, desc_t=>l_desc_t);
for i in 1..l_col_cnt loop
dbms_output.put_line(l_desc_t(i).col_name);
end loop;
dbms_sql.close_cursor(l_c);
exception
when others then
if (dbms_sql.is_open(l_c)) then
dbms_sql.close_cursor(l_c);
end if;
raise;
end;
/
which outputs:
EMPLOYEE_ID
FIRST_NAME
LAST_NAME
EMAIL
PHONE_NUMBER
HIRE_DATE
JOB_ID
SALARY
COMMISSION_PCT
MANAGER_ID
DEPARTMENT_ID
PL/SQL procedure successfully completed.
You can do whatever validation you need on the column names inside the loop.
Bear in mind that you'll only see (and validate) the column names or aliases for column expressions, which won't necessarily reflect the data that is actually being retrieved. Someone could craft a query that pulls any data from anywhere it has permission to access, but then gives the columns/expression aliases that are considered valid.
If you're trying to restrict access to specific data then look into other mechanisms like views, virtual private database, etc.
DBMS_SQL.PARSE will not execute a SELECT statement but it will execute a DDL statement. If the string 'select * from employees' is replaced by 'drop table employees' the code will fail but the table will still get dropped.
If you're only worried about the performance of retrieving the metadata then Alex Poole's answer will work fine.
If you're worried about running the wrong statement types then you'll want to make some adjustments to Alex Poole's answer.
It is surprisingly difficult to tell if a statement is a SELECT instead of something else. A simple condition checking that the string begins with select will work 99% of the time but getting from 99% to 100% is a huge amount of work. Simple regular expressions cannot keep up with all the different keywords, comments, alternative quoting format, spaces, etc.
/*comment in front -- */ select * from dual
select * from dual
with asdf as (select * from dual) select * from asdf;
((((((select * from dual))))));
If you need 100% accuracy I recommend you use my open source PLSQL_LEXER. Once installed you can reliably test the command types like this:
select
statement_classifier.get_command_name(' /*comment*/ ((select * from dual))') test1,
statement_classifier.get_command_name('alter table asdf move compress') test2
from dual;
TEST1 TEST2
----- -----
SELECT ALTER TABLE

Combining Multiple Statements in a Stored Procedure

I'm new to Oracle, what I'm trying to achieve I could in SQL but I'm having a tough time in doing it in Oracle.
So within a Stored Procedure, I'm trying to truncate a table, then insert values, lastly run a SELECT statement against the table.
Here is what I have but it doesn't work, when I run this script, it runs with no error but it seems it only goes through the first (TRUNCATE) statement and that's it.
I would like it create the Store Procedure (which it does) and then show me the contents of the table from the SELECT statement.
CREATE OR REPLACE procedure MYSTOREDPROCEDURE is
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';
INSERT INTO MYTABLE
(COL1,
COL2,
COL3)
SELECT COL1, COL2, COL3 FROM MYOTHERTABLE;
end ;
/
SELECT * FROM MYTABLE
END MYSTOREDPROCEDURE;
For clarification SQL is a language implmemented by many RDBMS including SQL Server, PostgreSQL etc. If you're doing this in Oracle you are using SQL. However, most RDBMS have also added a procedural extension to SQL such as T-SQL (SQL Server), pgPL/SQL (PostgreSQL) and PL/SQL (Oracle). In this case you're attempting to use PL/SQL.
From what you're attempting to do I assume you're used to SQL Server and temporary tables. It is less common and less necessary to use temporary tables in Oracle.
Firstly, EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';. It should rarely be necessary to perform DDL in a stored procedure in Oracle; it would normally be indicative of a flaw in the data-model. In this case it appears as though you're using an actual table as a temporary table. I wouldn't do this. If you need a temporary table use a global temporary table.
Secondly, SELECT * FROM MYTABLE. You can't do this in PL/SQL. If you need to select some data you have to use SELECT <columns> INTO <variables> FROM .... If you do this it won't show you the contents of the table.
From your description of what you're attempting you only need to do the following:
SELECT COL1, COL2, COL3 FROM MYOTHERTABLE;
There is no need for PL/SQL (stored procedures) at all.
I suspect you intended to execute the SELECT * FROM MYTABLE after the procedure was compiled and executed. The above could be restrucured as:
CREATE OR REPLACE procedure MYSTOREDPROCEDURE is
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE MYTABLE';
INSERT INTO MYTABLE (COL1, COL2, COL3)
SELECT COL1, COL2, COL3
FROM MYOTHERTABLE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error in MYSTOREDPROCEDURE : ' || SQLCODE || ' - ' || SQLERRM);
RAISE;
END MYSTOREDPROCEDURE;
/
EXECUTE MYSTOREDPROCEDURE
/
SELECT * FROM MYTABLE
/
Share and enjoy.
My DBA won't let me have TRUNCATE privileges even though truncate removes high water marks compared to delete MYTABLE; Your code will fail without these privileges. Both #Ben and #Bob Jarvis provide sound advice with the missing INTO and nicer form. One addition to Bob Jarvis' answer is that you need to start the command with SET SERVEROUTPUT ON SIZE 100000;. Otherwise, his nicely crafted error message will not be displayed. The serveroutput command is used for DBMS_OUTPUT and only lasts as long as your database session lasts.

CLOB vs. VARCHAR2 and are there other alternatives?

I am using DevArt's dotConnect and Entity Developer for my application. I've created the tables using the Entity-First feature.
I notice that many of the column types are set to CLOB. I only have experience with MySQL and Microsoft SQL server, so I am not sure about using CLOB for the application. I did some reading, and found out that CLOB are for large chunk of data.
The questions are:
Is using CLOB for most fields, such as the user's gender (which should be a varchar (1) ) or the full name, feasible? The steps for converting a CLOB field to VARCHAR2 requires dropping the column then re-creating it, and is buggy in DevArt's Entity Explorer, so I would like avoid it if possible. Edit: I just found out that if you set a maximum length for a string field it'll automatically be a VARCHAR2.
Are there any equivalents for TINYTEXT in Oracle?
It is a very bad idea to use a CLOB datatype for a column which ought to be VARCHAR2(1). Apart from the overheads (which are actually minimal, as Oracle will treat inline CLOBs of < 4000 characters as VARCHAR2) we should always strive to use the most accurate representation of our data in the schema: it's just good practice.
This really seems like a problem with the DevArt tool, or perhaps your understanding of how to use it (no offence). There ought to be some way for you to specify the datatype of an entity's attribute and/or a way of mapping those specifications to Oracle's physical datatypes. I apologise if this seems a little vague, I'm not familar with the product.
So, this is the basic problem:
SQL> desc t69
Name Null? Type
----------------------------------------- -------- --------
COL1 CLOB
SQL>
SQL> alter table t69 modify col1 varchar2(1)
2 /
alter table t69 modify col1 varchar2(1)
*
ERROR at line 1:
ORA-22859: invalid modification of columns
SQL>
We can fix it by using DDL to alter the table structure. Because the schema has many such columns it is worthwhile automating the process. This function drops the existing column and recreates it as a VARCHAR2. It offers the option to migrate data in the CLOB column to the VARCHAR2 column; you probably don't need this, but it's there for completeness. (This is not production quality code - it needs error handling, managing NOT NULL constraints, etc)
create or replace procedure clob2vc
( ptab in user_tables.table_name%type
, pcol in user_tab_columns.column_name%type
, pcol_size in number
, migrate_data in boolean := true )
is
begin
if migrate_data
then
execute immediate 'alter table '||ptab
||' add tmp_col varchar2('|| pcol_size|| ')';
execute immediate
'update '||ptab
||' set tmp_col = substr('||pcol||',1,'||pcol_size||')';
end if;
execute immediate 'alter table '||ptab
||' drop column '|| pcol;
if migrate_data
then
execute immediate 'alter table '||ptab
||' rename column tmp_col to '|| pcol;
else
execute immediate 'alter table '||ptab
||' add '||pcol||' varchar2('|| pcol_size|| ')';
end if;
end;
/
So, let's change that column...
SQL> exec clob2vc ('T69', 'COL1', 1)
PL/SQL procedure successfully completed.
SQL> desc t69
Name Null? Type
----------------------------------------- -------- ---------------
COL1 VARCHAR2(1)
SQL>
Calling this procedure can be automated or scripted in the usual ways.
Using a CLOB for something like a Gender column would, at a minimum, be extremely unusual. If the DDL this tool generates specifies that the LOB data should be stored inline rather than out of line, I wouldn't expect to be any horrible performance issues. But you probably will create problems for other tools accessing the database that don't handle LOBs particularly well.
There is no equivalent in Oracle to Tinytext in MySQL. A CLOB is a CLOB.
A simpler solution is to go to Model Explorer -> Model.Store -> Tables/Views, find the necessary column and change the type of this field to VARCHAR2.
Then run the Update Database from Model wizard to persist the changes to the database.
Don't forget to set the MaxLength facet (however, the problem with it is already fixed in the upcoming Beta build).

Resources