Table record count to unix log file - oracle

I need the count of records of a data base table from unix.
I am calling one sql script from unix and need the record count to any log file .

You can put the following into a test.sql file:
SET HEADING OFF;
SELECT COUNT(*) FROM dual;
QUIT;
and call it via SQL*Plus via script.
It will output:
1
since table dual has only one row. You should be able to write that into a log file.

You cant to append the output off your script to a named file by rediecting it like this.
$ sqlplus username/password#SID #your_script.sql >> /tmp/whatever.log
If your want to have more than a bald count in the output you'll need to include the boilerplate in the projectors:
SQL> select to_char(sysdate, 'YYYYMMDDHH24MISS')||'::Number of emps = '
2 , count(*)
3 from emp
4 group by to_char(sysdate, 'YYYYMMDDHH24MISS')||'::Number of emps = '
5 /
TO_CHAR(SYSDATE,'YYYYMMDDHH24MISS COUNT(*)
--------------------------------- ----------
20100210133747::Number of emps = 16
SQL>

Related

SQL*Plus - Spool into multiple files

I've got the next spool that stores the DDL of the user_tables into a file:
set pagesize 0
set long 90000
spool C:\Users\personal\Desktop\MAIN_USR\test.txt
select DBMS_METADATA.GET_DDL('TABLE',table_name,'MAIN_USR')
FROM user_tables ut;
spool off
exit
It returns the DDL of all user_tables into a single file, but I need it to be a little more dynamic and return them in separate files with the file name of their respective table. Something like this:
set pagesize 0
set long 90000
FOR tab_nam IN (SELECT table_name FROM user_tables) LOOP
spool C:\Users\personal\Desktop\MAIN_USR\test.txt
select DBMS_METADATA.GET_DDL('TABLE',table_name,'MAIN_USR')
FROM user_tables ut;
spool off
END LOOP;
exit
I know the one above won't work, but it's kind of an idea of what I want to do.
I appreciate any kind of help
You need to read USER_TABLES for creating N calls to DBMS_METADATA.GET_DDL each of which has its own spooled file. Spool everything to a file named out.sql and run it after spooling it off
set pagesize 0
set long 90000
SET TERMOUT OFF
spool out.sql
select 'spool C:\Users\personal\Desktop\MAIN_USR\'||REPLACE(table_name, '$', '_')||'.txt'||chr(13)||chr(10)||
'SELECT DBMS_METADATA.GET_DDL(''TABLE'','''||table_name||''',''MAIN_USR'') FROM DUAL;'||chr(13)||chr(10)||
'spool off' as cmd
FROM user_tables ut;
spool off
#OUT.SQL
exit
Using python's cx_Oracle module which enables access to Oracle Database might be elegant way for your case :
import cx_Oracle
con = cx_Oracle.connect('uname/pwd#host:port/service_name')
cur = con.cursor()
def OutputTypeHandler(cursor, name, defaultType, size, precision, scale):
if defaultType == cx_Oracle.CLOB:
return cursor.var(cx_Oracle.LONG_STRING, arraysize = cursor.arraysize)
cur.outputtypehandler = OutputTypeHandler
cur.execute("select table_name from user_tables order by 1")
rec = cur.fetchall()
for r in rec:
cur.execute("select dbms_metadata.get_ddl('TABLE',:tableName) from dual",tableName=r[0])
ddl, = cur.fetchone()
file = r'C:\\Users\\personal\\Desktop\\MAIN_USR\\'+r[0]+'.txt'
with open(file,"w") as f:
f.write(ddl)
f.close()
where the table names of the whole schema are determined through the first cur.execute, and their creation DDL are done in the second, and files are created with respective table names at the last step. Important thing to consider is related to use of OutputTypeHandler is because of getting rid of processing the CLOB result steming from Dbms_Metadata.Get_Ddl function. The compiler wouldn't want to get a CLOB value during the creation of the files, or DDL would be so long that will exceed the length 4000 chars which would prevent to_char conversion with raising error without using OutputTypeHandler.

How can i "Clean" output from sqlplus query?

i have got simple query and i need just one value from it = VALID
The query is:
select 'VALUE('||status||')' as value from user_indexes where index_name = '&1';
But i hve got in out:
C:\Program Files\zabbix\bin\win64\oracle>sqlplus -s #"C:\Program Files\zabbix\bi
n\win64\oracle\conn2.sql" OLAPTABLEVELSID
old 1: select status from user_indexes where index_name = '&1'
new 1: select status from user_indexes where index_name = 'OLAPTABLEVELSID'
VALID
What are this OLD and NEW strings? How can i dismiss it?
Thank you.
SET VERIFY OFF should help you. Please add such line in your script before query.
in sqplus you have substition variables. they are referenced by & or &&
when you run your script and pass it 'OLAPTABLEVELSID' - the query takes the '&1' and replaces it with ''OLAPTABLEVELSID'
SQL*Plus is telling you that in the output
SQL> set verify off
SQL> select '&1' from dual;
Enter value for 1: hello stackoverflow
'HELLOSTACKOVERFLOW
-------------------
hello stackoverflow
SQL>

Update or Insert on a Table based on a column value

I have two tables BASE and DAILY as shown below:
BASE
Cust ID IP address
1 10.5.5.5
2 10.5.5.50
3 10.5.5.6
DAILY
Cust ID IP address
1 10.5.5.5
2 10.5.5.70
4 10.5.5.67
The table DAILY is periodically refreshed every 24 hours. Now for every Cust Id in BASE I have to check if the IP address is modified in DAILY. If yes then update the row in BASE.
All the new entries in DAILY have to be inserted into BASE.
I have tried this using a Cursor comparing and then updating and then another cursor for insertion.
But it is taking lot of time.
What is the best possible way to do this?
You could also use MERGE depending on your database system.
SQL Server syntax would be
MERGE INTO BASE B
USING DAILY D
ON D.CustId = B.CustId
WHEN NOT MATCHED THEN
INSERT (CustId, Ip) VALUES (D.CustId, D.Ip)
WHEN MATCHED AND D.Ip <> B.Ip THEN
UPDATE SET B.Ip = D.Ip;
Oracle PL/SQL syntax seems to be much the same, take a look here
If you just want to update all your BASE table, use an UPDATE to update all the rows in your BASE table.
UPDATE `BASE`
SET `IP address` = (SELECT `IP address`
FROM DAILY
WHERE DAILY.`Cust ID` = `BASE`.`Cust ID`);
Then, use this INSERT INTO query to insert new values that not exists in your table BASE.
INSERT INTO `BASE`
SELECT `Cust ID`, `IP address`
FROM DAILY
WHERE DAILY.`Cust ID` NOT IN (SELECT `Cust ID` FROM BASE);
SQL>
declare
begin
for i in (select * from daily where ip_add not in (select ip_add from base))
loop
update base set ip_add=i.ip_add where custid=i.custid;
end loop;
end;
PL/SQL procedure successfully completed.
SQL> select * from base;
CUSTID IP_ADD
---------- ----------
1 10..5.5.5
2 10..5.5.20 -- updated value from base where ip_add is different
3 10..5.5.6
SQL> select * from base ;
CUSTID IP_ADD
---------- ----------
1 10..5.5.5
2 10..5.5.20
4 10..5.5.62
SQL>

SQLPlus - spooling to multiple files from PL/SQL blocks

I have a query that returns a lot of data into a CSV file. So much, in fact, that Excel can't open it - there are too many rows. Is there a way to control spool to spool to a new file everytime 65000 rows have been processed? Ideally, I'd like to have my output in files named in sequence, such as large_data_1.csv, large_data_2.csv, large_data_3.csv, etc...
I could use dbms_output in a PL/SQL block to control how many rows are output, but then how would I switch files, as spool does not seem to be accessible from PL/SQL blocks?
(Oracle 10g)
UPDATE:
I don't have access to the server, so writing files to the server would probably not work.
UPDATE 2:
Some of the fields contain free-form text, including linebreaks, so counting line breaks AFTER the file is written is not as easy as counting records WHILE the data is being returned...
Got a solution, don't know why I didn't think of this sooner...
The basic idea is that the master sqplplus script generates an intermediate script that will split the output to multiple files. Executing the intermediate script will execute multiple queries with different ranges imposed on rownum, and spool to a different file for each query.
set termout off
set serveroutput on
set echo off
set feedback off
variable v_rowCount number;
spool intermediate_file.sql
declare
i number := 0;
v_fileNum number := 1;
v_range_start number := 1;
v_range_end number := 1;
k_max_rows constant number := 65536;
begin
dbms_output.enable(10000);
select count(*)
into :v_err_count
from ...
/* You don't need to see the details of the query... */
while i <= :v_err_count loop
v_range_start := i+1;
if v_range_start <= :v_err_count then
i := i+k_max_rows;
v_range_end := i;
dbms_output.put_line('set colsep ,
set pagesize 0
set trimspool on
set headsep off
set feedback off
set echo off
set termout off
set linesize 4000
spool large_data_file_'||v_fileNum||'.csv
select data_string
from (select rownum rn, data_object
from
/* Details of query omitted */
)
where rn >= '||v_range_start||' and rn <= '||v_range_end||';
spool off');
v_fileNum := v_fileNum +1;
end if;
end loop;
end;
/
spool off
prompt executing intermediate file
#intermediate_file.sql;
set serveroutput off
Try this for a pure SQL*Plus solution...
set pagesize 0
set trimspool on
set headsep off
set feedback off
set echo off
set verify off
set timing off
set linesize 4000
DEFINE rows_per_file = 50
-- Create an sql file that will create the individual result files
SET DEFINE OFF
SPOOL c:\temp\generate_one.sql
PROMPT COLUMN which_dynamic NEW_VALUE dynamic_filename
PROMPT
PROMPT SELECT 'c:\temp\run_#'||TO_CHAR( &1, 'fm000' )||'_result.txt' which_dynamic FROM dual
PROMPT /
PROMPT SPOOL &dynamic_filename
PROMPT SELECT *
PROMPT FROM ( SELECT a.*, rownum rnum
PROMPT FROM ( SELECT object_id FROM all_objects ORDER BY object_id ) a
PROMPT WHERE rownum <= ( &2 * 50 ) )
PROMPT WHERE rnum >= ( ( &3 - 1 ) * 50 ) + 1
PROMPT /
PROMPT SPOOL OFF
SPOOL OFF
SET DEFINE &
-- Define variable to hold number of rows
-- returned by the query
COLUMN num_rows NEW_VALUE v_num_rows
-- Find out how many rows there are to be
SELECT COUNT(*) num_rows
FROM ( SELECT LEVEL num_files FROM dual CONNECT BY LEVEL <= 120 );
-- Create a master file with the correct number of sql files
SPOOL c:\temp\run_all.sql
SELECT '#c:\temp\generate_one.sql '||TO_CHAR( num_files )
||' '||TO_CHAR( num_files )
||' '||TO_CHAR( num_files ) file_name
FROM ( SELECT LEVEL num_files
FROM dual
CONNECT BY LEVEL <= CEIL( &v_num_rows / &rows_per_file ) )
/
SPOOL OFF
-- Now run them all
#c:\temp\run_all.sql
Use split on the resulting file.
utl_file is the package you are looking for. You can write a cursor and loop over the rows (writing them out) and when mod(num_rows_written,num_per_file) == 0 it's time to start a new file. It works fine within PL/SQL blocks.
Here's the reference for utl_file:
http://www.adp-gmbh.ch/ora/plsql/utl_file.html
NOTE:
I'm assuming here, that it's ok to write the files out to the server.
Have you looked at setting up an external data connection in Excel (assuming that the CSV files are only being produced for use in Excel)? You could define an Oracle view that limits the rows returned and also add some parameters in the query to allow the user to further limit the result set. (I've never understood what someone does with 64K rows in Excel anyway).
I feel that this is somewhat of a hack, but you could also use UTL_MAIL and generate attachments to email to your user(s). There's a 32K size limit to the attachments, so you'd have to keep track of the size in the cursor loop and start a new attachment on this basis.
While your question asks how to break the greate volume of data into chunks Excel can handle, I would ask if there is any part of the Excel operation that can be moved into SQL (PL/SQL?) that can reduce the volume of data. Ultimately it has to be reduced to be made meaningful to anyone. The database is a great engine to do that work on.
When you have reduced the data to more presentable volumes or even final results, dump it for Excel to make the final presentation.
This is not the answer you were looking for but I think it is always good to ask if you are using the right tool when it is getting difficult to get the job done.

Oracle - dynamic column name in select statement

Question:
Is it possible to have a column name in a select statement changed based on a value in it's result set?
For example, if a year value in a result set is less than 1950, name the column OldYear, otherwise name the column NewYear. The year value in the result set is guaranteed to be the same for all records.
I'm thinking this is impossible, but here was my failed attempt to test the idea:
select 1 as
(case
when 2 = 1 then "name1";
when 1 = 1 then "name2")
from dual;
You can't vary a column name per row of a result set. This is basic to relational databases. The names of columns are part of the table "header" and a name applies to the column under it for all rows.
Re comment: OK, maybe the OP Americus means that the result is known to be exactly one row. But regardless, SQL has no syntax to support a dynamic column alias. Column aliases must be constant in a query.
Even dynamic SQL doesn't help, because you'd have to run the query twice. Once to get the value, and a second time to re-run the query with a different column alias.
The "correct" way to do this in SQL is to have both columns, and have the column that is inappropriate be NULL, such as:
SELECT
CASE WHEN year < 1950 THEN year ELSE NULL END AS OldYear,
CASE WHEN year >= 1950 THEN year ELSE NULL END AS NewYear
FROM some_table_with_years;
There is no good reason to change the column name dynamically - it's analogous to the name of a variable in procedural code - it's just a label that you might refer to later in your code, so you don't want it to change at runtime.
I'm guessing what you're really after is a way to format the output (e.g. for printing in a report) differently depending on the data. In that case I would generate the heading text as a separate column in the query, e.g.:
SELECT 1 AS mydata
,case
when 2 = 1 then 'name1'
when 1 = 1 then 'name2'
end AS myheader
FROM dual;
Then the calling procedure would take the values returned for mydata and myheader and format them for output as required.
You will need something similar to this:
select 'select ' || CASE WHEN YEAR<1950 THEN 'OLDYEAR' ELSE 'NEWYEAR' END || ' FROM TABLE 1' from TABLE_WITH_DATA
This solution requires that you launch SQLPLUS and a .sql file from a .bat file or using some other method with the appropriate Oracle credentials. The .bat file can be kicked off manually, from a server scheduled task, Control-M job, etc...
Output is a .csv file. This also requires that you replace all commas in the output with some other character or risk column/data mismatch in the output.
The trick is that your column headers and data are selected in two different SELECT statements.
It isn't perfect, but it does work, and it's the closest to standard Oracle SQL that I've found for a dynamic column header outside of a development environment. We use this extensively to generate recurring daily/weekly/monthly reports to users without resorting to a GUI. Output is saved to a shared network drive directory/Sharepoint.
REM BEGIN runExtract1.bat file -----------------------------------------
sqlplus username/password#database #C:\DailyExtracts\Extract1.sql > C:\DailyExtracts\Extract1.log
exit
REM END runExtract1.bat file -------------------------------------------
REM BEGIN Extract1.sql file --------------------------------------------
set colsep ,
set pagesize 0
set trimspool on
set linesize 4000
column dt new_val X
select to_char(sysdate,'MON-YYYY') dt from dual;
spool c:\DailyExtracts\&X._Extract1.csv
select '&X-Project_id', 'datacolumn2-Project_Name', 'datacolumn3-Plant_id' from dual;
select
PROJ_ID
||','||
replace(PROJ_NAME,',',';')-- "Project Name"
||','||
PLANT_ID-- "Plant ID"
from PROJECTS
where ADDED_DATE >= TO_DATE('01-'||(select to_char(sysdate,'MON-YYYY') from dual));
spool off
exit
/
REM ------------------------------------------------------------------
CSV OUTPUT (opened in Excel and copy/pasted):
old 1: select '&X-Project_id' 'datacolumn2-Project_Name' 'datacolumn3-Plant_id' from dual
new 1: select 'MAR-2018-Project_id' 'datacolumn2-Project_Name' 'datacolumn3-Plant_id' from dual
MAR-2018-Project_id datacolumn2-Project_Name datacolumn3-Plant_id
31415 name1 1007
31415 name1 2032
32123 name2 3302
32123 name2 3384
32963 name3 2530
33629 name4 1161
34180 name5 1173
34180 name5 1205
...
...
etc...
135 rows selected.

Resources