How to align database output in text file using sql query - oracle

I have an issue while opening the text file of database output. The null value columns are replaced with the columns which have data. So the data is not properly formatting in the text file. I generated the text file from oracle sql developer. And after opening the text file I can see the alignment of columns is not proper(misplacing of values between null columns and columns which have data ). Can anyone please help me to solve this issue

Just guessing here, but - what you said reminds me of this. Example is based on Scott's EMP table where some employees have NULL in the COMM column:
SQL> select ename, job, comm, deptno
2 from emp
3 where deptno = 30;
ENAME JOB COMM DEPTNO
---------- --------- ---------- ----------
ALLEN SALESMAN 300 30
WARD SALESMAN 500 30
MARTIN SALESMAN 1400 30
BLAKE MANAGER 30
TURNER SALESMAN 30
JAMES CLERK 30
6 rows selected.
If you're spooling such data by concatenating columns, you get mess which is difficult to read:
SQL> select ename||'-'||job||'-'||comm||'-'||deptno
2 from emp
3 where deptno = 30;
ENAME||'-'||JOB||'-'||COMM||'-'||DEPTNO
----------------------------------------------------------
ALLEN-SALESMAN-300-30
WARD-SALESMAN-500-30
MARTIN-SALESMAN-1400-30
BLAKE-MANAGER--30
TURNER-SALESMAN--30
JAMES-CLERK--30
6 rows selected.
But, if you right-pad strings to certain length (RPAD function) and use NVL for missing data, then the result is somewhat prettier:
SQL> select rpad(ename, 10, ' ') ||'-'||
2 rpad(job , 10, ' ') ||'-'||
3 nvl(to_char(comm, '9990D00'), ' ') ||'-'||
4 deptno as result
5 from emp
6 where deptno = 30;
RESULT
---------------------------------------------------------------------
ALLEN -SALESMAN - 300,00-30
WARD -SALESMAN - 500,00-30
MARTIN -SALESMAN - 1400,00-30
BLAKE -MANAGER - -30
TURNER -SALESMAN - -30
JAMES -CLERK - -30
6 rows selected.
SQL>

Related

oracle pl/sql-problem /how I can add some dots after the name to make it reach 10 characters

For Oracle SQL problem how I can add some dots after the name to make it reach 10 characters such as ( Sara......)
and add dollar simple $ for every 100 in salary, if the salary= 500 so we need to add 5 simple$
Looks like a RPAD function, e.g.
SQL> select
2 rpad(ename, 10, '.') name,
3 sal,
4 rpad('$', (sal/100), '$') money
5 from emp
6 where deptno = 30;
NAME SAL MONEY
---------- ---------- ------------------------------
ALLEN..... 1600 $$$$$$$$$$$$$$$$
WARD...... 1250 $$$$$$$$$$$$
MARTIN.... 1250 $$$$$$$$$$$$
BLAKE..... 2850 $$$$$$$$$$$$$$$$$$$$$$$$$$$$
TURNER.... 1500 $$$$$$$$$$$$$$$
JAMES..... 950 $$$$$$$$$
6 rows selected.
SQL>

Is there any way to make output of my SQLPLUS script look better?

I am new to sql.I would like to ask if there is any way to format my output to look more complex and more like one table?
My script looks like this
spool "\\PathToPutOutputInTextFile"
SELECT a.ARCHIVEID, count(*) as "Number of Documents", ROUND(SUM(c.CLENGTH)/1024/1024,2) as "Documents Size in MB"
FROM ds_doc d
INNER JOIN ds_arch a ON d.ARCHIVENO = a.ARCHIVENO
INNER JOIN ds_comp c ON d.DOCIDNO = c.DOCIDNO
GROUP BY a.ARCHIVEID;
spool off;
And I was also able to automatize this with .bat file that looks like this
sqlplus usr/pass#nameofdb #D:\IXTENT\monitoring\ASCheck.sql -path "\\PathToPutOutputInTextFile\test.txt
I was somehow manage to make this somehow work but my output looks like sh..t :/
This is my output.
ARCHIVEID
Number of Documents
Documents Size in MB
test_rt 39
3.03
IL 36
104
TN 139823
20683.57
ARCHIVEID
Number of Documents
Documents Size in MB
T5 6931
331978.15
TA 4
.34
TT 23
3.09
Is there any way to make it complex and look more like one table?
Thanks a lot.
One option would be
set echo off verify off head off feed off term off lines 120 pages 0
col "Number of Documents" for 9999999999
col "Documents Size in MB" for 9999999999
col ARCHIVEID for a30
spool "\\PathToPutOutputInTextFile"
SELECT a.ARCHIVEID, count(*) as "Number of Documents", ROUND(SUM(c.CLENGTH)/1024/1024,2) as "Documents Size in MB"
FROM ds_doc d
INNER JOIN ds_arch a ON d.ARCHIVENO = a.ARCHIVENO
INNER JOIN ds_comp c ON d.DOCIDNO = c.DOCIDNO
GROUP BY a.ARCHIVEID;
spool off;
However, if you want to use this file to load data in another database, a good option is to use set markup csv , guessing you have Oracle 12 or higher.
set echo off verify off head off feed off term off lines 120 pages 0
set markup csv delimiter ";"
spool "\\PathToPutOutputInTextFile"
SELECT a.ARCHIVEID, count(*) as "Number of Documents", ROUND(SUM(c.CLENGTH)/1024/1024,2) as "Documents Size in MB"
FROM ds_doc d
INNER JOIN ds_arch a ON d.ARCHIVENO = a.ARCHIVENO
INNER JOIN ds_comp c ON d.DOCIDNO = c.DOCIDNO
GROUP BY a.ARCHIVEID;
spool off;
There is a way; format columns.
For example:
SQL> select * from emp where rownum < 3;
EMPNO ENAME JOB MGR HIREDATE SAL COMM
---------- ---------- --------- ---------- -------- ---------- ----------
DEPTNO
----------
7369 SMITH CLERK 7902 17.12.80 800
20
7499 ALLEN SALESMAN 7698 20.02.81 1600 300
30
By setting columns' format, you'd get
SQL> col empno format 99999
SQL> col ename format a8
SQL> col mgr format 9999
SQL> col sal format 9G990
SQL> col comm format 990
SQL>
SQL> select * from emp where rownum < 3;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
------ -------- --------- ----- -------- ------ ---- ----------
7369 SMITH CLERK 7902 17.12.80 800 20
7499 ALLEN SALESMAN 7698 20.02.81 1.600 300 30
SQL>
If line is too short, make it longer:
SQL> set linesize 120
Or, make the page larger:
SQL> set pagesize 1000
There are different options you can use; see SQL*Plus documentation.
If you want to format your query output, they you can use RPAD(field_name, number of spaces) ... for ex RPAD(Name, 50) ... it will fix the width of each field while printing and will look in sync.

How to compare two tables in oracle when number of columns in tables differs

I have two tables in my database they are EXP1 and EXP2. I tried with the below query, this query is working when both the tables have same number of columns but my table EXP1 has 1000 columns n EXP2 has 1000+4.
select *
from
(
(select * from exp1
minus
select * from exp2)
union all
(select * from exp2
minus
select * from exp1)
);
INTRO: Below I show how one can do "by hand" what the tools (SQL Developer for example) can do much faster and much better. My interest in this (and yours!) is two-fold: learn and use some ideas that can help in many other problems; and understand what those tools do under the hood in the first place.
OK. Suppose you have two tables, and they have many columns in common (possibly not in the same order) and a few columns may be different - there may be a handful of columns in one table but not in the other. First you want to be able to look just at the common columns.
Then, suppose that's done. Now what's left of the two tables has many rows in common, but there are a few that are different. A row may exist in one table but not in the other, or two rows, one from each table, may be very similar but they may differ in just one or a small number of column values. Logically these are still one row in the first table but not the second, and the other row only in the second table but not in the first. However, let's say both tables have the same PK column - then you may have the same PK value in both tables, but at least one of the OTHER columns has different values for that PK value in the two tables. And, you want to find these differences between the two tables.
In what follows I will assume that if two columns, in the two tables, have the same name, they will also have the same data type. If that is not guaranteed in your case, it can be fixed with a little more work in the part where I identify the "common columns" - instead of matching them just by name, from the catalog views, they would have to be matched also by data type.
When you get to comparing rows in the two tables in the final step, (A minus B) union all (B minus A) works, but is not very efficient. Each table is read twice, and minus is an expensive operator. The more efficient solution, which I illustrate below, was discussed in a long thread on AskTom several years ago. Namely: collect all the rows from both tables (with union all), group by all the columns, and disregard the groups that have a count of 2. This means rows that were found in both tables, so they are duplicates in the union all! Actually, you will see a small additional trick to identify from which table the "non-duplicated" rows come. Add a column for "table_name" and in the final select, after grouping and keeping the groups with count(*) = 1, select max(table_name). You need an aggregate function (like max()) because you are grouping, but for these rows each group only has one row, so the max() is really just the table name.
The beauty of this approach is that it can be used to identify the common columns, too! In that case, we will compare rows from the USER_TAB_COLS view - we select column names that appear in either of the tables, and keep only the column names that are duplicates (so the column names appear in both tables). In that part of the solution, I also retrieve column_id, which is used to order the columns. Don't worry if you are not familiar with keep (dense_rank first...) - it's not really that complicated, but it's not that important either.
First let's set up a test case. I copy the EMP table from the SCOTT schema to my own schema, I replicate it (so now I have two copies, named EMP1 and EMP2), and I modify them slightly. I delete a different column from each, I delete a few (different) rows from each, and I modify one salary in one table. I will not show the resulting (slightly different) tables, but if you are following along, just select * from both and compare them before you continue reading.
Create the tables:
create table EMP1 as select * from scott.emp;
Table EMP1 created.
select * from EMP1;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ---------- --------- ---- ------------------- ----- ------ -------
7369 SMITH CLERK 7902 1980-12-17 00:00:00 800 20
7499 ALLEN SALESMAN 7698 1981-02-20 00:00:00 1600 300 30
7521 WARD SALESMAN 7698 1981-02-22 00:00:00 1250 500 30
7566 JONES MANAGER 7839 1981-04-02 00:00:00 2975 20
7654 MARTIN SALESMAN 7698 1981-09-28 00:00:00 1250 1400 30
7698 BLAKE MANAGER 7839 1981-05-01 00:00:00 2850 30
7782 CLARK MANAGER 7839 1981-06-09 00:00:00 2450 10
7788 SCOTT ANALYST 7566 1987-04-19 00:00:00 3000 20
7839 KING PRESIDENT 1981-11-17 00:00:00 5000 10
7844 TURNER SALESMAN 7698 1981-09-08 00:00:00 1500 0 30
7876 ADAMS CLERK 7788 1987-05-23 00:00:00 1100 20
7900 JAMES CLERK 7698 1981-12-03 00:00:00 950 30
7902 FORD ANALYST 7566 1981-12-03 00:00:00 3000 20
7934 MILLER CLERK 7782 1982-01-23 00:00:00 1300 10
Modify them slightly:
create table EMP2 as select * from EMP1;
Table EMP2 created.
alter table emp1 drop column hiredate;
Table EMP1 altered.
alter table emp2 drop column comm;
Table EMP2 altered.
delete from EMP1 where ename like 'A%';
2 rows deleted;
delete from EMP2 where sal >= 3000;
3 rows deleted
update EMP2 set sal = 2950 where empno = 7698;
1 row updated
commit;
At this point you would do well to select * from EMP1; and select * from EMP2; and compare.
Now let's find out what columns the two tables have left in common.
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
order by col_id;
COLUMN_NAME COL_ID
----------- ------
EMPNO 1
ENAME 2
JOB 3
MGR 4
SAL 5
DEPTNO 7
6 rows selected
Perfect, so now we can compare the two tables, but only after we "project" them along the common columns only.
select max(table_name) as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
from (
select 'EMP1' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP1
union all
select 'EMP2' as table_name, EMPNO, ENAME, JOB, MGR, SAL, DEPTNO from EMP2
)
group by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
having count(*) = 1
order by EMPNO, ENAME, JOB, MGR, SAL, DEPTNO, table_name;
TABLE_NAME EMPNO ENAME JOB MGR SAL DEPTNO
---------- ----- ---------- --------- ------ ------ --------
EMP2 7499 ALLEN SALESMAN 7698 1600 30
EMP1 7698 BLAKE MANAGER 7839 2850 30
EMP2 7698 BLAKE MANAGER 7839 2950 30
EMP1 7788 SCOTT ANALYST 7566 3000 20
EMP1 7839 KING PRESIDENT 5000 10
EMP2 7876 ADAMS CLERK 7788 1100 20
EMP1 7902 FORD ANALYST 7566 3000 20
7 rows selected
The output is pretty much what we needed. Notice the first column, which tells us where the "unpaired" row comes from; and note BLAKE, who has different salary in the two tables (and the first column helps us to see what salary he has in which table).
This looks perfect so far, but what to do when you have 1000 columns? You could put it together in C or Java etc., using the result from the "common columns" query above - or you could do it all in Oracle, with dynamic SQL.
As far as I know, there is no set limit on the length of the text of an SQL statement in Oracle; the documentation says "The limit on how long a SQL statement can be depends on many factors, including database configuration, disk space, and memory" (and probably on your Oracle version, which they didn't mention). In any case, it will be more than 4000 characters, so we need to work with CLOB. In particular, we can't use listagg() - we need a workaround. I use xmlagg() below. Then, the documentation says if you concatenate text and at least one operand is CLOB the result will be CLOB; if that doesn't work for you, you may have to wrap the smaller text fragments within to_clob(). The "dynamic SQL" query below will produce the full text of the query I used above; you will simply copy it and paste it back into your front-end and execute it. You may have to delete wrapping double-quotes or such, depending on your front-end and settings.
First here is how we can create a (potentially very long) string, the list of common column names, which is repeated five times in the final query - just look again at the "final query" we used to compare the two tables above.
with
common_cols ( column_name, col_id ) as (
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
),
col_string ( str ) as (
select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id)
as clob), ', ') from common_cols
)
select * from col_string;
STR
-----------------------------------
EMPNO, ENAME, JOB, MGR, SAL, DEPTNO
And finally the full dynamic SQL query (the result is exactly the query I used to compare EMP1 and EMP2 on their common columns earlier):
with
common_cols ( column_name, col_id ) as (
select column_name,
min(column_id) keep(dense_rank first order by table_name) as col_id
from user_tab_cols
where table_name in ('EMP1', 'EMP2')
group by column_name
having count(*) = 2
),
col_string ( str ) as (
select rtrim(xmlcast(xmlagg(xmlelement(e, column_name, ', ') order by col_id)
as clob), ', ') from common_cols
)
select 'select max(table_name) as table_name, ' || str || chr(10) ||
'from (' || chr(10) ||
' select ''EMP1'' as table_name, ' || str || ' from EMP1' || chr(10) ||
' union all' || chr(10) ||
' select ''EMP2'' as table_name, ' || str || ' from EMP2' || chr(10) ||
' )' || chr(10) ||
'group by ' || str || chr(10) ||
'having count(*) = 1' || chr(10) ||
'order by ' || str || ', table_name;' as comp_sql_str
from col_string;

How can I count only NULL values in Oracle/PLSQL?

How can I count only NULL values in Oracle/PLSQL?
I want to count only the null values. Is there a function that does that?
I don't know Oracle specifally, but ANSI SQL, COUNT(rowName) does not count NULL values, but COUNT(*) does. So you can write
SELECT COUNT(*) FROM YourTable WHERE YourColumn IS NULL
which counts the rows in YourTable that have YourColumn set to NULL.
As an alternative to mdma's response.
If you don't want to put a filter in the where you can
SELECT COUNT(case when xxx IS NULL THEN 1 end) cnt_xxx_null
FROM table
The Oracle documentation states that:
All aggregate functions except
COUNT(*) and GROUPING ignore nulls.
You can use the NVL function in the
argument to an aggregate function to
substitute a value for a null.
As an example, using the scott schema:
SQL> select empno, sal, comm
2 from emp;
EMPNO SAL COMM
---------- ---------- ----------
7369 800
7499 1600 300
7521 1250 500
7566 2975
7654 1250 1400
7698 2850
7782 2450
7788 3000
7839 5000
7844 1500 0
7876 1100
7900 950
7902 3000
7934 1300
14 rows selected.
You can see that the Comm column has 4 known values (i.e. Not null) and 10 unknown values (i.e. Null)
As count(your_column_name) ignores nulls you need to substitute the unknown values for something you can refer to. This can be achieved using the NVL function.
SQL> select count(nvl(comm, -1)) "number of null values"
2 from emp
3 where nvl(comm, -1) = -1;
number of null values
---------------------
10
I have used the value "-1" as the "alias" for my null values because I know that "-1" is not an existing value within the comm column.
EDIT:
Following Rob's suggestion. It is possible to remove the where clause from the above example and use the NVL2 function as shown below:
SQL> select count(nvl2(comm,null,-1)) "number of null values"
2 from emp
3 /
number of null values
---------------------
10
If you wants to count other values too with null then use of COALESCE function will improves execution time
Oracle Differences between NVL and Coalesce
SELECT COUNT(COALESCE( _COLUMN, 1)) AS CNT FROM _TABLE
I might try to inverse the null, see results
SELECT
COUNT(DECODE(YourField, null, 1, null)) Nulls,
count(*) Everything,
COUNT(YourField) NotNulls
FROM YourTable
Everything should equal nulls + notnulls
select count(nvl(values, 0)) from emp where values is null;
Function:
create or replace function xxhrs_fb_count_null
return number
as
l_count_null number;
begin
select count(*) into l_count_null from emp where comm is null;
return l_count_null;
end;
Usage:
select xxhrs_fb_count_null from dual
I believe your requirement is as below:
Table emp has 100 rows. Against 20 employees, HIRE_DATE column is NULL. So basically, you want to get 20 as output.
This is another method apart from the answers given by other contributors in this forum.
-- COUNT (1) would return 100
-- COUNT (hire_date) would return 80
-- 100 - 80 = 20
SELECT COUNT (1) -
COUNT (hire_date)
AS null_count
FROM emp;

Oracle Hierarchical query: how to include top-level parent

I have a hierarchical query to track a reporting structure. This almost works, except that it's not reporting the very top level node, probably because the top-level people "report" to themselves.
The query is:
select
level,
empid,
parentid
from usertable
connect by nocycle prior parentid= empid
start with empid = 50
This produces:
LEVEL EMPID PARENTID
------ ----- --------
1 50 258
2 258 9555
3 9555 17839
I'm not getting a level 4, since it would look like:
4 17839 17839
Without changing data, is there a way to modify my query so that all 4 levels are returned? The goal is to get the empids, so I can do a check for
id in (hierarchical subquery)
BTW, if I remove the nocycle from the query I get an error.
Chris,
You only get 3 rows because your top level row is not set the way it should to handle hierarchical queries. Typically the top level row, or president KING in Oracle's well known EMP table, has no manager. In your case you should not set the parentid of 17389 to 17389 itself, but to NULL. Either update the table accordingly, or use a view to accomodate for this situation.
An example:
SQL> select empno
2 , mgr
3 from emp
4 where empno in (7876,7788,7566,7839)
5 /
EMPNO MGR
---------- ----------
7566 7839
7788 7566
7839 7839
7876 7788
4 rijen zijn geselecteerd.
This part of the EMP table has four levels with its top level row (7839) set to itself. The same as your empid 17839. And this leads to only three rows using your query:
SQL> select level
2 , empno
3 , mgr
4 from emp
5 connect by nocycle prior mgr = empno
6 start with empno = 7876
7 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
3 rijen zijn geselecteerd.
Either use a (inline) view to set the mgr/parentid column to null for the top level:
SQL> select level
2 , empno
3 , mgr
4 from ( select empno
5 , nullif(mgr,empno) mgr
6 from emp
7 )
8 connect by nocycle prior mgr = empno
9 start with empno = 7876
10 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
4 7839
4 rijen zijn geselecteerd.
Or fix your data with an UPDATE statement:
SQL> update emp
2 set mgr = null
3 where empno = 7839
4 /
1 rij is bijgewerkt.
SQL> select level
2 , empno
3 , mgr
4 from emp
5 connect by nocycle prior mgr = empno
6 start with empno = 7876
7 /
LEVEL EMPNO MGR
---------- ---------- ----------
1 7876 7788
2 7788 7566
3 7566 7839
4 7839
4 rijen zijn geselecteerd.
And you can leave out the NOCYCLE keyword as well, after you are done fixing.
Regards,
Rob.
You need to do the hierarchy the other way around, from the root to the leaves.
select
level,
empid,
parentid
from usertable
start with empid = 17839
connect by empid != 17839 and prior empid = parentid
LEVEL EMPID PARENTID
---------------------- ---------------------- ----------------------
1 17839 17839
2 9555 17839
3 258 9555
4 50 258
4 rows selected
You don't have to change your structure.
just use the following query
select
level,
empid,
parentid
from usertable
connect by prior parentid = empid
AND parentid <> empid -- This line prohibits cycling and ALLOWS a row where parentid = empid
start with empid = 50
Van Heddegem Roeland's answer doesn't work for me, I had already tried that, but I have managed to do it without an inline view, in the connect clause, by adding:-
and prior empid <> parentid
The following post explains why that works - if you can get your head round it! Although it does make sound logical sense once you do 'get it'. (It's to do with the order of evaluation of each side of the <> operator.)
Oracle: Connect By Loop in user data
The inline view will work but without research on your particular dataset, I don't know what impact an inline view might have on the query path. Adding the extra clause is probably the 'correct' way to do it in most situations, IMHO.
seems like you have a cycle in data. Without "nocycle" it will not work straight away. If you know that all your data has maximum nesting level 4, then you could add condition "and level <= 4" and remove nocycle. Should work.

Resources