Monday, June 30, 2008

External Tables

C:\Oracle\SQL>type shopping.txt
TJ|baking soda||Baking|07/15/08
TJ|crackers|3.3|Bread|08/15/08
VS|chocolate|2|Cashier|07/15/08
WF|honey|4.0/can|Baking|07/30/08
WH|kefir|2.7/quart|Refridgerated|07/01/08
To write queries against a file as if it were part of the Oracle database, several steps are needed:
The system must grant you access to a pre-existing directory:
SQL> CONNECT SYSTEM/SYSTEM

SQL> CREATE DIRECTORY ext AS 'c:\oracle\sql';

SQL> GRANT READ ON DIRECTORY ext TO scott;
SQL> GRANT WRITE ON DIRECTORY ext TO scott;

SQL> CONNECT SCOTT/TIGER

You then create an external table layout.
Drop Table Ext_Shopping Purge;

Create Table Ext_Shopping
( Store_Code Varchar2(2)
, Cost_Per Varchar2(20)
, Item_Name Varchar2(35)
, Category Varchar2(20)
, Needed_By Date
)
Organization External
(
Type Oracle_Loader
Default Directory Ext
Access Parameters
(
Records Delimited By Newline
Badfile 'Shop.Bad'
Logfile 'Shop.Log'
Fields Terminated By '|'
Missing Field Values Are Null
(
Store_Code,
Item_Name,
Cost_Per,
Category,
Needed_By Char Date_Format Date Mask "Mm/Dd/Yy"
)
)
Location (Ext:'Shopping.Txt')
)
Reject Limit Unlimited;

Notes:
  • To denote a tab-delimited file, code:
    FIELDS TERMINATED BY "\t"
  • Code the input fields as all 'CHAR'; in the table definitions, set up NUMBER, etc.
  • If your alpha fields have double quotes, use:
    Fields Terminated By ',' OPTIONALLY ENCLOSED BY '"'


When that is successfully created, you can write any select against it. If there are errors, look in the log file. Any rejected records are in the bad file.
Select *
From Ext_Shopping;

You can't manipulate the data or create an index on an external table. If you need to do either, load the data to an actual table in Oracle by creating a table and then performing an Insert Into.
Drop Table Int_Shopping Purge;
Create Table Int_Shopping
( Store_Code Varchar2(2)
, Cost_Per Varchar2(20)
, Item_Name Varchar2(35)
, Category Varchar2(20)
, Needed_By Date);

Insert Into Int_Shopping
( Store_Code ,
Cost_Per ,
Item_Name ,
Category ,
Needed_By
)
Select *
From Ext_Shopping;
or
Create Table x
As
Select *
From Ext_Table;


Select *
From Int_Shopping;

This is an example of creating an external table on Unix where the first record will be skipped as it contains column headings:
CREATE TABLE ext_client
( client VARCHAR2(3),
client_desc VARCHAR2(100)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY ext
ACCESS PARAMETERS
--------------------------------------------------
-- view record delimiter by using "od -c"
-- skip first record which contains column names
--------------------------------------------------
( RECORDS DELIMITED BY "\r\n"
CHARACTERSET US7ASCII
SKIP=1
FIELDS TERMINATED BY ','
)
LOCATION
( 'client.csv','OTHER.csv') -- the file name(s) as on Unix
)
REJECT LIMIT UNLIMITED


Note: Research "EXTERNAL_TABLE=GENERATE_ONLY" to see how to generate the code for an external table from SQL Loader.

See Oracle documentation for further detail:
External Tables Concepts

Displaying Changes in Audit Data


The subquery pulls the value in each column for that row.

With the 'Lead' function, the value from the next row is used to create a field in the current row.

The parent query gets that data and compares the current with the next, and sets 'Chg' to '*' if they're different.
Column Item_Id     Format 99     Heading Item|Id
Column Nxt_Item_Id Format 99 Heading Nxt|Item|Id
Column Lot_Id Format 999999 Heading Lot|Id
Column Nxt_Lot_Id Format 999999 Heading Nxt|Lot|Id
Column User_Nm Format A4 Heading User|Nm
Column Nxt_User_Nm Format A4 Heading Nxt|User|Nm
Column Chg Format A1 Heading C|h|g


Select Item_Id,
Nxt_Item_Id,
------------------------------------------
Lot_Id,
Case When Lot_Id <> Nxt_Lot_Id
Then '*'
Else ' '
End As
Chg,
Nxt_Lot_Id,
------------------------------------------
User_Nm,
Case When User_Nm <> Nxt_User_Nm
Then '*'
Else ' '
End As
Chg,
Nxt_User_Nm
From
(
Select Item_Id,
Lead(Item_Id) Over (Order By Item_Id)
As
Nxt_Item_Id,
----------------------------------------------
Lot_Id,
Lead(Lot_Id) Over (Order By Item_Id)
As
Nxt_Lot_Id,
-----------------------------------------------
User_Nm,
Lead(User_Nm) Over (Order By Item_Id)
As
Nxt_User_Nm
-----------------------------------------------
From AuditTbl
)
Where Nxt_Item_Id is not null;

See Oracle documentation for further detail:
Lead

Saturday, June 28, 2008

Comparing Data From Two Similar Queries

At one point my company was loading older data into Oracle tables. There was a constant stream of requests to compare two subsets of data from different queries. After awhile, I coded this master query which would run two other queries, and compare the data between the them.

In this example, I've created a second EMP table and made a couple of changes to the data.
Create Table Emp2
As
Select *
From Emp;
Update Emp
Set Sal = 950
Where EmpNo = 7369;
Update Emp2
Set Job = 'DBA'
Where EmpNo = 7566;
Insert Into Emp2 Values
(8484,'MICKEY','CEO',NULL,'01-JUL-80',9999,5000,10);
The names of these two queries need to be set equal to 'Code1' and 'Code2' in the Defines.
Empcd1:
Select Empno, Ename, Job, Hiredate, Sal, Deptno
From Emp
Where To_Char(Hiredate,'yyyy') Between 1980 And 1981;

Empcd2:
Select Empno, Ename, Job, Hiredate, Sal, Deptno
From Emp2
Where To_Char(Hiredate,'yyyy') Between 1980 And 1981;

The field that will be used to match the records needs to be set equal to Sort_Fld in the Define. In this example, I was matching on 'Empno'.

This query reads the data into two temporary tables shown in the 'With' clause. The two are compared with separate 'minus' queries and then unioned together to get a full set.
--+----------------------------+--
--| Fill In These Three Fields |
--+----------------------------+--
Define Sort_Fld = Empno
Define Code1 = Empcd1
Define Code2 = Empcd2

Break On &Sort_Fld Dup Skip 1
Column Srt Noprint
Column Tblx Noprint

With
Table1 As
(
@&Code1
),
Table2 As
(
@&Code2
)
Select vt1.&Sort_Fld Srt,
vt1.Tbl Tblx,
vt1.*
From
(
----------------------------------
-- Add User To Differences
----------------------------------
(Select 't1' Tbl, In1not2.*
From
-------------------------
-- Show Records In 1 Not 2
-------------------------
(Select * From Table1
Minus
Select * From Table2
)In1not2
)
Union
-----------------------------
-- Add User To Differences
-----------------------------
(Select 't2' Tbl, In2not1.*
From
-------------------------
-- Show Records In 1 Not 2
-------------------------
(Select * From Table2
Minus
Select * From Table1
)In2not1
)
)Vt1
Order By Vt1.&Sort_Fld, Vt1.Tbl;
See Oracle documentation for further detail:
Set Operators: Union and Minus
With Clause - Subquery Factoring

Wednesday, June 25, 2008

An Easy Way to Debug SQL Queries


This is a separate piece of code that makes it easy to debug another query. When there is a problem with your code, run this from the prompt. In my case, I've cleverly named this file D.SQL. When I type @D and answer 'on,' any query I run next will have echo, headings, verify, timing, feedback and term set to 'on.' Additionally, any columns not being printed (coded with 'noprint') will be displayed.

You use the following variables in your queries:

  • &OnOff For example:
   Set Term &OnOff
Set Echo &OnOff
  • &xPrint For example:
     Column Loc_ID &xPrint
Use these only when you normally want the function Off. If you want Term On at a certain point (i.e., with Accept), don't use this variable.

--+-----------------------------------------------------------------------+
--| Ask the question
--+-----------------------------------------------------------------------+
Set Echo Off
Set Term On

Prompt
Accept OnOff Format A3 Default Off -
Prompt 'Do you want Echo/Feedback/Verify/Timing/Print On or Off? '
Prompt

Set Term Off
--+-----------------------------------------------------------------------+
--| Set everything off so it doesn't show up in this program
--+-----------------------------------------------------------------------+

Set Feedback Off Timing Off Verify Off Heading Off Newpage None Echo off
Ttitle Off
Repheader Off

--+-----------------------------------------------------------------------+
--| Set up 'amper'-xprint to be used in code in place of 'noprint'
--+-----------------------------------------------------------------------+

Column PrintOrNo New_Value xPrint Noprint

Select Decode(Upper('&OnOff'), 'ON' , 'Print' ,
'OFF', 'NoPrint' ,
'NoPrint'
)
As
PrintOrNo
From Dual;
--+-----------------------------------------------------------------------+
--| Set Each Debugging Tool On Or Off As Requested.
--+-----------------------------------------------------------------------+
Set Feedback &Onoff
Set Timing &Onoff
Set Verify &Onoff
Set Echo &Onoff
Set Heading On
Set Term On
See Oracle documentation for further detail:
Accept
Decode
Set Feedback Timing Verify Heading Newpage Echo

Calculating Elapsed Time

A table (Time) is created with one date field (Curr_Min) in which, periodically, the Sysdate is entered.

Using the Lead function, the time from the next row is brought up next to the current row time so one can be subtracted from the other.

The calculation for elapsed time is a bit complicated since we're dealing with time rolling past midnight. (Thanks to Ray Dooley for this calculation.)
Column Elapsed_Time Heading Elapsed|HH:MI:SS
Column Row_Time Heading Row|Time
Column Next_Time Heading Next|Time

Select To_Char(Curr_Min, 'dd Dy Hh24:Mi:Ss')
As
Row_Time,
To_Char(Next_Min, 'dd Dy Hh24:Mi:Ss')
As
Next_Time,
--------------------------------------------------
To_Char(
Case
When Next_Secs >= Row_Secs
Then To_Date((Next_Secs-Row_Secs),'sssss' )
Else To_Date((86399 -Row_Secs)+ Next_Secs,'sssss')
End
,'Hh24:Mi:Ss'
)
As
Elapsed_Time
From
--+--------------------------------------------------------------------+
--| The subquery gets the current and next time formatted for display
--| and also in Secs in order to make the parent query easier to read.
--+--------------------------------------------------------------------+
(
Select Curr_Min,
-----------------------------------------------------------
Lead(Curr_Min,1,Null) Over (Order By Curr_Min)
As
Next_Min,
-----------------------------------------------------------
To_Char(Curr_Min,'sssss')
As
Row_Secs,
-----------------------------------------------------------
To_Char(Lead(Curr_Min,1,Null)
Over (Order By Curr_Min),'sssss')
As
Next_Secs
From Time
)
;
See Oracle documentation for further detail:
Lead

Calculating Leap Year


I set up a table with approximately 200 years in it. Then using the formula from Wikipedia, I calculated whether it was a leap year or not.
If (
(Year Modulo 4 Is 0) And
(Year Modulo 100 Is Not 0)
)
Or (Year Modulo 400 Is 0)
Then Leap
Else No_Leap
To double-check, I set up the year with February, and asked what the last day of the month was. The subquery did all the work; the parent query asked for a count of distinct combinations.
Column Leap_or_No Heading 'Leap|or No'
Column Day Heading 'Feb|Last|Day' Format A4

Select Leap_or_No,
To_Char(FebDay,'dd') Day,
Count(*)
From (
Select Year,
-------------------------------------------
Case When (Mod(Year, 4)= 0
And Mod(Year, 100)<>0
)
Or Mod(Year, 400)= 0
Then 'Leap'
Else 'No Leap'
End
As
Leap_or_No,
--------------------------------------------
Last_Day(To_Date('01-Feb-'||Year))
As
FebDay
---------------------------------------------
From Year
)
Group By Leap_or_No,
To_Char(FebDay,'dd') ;
See Oracle documentation for further detail: Mod

Tuesday, June 24, 2008

Retain Leading Zeros in Excel


To keep the leading zeros when taking data into Excel, do three things:

1) pad with zeros,
2) concatenate with an equal sign
3) set Entmap Off for that particular column
SET MARKUP HTML ON ENTMAP ON SPOOL ON PREFORMAT OFF
SET TERM OFF

COLUMN Empno ENTMAP OFF

SPOOL Employee.xls

Select '="'||LPad(EmpNo,8,0)||'"'
As
EmpNo,
--------------------------------
EName
From Emp;

SPOOL OFF
SET MARKUP HTML OFF ENTMAP OFF SPOOL OFF PREFORMAT OFF
SET TERM ON
See Oracle documentation for further detail:
Generating HTML Reports from SQL*Plus

Exists vs. In

The following two queries produce the same result. Using Exists can be a lot faster since often only an index needs to be checked.
Select Table_Name
From User_Tables
Where Table_Name In (Select Table_Name
From User_Constraints
)
;

Select Table_Name
From User_Tables Ut
Where Exists (Select 1
From User_Constraints Uc
Where Ut.Table_Name = Uc.Table_Name
)
;
See Oracle documentation for further detail:
Exists
Correlated Subquery

Monday, June 23, 2008

Case Examples

With this data, these are fairly nonsensical examples, but the basic logic is some from real code.
Example 1:


















Column NewJob Format A15 -
Heading 'New Job Desc'

Select EmpNo,
EName,
-------------------------------
Case When Job = 'CLERK'
Then 'Admin Assistant'
Else InitCap(Job)
End
As
NewJob
-------------------------------
From Emp
Where DeptNo = 30;



Example 2:
























Column NewGrade   Format A5  Heading 'New|Grade'

Select EmpNo,
EName,
Sal,
----------------------------------------
Case When (Sal >= 1000)
And (Sal &lt= 1500)
Then '0'
--------------------
When (Sal > 1500)
Then (
Select To_Char(Grade)
From SalGrade
Where Sal Between LoSal
and HiSal
)
--------------------
Else '?'
End
As
NewGrade
--------------------------------------
From Emp
Where DeptNo = 30;

Example 3:
Column DataLen Format A6 Heading 'Data|Length'

Select Column_Name,
Data_Type,
------------------------------------------------------
Case When (Data_Precision is Null)
Then To_Char(Data_Length,'999')
Else ' '||Data_Precision||'.'||Data_Scale
End
As
DataLen
------------------------------------------------------
From User_Tab_Columns
Where Table_Name = 'EMP';

Example 4:
Column CommStatus Format A16 Heading 'Commissioned|Status'


Select EmpNo, EName, Sal, Comm,
-------------------------------
Case When Comm is Null
Or Comm = 0
Then 'Not Commissioned'
Else 'Commissioned'
End
As
CommStatus
-------------------------------
From Emp
Where DeptNo = 30;


Example 5:

Select Ename, Job, Deptno, Mgr,
--------------------------------------
Case When Job In ('MANAGER' , 'SALESMAN' )
Then
Case When DeptNo = 10
Then 'Type A'
Else 'Type B'
End
Else
Case When Mgr = 7698
Then 'Type C'
Else 'Type D'
End
End
As
Type
--------------------------------------
From Emp;
See Oracle documentation for further detail:
Case expressions

Sunday, June 22, 2008

Cleaning up Defines


If you've ever had problems with code and realized a defined variable had not been cleaned up, this may come in handy. I realize that you should undefine things at the bottom of a query, but sometimes in the rush, this gets left out.

First an external table is created with the following code:
Drop Table Defines;

Create Table Defines
(Def_Var Varchar2(250)
)
Organization External
(Type Oracle_Loader
Default Directory Ext
Access Parameters
(Records Delimited By Newline)
Location (Ext:'defines.txt')
);
After that is done one time, the following query can be run at any time to clear any defines left behind. The spooled file will be read as a table to generate a list of defined names.
--+-------------------------------------------------------------------+
-- Send output from Define to a spool file that is set up as an
-- external table
--+-------------------------------------------------------------------+

Set Term Off

Spool Defines.Txt
Define
Spool Off

--+-------------------------------------------------------------------+
-- Get a list of the defines that need to be cleared and write that to
-- a file with a SQL extension to be executed.
--+-------------------------------------------------------------------+

Set Pagesize 0
Set Echo Off

Spool Undef.SQL

Select 'Undefine '||
------------------------------------------------------------------------
-- Pick up the word between 'DEFINE' and '='
------------------------------------------------------------------------
Substr(
Def_Var, -- search this string
8, -- start at this position
-----------------------------------------------
-- for this length of Instr -
-- (Instr is counting for one so subtract 8 -
-- from the results.) -
-----------------------------------------------
(
Instr(
Def_Var, -- search this string
'=' -- for this substring
)
-8
)
-----------------------------------------------
)
As
SubVar
------------------------------------------------------------------------
From Defines
Where Substr(Def_Var,8,1) <> '_' -- don't include system variables
And Substr(Def_Var,1,6) = 'DEFINE' -- ignore lines not DEFINEs
;
Spool Off

@Undef

Set Term On


Notes on Instr and Substr: INSTR returns the position of a particular occurrence of a substring in a string. SUBSTR returns certain characters from the string.

Instr(
Search_This_String,
For_This_Substring,
Begin_Search_At_Starting_Pos,
Look_For_This_Occurrence
)

Substr(
Search_This_String,
Start_From_This_Position,
For_Optional_Length
)
See Oracle documentation for further detail:
External Tables Concepts
Substr

Unrelated Data Side-by-Side


To show unrelated data side by side, gather the data as separate queries adding a 'key' by using RowNum.

The parent query will then read the individual queries and match them on the key. As stated in the heading, the data from subquery one for key '1' is not related to the data from subquery two for key '1', etc.

Be sure to state a full outer join so no records are left behind on either side.
Column Key Noprint
Column Divider1 Heading ' '
Column Divider2 Heading ' '

Select Key,
Empno , Ename, '|' as Divider1,
Deptno, Loc , Dname, '|' as Divider2,
Grade , Losal, Hisal
From
-------------------------------
(
Select Rownum As Key,
Empno, Ename
From Emp
) Vte
-------------------------------
Full Outer Join
(
Select Rownum As Key,
Deptno, Loc, Dname
From Dept
) Vtd
Using (Key)
-------------------------------
Full Outer Join
(
Select Rownum As Key,
Grade, Losal, Hisal
From Salgrade
) Vts
Using (Key)
-------------------------------
Order By Key ;
See Oracle documentation for further detail:
Joins
Rownum

In-line View


An in-line view is created when a Select statement is used in the From clause. This sets up a table that is there only until the query ends. The first and second examples of code produce exactly the same results.

The second example pulls only the data necessary from the individual tables before they are then joined with a parent Select.
Set Pagesize 60
Break on DeptNo on DName on Loc Skip 1




Select DeptNo, DName, Loc,
EmpNo, EName, Job
From Emp
Join Dept
Using (DeptNo)
Where Deptno != 30
And Job in ('CLERK', 'ANALYST')
Order By DeptNo;




Select DeptNo, DName, Loc,
EmpNo, Ename, Job
From
------------------------------
(
Select Deptno, Dname, Loc
From Dept
Where DeptNo != 30
)
------------------------------
Join
(
Select Empno, Ename, Job, Deptno
From Emp
Where Job in ('CLERK', 'ANALYST')
)
------------------------------
Using (Deptno)
Order by Deptno;
See Oracle documentation for further detail:

Friday, June 13, 2008

Grouping Sets

GROUPING SETS allow a set of records to be grouped in multiple ways. In these reports, we are looking for a total of the salary field based on job and department, or department only, or job only. The '( )' is used to indicate a total for the report.

(In these examples, the Where excludes department 30 just to reduce output.)

This report shows two groupings:
  • the sum of salary based on DeptNo & Job
  • total by DeptNo
 Select DeptNo, Job, Sum(Sal)
From Emp
Where Deptno != 30
Group By GROUPING SETS ( (DeptNo, Job),
DeptNo
)
Order By DeptNo, Job;

This report shows two groupings:
  • sum of each DeptNo
  • then the sum of each Job
 Select DeptNo, Job, Sum(Sal)
From Emp
Where Deptno != 30
Group By GROUPING SETS ( DeptNo,
Job
)
Order By DeptNo, Job;

This report shows four groupings:
  • Sum of Salary based on DeptNo & Job
  • the total for each DeptNo
  • the total for each Job
  • Total for report - indicated by ( )
 Select DeptNo, Job, Sum(Sal)
From Emp
Where Deptno != 30
Group By GROUPING SETS ( (DeptNo, Job),
DeptNo,
Job ,
( )
)
Order By DeptNo, Job;
See Oracle documentation for further detail:
Grouping Sets

Show Information on Duplicate Values



This report will let you not only see which rows have duplicate values, but the data associated with those values. In this example, I'm looking for the information for people with the same job.

The Break statement lets us view the duplicates in groups. I'm selecting the fields I want displayed, and then asking for a count within a job value. The parent query weeds out all the jobs that have only one value (i.e., King, President)
Break on Job Dup Skip 1
Select Job, EmpNo, EName, Hiredate, Sal
From
(
Select Job,
EmpNo,
EName,
Hiredate,
Sal,
------------------------------------------
Count(EName) Over (Partition By Job)
As
Cnt_Nm
------------------------------------------
From Emp
)
Where Cnt_Nm > 1;


See Oracle documentation for further detail:
Partition By clause

Thursday, June 12, 2008

Find Underscores in an Alphabetic Field


To find an underscore in a name, it must be 'escaped.' In this case, I'm using a backslash, but it can be other characters as well.

Select Table_Name
From All_Tables
Where Table_Name Like '%\_%' Escape '\'
And Rownum <>
See Oracle documentation for further detail:
Like & Escape

Single Quote in Alphabetic Fields

To insert a name with a single quote, type two single quotes:
Insert Into Emp
(EmpNo, EName , DeptNo)
Values (7474 , 'O''MALLEY', 10 );
Commit;
To search for a name with a single quote in it, type two single quotes:
Select EmpNo, Ename, DeptNo
From Emp
Where EName Like '%''%';

Sunday, June 8, 2008

Report Headings

To create report titles you can use Ttitle, Repheader, or Prompt.

TTITLE

With Ttitle, you can use system variables such as sql.pno. xToday and xTime is set in ‘GetDate.Sql.' These variables print at the top of every ‘page', which in this case, because of the break on Deptno is at the top of every department.
Define xTitle = 'Employees By Department'
Define xSpool = TitleTest3.Txt
@GetDate
------------------------------------------------------------------------
Ttitle -
Left 'User: ' SQL.USER Col 20 xTitle Col 50 Xtoday Xtime Skip 1 -
Left 'Page: ' Format 999 sql.pno Col 50 ' Spool: ' xSpool Skip 2 -
Left 'Dept: ' Format 99 xDeptno' - 'Format a20 xDept Skip 2
------------------------------------------------------------------------
@TitleRpt


REPHEADER
Repheader displays on the first page of the report. You can still use Ttitle to make subtitles. This would be great except that it insists on printing the title first (see highlighted lines).
Define xTitle = 'Employees By Department'
Define xSpool = TitleTest4.Txt
@GetDate

----------------------------------------------------------------------
repheader page -
Left 'User: ' SQL.USER Col 20 xTitle Col 50 Xtoday Xtime Skip 1 -
Left 'Page: ' Format 999 sql.pno Col 50 ' Spool: ' xSpool Skip 2

ttitle -
Left 'Department: 'Format 99 xDeptno' - 'Format a20 xDept Skip 2
-----------------------------------------------------------------------

Spool &xSpool
@TitleRpt


PROMPT

The Prompt will display on the screen (if Term is on) and also to the report if you are spooling at the time. You can't use the system variables. You have to use the '&' in front of the variables.
Define xTitle = 'Employees By Department'
Define xSpool = TitleTest5.Txt
@GetDate
---------------------------------------------------------------
Spool &xSpool

Prompt User: &xUser &xTitle &Xtoday&Xtime
Prompt Spool: &xSpool
Prompt

ttitle Left 'Department:'Format 99 xDeptno' - ' xDept Skip 2
---------------------------------------------------------------
@TitleRpt


GETDATE.SQL
Set Term Off
Column User New_Value xUser
Column ThisDay New_Value xToday
Column ThisTime New_Value xTime

Select User,
To_Char(Sysdate,'fm Month dd, yyyy Day') as ThisDay,
To_Char(Sysdate,' HH24:mi') as Thistime
From Dual;
Set Term On


TITLERPT.SQL
Break On DeptNo Skip Page

Column DeptNo New_Value xDeptNo NoPrint
Column Dept New_Value xDept NoPrint

Select EmpNo, EName, Job, Sal, HireDate, Mgr,
DeptNo, DName||', '||Loc as Dept
From Emp
Join Dept
Using (DeptNo)
Order By DeptNo;

Spool Off

Check for Alpha Only

This is a way to restrict data entry to alpha if you are using a release below 10G. It's not particularly pretty, but it works.

I'm basically translating all alpha characters to spaces, and then trimming them out,which of course, leaves everything else. Then I check for a length. Remove the UPPER if you don't want lower case.
Create Table Testalpha
(Alphaonly Varchar2(10)
Constraint Ck_Alphaonly Check
(Length
(Trim
(Translate
(Upper(alphaonly),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
' '
)
)
) is null
)
);
In 10G, you can use the REGEXP functions:

Create Table Testalpha
(Alphaonly Varchar2(10)
Constraint Ck_Alphaonly Check
(Regexp_Like(AlphaOnly, '^[[:alpha:]]+$')
)
);

Saturday, June 7, 2008

Correlated Subquery

Most subqueries are resolved before Oracle moves on to handle its parent query. Sometimes, that's not possible since the subquery if based on one of the columns on the parent query.

An example is shown below. We want to find who makes the least in each department. So, in the subquery, determining the mininum salary is done based on the department number of the employee in the parent query.
Select Empno, Ename, DeptNo, Sal
From Emp e1
Where Sal = (
Select Min(Sal)
From Emp E2
Where e2.DeptNo = e1.DeptNo
)
Order By DeptNo;

Self-Join

The employee records have two different employee numbers -- one for the individual and one for their boss. The people in department 10 are listed below. As shown, Clark reports to 7839, who we can see is King. Miller reports to 7782, who is Clark.


To do this in SQL, you need to join the table to itself. It is defined twice in the query, and given different aliases by which we can address each version of the table.
Select e1.EmpNo,
e1.EName,
e1.Mgr ,
e2.EName As Manager
From Emp e1
Join Emp E2
On e1.Mgr = e2.EmpNo
Where e1.DeptNo = 10
Order By EmpNo;

EMPNO ENAME MGR MANAGER
------ ---------- ---------- ---------
7782 CLARK 7839 KING
7934 MILLER 7782 CLARK

Outer Join


Column Key_L  Format 999999 Heading Key-L
Column Key_R Format 999999 Heading Key-R
Column Join_type Format A20 Heading " "
Break On Join_type

Drop Table Table_L;
Drop Table Table_R;

Create Table Table_L (Key_L Number(1));
Create Table Table_R (Key_R Number(1));

Insert Into Table_L Values (1);
Insert Into Table_L Values (2);

Insert Into Table_R Values (1);
Insert Into Table_R Values (3);

Select * from Table_L;
Select * from Table_R;

Prompt =========================================================
Prompt = Non-Ansi Joins
Prompt =========================================================

Define Join_type = "'Key_L = Key_R'"

Select &Join_Type as Join_type, KEY_L, KEY_R
From Table_L L
, Table_R R
Where L.Key_L = R.Key_R;

Define Join_type = "'Key_L(+) = Key_R'"
Select &Join_Type as Join_type, KEY_L, KEY_R
From Table_L L
, Table_R R
Where L.Key_L(+) = R.Key_R;

Prompt
Prompt =========================================================
Prompt = Ansi-Compliant Joins
Prompt =========================================================

Define Join_type = "'Join'"
Select &Join_Type as Join_type, KEY_L, KEY_R
From Table_L
Join Table_R
On ( Key_L = Key_R);

Define Join_type = "'Right Outer Join'"
Select &Join_Type as Join_type, KEY_L, KEY_R
From Table_L Right Outer
Join Table_R
On ( Key_L = Key_R);

Define Join_type = "'Full Outer Join'"
Select &Join_Type as Join_type, KEY_L, KEY_R
From Table_L Full Outer
Join Table_R
On ( Key_L = Key_R);

Friday, June 6, 2008

DML/DDL


T A B L E S P A C E S
Create
CREATE TABLESPACE tablespacename
DATAFILE datfile_name filesize; -- size 100m autoextend on;

T A B L E S
Create with Primary Key
CREATE TABLE table
(
field1
VARCHAR2(1) CONSTRAINT pk_constraint PRIMARY KEY
,field2 NUMBER NOT NULL
,field3 DATE
);

Create with Composite Primary Key
CREATE TABLE table
(
field1
VARCHAR2(5),
field2 NUMBER(7,2),
field3 DATE ,
CONSTRAINT pk_table1 PRIMARY KEY (field1, field2)
USING INDEX TABLESPACE tablespace
);
Create Foreign Key
CREATE TABLE table
(
field1
VARCHAR2(5) CONSTRAINT pk_constraint PRIMARY KEY,
field2 VARCHAR2(5) REFERENCES table2 (field2) ON DELETE SET NULL,
field3 VARCHAR2(5)
);
Create Partitioned Table By Range
CREATE TABLE table
(
field1
VARCHAR2(5) CONSTRAINT pk_constraint PRIMARY KEY,
field2 NUMBER(7,2),
field3 DATE
)
PARTITION BY RANGE (field2)
(
PARTITION part1 VALUES LESS THAN ('value') TABLESPACE tablespace
PARTITION part2 VALUES LESS THAN (MAXVALUE) TABLESPACE tablespace
);
Create Partitioned Table By Hash
  • Placement is determined by hashing the partition key.
  • Decreases I/O contention by a greater distribution of data.
CREATE TABLE table
(
field1 VARCHAR2(1) CONSTRAINT pk_constraint PRIMARY KEY
,field2 NUMBER NOT NULL
,field3 DATE
)
PARTITION BY HASH (field2)
(
PARTITION part1 TABLESPACE tablespace1
,PARTITION part2 TABLESPACE tablespace2
);
Create Subpartition
CREATE TABLE table
(
field1 VARCHAR2(1) CONSTRAINT pk_constraint PRIMARY KEY
,field2 NUMBER NOT NULL
,field3 DATE
)
PARTITION BY RANGE (field2)
SUBPARTITION BY HASH (field3) SUBPARTITIONS number
(
SUBPARTITION part1 VALUES LESS THAN ('value') TABLESPACE tablespace
,SUBPARTITION part2 VALUES LESS THAN (MAXVALUE) TABLESPACE tablespace
);
Create Partitioned Table By List
CREATE TABLE table
(
field1
VARCHAR2(5) CONSTRAINT pk_constraint PRIMARY KEY
,field2 NUMBER(7,2)
,field3 DATE
)
PARTITION BY LIST (field2)
(
PARTITION part1 VALUES ('value1','value2') TABLESPACE tablespace1
,PARTITION part2 VALUES ('value3','value4') TABLESPACE tablespace2
);
Create Index-Organized Table
  • Sorted by primary key.
  • All data is stored in the index.
  • If 'Where' always uses primary key
  • Use only if the data is very static
  • Most effective when primary key is most of the table
  • You can use the 'move online'* option of 'alter table'
CREATE TABLE table
(
field1
VARCHAR2(5) CONSTRAINT pk_constraint PRIMARY KEY,
field2 VARCHAR2(5) ,
field3 VARCHAR2(5)
)
ORGANIZATION INDEX
Copy Some Rows & Columns (but not constraints) (not allowed if LONG type)
CREATE TABLE table2
AS SELECT field1, field2, field3+field4 as field34
FROM table1
WHERE table1.field = value;
Copy Structure Only
CREATE TABLE table2
AS SELECT *
FROM table1
WHERE 1 = 2;
Copy Table without Generating Redo Log
CREATE TABLE table2
NOLOGGING
AS SELECT *
FROM table1;
Drop (2 ways)
DROP TABLE table [CASCADE CONSTRAINTS] PURGE;
DROP TABLE table;
PURGE TABLE table;
Truncate (can't rollback) (also see delete rows)
TRUNCATE TABLE table;
Rename (2 ways)
RENAME table1 TO table2; 
ALTER TABLE table RENAME TO table;
Comment
COMMENT ON TABLE table IS 'some table comment';
V I E W S

Create
CREATE [OR REPLACE] VIEW view
AS SELECT *
FROM table1
[WITH READ ONLY];
CREATE [OR REPLACE] VIEW view
(
field1
, field2
)

AS SELECT fielda, fieldb
FROM table1;
Drop
DROP VIEW view;
C O N S T R A I N T S

Add

ALTER TABLE table
ADD CONSTRAINT pk_constraint PRIMARY KEY (field);
ADD CONSTRAINT fk_constraint FOREIGN KEY (field) REFERENCES table2 (field);
ADD CONSTRAINT uk_constraint UNIQUE (field);
ADD CONSTRAINT ck_constraint CHECK (field = value);
MODIFY (field NOT NULL);
Disable
ALTER TABLE table
DISABLE PRIMARY KEY [CASCADE];
DISABLE CONSTRAINT uk_constraint;
Drop
ALTER TABLE table
DROP PRIMARY KEY CASCADE;
DROP CONSTRAINT constraint;
DROP UNIQUE (field);
MODIFY (field NULL);
Enable
ALTER TABLE table
ENABLE [NOVALIDATE] PRIMARY KEY;
ENABLE CONSTRAINT uk_constraint;
I N D E X E S

  • a B*-tree is typical - matches column values to their related RowIDs
Create
CREATE UNIQUE INDEX index
ON table1(field1,field2) /* if multi-column, put most accessed column first */
[REVERSE];

Add Primary Key
ALTER TABLE table
ADD PRIMARY KEY (field);
Bitmap Index
  • Use only if data infrequently updated
  • Use only in batch processing
  • Only on column with just a few values
  • i.e.,
         Y 00010011
N 11101100

CREATE BITMAP INDEX indexname ON table(column);
Create Index for Subpartition
CREATE INDEX index ON table1(field1)
LOCAL /* Create a separate index for each partition of table */
(
PARTITION part1 TABLESPACE tablespace,
PARTITION part2 TABLESPACE tablespace
);
Drop
DROP INDEX index;
Rebuild
  • Rebuilt from the index so there must be enough space for original and new.
  • 'Online' allows rebuild while being accessed.
ALTER INDEX index REBUILD [ONLINE]
STORAGE (INITIAL 10m NEXT 5m PCTINCREASE 0)
TABLESPACE tablespace;
C O L U M N S

Add
ALTER TABLE table  ADD (field data_type);
Add a Not Null Column (3 steps)
ALTER TABLE table
ADD (field data_type);
UPDATE table
SET column =(
SELECT column
FROM table2
WHERE table1.key = table2.key
);

ALTER TABLE table
MODIFY (field NOT NULL);
Drop (option 1 - 2 steps)
ALTER TABLE table  SET UNUSED COLUMN field;
ALTER TABLE table DROP UNUSED COLUMNS;
Drop (option 2)
ALTER TABLE table
DROP COLUMN field1 ; -- one column
DROP (field1, field2); -- more than one column
Rename
ALTER TABLE table RENAME COLUMN current_column TO new_column;
Change Length
ALTER TABLE table  MODIFY (field data_type);
Decrease Length (4 steps)
CREATE TABLE table2 (key, column)
AS
SELECT key, column
FROM table1;

UPDATE table1
SET column = NULL;

ALTER TABLE table1
MODIFY (column VARCHAR2(5));

UPDATE table
SET column =(SELECT column
FROM table2
WHERE table1.key = table2.key);
Comment
COMMENT ON COLUMN table.field IS 'column comment';
D A T A ( D M L )

Insert (2 ways)
INSERT INTO table
VALUES ('xyz',123);

INSERT /*+ APPEND */ INTO table -- hint is optional
SELECT field1, field2
FROM table1;
COMMIT; -- set autocommit off/on/#
Change
UPDATE table
SET field1 = 'value' ,
field2 = value
WHERE key = value;
COMMIT;
Delete All Rows
DELETE FROM table;
ROLLBACK;
Delete Some Rows
DELETE FROM table where field1 = value;
COMMIT;
R E C Y C L E B I N

Purge
PURGE RECYCLEBIN;
C O M M I T / R O L L B A C K

Note: Exit, Quit or any DDL statement force a commit.
If the machine goes down, an automatic rollback occurs.
Set autocommit off; -- preferred

Savepoint

Insert ...
Delete ...
Savepoint A;

Insert ...
Delete ...
Savepoint B;

Select ...
Rollback to savepoint B;

Creating a Simple Menu with SQL+


This query does the following: Clear the screen before prompting for values. After the user selects an option, the terminal is turned off so the decode is behind the scenes. It's then turned back on. The program is run using '@'. The Accept is needed afterwards to get this query to stop and allow them to view the screen. At the end, this code is called again. You'll need to Ctrl+C to get out of this loop.
Clear Screen
Prompt
Prompt +-----------------------------+
Prompt | M y T a b l e s |
Prompt +-----------------------------+
Prompt | |
Prompt | 1. Insert Data |
Prompt | 2. View Data |
Prompt +-----------------------------+
Prompt
Accept Report Prompt 'Please Enter Choice: '
Clear Screen

Set Verify Off
Set Term Off
-- Set up an amper variable with the program name from the decode
Column DecodeReport New_Value NewReport

Select Decode (&Report, '1', 'iexample', -- insert into table
'2', 'sexample', -- Select all recs from table
'mexample' -- if not valid, rerun
) As DecodeReport
From Dual;

Set Term On Feedback Off

@&NewReport -- run the code selected in the decode

-- Accept needed to hold on screen
Accept Holder Prompt 'Press Enter to continue...'

Undefine Report NewReport Holder

@mExample -- when done, rerun this menu
-- (Ctrl+C to escape)

Use Variables to Insert Data














In this example, a user is asked for three kinds of variables: Character, Date & Number. They are then inserted into an 'INSERT INTO' statement. (In entering the date, the year is assumed to be the current one.) After insertion, a SELECT is performed to show the user the data entered as it is in the table.
Set Verify Off
Set Feedback Off
Prompt Please Enter...

--+------------------------------------------------------------------
--| Ask for data
--+------------------------------------------------------------------

Accept Who Prompt 'Name: '
Accept Month Format 99 Prompt 'Month: '
Accept Day Format 99 Prompt 'Day: '
Accept Amount Prompt 'Amount: '

--+- 1 --------------------------------------------------------------
--| Insert the record based on the values typed (Assume current year)
--+------------------------------------------------------------------

Insert Into TestData Values
(
Test_Seq.NextVal
,
Upper('&Who')
,
&Day
|| '-'
|| Decode(&Month, 1,'JAN', 2,'FEB', 3,'MAR', 4,'APR',
5,'MAY', 6,'JUN', 7,'JUL', 8,'AUG',
9,'SEP', 10,'OCT', 11,'NOV', 12,'DEC',
'???')
|| '-'
|| To_Char(Sysdate,'YY')
,
&Amount
);
Commit;

--+- 2 --------------------------------------------------------------
--| Get the current value
--+------------------------------------------------------------------

Set Term Off

Column CurrVal New_Value CurrVal

Select Test_Seq.Currval CurrVal
From Dual;

--+- 3 --------------------------------------------------------------
--| Display the record just entered.
--+------------------------------------------------------------------

Set Term On

Select *
From Test_Data
Where Test_Id = &CurrVal
;
Undefine Who Month Day Amount
Set Feedback On
Set Verify On

Oracle documentation:

Wednesday, June 4, 2008

Create Synonyms for Entire Schema



This query generates another SQL query (output on left) that issues DROP and then CREATE SYNONYM commands. In this instance, it is doing it for the tables in ALL_CATALOG that begin with 'USER.' It doesn't actually run the code so you have a chance to evaluate if it's doing what you want.

The second part generates a list (output on right) of those synonyms that had to have a number attached to make a unique synonym. So, unless a table/view name appears on this list, you would just have to type the initials. For example to query User_Nested_Tables, type UNT. But, since there are User_Objects and User_Opancillary, you can't type UO; you must type UO1 or UO2.
--+- 1 -----------------------------------------------------------------
--| This data is pulled twice. Once by the query generation, and a
--| second time by the report that shows like keys.
--+---------------------------------------------------------------------

CREATE GLOBAL TEMPORARY TABLE Global_Temp_TabKeys
ON COMMIT PRESERVE ROWS
AS
Select Table_Key||
------------------------------------------------------------
-- When the number of like keys is greater than one, take
-- the row_number and concatenate it to the key to get the
-- synonym.
------------------------------------------------------------
Case When Count(*) Over(Partition By Table_Key)>1
Then Row_Number( ) Over(Partition By Table_Key
Order By Table_Key)
End As
xSynonym,
---------------------------------------------------------
Count(*) Over(Partition By Table_Key) As
Ctr,
---------------------------------------------------------
Table_Name,
---------------------------------------------------------
Table_Key
From
--+- 1.1 ---------------------------------------------------
--| Create the key by getting the first character.
--| If there is a 1st occurence of '_' in the name,
--| then pick up the next character.
--| If there is a 2nd occurence of '_' in the name,
--| then pick up the next character. Etc.
--+---------------------------------------------------------
(
Select Substr(Table_Name,1,1) ||
---------------------------------------
Case When Instr(Table_Name,'_',2,1) <> 0
Then Substr(Table_Name,
(Instr(Table_Name,'_',2,1))+1,1)
End ||
---------------------------------------
Case When Instr(Table_Name,'_',2,2) <> 0
Then Substr(Table_Name,
(Instr(Table_Name,'_',2,2))+1,1)
End ||
---------------------------------------
Case When Instr(Table_Name,'_',2,3) <> 0
Then Substr(Table_Name,
(Instr(Table_Name,'_',2,3))+1,1)
End ||
---------------------------------------
Case When Instr(Table_Name,'_',2,4) <> 0
Then Substr(Table_Name,
(Instr(Table_Name,'_',2,4))+1,1)
End
---------------------------------------
As
Table_Key,
---------------------------------------------------
Table_Name
---------------------------------------------------
From All_Catalog
Where Table_Type In ('TABLE','VIEW')
And Table_Name Like 'USER%'
)
;

--+- 2 -----------------------------------------------------------------
--| This will generate a bit of code that will drop and create all
--| synonyms based on the keys created in the global temp table.
--+---------------------------------------------------------------------

Set Term Off
Set Pagesize 0

Column Table_Key Print
Column Semicolon Fold_After Format A10
Column xSynonym Format A10

Spool CrSyn.Sql

Select 'Drop Synonym', -- spaced to match Create
-------------------------------------------------
xSynonym,
';' As
Semicolon, -- named so I can fold after
-------------------------------------------------
'Create Synonym',
-------------------------------------------------
xSynonym,
-------------------------------------------------
'for ' || Table_Name || ';'
From Global_Temp_TabKeys;

Spool Off

--+- 3 -----------------------------------------------------------------
--| Make a list of those tables that need a number to be unique.
--+---------------------------------------------------------------------

Spool SynLst.Txt
Break On Table_Key Skip 1
Column Table_Key &xPrint
Set Term On

Prompt
Prompt These Tables Have Liked-name Keys:
Prompt

Select xSynonym, Table_Name , Table_Key
From Global_Temp_TabKeys
Where Ctr > 1
Order By xSynonym
/
Spool Off

Prompt
Prompt Your Sql code has been spool to CrSyn.Sql.
Prompt Execute it after you check and approve it.
Prompt
Prompt This report is SynLst.Txt
Prompt

TRUNCATE TABLE Global_Temp_TabKeys;
DROP TABLE Global_Temp_TabKeys;