I am trying to write a query that will provide all non UTF-8 encoded characters in a table, that is not specific to a column name. I am doing so by comparing the length of a column not equal to the byte length. %1 is the table name I want to check entered in a parameter. I am joining to user_tab_columns to get the COLUMN_NAME. I then want to take the COLUMN_NAME results and filter down to only show rows that have bad UTF-8 data (where length of a column is not equal to the byte length). Below is what I have come up with but it's not functioning. Can somebody help me tweak this query to get desired results?
SELECT
user_tab_columns.TABLE_NAME,
user_tab_columns.COLUMN_NAME AS ColumnName,
a.*
FROM %1 a
JOIN user_tab_columns
ON UPPER(user_tab_columns.TABLE_NAME) = UPPER('%1')
WHERE (SELECT * FROM %1 WHERE LENGTH(a.ColumnName) != LENGTHB(a.ColumnName))
In your query LENGTH(a.ColumnName) would represent the length of the column name, not the contents of that column. You can't use a value from one table as the column name in another table in static SQL.
Here's a simple demonstration of using dynamic SQL in an anonymous block to report which columns contain any multibyte characters, which is what comparing length with lengthb will tell you (discussed in comments to not rehashing that here):
set serveroutput on size unlimited
declare
sql_str varchar2(256);
flag pls_integer;
begin
for rec in (
select utc.table_name, utc.column_name
from user_tab_columns utc
where utc.table_name = <your table name or argument>
and utc.data_type in ('VARCHAR2', 'NVARCHAR2', 'CLOB', 'NCLOB')
order by utc.column_id
) loop
sql_str := 'select nvl(max(1), 0) from "' || rec.table_name || '" '
|| 'where length("' || rec.column_name || '") '
|| '!= lengthb("' || rec.column_name || '") and rownum = 1';
-- just for debugging, to see the generated query
dbms_output.put_line(sql_str);
execute immediate sql_str into flag;
-- also for debugging
dbms_output.put_line (rec.table_name || '.' || rec.column_name
|| ' flag: ' || flag);
if flag = 1 then
dbms_output.put_line(rec.table_name || '.' || rec.column_name
|| ' contains multibyte characters');
end if;
end loop;
end;
/
This uses a cursor loop to get the column names - I've included the table name too in case you want to wild-card or remove the filter - and inside that loop constructs a dynamic SQL statement, executes it into a variable, and then checks that variable. I've left some debugging output in to see what's happening. With a dummy table created as:
create table t42 (x varchar2(20), y varchar2(20));
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'single byte');
insert into t42 values ('single byte test', 'multibyte ' || unistr('\00FF'));
running that block gets the output:
anonymous block completed
select nvl(max(1), 0) from "T42" where length("X") != lengthb("X") and rownum = 1
T42.X flag: 0
select nvl(max(1), 0) from "T42" where length("Y") != lengthb("Y") and rownum = 1
T42.Y flag: 1
T42.Y contains multibyte characters
To display the actual multibyte-containing values you could use a dynamic loop over the selected values:
set serveroutput on size unlimited
declare
sql_str varchar2(256);
curs sys_refcursor;
val_str varchar(4000);
begin
for rec in (
select utc.table_name, utc.column_name
from user_tab_columns utc
where utc.table_name = 'T42'
and utc.data_type in ('VARCHAR2', 'NVARCHAR2', 'CLOB', 'NCLOB')
order by utc.column_id
) loop
sql_str := 'select "' || rec.column_name || '" '
|| 'from "' || rec.table_name || '" '
|| 'where length("' || rec.column_name || '") '
|| '!= lengthb("' || rec.column_name || '")';
-- just for debugging, to see the generated query
dbms_output.put_line(sql_str);
open curs for sql_str;
loop
fetch curs into val_str;
exit when curs%notfound;
dbms_output.put_line (rec.table_name || '.' || rec.column_name
|| ': ' || val_str);
end loop;
end loop;
end;
/
Which with the same table gets:
anonymous block completed
select "X" from "T42" where length("X") != lengthb("X")
select "Y" from "T42" where length("Y") != lengthb("Y")
T42.Y: multibyte ΓΏ
As a starting point anyway; it would need some tweaking if you have CLOB values, or NVARCHAR2 or NCLOB - for example you could have one local variable of each type, include the data type in the outer cursor query, and fetch into the appropriate local variable.
Related
I would like to display results of dynamic sql based on antoher sql, but get error message. My code:
DECLARE
sql_qry VARCHAR2(1000) := NULL;
TYPE results IS
TABLE OF all_tab_columns%rowtype;
results_tbl results;
BEGIN
FOR i IN (
SELECT
*
FROM
all_tab_columns
WHERE
owner = 'OWNER_XYZ'
AND upper(column_name) LIKE '%COLUMN_XYZ%'
ORDER BY
table_name,
column_name
) LOOP
sql_qry := ' SELECT DISTINCT '
|| i.column_name
|| ' as column_name '
|| ' FROM '
|| i.owner
|| '.'
|| i.table_name
|| ' WHERE SUBSTR('
|| i.column_name
|| ',1,1) = ''Y''';
EXECUTE IMMEDIATE sql_qry BULK COLLECT
INTO results_tbl;
dbms_output.put_line(results_tbl);
END LOOP;
END;
I get the error message:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
In fact I need the results of all queries with an union between them like that
[1] [1]: https://i.stack.imgur.com/llxzr.png
Your dynamic query is selecting a single column, but you are bulk collecting into results_tbl, which is of type results, based on all_tab_columns%rowtype - which has lots of columns.
If you define your collection type as a single column of the data type you need, i.e. some length of string:
DECLARE
sql_qry VARCHAR2(1000) := NULL;
TYPE results IS
TABLE OF varchar2(4000);
results_tbl results;
...
You will then bulk collect a single column into that single-column collection. To display the results you need to loop over the collection:
FOR j IN 1..results_tbl.COUNT loop
dbms_output.put_line(results_tbl(j));
END LOOP;
so the whole block becomes:
DECLARE
sql_qry VARCHAR2(1000) := NULL;
TYPE results IS
TABLE OF varchar2(4000);
results_tbl results;
BEGIN
FOR i IN (
SELECT
*
FROM
all_tab_columns
WHERE
owner = 'OWNER_XYZ'
AND upper(column_name) LIKE '%COLUMN_XYZ%'
ORDER BY
table_name,
column_name
) LOOP
sql_qry := ' SELECT DISTINCT '
|| i.column_name
|| ' as column_name '
|| ' FROM '
|| i.owner
|| '.'
|| i.table_name
|| ' WHERE SUBSTR('
|| i.column_name
|| ',1,1) = ''Y''';
EXECUTE IMMEDIATE sql_qry BULK COLLECT
INTO results_tbl;
FOR j IN 1..results_tbl.COUNT loop
dbms_output.put_line(results_tbl(j));
END LOOP;
END LOOP;
END;
/
However, you can also do this without PL/SQL, using a variation on an XML trick. You can get the results of the dynamic query as an XML document using something like:
select dbms_xmlgen.getxmltype(
'select distinct "' || column_name || '" as value'
|| ' from "' || owner || '"."' || table_name || '"'
|| ' where substr("' || column_name || '", 1, 1) = ''Y'''
)
from all_tab_columns
where owner = 'OWNER_XYZ'
and upper(column_name) like '%COLUMN_XYZ%';
and then extract the value you want from that:
with cte (xml) as (
select dbms_xmlgen.getxmltype(
'select distinct "' || column_name || '" as value'
|| ' from "' || owner || '"."' || table_name || '"'
|| ' where substr("' || column_name || '", 1, 1) = ''Y'''
)
from all_tab_columns
where owner = 'OWNER_XYZ'
and upper(column_name) like '%COLUMN_XYZ%'
)
select x.value
from cte
cross apply xmltable(
'/ROWSET/ROW'
passing cte.xml
columns value varchar2(4000) path 'VALUE'
) x;
You can also easily include the table each value came from if you want that information (and the owner, and actual column name, etc.).
db<>fiddle showing both approaches.
How to add a new column in a table after the 2nd or 3rd column in the table using postgres?
My code looks as follows
ALTER TABLE n_domains ADD COLUMN contract_nr int after owner_id
No, there's no direct way to do that. And there's a reason for it - every query should list all the fields it needs in whatever order (and format etc) it needs them, thus making the order of the columns in one table insignificant.
If you really need to do that I can think of one workaround:
dump and save the description of the table in question (using pg_dump --schema-only --table=<schema.table> ...)
add the column you want where you want it in the saved definition
rename the table in the saved definition so not to clash with the name of the old table when you attempt to create it
create the new table using this definition
populate the new table with the data from the old table using 'INSERT INTO <new_table> SELECT field1, field2, <default_for_new_field>, field3,... FROM <old_table>';
rename the old table
rename the new table to the original name
eventually drop the old, renamed table after you make sure everything's alright
The order of columns is not irrelevant, putting fixed width columns at the front of the table can optimize the storage layout of your data, it can also make working with your data easier outside of your application code.
PostgreSQL does not support altering the column ordering (see Alter column position on the PostgreSQL wiki); if the table is relatively isolated, your best bet is to recreate the table:
CREATE TABLE foobar_new ( ... );
INSERT INTO foobar_new SELECT ... FROM foobar;
DROP TABLE foobar CASCADE;
ALTER TABLE foobar_new RENAME TO foobar;
If you have a lot of views or constraints defined against the table, you can re-add all the columns after the new column and drop the original columns (see the PostgreSQL wiki for an example).
The real problem here is that it's not done yet. Currently PostgreSQL's logical ordering is the same as the physical ordering. That's problematic because you can't get a different logical ordering, but it's even worse because the table isn't physically packed automatically, so by moving columns you can get different performance characteristics.
Arguing that it's that way by intent in design is pointless. It's somewhat likely to change at some point when an acceptable patch is submitted.
All of that said, is it a good idea to rely on the ordinal positioning of columns, logical or physical? Hell no. In production code you should never be using an implicit ordering or *. Why make the code more brittle than it needs to be? Correctness should always be a higher priority than saving a few keystrokes.
As a work around, you can in fact modify the column ordering by recreating the table, or through the "add and reorder" game
See also,
Column tetris reordering in order to make things more space-efficient
The column order is relevant to me, so I created this function. See if it helps. It works with indexes, primary key, and triggers. Missing Views and Foreign Key and other features are missing.
Example:
SELECT xaddcolumn('table', 'col3 int NOT NULL DEFAULT 0', 'col2');
Source code:
CREATE OR REPLACE FUNCTION xaddcolumn(ptable text, pcol text, pafter text) RETURNS void AS $BODY$
DECLARE
rcol RECORD;
rkey RECORD;
ridx RECORD;
rtgr RECORD;
vsql text;
vkey text;
vidx text;
cidx text;
vtgr text;
ctgr text;
etgr text;
vseq text;
vtype text;
vcols text;
BEGIN
EXECUTE 'CREATE TABLE zzz_' || ptable || ' AS SELECT * FROM ' || ptable;
--colunas
vseq = '';
vcols = '';
vsql = 'CREATE TABLE ' || ptable || '(';
FOR rcol IN SELECT column_name as col, udt_name as coltype, column_default as coldef,
is_nullable as is_null, character_maximum_length as len,
numeric_precision as num_prec, numeric_scale as num_scale
FROM information_schema.columns
WHERE table_name = ptable
ORDER BY ordinal_position
LOOP
vtype = rcol.coltype;
IF (substr(rcol.coldef,1,7) = 'nextval') THEN
vtype = 'serial';
vseq = vseq || 'SELECT setval(''' || ptable || '_' || rcol.col || '_seq'''
|| ', max(' || rcol.col || ')) FROM ' || ptable || ';';
ELSIF (vtype = 'bpchar') THEN
vtype = 'char';
END IF;
vsql = vsql || E'\n' || rcol.col || ' ' || vtype;
IF (vtype in ('varchar', 'char')) THEN
vsql = vsql || '(' || rcol.len || ')';
ELSIF (vtype = 'numeric') THEN
vsql = vsql || '(' || rcol.num_prec || ',' || rcol.num_scale || ')';
END IF;
IF (rcol.is_null = 'NO') THEN
vsql = vsql || ' NOT NULL';
END IF;
IF (rcol.coldef <> '' AND vtype <> 'serial') THEN
vsql = vsql || ' DEFAULT ' || rcol.coldef;
END IF;
vsql = vsql || E',';
vcols = vcols || rcol.col || ',';
--
IF (rcol.col = pafter) THEN
vsql = vsql || E'\n' || pcol || ',';
END IF;
END LOOP;
vcols = substr(vcols,1,length(vcols)-1);
--keys
vkey = '';
FOR rkey IN SELECT constraint_name as name, column_name as col
FROM information_schema.key_column_usage
WHERE table_name = ptable
LOOP
IF (vkey = '') THEN
vkey = E'\nCONSTRAINT ' || rkey.name || ' PRIMARY KEY (';
END IF;
vkey = vkey || rkey.col || ',';
END LOOP;
IF (vkey <> '') THEN
vsql = vsql || substr(vkey,1,length(vkey)-1) || ') ';
END IF;
vsql = substr(vsql,1,length(vsql)-1) || ') WITHOUT OIDS';
--index
vidx = '';
cidx = '';
FOR ridx IN SELECT s.indexrelname as nome, a.attname as col
FROM pg_index i LEFT JOIN pg_class c ON c.oid = i.indrelid
LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
LEFT JOIN pg_stat_user_indexes s USING (indexrelid)
WHERE c.relname = ptable AND i.indisunique != 't' AND i.indisprimary != 't'
ORDER BY s.indexrelname
LOOP
IF (ridx.nome <> cidx) THEN
IF (vidx <> '') THEN
vidx = substr(vidx,1,length(vidx)-1) || ');';
END IF;
cidx = ridx.nome;
vidx = vidx || E'\nCREATE INDEX ' || cidx || ' ON ' || ptable || ' (';
END IF;
vidx = vidx || ridx.col || ',';
END LOOP;
IF (vidx <> '') THEN
vidx = substr(vidx,1,length(vidx)-1) || ')';
END IF;
--trigger
vtgr = '';
ctgr = '';
etgr = '';
FOR rtgr IN SELECT trigger_name as nome, event_manipulation as eve,
action_statement as act, condition_timing as cond
FROM information_schema.triggers
WHERE event_object_table = ptable
LOOP
IF (rtgr.nome <> ctgr) THEN
IF (vtgr <> '') THEN
vtgr = replace(vtgr, '_#eve_', substr(etgr,1,length(etgr)-3));
END IF;
etgr = '';
ctgr = rtgr.nome;
vtgr = vtgr || 'CREATE TRIGGER ' || ctgr || ' ' || rtgr.cond || ' _#eve_ '
|| 'ON ' || ptable || ' FOR EACH ROW ' || rtgr.act || ';';
END IF;
etgr = etgr || rtgr.eve || ' OR ';
END LOOP;
IF (vtgr <> '') THEN
vtgr = replace(vtgr, '_#eve_', substr(etgr,1,length(etgr)-3));
END IF;
--exclui velha e cria nova
EXECUTE 'DROP TABLE ' || ptable;
IF (EXISTS (SELECT sequence_name FROM information_schema.sequences
WHERE sequence_name = ptable||'_id_seq'))
THEN
EXECUTE 'DROP SEQUENCE '||ptable||'_id_seq';
END IF;
EXECUTE vsql;
--dados na nova
EXECUTE 'INSERT INTO ' || ptable || '(' || vcols || ')' ||
E'\nSELECT ' || vcols || ' FROM zzz_' || ptable;
EXECUTE vseq;
EXECUTE vidx;
EXECUTE vtgr;
EXECUTE 'DROP TABLE zzz_' || ptable;
END;
$BODY$ LANGUAGE plpgsql VOLATILE COST 100;
#Jeremy Gustie's solution above almost works, but will do the wrong thing if the ordinals are off (or fail altogether if the re-ordered ordinals make incompatible types match). Give it a try:
CREATE TABLE test1 (one varchar, two varchar, three varchar);
CREATE TABLE test2 (three varchar, two varchar, one varchar);
INSERT INTO test1 (one, two, three) VALUES ('one', 'two', 'three');
INSERT INTO test2 SELECT * FROM test1;
SELECT * FROM test2;
The results show the problem:
testdb=> select * from test2;
three | two | one
-------+-----+-------
one | two | three
(1 row)
You can remedy this by specifying the column names in the insert:
INSERT INTO test2 (one, two, three) SELECT * FROM test1;
That gives you what you really want:
testdb=> select * from test2;
three | two | one
-------+-----+-----
three | two | one
(1 row)
The problem comes when you have legacy that doesn't do this, as I indicated above in my comment on peufeu's reply.
Update: It occurred to me that you can do the same thing with the column names in the INSERT clause by specifying the column names in the SELECT clause. You just have to reorder them to match the ordinals in the target table:
INSERT INTO test2 SELECT three, two, one FROM test1;
And you can of course do both to be very explicit:
INSERT INTO test2 (one, two, three) SELECT one, two, three FROM test1;
That gives you the same results as above, with the column values properly matched.
The order of the columns is totally irrelevant in relational databases
Yes.
For instance if you use Python, you would do :
cursor.execute( "SELECT id, name FROM users" )
for id, name in cursor:
print id, name
Or you would do :
cursor.execute( "SELECT * FROM users" )
for row in cursor:
print row['id'], row['name']
But no sane person would ever use positional results like this :
cursor.execute( "SELECT * FROM users" )
for id, name in cursor:
print id, name
Well, it's a visual goody for DBA's and can be implemented to the engine with minor performance loss. Add a column order table to pg_catalog or where it's suited best. Keep it in memory and use it before certain queries. Why overthink such a small eye candy.
# Milen A. Radev
The irrelevant need from having a set order of columns is not always defined by the query that pulls them. In the values from pg_fetch_row does not include the associated column name and therefore would require the columns to be defined by the SQL statement.
A simple select * from would require innate knowledge of the table structure, and would sometimes cause issues if the order of the columns were to change.
Using pg_fetch_assoc is a more reliable method as you can reference the column names, and therefore use a simple select * from.
I am trying to generate a Create Table Statement. To do this, I have created 2 procedures to Extract Tables and Columns from my Schema. The Output of these procedures is then spooled to a SQL file and is supposed to look like this:
The Formatted Output
Right now, My output Looks like this:
----
---- Run on October 04, 2020 at 22:00
----
-- Start Extracting table IMAGE
CREATE TABLE IMAGE (
MFR CHAR(3,)
, PRODUCT CHAR(5,)
, IMAGE BLOB(4000,)
, TECHSPECS BFILE(530,)
);-- END of Table IMAGE creation
--
--
-- Start Extracting table ORDERS
CREATE TABLE ORDERS (
ORDERNUM NUMBER(227,0)
, ORDERDATE DATE(7,)
, CUST NUMBER(223,0)
, REP NUMBER(223,0)
, MANUF CHAR(3,)
, PROD CHAR(5,)
, QTY NUMBER(225,0)
, AMOUNT NUMBER(225,2)
);-- END of Table ORDERS creation
--
--
-- Start Extracting table PRODUCTS
CREATE TABLE PRODUCTS (
MFR CHAR(3,)
, PRODUCT CHAR(5,)
, DESCRIPTION VARCHAR2(100,)
, PRICE NUMBER(225,2)
, QTYONHAND NUMBER(225,0)
);-- END of Table PRODUCTS creation
--
--
---- Oracle Catalog Extract Utility V1.0 ----
---- Run on October 04, 2020 at 22:00
As you can see, the Data types, along with their Data_length, Data_scale, and DAta_precision are not perfectly formatted as per the requirements. I also haven't been able to make a column that displays the null status of each column. The Date, BFILE, and BLOB as shown in the Output are especially troublesome for me to format properly.
This is my code as of now:
SET ECHO OFF
SET FEEDBACK ON
SET WRAP OFF
--The Procedure to Extract The Columns
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE Extract_Columns (
--Creating Variables of passed values
wSee IN OUT varchar2,
wTable IN USER_TABLES.table_name%type
)
AS
--The Cursor to run through the Columns
CURSOR Extract_C IS
SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_SCALE, DATA_LENGTH
FROM USER_TAB_COLUMNS
WHERE TABLE_NAME = wTable
ORDER BY Column_ID;
CurrentRow Extract_C%ROWTYPE;
--Creating variables
wItterations NUMBER(1):=0;
BEGIN
FOR CurrentRow IN Extract_C LOOP
IF wItterations = 0 THEN
wsee := wsee || CHR(10) || ' ' || RPAD(currentrow.column_name, 15) || currentrow.data_type || '(' || currentrow.DATA_LENGTH || currentrow.DATA_SCALE || ')';
ELSE
wsee := wsee || CHR(10) || ', ' || RPAD(currentrow.column_name, 15) || currentrow.data_type || '(' || currentrow.DATA_LENGTH || currentrow.DATA_PRECISION || ',' || currentRow.DATA_SCALE || ')';
END IF;
wItterations:= wItterations +1;
END LOOP;
END;
/
SHOW ERRORS;
SET SERVEROUTPUT ON
CREATE OR REPLACE PROCEDURE Extract_Tables
AS
l_crt varchar2(356);
CURSOR Extract_T IS
SELECT TABLE_NAME
FROM USER_TABLES
ORDER BY TABLE_NAME;
CurrentRow Extract_T%ROWTYPE;
--Creating Variables
wvers VARCHAR2(256) := 'V1.0';
wcur_Tim VARCHAR2(200) := '' || TO_CHAR( CURRENT_DATE, 'Month DD, YYYY') || ' at ' || To_CHAR(CURRENT_DATE, 'HH24:MI');
wheader VARCHAR2(45) := 'CREATE TABLE ' || CurrentRow.table_name ||' (';
wspacing NUMBER := length(wheader);
BEGIN
DBMS_OUTPUT.PUT_LINE('---- Oracle Catalog Extract Utility ' || wvers || ' ----');
DBMS_OUTPUT.PUT_LINE('----');
DBMS_OUTPUT.PUT_LINE('---- Run on ' || wcur_Tim);
DBMS_OUTPUT.PUT_LINE('----');
FOR CurrentRow IN Extract_T LOOP
DBMS_OUTPUT.PUT_LINE('-- Start Extracting table ' || CurrentRow.table_name || '');
DBMS_OUTPUT.PUT_LINE('CREATE TABLE ' || CurrentRow.table_name ||' (');
--This is where i should be calling the other procedure
Extract_Columns(l_crt, CurrentRow.table_name);
--This is where create tables procedure begins again
DBMS_OUTPUT.PUT_LINE(l_crt || chr(10) || LPAD(' ', 15 + length(currentrow.Table_name)) || ');' || '-- END of Table ' || currentrow.Table_name || ' creation');
DBMS_OUTPUT.PUT_LINE('--' || CHR(10) || '--' );
l_crt := NULL;
END LOOP;
--Ending Statement
DBMS_OUTPUT.PUT_LINE('---- Oracle Catalog Extract Utility ' || wvers || ' ----');
DBMS_OUTPUT.PUT_LINE('---- Run on ' || wcur_Tim);
END;
/
SHOW ERRORS;
To execute, I have to compile the 'Extract_column' procedure and then 'Extract_tables' procedure, then I run the tables procedure to generate my output.
How can I format my current output in a way that it exactly matches the picture above?
I want to do a number of data updates as follows:
CREATE OR REPLACE PROCEDURE PROC_MDM_RUN_DATA_MAPPING
IS
sqlString VARCHAR2(2000) := '';
CURSOR csr_updates
IS
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
trim(old_value) AS old_value,
trim(new_value) AS new_value
FROM C_MDM_MAPPING_TABLE
WHERE area = 'MDM' and rownum < 10
AND run_update = 'Y' ;
BEGIN
FOR rec IN csr_updates
LOOP
BEGIN
sqlString := 'UPDATE ' || rec.table_name ||
' SET ' || rec.column_name || ' = ''' || rec.new_value || '''
WHERE TRIM(' || rec.column_name || ') = ''' || rec.old_value || ''';' ;
INSERT INTO C_MDM_ERROR_LOG ( msg ) VALUES ( sqlString );
dbms_output.put_line(sqlString) ; -- works, giving correct SQL
execute immediate sqlString ; -- fails
END;
END LOOP;
COMMIT;
END PROC_MDM_RUN_DATA_MAPPING;
/
The procedure compiles and generates the correct SQL for each of the data updates - example below:
UPDATE S_CFM_UNIVERSITY_ALL SET MASTER_UNI_NAME = 'Chung-Ang University'
WHERE TRIM(MASTER_UNI_NAME) = 'Chung Ang University';
but the execute immediate statement gives the error
"PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
;
The symbol ";" was substituted for "end-of-file" to continue."
I have tried excluding the trailing semi colon, but that returns the error
ORA-00933: SQL command not properly ended
Any help appreciated, Thanks.
Try generating the SQL without the embedded newline, as in:
CREATE OR REPLACE PROCEDURE PROC_MDM_RUN_DATA_MAPPING IS
sqlString VARCHAR2(2000) := '';
CURSOR csr_updates IS
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
trim(old_value) AS old_value,
trim(new_value) AS new_value
FROM C_MDM_MAPPING_TABLE
WHERE area = 'MDM' AND
rownum < 10 AND
run_update = 'Y';
BEGIN
FOR rec IN csr_updates LOOP
sqlString := 'UPDATE ' || rec.table_name ||
' SET ' || rec.column_name || ' = ''' || rec.new_value ||
''' WHERE TRIM(' || rec.column_name || ') = ''' ||
rec.old_value || '''';
INSERT INTO C_MDM_ERROR_LOG ( msg ) VALUES ( sqlString );
dbms_output.put_line(sqlString) ; -- works, giving correct SQL
execute immediate sqlString ;
END LOOP;
COMMIT;
END PROC_MDM_RUN_DATA_MAPPING;
I removed the semicolon in the generated SQL as I believe it's not needed, and got rid of the BEGIN...END pair inside the loop which is also not needed.
I hope this helps. Share and enjoy.
Thanks for all the help - the answer above are correct in that the trailing semi colon is not needed, but as a follow up - there was a 2nd issue which was single quotes in the data.
Changing the cursor as follows fixed that issue
SELECT trim(table_name) AS table_name,
trim(column_name) AS column_name,
REPLACE(trim(old_value),'''','''''') AS old_value,
REPLACE(trim(new_value),'''','''''') AS new_value
FROM C_MDM_MAPPING_TABLE
Table and column names are known to be ok (they come from the system), but it gets back to "Never trust the input".
Thanks
I have a table that exists in an Oracle database, but doesn't show on my list of tables in the tool SQL Developer. However, if I go to SQL*Plus, and do a
select table_name from user_tables;
I get the table listed. If I type
desc snp_clearinghouse;
it shows me the fields. I'd like to get the create statement, because I need to add a field. I can modify the table to add the field, but I still need the create statement to put into our source control. What pl/sql statement is used to get the create statement for a table?
From Get table and index DDL the easy way:
set heading off;
set echo off;
Set pages 999;
set long 90000;
spool ddl_list.sql
select dbms_metadata.get_ddl('TABLE','DEPT','SCOTT') from dual;
select dbms_metadata.get_ddl('INDEX','DEPT_IDX','SCOTT') from dual;
spool off;
Same as above but generic script found here gen_create_table_script.sql
-- #############################################################################################
--
-- %Purpose: Generate 'CREATE TABLE' Script for an existing Table in the database
--
-- Use: SYSTEM, SYS or user having SELECT ANY TABLE system privilege
--
-- #############################################################################################
--
set serveroutput on size 200000
set echo off
set feedback off
set verify off
set showmode off
--
ACCEPT l_user CHAR PROMPT 'Username: '
ACCEPT l_table CHAR PROMPT 'Tablename: '
--
DECLARE
CURSOR TabCur IS
SELECT table_name,owner,tablespace_name,
initial_extent,next_extent,
pct_used,pct_free,pct_increase,degree
FROM sys.dba_tables
WHERE owner=upper('&&l_user')
AND table_name=UPPER('&&l_table');
--
CURSOR ColCur(TableName varchar2) IS
SELECT column_name col1,
DECODE (data_type,
'LONG', 'LONG ',
'LONG RAW', 'LONG RAW ',
'RAW', 'RAW ',
'DATE', 'DATE ',
'CHAR', 'CHAR' || '(' || data_length || ') ',
'VARCHAR2', 'VARCHAR2' || '(' || data_length || ') ',
'NUMBER', 'NUMBER' ||
DECODE (NVL(data_precision,0),0, ' ',' (' || data_precision ||
DECODE (NVL(data_scale, 0),0, ') ',',' || DATA_SCALE || ') '))) ||
DECODE (NULLABLE,'N', 'NOT NULL',' ') col2
FROM sys.dba_tab_columns
WHERE table_name=TableName
AND owner=UPPER('&&l_user')
ORDER BY column_id;
--
ColCount NUMBER(5);
MaxCol NUMBER(5);
FillSpace NUMBER(5);
ColLen NUMBER(5);
--
BEGIN
MaxCol:=0;
--
FOR TabRec in TabCur LOOP
SELECT MAX(column_id) INTO MaxCol FROM sys.dba_tab_columns
WHERE table_name=TabRec.table_name
AND owner=TabRec.owner;
--
dbms_output.put_line('CREATE TABLE '||TabRec.table_name);
dbms_output.put_line('( ');
--
ColCount:=0;
FOR ColRec in ColCur(TabRec.table_name) LOOP
ColLen:=length(ColRec.col1);
FillSpace:=40 - ColLen;
dbms_output.put(ColRec.col1);
--
FOR i in 1..FillSpace LOOP
dbms_output.put(' ');
END LOOP;
--
dbms_output.put(ColRec.col2);
ColCount:=ColCount+1;
--
IF (ColCount < MaxCol) THEN
dbms_output.put_line(',');
ELSE
dbms_output.put_line(')');
END IF;
END LOOP;
--
dbms_output.put_line('TABLESPACE '||TabRec.tablespace_name);
dbms_output.put_line('PCTFREE '||TabRec.pct_free);
dbms_output.put_line('PCTUSED '||TabRec.pct_used);
dbms_output.put_line('STORAGE ( ');
dbms_output.put_line(' INITIAL '||TabRec.initial_extent);
dbms_output.put_line(' NEXT '||TabRec.next_extent);
dbms_output.put_line(' PCTINCREASE '||TabRec.pct_increase);
dbms_output.put_line(' )');
dbms_output.put_line('PARALLEL '||TabRec.degree);
dbms_output.put_line('/');
END LOOP;
END;
/
If you are using oracle SQLcl command-line client, the simplest way is to use the ddl built-in command. For example
ddl ownername.tablename
You will get the complete definition for that table. Warning: it could be very long based on your target table.