Migrate Oracle To SQL Server 2008
Migrate Oracle To SQL Server 2008
Server 2008
Writers: Vladimir Kisil (DB Best Technologies), Valery Fomenko (DB Best
Technologies), Yuri Rusakov (DB Best Technologies)
Summary: This white paper explores challenges that arise when you migrate from an
Oracle 7.3 database or later to SQL Server 2008. It describes the implementation
differences of database objects, SQL dialects, and procedural code between the two
platforms. The entire migration process using SQL Server Migration Assistant (SSMA)
2008 for Oracle is explained in depth, with a special focus on converting database
objects and PL/SQL code.
E-mail: info@dbbest.com
Web: www.dbbest.com
Copyright
This is a preliminary document and may be changed substantially prior to final
commercial release of the software described herein.
The information contained in this document represents the current view of Microsoft
Corporation on the issues discussed as of the date of publication. Because Microsoft
must respond to changing market conditions, it should not be interpreted to be a
commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of
any information presented after the date of publication.
Complying with all applicable copyright laws is the responsibility of the user. Without
limiting the rights under copyright, no part of this document may be reproduced, stored
in or introduced into a retrieval system, or transmitted in any form or by any means
(electronic, mechanical, photocopying, recording, or otherwise), or for any purpose,
without the express written permission of Microsoft Corporation.
Microsoft and SQL Server are registered trademarks of Microsoft Corporation in the
United States and other countries.
The names of actual companies and products mentioned herein may be the trademarks
of their respective owners.
Contents
Introduction..................................................................................................................... 6
Overview of Oracle-to-SQL Server 2008 Migration.........................................................7
Main Migration Steps...................................................................................................7
Conversion of Database Objects.................................................................................7
Differences in SQL Languages....................................................................................9
PL/SQL Conversion.....................................................................................................9
Data Migration Architecture of SSMA for Oracle...........................................................11
Implementation in SSMA...........................................................................................11
Solution Layers.......................................................................................................... 11
Client Application.......................................................................................................11
Stored Procedures Interface......................................................................................12
Database Layer......................................................................................................... 12
Migration Executable.................................................................................................12
Message Handling.....................................................................................................13
Validation of the Results............................................................................................13
Migrating Oracle Data Types.........................................................................................14
Numeric Data Types..................................................................................................15
Character Data Types................................................................................................15
Date and Time........................................................................................................... 17
Boolean Type............................................................................................................. 17
Large Object Types...................................................................................................17
XML Type.................................................................................................................. 18
ROWID Types............................................................................................................ 18
Migrating Oracle Spatial Data........................................................................................19
Emulating Oracle System Objects.................................................................................21
Converting Oracle System Views...............................................................................21
Converting Oracle System Functions.........................................................................27
Converting Oracle System Packages........................................................................37
Converting Nested PL/SQL Subprograms.....................................................................57
Inline Substitution......................................................................................................57
Emulation by Using Transact-SQL Subprograms.......................................................62
Migrating Oracle User-Defined Functions......................................................................66
Conversion Algorithm.................................................................................................66
Converting Function Calls When a Function Has Default Values for Parameters and
with Various Parameter Notations..............................................................................73
Migrating Oracle Triggers..............................................................................................75
Conversion Patterns..................................................................................................77
Emulating Oracle Packages..........................................................................................97
Converting Procedures and Functions.......................................................................97
Converting Overloaded Procedures...........................................................................98
Converting Packaged Variables.................................................................................98
Converting Packaged Cursors...................................................................................99
Converting Initialization Section...............................................................................100
Package Conversion Code Example.......................................................................102
Emulating Oracle Sequences......................................................................................104
How SSMA for Oracle V4.0 Creates and Drops Sequences....................................104
NEXTVAL and CURRVAL Simulation in SSMA for Oracle V4.0..............................106
Examples of Conversion..........................................................................................107
Migrating Hierarchical Queries....................................................................................111
Emulating Oracle Exceptions......................................................................................115
Exception Raising....................................................................................................115
Exception Handling..................................................................................................117
SSMA Exceptions Migration.....................................................................................118
Migrating Oracle Cursors.............................................................................................121
Syntax...................................................................................................................... 121
Declaring a Cursor...................................................................................................122
Opening a Cursor....................................................................................................124
Fetching Data.......................................................................................................... 125
CURRENT OF Clause.............................................................................................130
Closing a Cursor......................................................................................................130
Examples of SSMA for Oracle V4.0 Conversion......................................................132
Simulating Oracle Transactions in SQL Server 2008...................................................136
Choosing a Transaction Management Model...........................................................136
Autocommit Transactions........................................................................................136
Implicit Transactions................................................................................................136
Explicit Transactions................................................................................................136
Choosing a Concurrency Model...............................................................................137
Make Transaction Behavior Look Like Oracle..........................................................137
Simulating Oracle Autonomous Transactions..............................................................138
Simulating Autonomous Procedures and Packaged Procedures.............................139
Simulating Autonomous Functions and Packaged Functions...................................140
Simulation of Autonomous Triggers.........................................................................141
Code Example......................................................................................................... 142
Migrating Oracle Records and Collections...................................................................143
Implementing Collections.........................................................................................143
Implementing Records.............................................................................................153
Implementing Records and Collections via XML......................................................155
Sample Functions for XML Record Emulation.........................................................158
Emulating Records and Collections via CLR UDT...................................................159
Conclusion.................................................................................................................. 167
About DB Best Technologies...................................................................................167
Introduction
Migrating from an Oracle database to Microsoft® SQL Server® 2008 frequently gives
organizations benefits that range from lowered costs to a more feature-rich
environment. The free Microsoft SQL Server Migration Assistant (SSMA) for Oracle
speeds the migration process. SSMA for Oracle V4.0 converts Oracle database objects
(including stored procedures) to SQL Server database objects, loads those objects into
SQL Server, migrates data from Oracle to SQL Server, and then validates the migration
of code and data.
This white paper explores the challenges that arise during migration from an Oracle
database to SQL Server 2008. It describes the implementation differences of database
objects, SQL dialects, and procedural code between the two platforms.
Overview of Oracle-to-SQL Server 2008 Migration
This section explains the entire SSMA for Oracle migration process, with a special focus
on converting database objects and PL/SQL code.
The next step is to choose how to map the Oracle schemas to the target. In
SQL Server, schemas are not necessarily linked to a specific user or a login, and one
server contains multiple databases.
SSMA applies the selected schema-mapping method consistently when it converts both
database objects and the references to them.
After you chose your optimal schema mapping, you can start creating the target
SQL Server database and its required schemas. Because the SQL Server security
scheme is quite different from Oracle’s, we chose not to automate the security item
migration in SSMA. That way, you can consider all possibilities and make the proper
decisions yourself.
The typical SSMA migration includes connecting to the source Oracle server, selecting
the server that is running SQL Server as the target, and then performing the Convert
Schema command. When the target objects are created in the SSMA workspace, you
can save them by using the Load to Database command. Finally, execute the Migrate
Data command, which transfers the data from the source to the target tables, making
the necessary conversions. The data migration process is executed on the server that is
running SQL Server. The internal implementation of this feature is described in Data
Migration Architecture of SSMA for Oracle.
Oracle DML triggers are converted to SQL Server triggers, but because the
trigger functionality is different, the number of triggers and their types can be
changed. See a description of trigger conversion in Migrating Oracle Triggers.
SQL Server has no exact equivalent to Oracle sequences. SSMA can use one of
two sequence conversion methods. The first method is to convert a sequence to
an SQL Server identity column. That is the optimal solution, but as Oracle
sequence objects are not linked to tables, using sequences may not be
compatible with identity column functionality. In that situation, SSMA uses a
second method, which is to emulate sequences by additional tables. This is not
as effective as the first method, but it ensures better compatibility with Oracle.
See details in Emulating Oracle Sequences.
Or consider how SSMA handles another nonstandard Oracle feature: the special outer
join syntax with the (+) qualifier. SSMA converts these queries by transforming them into
ANSI format.
SSMA does not convert dynamic SQL statements because the actual statement is not
known until execution time and, in most cases, it cannot be reconstructed at conversion
time. There is a workaround: The Oracle metabase tree displayed in SSMA contains a
special node named Statements in which you can create and convert ad hoc SQL
statements. If you can manually reproduce the final form of a dynamic SQL command,
you can convert it as an object in the Statements node.
PL/SQL Conversion
The syntax of Oracle’s PL/SQL language is significantly different from the syntax of
SQL Server’s procedural language, Transact-SQL. This makes converting PL/SQL code
from stored procedures, functions, or triggers a challenge. SSMA, however, can resolve
most of the problems related to these conversions. SSMA also allows establishing
special data type mappings for PL/SQL variables.
Some conversion rules for PL/SQL are straightforward, such as converting assignment,
IF, or LOOP statements. Other SSMA conversion algorithms are more complicated.
Consider one difficult case: converting Oracle exceptions, which is described in
Emulating Oracle Exceptions. The solution detailed there allows emulating Oracle
behavior as exactly as possible, but you may need to review the code in order to
eliminate dependencies on Oracle error codes and to simplify the processing of such
conditions as NO_DATA_FOUND.
Implementation in SSMA
We based the SSMA for Oracle V4.0 implementation on the SqlBulkCopy class,
defined in the .NET Framework 2.0. SqlBulkCopy functionality resembles the bcp
utility, which allows transferring large amounts of data quickly and efficiently. Access to
the source database is established by the .NET Framework Data Provider for Oracle,
which uses the Oracle Call Interface (OCI) from Oracle client software. Optionally, you
can use .NET Framework Data Provider for OLE DB, which requires an installed Oracle
OLE DB provider.
The data transfer process must run on SQL Server. That limits the number of
installed Oracle clients and reduces network traffic.
All tables that are selected for migration are transferred by a single execution
command from the SSMA user.
The user monitors the data flow progress and can terminate it at any time.
Solution Layers
Four layers participate in the data migration process:
The server executable, which starts as part of a SQL Server job, executes the
data transfer, and reflects its status
Client Application
SSMA lets users choose an arbitrary set of source tables for migration. The batch size
for bulk copy operations is a user-defined setting.
When the process starts, the program displays the progress bar and a Stop button. If
any errors are found, SSMA shows the appropriate error message and terminates the
transfer. In addition, the user can click Stop to terminate the process. If the transfer is
completed normally, SSMA compares the number of rows in each source with the
corresponding target table. If they are equal, the transfer is considered to be successful.
As the client application does not directly control the data migration process, SSMA
uses a Messages table to receive feedback about the migration status.
Database Layer
SSMA uses a Packages table, named [ssma_oracle].[bcp_migration_packages], to
store information about the current package. Each row corresponds to one migration
run. It contains package GUID and XML that represents RSA-encrypted connection
strings and the tables that should be migrated.
Migration Executable
The migration application, SSMA for Oracle Data Migration Assistant.exe, is executed
on a SQL Server host. The executable's directory is determined during the Extension
Pack installation. When bcp_start_migration_package starts the application, it uses
hard-coded file names and retrieves the directory name from a server environment
variable.
When it starts, the migration application gets the package ID from the command string
and reads all other package-related information from the Packages table. That
information includes source and destination connection strings, and a list of the tables to
migrate. Then the tables are processed one at a time. You get source rows via the
IDataReader interface and move them to the target table with the WriteToServer
method.
The BatchSize setting defines the number of rows in a buffer. When the buffer is full, all
rows in it are committed to the target.
To notify you about the progress of a bulk copy operation, the data migration executable
uses the SqlRowsCopied event and NotifyAfter property. When a SqlRowsCopied
event is generated, the application inserts new rows, sending information about the
progress to the Messages table. The NotifyAfter property defines the number of rows
that are processed before generating a SqlRowsCopied event. This number is
25 percent of the source table's row count.
Message Handling
The client application receives feedback from the migration executable by means of the
Messages table. During migration, the client is in the loop, polling this table and verifying
that new rows with the proper package ID appear there. If there are no new rows during
a significant period of time, this may indicate problems with the server executable and
the process terminates with a time-out message.
When the table migration completes, the server executable writes a successful
completion message. If the table is large enough, you may see many intermediate
messages, which show that the next batch was successfully committed. If an error
occurs, the client displays the error message that was received from the server process.
After the migration completes, the client must calculate the target table's row counts. If
they are equal, the overall migration result is considered to be successful. Otherwise,
the user is notified of the discrepancy and can view the source and destination counts.
Migrating Oracle Data Types
Most data types used in Oracle do not have exact equivalents in Microsoft
SQL Server 2008. They differ in scale, precision, length, and functionality. This section
explains the data type mapping implemented in SSMA for Oracle V4.0, and it includes
remarks about conversion issues.
SSMA supports the ANSI and DB2 types implemented in Oracle, as well as the built-in
Oracle types. SSMA type mapping is applied to table columns, subprogram arguments,
a function's returned value, and to local variables. Usually the mapping rules are the
same for all these categories, but in some cases there are differences. In SSMA, you
can adjust mapping rules for some predefined limits. You can establish custom
mappings for the whole schema, for specific group of objects, or to a single object on
the Oracle view pane's Type Mapping tab (Figure 1).
This section does not describe migrating complex data types such as object types,
collections, or records. It does not cover ANY types and some specific structures, such
as spatial or media types.
Oracle allows you to create subtypes that are actually aliases of some basic types.
SSMA does not process subtypes, but you can emulate that functionality manually if you
can convert the basic type. Generally it is enough to replace the Oracle declaration:
You may need to change the target <type-name> if the subtype is defined in the Oracle
package. To establish the scope of this name, add a package prefix such as
PackageName$<type-name>.
You may want to customize the default mapping of numeric types if you know the exact
range of actual values. In fact, you can choose any SQL Server numeric type as the
target for the mapping. Be cautious when mapping a source type to a type that has less
precision, such as NUMBER -> smallint or NUMBER(20) -> int. Doing so could create
overflows or loss of precision during data migration or during code execution. In some
cases, you may want to set the precision to larger than the default, such as when
mapping INTEGER to bigint.
You may find another reason to change default number mappings: when you convert a
NUMBER field to a SQL Server identity column. Because SQL Server does not support
float numbers as identities, change it to an int or numeric type.
If some formal parameter of a procedure or a function has a character type, Oracle does
not require that its length be explicitly declared. Meanwhile, SQL Server always wants to
know the exact size of varchar or char parameters. As a result, SSMA has no other
choice than to apply the maximum length by default. That means that VARCHAR2 or
CHAR parameters are automatically declared as varchar(max) in the target code. If you
know the exact length of the source data, you can change the default mapping.
Otherwise, non-ASCII strings can be distorted during data migration or target code
execution. Note that source strings declared as national (NVARCHAR2 and NCHAR)
are automatically mapped to nvarchar and nchar.
A similar approach is applied to Oracle RAW strings. This type can be mapped to binary
or varbinary (the default), but if their size exceeds the 8,000-byte limit, map them to
varbinary(max).
Another Oracle type that holds the date and time is TIMESTAMP. It resembles DATE
except that it has greater precision (up to nanoseconds). The SQL Server timestamp is
a completely different type not related to a moment in time. Thus, the best way to
convert TIMESTAMP is to use the default SSMA mapping to datetime2. The accuracy
of datetime2 is 100 nanoseconds. In most cases, the loss of precision caused by this
conversion is acceptable. The SQL Server 2008 can store time zone information in
dates. This is supported by the datetimeoffset data type.
The Oracle INTERVAL data type does not have a corresponding type in SQL Server,
but you can emulate any operations with intervals by using the SQL Server functions
DATEADD and DATEDIFF. The syntax of DATEADD is quite different from the syntax
of DATEDIFF, and as of this writing SSMA does not perform these conversions
automatically.
Boolean Type
SQL Server does not have a Boolean type. Statements containing Boolean values are
transformed by SSMA to replace the value with conditional expressions. SSMA
emulates stored Boolean data by using the SQL Server bit type.
NCLOB nvarchar(max)
You can change SSMA mapping to use the older-style text, ntext, and image types, but
this is not recommended. SQL Server 2005 and SQL Server 2008 operations over new
types are simple compared to the approaches in both Oracle and SQL Server 2000.
Currently, SSMA does not automatically convert operations on large types. Still, it can
migrate the data of all the above types. The BFILE type is somewhat different; because
SSMA does not convert the Oracle concept of saving data out of the database, the
result of the data migration is that the file contents are loaded into a SQL Server table in
binary format. You may consider converting that result into a varchar format if the file is
a text file. If you need to store large binary fields in file system, you can manually
convert them by using new SQL Server 2008 FILESTREAM attribute with the
varbinary(max) data type.
If the Oracle server supports multibyte encoding of characters, map LONG and CLOB
types to nvarchar(max) to preserve the Unicode characters.
XML Type
The default mapping of the Oracle XMLType is to SQL Server xml. All XML data in
XMLType columns can be successfully migrated by using SSMA. Note that XQuery
operations on these types are similar in Oracle and SQL Server, but differences exist
and you should handle them manually.
ROWID Types
The ROWID and UROWID types are mapped to uniqueidentifier, which is a GUID that
could be generated for each row. Before you convert any code that relies on the ROWID
pseudocolumn, ensure that SSMA added the ROWID column (see option Generate
ROWID column in the SSMA project settings). You can migrate data in columns of
ROWID type to SQL Server as is, but their correspondence with the SSMA-generated
ROWID column will be broken because uniqueidentifier no longer represents the
physical address of a row like it was in Oracle.
Migrating Oracle Spatial Data
Oracle Spatial is an Oracle subsystem which provides SQL functions to facilitate the
handling of spatial features in an Oracle database. The geometric description of a
spatial object is stored in a single row, in a column of dedicated object type
MDSYS.SDO_GEOMETRY.
SQL Server 2008 also supports spatial data. They are implemented as SQL CLR types
named geography and geometry. The geography type allows you to store objects
defined by coordinates on Earth's surface, and the geometry type is used for planar
objects. SQL Server 2008 spatial data types implement methods for importing and
exporting data in Well Known Text (WKT) and Well Known Binary (WKB) formats that
are defined by Open Geospatial Consortium (OGC) specification. Spatial functionality is
supported in all editions of SQL Server 2008, including Express.
SSMA for Oracle V4.0 does not support migration of table columns that have
SDO_GEOMETRY type. Straightforward use of SQL Server Integration Services (SSIS)
does not help much, because the Oracle Spatial types are not recognized by existing
OLE DB, ADO.NET or ODBC providers.
The proposed solution is based on the fact that both Oracle Spatial and SQL Server
2008 support conversion to WKT format. Next, we are assuming that the source
SDO_GEOMETRY column is mapped to SQL Server column of the geography type.
Before transferring the data, we should create a SQL Server linked server pointing at
the source Oracle instance. To perform the migration, we need to convert the source
column value into WKT format, which makes it a plain text, and insert the result into the
target geography column using OPENQUERY statement.
Example:
In this case, the following INSERT statement will correctly copy the spatial data.
ALL_INDEXES
DBA_INDEXES
ALL_OBJECTS
DBA_OBJECTS
ALL_SYNONYMS
DBA_SYNONYMS
ALL_TAB_COLUMNS
DBA_TAB_COLUMNS
ALL_TABLES
DBA_TABLES
ALL_CONSTRAINTS
DBA_ CONSTRAINTS
ALL_SEQUENCES
DBA_SEQUENCES
ALL_VIEWS
DBA_VIEWS
ALL_USERS
DBA _USERS
ALL_SOURCE
DBA_SOURCE
GLOBAL_NAME
ALL_JOBS
DBA_ JOBS
V$SESSION
ALL_EXTENTS
V$LOCKED_OBJECT
DBA_FREE_SPACE
DBA_SEGMENTS
Location of Generated System View Emulations for SSMA for Oracle V4.0
Views emulating Oracle DBA_* views and ALL_* views are created in
<target_db>.ssma_oracle.DBA_* and <target_db>.ssma_oracle.ALL_*,
correspondingly.
USER_* views are created in each scheme where these views are used, and they have
additional WHERE conditions with the format:
OWNER = <target_schema>
Note that SSMA creates only those target views that are actually referenced in the
generated code.
Note In the following code we assume that SSMA creates DBA_* and USER_* views
based on ALL_* and therefore we do not describe DBA_* and USER_*in this document.
Example:
CREATE VIEW ssma_oracle.ALL_TRIGGERS
AS
select
UPPER(t.name) as TRIGGER_NAME,
UPPER(s.name) as TABLE_OWNER,
UPPER(o.name) as TABLE_NAME,
CASE
END as STATUS
GO
AS
FOR TEST_DATABASE.ssma_oracle.ALL_TRIGGERS
insert #extentinfo
exec( '
' )
select
UPPER(s.name) AS owner,
UPPER(t.name) AS object_name,
'TABLE' AS segment_type,
ext_size*8192 as bytes,
ext_size as blocks
UNION ALL
select
UPPER(s.name) AS owner,
UPPER(i.name) AS object_name,
'INDEX' AS segment_type,
ext_size*8192 as bytes,
ext_size as blocks
sys.tables AS t, sys.schemas AS s
SELECT
s.hostname as OS_USER_NAME,
s.spid as SESSION_ID,
UPPER(u.name) as ORACLE_USERNAME,
CASE
END as LOCKED_MODE
SELECT
a.data_space_id as FILE_ID,
FROM sys.allocation_units as a
GROUP BY a.data_space_id
SELECT
UPPER(s.name) AS owner,
UPPER(o.name) AS SEGMENT_NAME,
'TABLE' AS SEGMENT_TYPE,
SUM(a.used_pages*8192) as BYTES
FROM sys.tables AS o INNER JOIN
on o.object_id = p.object_id
WHERE (o.is_ms_shipped = 0)
UNION ALL
SELECT
UPPER(s.name) AS owner,
UPPER(i.name) AS SEGMENT_NAME,
'INDEX' AS OBJECT_TYPE,
SUM(a.used_pages*8192) as BYTES
on o.object_id = p.object_id
ACOS(p1) Y M ACOS(p1)
ASCII(p1) Y M ASCII(p1)
ASIN(p1) Y M ASIN(p1)
AVG(p1) Y M AVG(p1)
ATAN(p1) Y M ATAN(p1)
CEIL(p1) Y M CEILING(p1)
COALESCE(p1, …) Y M COALESCE(p1, …)
COS(p1) Y M COS(p1)
COUNT(p1) Y M COUNT(p1)
DENSE_RANK() Y M DENSE_RANK()
EXP(p1) Y M EXP(p1)
FLOOR(p1) Y M FLOOR(p1)
GREATEST_INT(p1, p2)
GREATEST_NVARCHAR(p1, p2)
GREATEST_REAL(p1, p2)
GREATEST_VARCHAR(p1, p2)
INSTR2_NVARCHAR(p1, p2)
INSTR2_VARCHAR(p1, p2)
LAST_DAY(p1) Y F ssma_oracle.LAST_DAY(p1)
LENGTH_NCHAR(p1)
LENGTH_NVARCHAR(p1)
LENGTH_VARCHAR(p1)
LN(p1) Y M LOG(p1)
LOCALTIMESTAMP Y M SYSDATETIME()
LOWER(p1) Y M LOWER(p1)
LTRIM(p1) Y M LTRIM(p1)
following argument
types: CHAR, NCHAR,
MOD(p1, p2) Y M Into expression (p1 % p2) VARCHAR2,
No check of parameter
data types.
NTILE() Y M NTILE()
POWER(p1,p2) Y M POWER(p1,p2)
RANK() Y M RANK()
ROUND(p1) [ p1 Y F ssma_oracle.ROUND_NUMERIC_0
numeric ] (p1)
ROW_NUMBER() Y M ROW_NUMBER()
RTRIM(p1) Y M RTRIM(p1)
SIN(p1) Y M SIN(p1)
SINH(p1) Y F ssma_oracle.SINH(p1)
SUBSTR2_NVARCHAR(p1,p2)
SUBSTR2_VARCHAR(p1,p2)
SUBSTR3_CHAR(p1,p2,p3)
SUBSTR3_NCHAR(p1,p2,p3)
SUBSTR3_NVARCHAR(p1,p2,p3)
SUBSTR3_VARCHAR(p1,p2,p3)
SUM() Y M SUM()
SYSDATE Y M -SYSDATETIME()
SYSTIMESTAMP Y M SYSDATETIMEOFFSET()
TAN(p1) Y M TAN(p1)
TANH(p1) Y F ssma_oracle.TANH(p1)
TRUNC_DATE2(p1, p2)
UPPER(p1) Y M UPPER(p1)
USER Y M SESSION_USER
Oracle System S T Conversion to SQL Server Comment
Function
WIDTH_BUCKET(p1, p2, Y F ssma_oracle.WIDTH_BUCKET(p1,
p3, p4) p2, p3, p4)
Converting Oracle System Packages
This section covers the migration of commonly used subroutines in Oracle standard
packages. Some of the modules are migrated automatically by SSMA, and some should
be handled manually. Examples illustrate our approach for the conversion.
DBMS_SQL Package
SSMA automatically covers cases where:
Example:
Oracle
declare
cur int;
ret int;
begin
cur := dbms_sql.open_cursor();
ret := dbms_sql.execute(cur);
dbms_sql.close_cursor(cur);
end;
SQL Server
Declare
@cur numeric(38),
@ret numeric(38)
begin
end
DBMS_OUTPUT Package
SSMA can handle commonly used PUT_LINE functions.
Oracle
declare
tname varchar2(255);
begin
tname:='Hello, world!';
dbms_output.put_line(tname);
end;
SQL Server
DECLARE
@tname varchar(255)
BEGIN
PRINT @tname
END
UTL_FILE Package
The following table lists the UTL_FILE subprograms that SSMA processes
automatically.
FCLOSE(p1) S UTL_FILE_FCLOSE p1
PUT S UTL_FILE_PUT(p1,p2)
Oracle function T Conversion to SQL Server Comment
or procedure
PUTF(p1, p2) S UTL_FILE_PUTF(p1,p2)
PUT_LINE S UTL_FILE_PUT_LINE(p1,p2)
Example:
Oracle
DECLARE
outfile utl_file.file_type;
V1 VARCHAR2(32767);
Begin
outfile := utl_file.fopen('USER_DIR','1.txt','w',1280);
utl_file.put_line(outfile,'Hello, world!');
UTL_FILE.FFLUSH (outfile);
IF utl_file.is_open(outfile) THEN
Utl_file.fclose(outfile);
END IF;
outfile := utl_file.fopen('USER_DIR','1.txt','r');
UTL_FILE.GET_LINE(outfile,V1,32767);
DBMS_OUTPUT.put_line('V1= '||V1);
IF utl_file.is_open(outfile) THEN
Utl_file.fclose(outfile);
END IF;
End write_log_file;
SQL Server
DECLARE
@outfile XML,
@my_world varchar(4),
@V1 varchar(max)
BEGIN
IF (sysdb.ssma_oracle.UTL_FILE_IS_OPEN(@outfile) != /*
FALSE */ 0)
IF (sysdb.ssma_oracle.UTL_FILE_IS_OPEN(@outfile) != /*
FALSE */ 0)
END
DBMS_UTILITY Package
SSMA supports only the GET_TIME function.
DBMS_SESSION Package
SSMA supports only the UNIQUE_SESSION_ID function.
DBMS_PIPE Package
SSMA for Oracle V4.0 does not convert the DBMS_PIPE system package. To emulate it
manually, follow these suggestions.
function Create_Pipe()
procedure Pack_Message()
function Send_Message()
function Receive_Message()
function Next_Item_Type()
procedure Unpck_Message()
procedure Remove_Pipe()
procedure Purge()
procedure Reset_Buffer()
function Unique_Session_Name()
Here’s an example:
Use sysdb
Go
DataValue Varchar(8000)
);
go
Go
The pack-send and receive-unpack commands are usually used in pairs. Therefore, you
can do the following replacement:
Oracle
s := dbms_pipe.receive_message('<Pipe_Name>');
if s = 0 then
dbms_pipe.unpack_message(chr);
end if;
SQL Server
DECLARE
@s bigint,
@chr varchar(8000)
BEGIN
If @s is not null
Begin
End
END
Oracle
dbms_pipe.pack_message(info);
status := dbms_pipe.send_message('<Pipe_Name>');
SQL Server
Create_Pipe(). Can be ignored.
DBMS_LOB Package
SSMA can automatically convert some functions of DBMS_LOB package. Their
emulation is performed by SSMA extension pack procedures and functions.
The following table lists the DBMS_LOB subprograms that SSMA processes
automatically.
ssma_oracle.dbms_lob$read_clob
DBMS_LOB.WRITE S ssma_oracle.dbms_lob$write_blob
ssma_oracle.dbms_lob$write_clob
DBMS_LOB.WRITEAPPE S ssma_oracle.dbms_lob$writeappend
ND blob
ssma_oracle.dbms_lob$writeappend
clob
DBMS_LOB.GETLENGTH S ssma_oracle.dbms_lob$getlength_blob -
ssma_oracle.dbms_lob$getlength_clob
DBMS_LOB.SUBSTR S ssma_oracle.dbms_lob$substr_blob -
ssma_oracle.dbms_lob$substr_clob
DBMS_JOB.SUBMIT (
<what> IN varchar2,
Where:
<job_id> is the identifier of the job just created; usually it is saved by the
program and used afterwards to reference this job (in a REMOVE statement).
<next_date> is the moment when the first run of the job is scheduled.
The <instance> and <force> parameters are related to the Oracle clustering mechanism
and we ignore them here. Also, we don’t convert the <no_parse> parameter, which
controls when Oracle parses the command.
Note Convert the <what> and <interval> dynamic SQL strings independently. The
important thing is to add the [database].[owner] qualifications to all object names that
are referenced by this code. This is necessary because DB defaults are not effective
during job execution.
Convert the SUBMIT and REMOVE routines into new stored procedures named
DBMS_JOB_SUBMIT and DBMS_JOB_REMOVE, respectively. In addition, create a
new special wrapper procedure _JOB_WRAPPER for implementing intime evaluations
and scheduling the next run.
Note that Oracle and SQL Server use different identification schemes for jobs. In
Oracle, the job is identified by sequential binary integer (job_id). In SQL Server, job
identification is by uniqueidentifier job_id and by unique job name.
In our emulation scheme, we create three SQL Server stored procedures, which are
described here.
DBMS_JOB_SUBMIT procedure
This SQL Server procedure creates a job and schedules its first execution. Find the full
text of the procedure later in this section.
To save Oracle job information, store the Oracle <job_id> in the Transact-SQL
job_name parameter and the <what> command as the job description. Because the job
description is nvarchar(512), you cannot convert any command that is longer than
512 Unicode characters. The MS SQL identifier is generated automatically as job_id
during execution of sp_add_job.
DBMS_JOB_REMOVE procedure
This procedure locates the SQL Server job ID by using the supplied Oracle job number,
and it removes the job and all associated information by using sp_delete_job.
JOB_WRAPPER procedure
This procedure executes the job command and changes the job schedule so that the
next run is set according to the <interval> parameter.
DBMS_JOB.SUBMIT
Convert a call to the SUBMIT procedure into the following SQL Server code:
EXEC DBMS_JOB_SUBMIT
<job-id-ora> OUTPUT,
<ms-command>,
<next_date>,
<interval>,
<ora_command>
Where:
<job-id-ora> is the Oracle-type job number; its declaration must be present in the
source program.
Note that the <no_parse>, <instance>, and <force> parameters are not included in the
converted statement. Instead the new <ora_command> item is used.
DBMS_JOB.REMOVE
Convert a call to the REMOVE procedure into the following code:
<job-id-ora> is the Oracle-type number of the job that you want to delete. The source
program must supply its declaration.
Oracle PL/SQL
Job submitting:
declare j number;
sInterval varchar2(50);
begin
sInterval := 'sysdate + 1/8640'; -- 10 sec
dbms_job.submit(job => j,
what => 'ticker(sysdate);',
next_date => sysdate + 1/8640, -- 10 sec
interval => sInterval);
dbms_output.put_line('job no = ' || j);
end;
SQL Server
In this example, commands are executed by the sa user in a database called AUS:
USE AUS
GO
GO
BEGIN
END;
GO
Job submitting:
declare @j float(53),
@sInterval varchar(50)
begin
/* note AUS.DBO.ticker */
exec DBMS_JOB_SUBMIT
@j OUTPUT,
N'DECLARE @param_expr_1 DATETIME; SET @param_expr_1 =
getdate(); EXEC AUS.DBO.TICKER @param_expr_1',
@param_expr_0,
@sInterval,
end
go
This solution uses emulation of the Oracle USER_JOBS system view, which can be
generated by SSMA for Oracle V4.0.
Oracle
declare j number;
begin
SELECT job INTO j
FROM user_jobs
WHERE (what = 'ticker(sysdate);');
dbms_output.put_line(j);
dbms_job.remove(j);
end;
SQL Server
declare @j float(53);
begin
SELECT @j = job
FROM USER_JOBS
print @j
exec DBMS_JOB_REMOVE @j
end
Source of new sysdb procedures
------------------------S U B M I T-------------------
select @v_job_ora =
max(
case isnumeric(substring(name,6,100))
when 1 then cast(substring(name,6,100) as int)
else 0
end
)
from msdb..sysjobs
where substring(name,1,5)='_JOB_'
exec msdb..sp_add_job
@job_name = @v_name,
@description = @p_what_ora, -- saving non-converted Oracle
command for reference
@job_id = @v_job_ms OUTPUT
exec msdb..sp_add_jobstep
@job_id = @v_job_ms,
@step_name = N'oracle job emulation',
@command = @v_command
exec msdb..sp_add_jobserver
@job_id = @v_job_ms,
@server_name = N'(LOCAL)'
exec msdb..sp_add_jobschedule
@job_id = @v_job_ms,
@name = 'oracle job emulation',
@freq_type = 1,
@freq_subday_type = 1,
@active_start_date = @v_nextdate,
@active_start_time = @v_nexttime
end
go
-----------------------------R E M O V
E-----------------------------
use sysdb
go
create procedure DBMS_JOB_REMOVE (
@p_job_id int -- Oracle-style job id
)
as
begin
declare @v_job_id uniqueidentifier -- SQL Server job id
end
go
--------------------------W R A P P E
R------------------------------
use sysdb
go
execute (@p_what)
set @v_command =
'set @buf = convert(varchar, ' + @p_interval + ', 20)'
exec msdb..sp_update_jobschedule
@job_id = @p_job_id_ms,
@name = 'oracle job emulation',
@enabled = 1,
@freq_type = 1,
@freq_subday_type = 1,
@active_start_date = @v_nextdate,
@active_start_time = @v_nexttime
end
Converting Nested PL/SQL Subprograms
Oracle allows PL/SQL subprogram (procedure or function) definitions to be nested
within another subprogram. These subprograms can be called only from inside the
PL/SQL block or the subprogram in which they were declared. There are no special
limitations for parameters or the functionality of nested procedures or functions. That
means that any of these subprograms can in turn include other subprogram
declarations, which makes multiple levels of nesting possible. In addition, the nested
modules can be overloaded; that is, they can use the same name a few times with
different parameter sets.
Microsoft SQL Server 2008 does not provide similar functionality. It is possible to create
a stand-alone SQL Server procedure or function that emulates Oracle nested
subprograms. But doing so presents the problem of how to handle local variables. In
PL/SQL, a nested subprogram declared at level N has full access to all local variables
declared at levels N, N-1, . . . 1. In SQL Server, the local declarations of other
procedures are not visible.
Inline Substitution
If the type of local modules conversion is set to inline substitution, a nested module itself
is not converted to any target object, but each call of the module is expanded to inline
blocks in the outermost subprogram. The inline block is formed according to the
following pattern:
<parameter_declaration>
<return_value_parameter_declaration>
<parameters_assignments>
<module_body>
<output_parameters_assignments>
<return_value_assignment>
Example 1
Oracle
dept_sales int := 0;
procedure DeptSales(dept_id int) is
lv_sales int;
procedure Add is
begin
end Add;
begin
dept_sales := dept_sales + i;
end Add;
begin
Add;
Add(200);
end DeptSales;
begin
DeptSales(100);
end Proc1;
SQL Server
AS
BEGIN
DECLARE
@dept_sales int = 0
BEGIN
DECLARE
@DeptSales$dept_id int
BEGIN
DECLARE
@DeptSales$lv_sales int
FROM dbo.DEPARTMENTSALES
EXECUTE
sysdb.ssma_oracle.db_error_exact_one_row_check @@ROWCOUNT
BEGIN
BEGIN
END
END
BEGIN
DECLARE
@DeptSales$ADD$i int
BEGIN
SET @dept_sales = @dept_sales +
@DeptSales$ADD$i
END
END
END
END
END
Example 2
To convert an output parameter, SSMA adds an assignment statement that saves the
output value stored in the intermediate variable.
Oracle
dept_sales int;
lv_out_sales int;
begin
end DeptSales;
begin
DeptSales(dept_sales, lv_out_sales);
end Proc1;
SQL Server
BEGIN
DECLARE
@lv_out_sales int,
@dept_sales int
BEGIN
DECLARE
@DeptSales$dept_id int
DECLARE
@DeptSales$lv_sales int
BEGIN
FROM dbo.DEPARTMENTSALES
EXECUTE
sysdb.ssma_oracle.db_error_exact_one_row_check @@ROWCOUNT
END
END
END
Emulation by Using Transact-SQL Subprograms
If the Type of local modules conversion option is set to create a separate stored
procedure, SSMA converts nested PL/SQL subprograms into separate stored
procedures and functions with special naming rules. This is reasonable if you are
working with large nested subprograms with a limited number of variables.
SSMA analyzes the original module and collects the following information:
A list of the variables and parameters of outer modules used in each nested
module
The type of access to the external variables in a nested module—the type can
be read/write or read-only
After that, SSMA creates a set of procedures that emulate Oracle nested modules and
adds additional input/output parameters for access to external variables.
FROM dbo.DEPARTMENTSALES
EXECUTE sysdb.ssma_oracle.db_error_exact_one_row_check
@@ROWCOUNT
Example
In this example, the nested module calls another nested module that is defined at the
same level. In this case, all external variables used in the caller module should also be
passed to the called module.
Oracle
dept_sales int;
procedure DeptSales(dept_id int) is
lv_sales int;
begin
dept_sales := lv_sales;
end DeptSales;
procedure DeptSales_300 is
begin
DeptSales(300);
end DeptSales_300;
begin
DeptSales(100);
DeptSales_300;
end Proc1;
SQL Server
@dept_id int,
AS
BEGIN
FROM dbo.DEPARTMENTSALES
WHERE DEPARTMENTSALES.ID = @dept_id AND
DEPARTMENTSALES.YEAR = @on_year
EXECUTE sysdb.ssma_oracle.db_error_exact_one_row_check
@@ROWCOUNT
END
GO
AS
BEGIN
Execute Proc1$DeptSales
300,
@on_year,
END
GO
AS
BEGIN
Execute Proc1$DeptSales
100,
@on_year,
Execute Proc1$DeptSales_300
@on_year,
END
GO
Migrating Oracle User-Defined Functions
This section describes how SSMA for Oracle V4.0 converts Oracle user-defined
functions. While Oracle functions closely resemble Transact-SQL functions, significant
differences do exist. The main difference is that Transact-SQL functions cannot contain
DML statements and cannot invoke stored procedures. In addition, Transact-SQL
functions do not support transaction-management commands. These are stiff
restrictions. A workaround implements a function body as a stored procedure and
invokes it within the function by means of an extended procedure. Note that some
Oracle function features, such as output parameters, are not currently supported.
Conversion Algorithm
The general format of an Oracle user-defined function is:
) ]
RETURN <return_data_type>
[DETERMINISTIC]
[AGGREGATE | PIPELINED]
[<declaration statements>]
BEGIN
<executable statements>
[EXCEPTION
[ = default_value ] } [ ,...n ]
RETURNS <return_data_type>
[ AS ]
BEGIN
<function_body>
RETURN <scalar_expression>
END
[ ; ]
The following clauses and arguments are not supported by SSMA and are ignored
during conversion:
AGGREGATE
DETERMINISTIC
LANGUAGE
PIPELINED
PARALLEL_ENABLE
For the remaining function options, the following rules are applied during conversion:
Two objects:
Transaction-management commands
Exception-handling statements
If any of these conditions are present, the function is implemented both as a procedure
and a function. In this case, the procedure is used in a call via an extended procedure in
the function body. The function body is implemented according to the following pattern:
<parameters list>
RETURNS <return_type>
AS
BEGIN
DECLARE
@return_value_variable <function_return_type>
RETURN @return_value_variable
END
xp_ora2ms_exec2_ex
<active_spid> int,
<login_time> datetime,
<ms_db_name> varchar,
<ms_schema_name> varchar,
<ms_procedure_name> varchar,
<bind_to_transaction_flag> varchar,
[optional_parameters_for_procedure]
Where:
<login_time> [input parameter] is the login time of the current user process.
<ms_db_name> [input parameter] is the database name owner of the stored
procedure.
<parameters list> ,
AS
BEGIN
<function implementation>
RETURN
END
PL-SQL code
RETURN <return_expresion>;
Transact-SQL code
RETURN
PL-SQL code
...
IF <condition> THEN
RETURN <return_expresion_1>;
ELSE
RETURN <return_expresion_2>;
ENDIF
...
Transact-SQL code
...
IF <condition>
BEGIN
RETURN
END
ELSE
BEGIN
RETURN
END
...
Example
PL-SQL code
begin
i:=fn_test2();
DBMS_OUTPUT.PUT_LINE(i);
end;
Transact-SQL code
DECLARE @i int
exec FN_TEST1$IMPL @i out
BEGIN
PRINT @i
END
Converting Function Calls When a Function Has Default Values for
Parameters and with Various Parameter Notations
When calling functions in Oracle, you can pass parameters by using:
Positional notation. Parameters are specified in the order in which they are
declared in the procedure.
Named notation. The name of each parameter is specified along with its value.
An arrow (=>) serves as the association operator. The order of the parameters is
not significant.
Mixed notation. The first parameters are specified with positional notation, and
then they are switched to named notation for the last parameters.
Because SQL Server does not support named notation for parameters that are passed
to functions, the named notation is converted to the positional notation call. In addition,
SQL Server functions do not support omitted parameters, so if the default parameters
are omitted, the statement is converted by adding the keyword, default, instead of the
omitted parameters.
Examples
PL-SQL code
RETURN VARCHAR2 IS
BEGIN
return null;
END;
declare a varchar2(50);
begin
a:= fn_test('p_1','hello','world');
a:= fn_test('p_1');
a:= fn_test('p_1',p_3=>'world');
a:= fn_test(p_2=>'hello',p_3=>'world',p_1=>'p_1');
end;
Transact-SQL code
CREATE FUNCTION fn_test (
@p_1 VARCHAR(max),
RETURNS VARCHAR(max) as
BEGIN
return null;
END;
GO
select dbo.fn_test('p1',default,default)
declare @a varchar(50)
begin
set @a = dbo.fn_test('p_1','hello','world')
set @a = dbo.fn_test('p_1','hello','world')
end;
Migrating Oracle Triggers
This section describes the differences between Oracle and Microsoft SQL Server 2008
triggers, and how SSMA for Oracle V4.0 handles them when it converts Oracle triggers
to SQL Server. (This section does not cover DDL or system triggers. The discussion is
limited to DML triggers, that is, triggers on INSERT, UPDATE, or DELETE statements.)
The first major difference between Oracle and SQL Server triggers is that the most
common Oracle trigger is a row-level trigger (FOR EACH ROW), which fires for each
row of the source statement. SQL Server, however, supports only statement-level
triggers, which fire only once per statement, irrespective of the number of rows affected.
In a row-level trigger, Oracle uses an :OLD alias to refer to column values that existed
before the statement executes, and to the changed values by using a :NEW alias.
SQL Server uses two pseudotables, inserted and deleted, which can each have
multiple rows. If the triggering statement is UPDATE, a row's older version is present in
deleted, and the newer in inserted. But it is not easy to tell which pair belongs to the
same row if the updated table does not have a primary key or the primary key was
modified.
You can resolve this problem only if SSMA generates a special ROWID column for the
table. Therefore, if you are converting tables with UPDATE triggers, we recommend
setting the Generate ROWID column option to Yes or Add ROWID column for tables
with triggers in the SSMA project settings (See Figure 2). To emulate row-level
triggers, SSMA processes each row in a cursor loop.
In some cases, you cannot convert Oracle triggers to SQL Server triggers with one-to-
one correspondence. If an Oracle trigger is defined for several events at once (for
example, INSERT or UPDATE), you must create two separate target triggers, one for
INSERT and one for UPDATE. In addition, because SQL Server supports only one
INSTEAD OF trigger per table, SSMA combines the logic of all BEFORE triggers on that
table into a single target trigger. This means that triggers are not converted
independently of each other; SSMA takes the entire set of triggers belonging to a table
and converts them into another set of SQL Server triggers so that the general relation is
many-to-many.
All BEFORE triggers for a table are converted into one INSTEAD OF trigger.
Triggers that are defined for multiple events are split into separate target
triggers.
Sometimes an Oracle trigger is defined for a specific column with the UPDATE OF
column [, column ]... ] clause. To emulate this, SSMA wraps the trigger body with the
following SQL Server construction:
BEGIN
<trigger body>
END
. . .
The UPDATING function can have a column name as an argument. SSMA can convert
such usage if the argument is a character literal. In this case, the Oracle expression:
UPDATING (‘column_name’)
Is transformed into:
UPDATE (columns_name)
Conversion Patterns
This section illustrates the conversion algorithms SSMA uses to convert various types of
Oracle triggers. Each example schematically outlines a particular type of trigger.
Comments describe the typical contents of source triggers and the structure of the
corresponding target triggers as generated by SSMA.
AFTER Triggers
Table-level triggers
Table-level AFTER triggers fire only once per table, resembling the behavior of
SQL Server AFTER triggers. Thus, the required changes are minimal. Table-level
triggers are converted according to this pattern:
Row-level triggers
Because Oracle Database fires a row-level trigger once for each row, emulate row-level
triggers with cursor processing.
For row-level triggers, a restriction can be specified in the WHEN clause. The restriction
is an SQL condition that must be satisfied for the database to fire the trigger. Also, the
special variables :NEW and :OLD are available in row-level triggers to refer to new and
old records respectively.
In SQL Server, the new and old records are stored in the inserted and deleted tables.
So, row-level triggers are emulated in the same way as table-level ones, except for the
trigger implementation wrapped into the cursor processing block.
Replace references to :OLD and :NEW values with values fetched into variables from
deleted or updated tables, respectively.
@column_new_value$X <COLUMN_X_TYPE>,
@column_new_value$Y <COLUMN_Y_TYPE>,
...
@column_old_value$A <COLUMN_A_TYPE>,
@column_old_value$B <COLUMN_B_TYPE>
...
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
/* trigger has NO references to :OLD or :NEW or has an explicit
reference to ROWID */
@column_new_value$0
WHILE @@fetch_status = 0
BEGIN
-----------------------------------------------------------------------
------
/* Oracle-trigger implementation: begin */
BEGIN
IF <WHILE_CLAUSE>
BEGIN
<TRIGGER_BODY>
END
END
/* Oracle-trigger implementation: end */
-----------------------------------------------------------------------
------
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
/* trigger has NO references to :NEW or has an explicit reference
to ROWID */
@column_new_value$0
END
CLOSE ForEachInsertedRowTriggerCursor
DEALLOCATE ForEachInsertedRowTriggerCursor
OPEN ForEachDeletedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
[@column_old_value$0,] [@column_old_value$A, @column_old_value$B ... ]
WHILE @@fetch_status = 0
BEGIN
-----------------------------------------------------------------------
------
/* Oracle-trigger implementation: begin */
BEGIN
IF <WHERE_CLAUSE>
BEGIN
<TRIGGER_BODY>
END
END
/* Oracle-trigger implementation: end */
-----------------------------------------------------------------------
------
/*this is a trigger for delete event or a trigger for update event that
has no references both to :OLD and :NEW */
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
[@column_old_value$0,] [@column_old_value$A, @column_old_value$B ... ]
END
CLOSE ForEachDeletedRowTriggerCursor
DEALLOCATE ForEachDeletedRowTriggerCursor
/*the trigger has NO references both to :OLD and :NEW or has references
only to :OLD*/
OPEN ForEachDeletedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
@column_old_value$0
/*the trigger has references to :NEW. If the trigger has references
both to :OLD and :NEW then we have to declare cursor for select ROWID
from inserted to synchronize inserted row with deleted row.
*/
DECLARE ForEachInsertedRowTriggerCursor CURSOR LOCAL FORWARD_ONLY
READ_ONLY FOR
SELECT [ROWID,] <COLUMN_X_NAME>, <COLUMN_Y_NAME> ... FROM
inserted
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
[@column_new_value$0,] @column_new_value$X, @column_new_value$Y
WHILE @@fetch_status = 0
BEGIN
-------------------------------------------------------------------
/* Oracle-trigger implementation: begin */
BEGIN
-- UPDATE OF CLAUSE
-- (UPDATE OF COLUMN[, COLUMN] ... ])
IF (UPDATE(<COLUMN>) OR UPDATE((<COLUMN>) ...)
BEGIN
IF <WHERE_CLAUSE>
BEGIN
<TRIGGER_BODY>
END
END
END
/* Oracle-trigger implementation: end */
-------------------------------------------------------------------
/*the trigger has NO references both to :OLD and :NEW or has references
only to :OLD*/
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
[@column_old_value$0,] [@column_old_value$A, @column_old_value$B ... ]
END
CLOSE ForEachDeletedRowTriggerCursor
DEALLOCATE ForEachDeletedRowTriggerCursor
BEFORE Triggers
Because BEFORE triggers do not exist in SQL Server, SSMA emulates them by means
of INSTEAD OF triggers. That change requires that the triggering statement be moved
into the body of the trigger. Also, all triggers for a specific event should go into one
target INSTEAD OF trigger.
CREATE
TRIGGER [ schema. ] INSTEAD_OF_DELETE_ON_<table> ON <table>
INSTEAD OF DELETE
AS
/* beginning of trigger implementation */
SET NOCOUNT ON
-------------------------------------------------------------------
OPEN ForEachDeletedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
@column_old_value$0
/*if the trigger has references to :OLD*/
, @column_old_value$A
,@column_old_value$B ...
WHILE @@fetch_status = 0
BEGIN
...
/* DML-operation emulation */
DELETE FROM <table>
WHERE
ROWID = @column_old_value$0
CLOSE ForEachDeletedRowTriggerCursor
DEALLOCATE ForEachDeletedRowTriggerCursor
CREATE
TRIGGER dbo.INSTEAD_OF_UPDATE_ON_<table> ON <table>
INSTEAD OF UPDATE
AS
/* beginning of trigger implementation */
SET NOCOUNT ON
------------------------------------------------------------------
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
@column_new_value$0, @column_new_value$1, @column_new_value$2, ...
WHILE @@fetch_status = 0
BEGIN
...
/* DML-operation emulation */
UPDATE <table>
SET
<COLUMN_NAME_1> = @column_new_value$1,
<COLUMN_NAME_1> = @column_new_value$1,
...
WHERE
ROWID = @column_new_value$0
END
CLOSE ForEachInsertedRowTriggerCursor
DEALLOCATE ForEachInsertedRowTriggerCursor
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
@column_new_value$1, @column_new_value$2, ...
WHILE @@fetch_status = 0
BEGIN
...
/* DML-operation emulation */
INSERT INTO <table> (<COLUMN_1_NAME>,<COLUMN_2_NAME> ...)
VALUES (@column_new_value$1, @column_new_value$2, ...)
CLOSE ForEachInsertedRowTriggerCursor
DEALLOCATE ForEachInsertedRowTriggerCursor
INSTEAD OF Triggers
Oracle INSTEAD OF triggers remain INSTEAD OF triggers in SQL Server. Combine
multiple INSTEAD OF triggers that are defined on the same event into one trigger.
INSTEAD OF trigger statements are implicitly activated for each row.
CREATE
TRIGGER [schema. ]INSTEAD_OF_UPDATE_ON_VIEW_<table> ON <table>
INSTEAD OF {UPDATE | DELETE}
AS
/* beginning of trigger implementation */
SET NOCOUNT ON
@column_old_value$X <COLUMN_X_TYPE>,
@column_old_value$Y <COLUMN_Y_TYPE>,
...
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
@column_new_value$A, @column_new_value$B ...
OPEN ForEachDeletedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
/* trigger has no references to :OLD*/
@column_old_value$1
/* trigger has references to :OLD*/
@column_old_value$X, @column_old_value$Y ...
WHILE @@fetch_status = 0
BEGIN
-----------------------------------------------------------------------
------
/* Oracle-trigger INSTEAD OF UPDATE/DELETE trigger_1 implementation:
begin */
BEGIN
< INSTEAD OF UPDATE/DELETE trigger_1 BODY>
END
/* Oracle-trigger INSTEAD OF UPDATE/DELETE trigger_1 implementation:
end */
...
-----------------------------------------------------------------------
------
/*Only for trigger for UPDATE event that has references to :NEW*/
FETCH NEXT FROM ForEachInsertedRowTriggerCursor INTO
@column_new_value$A, @column_new_value$B ...
OPEN ForEachDeletedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
/* trigger has no references to :OLD*/
@column_old_value$1
/* trigger has references to :OLD*/
@column_old_value$X, @column_old_value$Y ...
END
/*Only for trigger for UPDATE event that has references to :NEW*/
CLOSE ForEachInsertedRowTriggerCursor
DEALLOCATE ForEachInsertedRowTriggerCursor
CLOSE ForEachDeletedRowTriggerCursor
DEALLOCATE ForEachDeletedRowTriggerCursor
INSTEAD OF triggers are converted in the same way as DELETE and UPDATE
triggers, except the iteration for each row is made with the inserted table.
@column_new_value$X <COLUMN_X_TYPE>,
@column_new_value$Y <COLUMN_Y_TYPE>,
...
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
/* trigger has no references to :NEW*/
@column_new_value$1
/* trigger has references to :NEW*/
@column_new_value$X, @column_new_value$Y ...
WHILE @@fetch_status = 0
BEGIN
-----------------------------------------------------------------------
------
/* Oracle-trigger INSTEAD OF INSERT trigger_1 implementation:
begin */
BEGIN
< INSTEAD OF INSERT trigger_1 BODY>
END
/* Oracle-trigger INSTEAD OF INSERT trigger_1 implementation:
end */
/* Oracle-trigger INSTEAD OF INSERT trigger_2 implementation:
begin */
BEGIN
< INSTEAD OF INSERT trigger_1 BODY>
END
/* Oracle-trigger INSTEAD OF INSERT trigger_2 implementation:
end */
...
-----------------------------------------------------------------------
------
OPEN ForEachInsertedRowTriggerCursor
FETCH NEXT FROM ForEachDeletedRowTriggerCursor INTO
/* trigger has no references to :NEW*/
@column_new_value$1
/* trigger has references to :NEW*/
@column_new_value$X, @column_new_value$Y ...
END
CLOSE ForEachInsertedRowTriggerCursor
DEALLOCATE ForEachInsertedRowTriggerCursor
For row-level triggers, SSMA passes NEW and OLD rows to the procedure. In BEFORE
UPDATE and BEFORE INSERT row-level triggers, you can write to the :NEW value. So
in autonomous transactions you must pass a :NEW value back to a trigger.
In that way, the pattern for row-level trigger-body procedure implementation looks like
following.
Pattern for implementing AFTER, INSTEAD OF, and BEFORE DELETE row-level
triggers
In row-level triggers for the INSERT event, you pass references to the :NEW
value and null values instead of the :OLD value.
In row-level triggers for the DELETE event, you pass references to the :OLD
value and null values instead of the :NEW value.
In row-level triggers for the UPDATE event, you pass references to both the
:OLD value and the :NEW value.
Packaged variables.
Packaged cursors.
procedure MySimpleProcedure
is begin
dbms_output.put_line(MyFunction);
end;
In SQL Server 2008, you can group procedures and functions by giving them names
such as Scott.MY_PACKAGE$MySimpleProcedure and
Scott.MY_PACKAGE$MyFunction. The naming pattern is <schema name>.<package
name>$<procedure or function name>. For more information about converting
functions, see Migrating Oracle User-Defined Functions.
Convert the Invoker rights clause AUTHID to an EXECUTE AS clause, and apply it to all
packaged procedures and functions. Also convert the CURRENT_USER argument to
the CALLER argument, and convert the DEFINER argument to the OWNER argument.
Note If a packaged variable is declared with an initial value, you must move the
initialization to the package's initialization section.
In some cases you can replace constant packaged variables with user-defined functions
that return the appropriate value. For example, you could convert the packaged variable
unitname (from the earlier example) as:
dbms_output.put_line(my_package.unitname);
To:
print scott.my_package$unitname()
The declaration of cursor is invoked in the package initialization section. Each database
method that uses packaged cursors contains the call of the package initialization
procedure. The call is invoked before the first usage of the packaged cursor.
(For basic information about cursor conversion, see Migrating Oracle Cursors. You will
also find a description of converting FOUND, ISOPEN, and NOTFOUND cursor
attributes.)
The ROWCOUNT attribute is converted as a package variable. The variable is initialized
to null in the init section; after OPEN, its value is set to zero and is incremented after
each FETCH.
Converting Initialization Section
The initialization section itself is converted as the usual packaged procedure. Within
each converted procedure or function, a call to the initialization procedure is included.
Note Initialization should be performed only one time per session, so the initialization
procedure must check each package’s initialization status.
As a mark of package initialization, SSMA uses package variable with a name such as
$<dbname>.<schema>.<package>$init$. If that variable is present in the db_storage
table, the package is already initialized, and therefore no initialization call is required.
Because it is not possible to call a procedure from a user-defined function, the check for
initialization is performed by the function db_fn_check_init_package. In its turn
db_fn_check_init_package makes a call to xp_ora2ms_exec2 to execute the
package initialization routine.
Each initialization procedure cleans the storage table and sets default values for each
packaged variable:
The essential tasks that the sequences simulating engine should provide are:
Retrieve current value of the sequence by using the CURRVAL method. This
value is bound to the current session scope.
The SSMA for Oracle V4.0 solution is based on SQL Server identity columns. A table
with an identity column is created for every sequence. In the IDENTITY property, the
same properties are used as in the ORACLE sequence, except for MAXVALUE,
MINVALUE, and CYCLE. The identity value is transaction-independent.
sysdb.ssma_oracle.db_create_sequence
@dbname,
@schema,
@name,
@seed,
@increment
Arguments:
The procedure creates a permanent table with the name that identifies the sequence.
The table has one identity column of numeric(38) data type named as ID. Also, the
db_create_sequence procedure creates a procedure that inserts the default value into
the given table. The procedure is created in the same database in which the sequence
table is located. Execute permission on the procedure is granted to public when the
sequence is created, giving users indirect access to the sequence tables.
The following example creates a sequence with the name orders_seq in the target
database:
sysdb.ssma_oracle.db_drop_sequence
@dbname,
@schema,
@name
Arguments:
The following example drops a sequence named orders_seq in the target database:
The NEXTVAL simulation method executes an insert command. The insert command is
rolled back immediately to keep the table empty. This approach gains maximum speed.
If there is an external transaction, the transaction point is saved and the transaction is
rolled back to it after insert.
sysdb.ssma_oracle.db_sp_get_next_sequence_value(
@dbname,
@schema,
@name,
[@curval] output
Arguments:
sysdb.ssma_oracle.db_get_next_sequence_value(@dbname,@schema,@name)
Arguments:
sysdb.ssma_oracle. db_get_curval_sequence_value(@dbname,@schema,@name)
Arguments:
Examples of Conversion
Inserting Sequence Values into a Table
This example increments the employee sequence and uses its value for a new
employee inserted into the sample table employees.
Oracle
Transact-SQL
The following statement more closely follows the original but takes more time to
execute:
The second example adds a new order with the next order number to the order table.
Then it adds suborders with this number to the detail order table.
Oracle
INSERT INTO orders(id, customer_id)
SELECT orders_seq.nextval, customer_id from orders_cache;
Transact-SQL
Optimization Tips
You can try an easier way to convert your Oracle sequences and get more
performance, but only if you know exactly how the sequence is used. For example, if
there are no methods using CURRVAL without previous NEXTVAL calls, you need not
save and store the current sequence value, and you can use a local variable to store it.
That gains performance because it’s not necessary to use DML routines to save and get
the sequence current value.
Oracle
begin
INSERT INTO employees (id, name)
VALUES(employees_seq.nextval, 'David Miller');
end;
Transact-SQL
begin
declare @curval numeric(38)
begin tran
insert employees_seq default values
set @curval=scope_identity()
rollback
INSERT INTO employees (id, name)
VALUES(@curval, 'David Miller');
end;
You can wrap the INSERT statement in a stored procedure. Additionally, it should check
for an external opened transaction. If one exists, the transaction point should be saved;
a new one should not be opened:
begin
declare @curval numeric(38)
exec employees_seq_nextval @curval out
INSERT INTO employees (id, name) VALUES(@curval, Dylan Miller');
end;
To invoke the xp_ora2ms_exec2 procedure, you must pass the current process ID and
login time as parameters:
1. Evaluates a join first, if one is present, whether the join is specified in the FROM
clause or with WHERE clause predicates.
2. Evaluates the CONNECT BY condition.
3. Evaluates any remaining WHERE clause predicates.
Oracle then uses the information from these evaluations to form the hierarchy as
follows:
4. Oracle selects the hierarchy’s root row(s) (those rows that satisfy the START
WITH condition).
5. Oracle selects each root row's child rows. Each child row must satisfy the
CONNECT BY condition with respect to one of the root rows.
6. Oracle selects successive generations of child rows. Oracle first selects the
children of the rows returned in Step 2, and then the children of those children,
and so on. Oracle always selects children by evaluating the CONNECT BY
condition with respect to a current parent row.
7. If the query contains a WHERE clause without a join, Oracle eliminates all rows
from the hierarchy that do not satisfy the WHERE clause's conditions. Oracle
evaluates that condition for each row individually, rather than removing all the
children of a row that does not satisfy the condition.
8. Oracle returns the rows in the order shown in Figure 3. In the figure, children
appear below their parents.
1
2 7 9
3 4 8 10 12
5 6 11
In SQL Server 2008, you can use a recursive common table expression (CTE) to
retrieve hierarchical data. For more information about the recursive CTE, see Recursive
Queries Using Common Table Expression (http://msdn.microsoft.com/en-
us/library/ms186243.aspx) in SQL Server Books Online.
Use the START WITH condition in the anchor member subquery of the CTE. If
there is no START WITH condition, the result of the anchor member subquery
should consist of all root rows. Because the START WITH condition is
processed before the WHERE condition, ensure that the anchor member
subquery returns all necessary rows. This is sometimes needed to move some
WHERE conditions from the CTE to the base query.
Use the CONNECT BY condition in the recursive member subquery. The result
of the recursive member subquery should consist of all child rows joined with the
CTE itself on the CONNECT BY condition. Use the CTE itself as the inner join
member in the recursive subquery. Replace the PRIOR operator with the CTE
recursive reference.
The base query consists of the selection from the CTE, and the WHERE clause
to provide all necessary restrictions.
You can use GROUP BY and HAVING clauses only in the base query.
SQL Server 2008 cannot detect the cycles in a hierarchical query. You can control the
recursion level with the MAXRECURSION query hint.
Example:
The following example code demonstrates how to migrate a simple hierarchical query:
Oracle
SQL Server
WITH
h$cte AS
(
SELECT COMPANY.NAME, COMPANY.PARENT, 1 AS LEVEL,
CAST(row_number() OVER(
ORDER BY @@spid) AS varchar(max)) AS path
FROM dbo.COMPANY
WHERE ((COMPANY.NAME = 'Company Ltd'))
UNION ALL
SELECT COMPANY.NAME, COMPANY.PARENT, h$cte.LEVEL + 1 AS LEVEL,
path + ',' + CAST(row_number() OVER(
ORDER BY @@spid) AS varchar(max)) AS path
FROM dbo.COMPANY, h$cte
WHERE ((COMPANY.PARENT = h$cte.NAME))
)
Exception Raising
The Oracle exception raising model comprises the following features:
The SELECT INTO statement causes an exception if not exactly one row is
returned.
The RAISE statement can raise any exception, including system errors.
If the SELECT statement can return zero, one, or many rows, it makes sense to check
the number of rows by using the @@ROWCOUNT function. Its value can be used to
emulate any logic that was implemented in Oracle by using the TOO_MANY_ROWS or
NO_DATA_FOUND exceptions. Normally, the SELECT INTO statement should return
only one row, so in most cases you don’t need to emulate this type of exception raising.
For example:
Oracle
BEGIN
SELECT <expression> INTO <variable> FROM <table>;
EXCEPTION
WHEN NO_DATA_FOUND THEN
<Statements>
END
IF @@ROWCOUNT = 0
BEGIN
<Statements>
END
Also, PL/SQL programs can sometimes use user-defined exceptions to provide
business logic. These exceptions are declared in the PL/SQL block's declaration
section. In Transact-SQL, you can replace that behavior by using flags or custom error
numbers.
For example:
Oracle
declare
myexception exception;
BEGIN
…
IF <condition> THEN
RAISE myexception;
END IF;
…
EXCEPTION
WHEN myexception THEN
<Statements>
END
BEGIN TRY
…
IF <condition>
RAISERROR (‘myexception’, 16, 1)
…
END TRY
BEGIN CATCH
IF ERROR_MESSAGE() = ‘myexception’
BEGIN
<Statements>
END
ELSE
<rest_of_handler code>
END CATCH
If the user-defined exception is associated with some error number by using pragma
EXCEPTION_INIT, you can handle the system error in the CATCH block as described
later.
To emulate the raise_application_error procedure and the system predefined
exception raising, you can use the RAISERROR statement with a custom error number
and message. Also, change the application logic in that case to support SQL
Server 2008 error numbers.
Note that SQL Server 2008 treats exceptions with a severity of less than 11 as
information messages. To interrupt execution and pass control to a CATCH block, the
exception severity must be at least 11. (In most cases you should use a severity level
of 16.)
Exception Handling
Oracle provides the following exception-handling features:
Exception reraising
To recognize the exception (WHEN … THEN functionality), you can use the following
system functions:
ERROR_NUMBER
ERROR_LINE
ERROR_PROCEDURE
ERROR_SEVERITY
ERROR_STATE
ERROR_MESSAGE
You can use the ERROR_NUMBER and ERROR_MESSAGE functions instead of the
SQLCODE and SQLERRM Oracle functions. Note that error messages and numbers
are different in Oracle and SQL Server, so they should be translated during migration.
For example:
Oracle
BEGIN
…
INSERT INTO <table> VALUES …
…
EXCEPTION
…
WHEN DUP_VAL_ON_INDEX THEN
<Statements>
…
END
BEGIN TRY
…
INSERT INTO <table> VALUES …
…
END TRY
BEGIN CATCH
…
IF ERROR_NUMBER() = 2627
<Statements>
…
END CATCH
Unfortunately, SQL Server 2008 does not support exception reraising. If the exception is
not handled, it can be passed to the calling block by using the RAISERROR statement
with a custom error number and appropriate message.
Oracle exceptions are encoded into a character string according to the following rules:
oracle:{<OWNER_NAME>|<PACKAGE_NAME>|<EXCEPTION_NAME>}
Where:
local:oracle:{<OWNER_NAME>|<MODULE_NAME>}:<EXCEPTION_NAME>:N
Where:
local:PL\SQL:<EXCEPTION_NAME>:N
To support Oracle error numbers, system errors are stored in the following
format:
‘ORAXXXXXX’
1. All statements between BEGIN and EXCEPTION are enclosed with BEGIN TRY
… END TRY.
2. An exception handler is placed into BEGIN CATCH … END CATCH.
3. Error numbers are translated to Oracle format by using the
sysdb.ssma_oracle.db_error_get_oracle_exception_id() function. That
function returns an exception identifier as a character string, as described
earlier. Each WHEN…THEN statement is migrated to an IF statement that
compares the exception identifier to constant exception names that are
translated according to the same rules.
4. The exception handler for OTHERS, if any, is migrated as an alternative
execution block after all handlers.
5. If there is no OTHERS exception handler, the exception is reraised by the
special stored procedure sysdb.ssma_oracle.ssma_rethrowerror that
emulates reraising using a custom error number. It also emulates a RAISE
statement with no exception name.
6. To emulate predefined Oracle exceptions NO_DATA_FOUND and
TOO_MANY_ROWS, the special stored procedure EXEC
sysdb.ssma.db_error_exact_one_row_check @@ROWCOUNT is placed
after all SELECT statements. The procedure checks the row count and raises an
exception with the custom number 59999 and the message ‘ORA+00100’ or
‘ORA-01422,’ depending on its value.
7. The number 59999 is used for all Oracle system, user-defined, or predefined
exceptions.
8. The RAISE statement is migrated to the RAISERROR statement with a 59999
error number and the exception identifier as a message. The exception identified
is formed as described earlier.
9. To emulate the raise_application_error procedure, there is the additional error
number 59998. The procedure call is replaced by a RAISERROR call with error
number 59998 and the following string as a message:
‘ORA<error_number>:<message>’
For example:
10. All exceptions are raised with severity level 16 to provide handling by a CATCH
block.
11. sysdb.ssma.db_error_sqlcode user-defined function emulates the SQLCODE
function. It returns an Oracle error number.
12. Either sysdb.ssma.db_error_sqlerrm_0 or sysdb.ssma.db_error_sqlerrm_1
emulates the SQLERRM function, depending on the parameters.
13. SSMA does not support using the SQLCODE and SQLERRM functions outside
of an EXCEPTION block.
Migrating Oracle Cursors
This section describes problems and solutions for Oracle cursor migration. Keep in mind
that a packaged cursor needs special handling during conversion. For more information,
see Emulating Oracle Packages.
Oracle always requires that cursors be used with SELECT statements, regardless of the
number of rows requested from the database. In Microsoft SQL Server 2008, a SELECT
statement that is not enclosed within a cursor returns rows to the client as a default
result set. This is an efficient way to return data to a client application.
When cursors from a DB-Library, ODBC, or OLE DB program are used, the
SQL Server client libraries transparently call built-in server functions to handle
cursors more efficiently.
Syntax
The following table shows cursor statement syntax in both platforms.
| record_type_name
| ref_cursor_type_name}];
Opening a cursor OPEN cursor_name OPEN cursor_name
[(cursor_parameter(s))];
Cursor attributes { cursor_name See below.
| cursor_variable_name
| :host_cursor_variable_name}
% {FOUND | ISOPEN | NOTFOUND |
ROWCOUNT}
SQL cursors SQL % See below.
{FOUND | ISOPEN | NOTFOUND |
ROWCOUNT | BULK_ROWCOUNT(index) |
BULK_EXCEPTIONS(index).{ERROR_INDEX
| ERROR_CODE}}
Fetching from FETCH cursor_name INTO variable(s) FETCH [[NEXT |
cursor PRIOR | FIRST |
LAST | ABSOLUTE {n
| @nvar} |
RELATIVE {n |
@nvar}]
FROM] cursor_name
[INTO
@variable(s)]
Update fetched UPDATE table_name UPDATE table_name
row SET statement(s)… SET statement(s)…
WHERE CURRENT OF cursor_name; WHERE CURRENT OF
cursor_name
Delete fetched DELETE FROM table_name DELETE FROM
row WHERE CURRENT OF cursor_name; table_name
WHERE CURRENT OF
cursor_name
Closing cursor CLOSE cursor_name; CLOSE cursor_name
Declaring a Cursor
Although the Transact-SQL DECLARE CURSOR statement does not support cursor
arguments, it does support local variables. The values of these local variables are used
in the cursor when it is opened. Microsoft SQL Server 2008 offers numerous additional
capabilities in its DECLARE CURSOR statement.
The INSENSITIVE option defines a cursor that makes a temporary copy of the data to
be used by that cursor. The temporary table answers all of the requests to the cursor.
Consequently, modifications made to base tables are not reflected in the data returned
by fetches made to that cursor. Data accessed by this cursor type cannot be modified.
Applications can request a cursor type, and then execute a Transact-SQL statement
that is not supported by server cursors of the type requested. SQL Server returns an
error that indicates that the cursor type has changed, or, given a set of factors, implicitly
converts a cursor.
The following table shows the factors that trigger SQL Server to implicitly convert a
cursor from one type to another.
If the READ ONLY option is chosen, updates are prevented from occurring against any
row within the cursor. That option overrides the default capability of a cursor to be
updated.
The UPDATE [OF column_list] statement defines updatable columns within the cursor.
If [OF column_list] is supplied, only the columns listed allow modifications. If a list is not
supplied, all columns can be updated, unless the cursor is defined as READ ONLY.
Note that the name scope for a SQL Server cursor is the connection itself. That differs
from the name scope of a local variable. A second cursor with the same name as an
existing cursor on the same user connection cannot be declared until the first cursor is
deallocated.
Following are descriptions of the SSMA algorithm of cursor conversion for several
specific cases.
SSMA puts this cursor declaration directly before the OPEN statement that
opens the cursor and removes the RETURN clause.
SSMA declares a local variable for each parameter with the following naming
pattern:
@CURSOR_PARAM_<cursor_name>_<parameter_name>
The data type is converted according to the effective SSMA type mapping for
local variables.
cursor_variable_declaration ::=
cursor_variable_name type_name;
Convert to:
@cursor_variable_name CURSOR;
Opening a Cursor
Unlike PL/SQL, Transact-SQL does not support passing arguments to a cursor when it
is opened. When a Transact-SQL cursor is opened, the result set membership and
ordering are fixed. Updates and deletes that have been committed against the cursor's
base tables by other users are reflected in fetches made against all cursors defined
without the INSENSITIVE option. In the case of an INSENSITIVE cursor, a temporary
table is generated.
SSMA tests to see whether the cursor was declared with formal cursor parameters. For
each formal cursor parameter, generate a SET statement before the cursor declaration
to assign the actual cursor parameter to the appropriate local variable:
SET @CURSOR_PARAM_<cursor_name>_<parameter_name> =
actual_cursor_parameter
If there is no actual parameter for the formal parameter, use a DEFAULT expression as
declared in the cursor parameter declaration:
Fetching Data
Oracle cursors can move in a forward direction only—there is no backward or relative
scrolling capability. SQL Server 2008 cursors can scroll forward and backward with the
fetch options shown in the following table. You can use these fetch options only if the
cursor is declared with the SCROLL option.
FIRST Moves the cursor to the first row in the result set and returns
the first row.
LAST Moves the cursor to the last row in the result set and returns
the last row.
ABSOLUTE n Returns the nth row in the result set. If n is a negative value,
the returned row is the nth row counting backward from the
last row of the result set.
RELATIVE n Returns the nth row after the currently fetched row. If n is a
negative value, the returned row is the nth row counting
backward from the cursor's relative position.
The Transact-SQL FETCH statement does not require the INTO clause. If return
variables are not specified, the row is automatically returned to the client as a single-row
result set. However, if your procedure must get the rows to the client, a noncursor
SELECT statement is much more efficient.
Issues
SSMA recognizes the following FETCH formats:
FETCH INTO <record>: SSMA splits the record into its components and fetches
each variable separately.
IF @@FETCH_STATUS = 0
SET @v_<cursor_name | cursor_variable_name >_rowcount =
@v_<cursor_name | cursor_variable_name >_rowcount + 1
ISOPEN: Converts to any condition that is always false, for example (1=2)
Oracle
IF SQL%FOUND THEN …;
IF @@ROWCOUNT > 0 …
SQL Server does not support Oracle’s cursor FOR loop syntax, but SSMA can convert
these loops. See the examples in the previous section.
The SSMA conversion option Convert OPEN-FOR statement for subprogram out
parameters (see Figure 4) is used because there is an ambiguity when a REF CURSOR
output parameter is opened in the procedure. The REF CURSOR might be fetched in
the caller procedure (SSMA does not support this usage) or used directly by the
application (SSMA can handle this if the option is set to Yes).
Figure 4: Setting the Convert OPEN-FOR statement for REF CURSOR OUT
parameters SSMA conversion option
If the OPEN-FOR statement is used for a local cursor variable, SSMA converts it
to:
If the OPEN-FOR statement is used for an output procedure parameter and the
option is set to ON, it’s converted to:
select_statement
If the OPEN-FOR statement is used for an output procedure parameter and the
option is set to OFF, SSMA generates the following error:
“Conversion of OPEN-FOR statement is disabled.”
DEALLOCATE <cursor_variable_name>
SET @auxiliary_exec_param$N = '[@auxiliary_paramN <datatype>
[OUTPUT],] … @auxiliary_tmp_cursor$N cursor OUTPUT'
2. Then SSMA generates the following error message: ‘OPEN ... FOR statement
will be converted, but the dynamic string must be converted manually.’
3. It adds the following line into the Attempted target code section:
SSMA uses integer value N as part of declared variable names to provide scope
name uniqueness.
When used for an ouput procedure parameter, the OPEN-FOR-USING statement and
the Convert OPEN-FOR statement for subprogram out parameters option is set to
ON.
DECLARE
@auxiliary_cursor_definition_sql$N NVARCHAR(max),
@auxiliary_exec_param$N NVARCHAR(max)
SET @auxiliary_exec_param$N = '[@auxiliary_paramN <datatype> [OUTPUT]]'
2. Then it generates the following error message: “OPEN ... FOR statement will be
converted, but the dynamic string must be converted manually.”
3. SSMA puts the following line into the Attempted target code section:
SSMA uses the integer value N as part of the declared variable names to
provide scope name uniqueness.
CURRENT OF Clause
The CURRENT OF clause syntax and function for updates and deletes is the same in
both PL/SQL and Transact-SQL. A positioned UPDATE or DELETE operation is
performed against the current row within the specified cursor.
Closing a Cursor
The Transact-SQL CLOSE CURSOR statement closes the cursor but leaves the data
structures accessible for reopening. The PL/SQL CLOSE CURSOR statement closes
and releases all data structures.
Oracle
SQL Server
DECLARE
@emp_rec$empno float(53),
@emp_rec$ename varchar(max)
DECLARE
emp_cursor CURSOR LOCAL FORWARD_ONLY FOR
SELECT EMP.EMPNO, EMP.ENAME
FROM dbo.EMP
WHERE EMP.MGR = @mgr_param
OPEN emp_cursor
WHILE 1 = 1
BEGIN
FETCH emp_cursor
INTO @emp_rec$empno, @emp_rec$ename
IF @@FETCH_STATUS = -1
BREAK
UPDATE dbo.EMP
SET
SAL = EMP.SAL * 1.1
END
CLOSE emp_cursor
DEALLOCATE emp_cursor
END
END
Oracle
SQL Server
OPEN rank_cur
DECLARE
@CURSOR_PARAM_rank_cur_id_ float(53)
SET @CURSOR_PARAM_rank_cur_id_ = 2
DECLARE
@CURSOR_PARAM_rank_cur_sn varchar(max)
SET @CURSOR_PARAM_rank_cur_sn = 'd'
DECLARE
rank_cur CURSOR LOCAL FOR
SELECT RANK_TABLE.RANK, RANK_TABLE.RANK_NAME
FROM dbo.RANK_TABLE
WHERE RANK_TABLE.R_ID = @CURSOR_PARAM_rank_cur_id_ AND
RANK_TABLE.R_SN = @CURSOR_PARAM_rank_cur_sn
OPEN rank_cur
END
Oracle
SQL Server
DECLARE
Cur CURSOR LOCAL FOR
SELECT RANK_TABLE.ID
FROM dbo.RANK_TABLE
SET @v_Cur_rowcount = 0
OPEN Cur
END
WHILE 1 = 1
BEGIN
FETCH Cur
INTO @ID
IF @@FETCH_STATUS = 0
SET @v_Cur_rowcount = @v_Cur_rowcount + 1
IF @@FETCH_STATUS <> 0
BREAK
END
CLOSE Cur
DEALLOCATE Cur
END
Simulating Oracle Transactions in SQL Server 2008
During migration from Oracle to Microsoft SQL Server 2008, you must account for the
differences in their default transaction management behavior. SSMA for Oracle V4.0
can convert Oracle’s transaction-related statements, but you will find additional issues to
consider, as described in this section.
If the SSMA Convert transaction processing statements option is turned on, SSMA
tries to convert the Oracle statements for transaction management (COMMIT,
ROLLBACK, and SAVEPOINT), but it does not add any statement for opening a
transaction. So, you must decide which transaction management model to use in your
application. Because SQL Server 2008 now allows optimistic escalation mode, choose
between a pessimistic and an optimistic concurrency model.
Autocommit Transactions
Autocommit transactions are the default mode for SQL Server 2008. Each individual
Transact-SQL statement is committed when it completes. You do not have to specify
any statements to control transactions.
Implicit Transactions
As in Oracle, an implicit transaction starts whenever an INSERT, UPDATE, DELETE, or
other data manipulating function is performed. To allow implicit transactions, use the
SET IMPLICIT_TRANSACTIONS ON statement.
If this option is ON and there are no outstanding transactions, every SQL statement
automatically starts a transaction. If there is an open transaction, no new transaction will
start. The user must explicitly commit the open transaction with the COMMIT
TRANSACTION statement for the changes to take effect and for all locks to be
released.
Explicit Transactions
An explicit transaction is a grouping of SQL statements surrounded by BEGIN TRAN
and COMMIT or ROLLBACK commands. Therefore, for the complete emulation of the
Oracle transaction behavior, use a SET IMPLICIT_TRANSACTIONS ON statement.
Choosing a Concurrency Model
Consider changing your application's isolation level. In a multiple-user environment,
there are two models for updating data in a database:
Pessimistic concurrency involves locking the data at the database when you
read it. You exclusively lock the database record and don't allow anyone to touch
it until you are done modifying and saving it back to the database. You have
100 percent assurance that nobody will modify the record while you have it
checked out. Another person must wait until you have made your changes.
Pessimistic concurrency complies with ANSI-standard isolation levels as defined
in the SQL-99 standard. Microsoft SQL Server 2008 has four pessimistic
isolation levels:
READ COMMITTED
READ UNCOMMITTED
REPEATABLE READ
SERIALIZABLE
Optimistic concurrency means that you read the database record but don't lock
it. Anyone can read and modify the record at any time, so the record might be
modified by someone else before you modify and save it. If data is modified
before you save it, a collision occurs. Optimistic concurrency is based on
retaining a view of the data as it is at the start of a transaction. This model is
embodied in Oracle. The transaction isolation level that implements an optimistic
form of database concurrency is called a row versioning-based isolation level.
Because SQL Server 2008 has completely controllable isolation-level models, you can
choose the most appropriate isolation level. To control a row-versioning isolation level,
use the SET TRANSACTION ISOLATION LEVEL command. SNAPSHOT is the
isolation level that is similar to Oracle and does optimistic escalations.
Alternatively, the autonomous block must be started with the READ COMMITTED
isolation level with the READ_COMMITTED_SNAPSHOT database option set to ON.
Simulating Oracle Autonomous Transactions
This section describes how SSMA for Oracle V4.0 handles autonomous transactions
(PRAGMA AUTONOMOUS_TRANSACTION). These autonomous transactions do not
have direct equivalents in Microsoft SQL Server 2008.
When you define a PL/SQL block (anonymous block, procedure, function, packaged
procedure, packaged function, database trigger) as an autonomous transaction, you
isolate the DML in that block from the caller's transaction context. The block becomes
an independent transaction started by another transaction, referred to as the main
transaction.
PRAGMA AUTONOMOUS_TRANSACTION;
SQL Server 2008 does not support autonomous transactions. The only way to isolate a
Transact-SQL block from a transaction context is to open a new connection.
xp_ora2ms_exec2
<active_spid> int,
<login_time> datetime,
<ms_db_name> varchar,
<ms_schema_name> varchar,
<ms_procedure_name> varchar,
<bind_to_transaction_flag> varchar,
[optional_parameters_for_procedure]
Where:
<login_time> [input parameter] is the login time of the current user process.
<ms_db_name> [input parameter] is the database name owner of the stored
procedure.
In general, you can retrive the active_spid parameter from the @@spid system function.
You can query the login_time parameter with the statement:
We recommend that you use SSMA Extension Pack methods to retrieve the active_spid
and login_time values before passing them to the xp_ora2ms_exec2 procedure. Use
the following recommended general template to invoke xp_ora2ms_exec2:
Note that the parameters list that is passed to the xp_ora2ms_exec2 procedure
should keep the IN/OUT options in the parameters for
<procedure_name>$IMPL.
RETURN @return_value_variable
END
The function body will be transformed into the following procedure:
Oracle
IF @@TRANCOUNT > 0
COMMIT WORK
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK WORK
END CATCH
END
Migrating Oracle Records and Collections
Unlike Oracle, Microsoft SQL Server 2008 supports neither records nor collections.
When you migrate from Oracle to SQL Server 2008, therefore, you must apply
substantial transformations to the PL/SQL code.
The approach used by SSMA for Oracle V4.0 is to convert both records and collections
as a user-defined type implemented as SQL CLR type.
Note: SSMA for Oracle V3.0 does not convert collections. Therefore, this section
describes manual migration activity.
Implementing Collections
To emulate collections, you have four options:
Option 2. When collections are used within the scope of a subroutine, you can
use SQL Server table variables.
Option 5. You can use the xml data type to represent the internal structure of a
collection. For more information, see Implementing Records and Collections via
XML.
Option 6. Use SQL Server CLR user-defined type to create an analog of PL/SQL
collection. As said before, this approach was chosen for implementation in
SSMA for Oracle V4.0. For more information, see Emulating Records and
Collections via CLR UDT.
Option 1. Rewrite your code to avoid records and collections. In many cases,
collections or records are not justified. Generally, you can perform the same tasks by
using set-oriented operators, meanwhile gaining performance benefits and code
clearness.
In the PL/SQL code (from here and following we use the SCOTT demo scheme):
declare
type emptable is table of integer;
emps emptable;
i integer;
begin
select empno bulk collect into emps
from Emp where deptno = 20;
for i in emps.first..emps.last loop
update scott.emp set sal=sal*1.2 where EmpNo=emps(i);
end loop;
end;
Usually, nobody would write such awkward code in Oracle, but you may find something
similar in, for example, proprietary systems. It might be a good opportunity to refactor
the source code to use SQL where possible.
Option 2. In some situations you have no choice but to use collections (or something
similar such as arrays).
Suppose you want to retrieve a list of employer IDs, and for each ID from the list
execute a stored procedure to raise each salary.
declare
type emptable is table of integer;
emps emptable;
i integer;
begin
select empno bulk collect into emps
from Emp
where deptno = 20;
for i in emps.first..emps.last loop
scott.raisesalary(Emp => emps(i),Amount => 10);
end loop;
end;
Sometimes you need not only to run through a list and make an action for each record
(as seen earlier), but you also want to randomly access elements in the list.
In this situation it is useful to use table variables. The general idea is to replace a
collection (integer-indexed array) with a table (indexed by its primary key).
declare
type emptable is table of integer;
emps emptable;
i integer;
s1 numeric;
s2 numeric;
begin
select empno bulk collect into emps
from Emp;
for i in emps.first+1..emps.last-1 loop
select sal into s1 from scott.emp where empno = emps(i-1);
select sal into s2 from scott.emp where empno = emps(i+1);
update emp set sal=(s1+s2)/2 where EmpNo=emps(i);
end loop;
end;
declare @tab table(_idx_ int not null primary key, empno int)
insert into @tab(_idx_,empno) select row_number() over(order by
empno),empno from emp
declare @first int,@last int,@i int,@s1 money,@s2 money
select top 1 @first=_idx_ from @tab order by _idx_ asc
select top 1 @last =_idx_ from @tab order by _idx_ desc
set @i = @first+1
while @i < @last-1 begin
select @s1 = sal from emp where empno = (select empno from @tab where
_idx_=@i-1)
select @s2 = sal from emp where empno = (select empno from @tab where
_idx_=@i+1)
update emp set sal = (@s1+@s2)/2 where empno = (select empno from @tab
where _idx_=@i)
set @i = @i +1
end
In this example, the table variable @tab, indexed with an _idx_ field, represents our
collection.
Pay attention to the row_number() function in the select statement. If you do not plan to
insert explicit values in the collection, you can avoid using row_number:
declare @tab table(_idx_ int identity(1,1) not null primary key, empno
int)
insert into @tab(empno) select empno from emp
If you are using a collection of %ROWTYPE, you can declare a table variable with an
appropriate list of fields and use it as shown earlier.
By using table variables, you can emulate the functionality of almost any local collection,
as shown in the following table.
WHILE @n <> 0
BEGIN
@t_cur_value =
@t_cur_value + 1
INSERT INTO @emps
(_idx_, empno)
VALUE(@t_cur_value,
NULL)
SET @n = @n-1
END
----------------------------
-------
SELECT @t_cur_value =
ISNULL(MAX(_idx_),0) FROM
@emps
SELECT @v = empno FROM
@emps where _idx_ = @i
WHILE @n <> 0
BEGIN
@t_cur_value =
@t_cur_value + 1
INSERT INTO @emps
Task Collection Emulation with table Remarks
variable
(_idx_, empno)
VALUE(@t_cur_value, @v)
SET @n = @n-1
END
FORALL … FORALL i IN INSERT INTO emp (empno)
INSERT 1..20 SELECT empno FROM @emps
INTO INSERT INTO WHERE _idx_ between 1 and 20
emp(empno)
VALUES
(t(i))
The solution is similar to the solution that uses table variables. The main difference is that
instead of a table variable you use a local temporary table (#tab, for example). The table will be
visible in the procedure that created this table and in all subsequent procedures.
PL/SQL code
Stored procedure:
Procedure call:
declare
type emptable is table of integer;
emps emptable;
begin
select empno
bulk collect into emps
from scott.emp;
emp_raise(emps);
end;
Transact-SQL code
Stored procedure:
Procedure call:
Instead of using a collection, you pass needed data to a stored procedure via a temporary table.
Of course you miss useful things such as parameter substitution. (The name of the temporary
table you create outside of the stored procedure must be the same name as the temporary table
in the stored procedure.) That is, you do not cover situations in which different actual collections
are passed to the procedure. But, unfortunately, you cannot access a temporary table from
within SQL Server functions.
Option 4. This option is a slight modification of Option 3. Instead of using temporary tables
(which cannot be accessed from within function), you use permanent tables.
Unlike temporary tables, you can access permanent tables and views from within functions. But
be aware that you cannot use DML statements in functions, so this collection emulation is read-
only. If you want to modify a collection from within a user-defined function, you must use
another kind of emulation; you cannot modify permanent tables from within user-defined
functions. (For more information, see Sample Functions for XML Record Emulation.)
The only difference between Option 4 and Option 3 is that the table should be cleaned before
use.
PL/SQL code
declare
type emptable is table of integer;
emps emptable;
i integer;
s1 numeric;
s2 numeric;
begin
select empno bulk collect into emps
from Emp;
for i in emps.first+1..emps.last-1 loop
select sal into s1 from scott.emp where empno = emps(i-1);
select sal into s2 from scott.emp where empno = emps(i+1);
update emp set sal=(s1+s2)/2 where EmpNo=emps(i);
end loop;
end;
Transact-SQL code
create table emps_t(SPID smallint not null default @@SPID,_idx_ int not
null,empno int null)
go
create clustered index cl on emps_t(SPID,_idx_)
go
create view emps
as select _idx_,empno from emps_t where spid = @@spid
go
delete emps
Implementing Records
Usually you use records to simplify your PL/SQL code.
declare
empno number(4);
ename varchar(10);
job varchar(9);
mgr number(4);
hiredate date;
sal number(7,2);
comm number(7,2);
deptno number(2);
begin
select * into empno,ename,job,mgr,hiredate,sal,comm,deptno from scott.emp
where empno = 7369;
dbms_output.put_line(ename);
end;
declare
emps scott.emp%rowtype;
begin
select * into emps from scott.emp where empno = 7369;
dbms_output.put_line(emps.ename);
end;
Unfortunately, SQL Server doesn’t support records. The default SSMA for Oracle V4.0
approach is to split the record into a group of the constituting variables.
To do that, declare a separate variable for each column as in the following code:
print @ename
The situation is the same situation passing records into procedures or functions; you should
pass variables one by one into a procedure.
PL/SQL code
declare
emps scott.emp%rowtype;
begin
select * into emps from scott.emp where empno = 7369;
raise_emp_salary(emps);
end;
Transact-SQL code
Implementing Records
For complex cases you can emulate records via XML. For example, you could emulate
scott.emp%rowtype with the following XML structure:
<row>
<f_name>DEPTNO</f_name>
<_val>20</_val>
</row>
<row>
<f_name>SAL</f_name>
<_val>800</_val>
</row>
<row>
<f_name>HIREDATE</f_name>
<_val>Dec 17 1980 12:00:00:000AM</_val>
</row>
<row>
<f_name>MGR</f_name>
<_val>7902</_val>
</row>
<row>
<f_name>JOB</f_name>
<_val>CLERK</_val>
</row>
<row>
<f_name>ENAME</f_name>
<_val>SMITH</_val>
</row>
<row>
<f_name>EMPNO</f_name>
<_val>7369</_val>
</row>
To work with such a structure you need additional supplemental procedures and functions to
simplify access to the data. (Examples of the modules provided by SSMA are at the end of this
section.)
DECLARE
CURSOR emp_cursor IS
SELECT empno, ename FROM scott.emp;
emps emp_cursor%rowtype;
BEGIN
open emp_cursor;
loop
fetch emp_cursor into emps;
exit when emp_cursor%notfound;
raise_emp_salary(emp_rec);
end loop;
close emp_cursor;
END;
The code here is slightly different from SSMA-generated code. It shows only basic techniques
for working with XML records. (You fetch data from a cursor into separate variables, and then
you construct from it and an XML record.)
To extract data back from XML you could use an appropriate function such as:
set @ename = sysdb.ssma_oracle.GetRecord_varchar(@emps, N'ENAME')
Implementing Collections
PL/SQL code
DECLARE
TYPE Colors IS TABLE OF VARCHAR2(16);
rainbow Colors;
BEGIN
rainbow := Colors('Red', 'Yellow');
END;
DECLARE @x XML
SET @x =
'<coll_row _idx_="1">
<row>
<f_name>record_field_1</f_name>
<_val>value_1</_val>
</row>
</coll_row>
<coll_row _idx_="2">
<row>
<f_name>record_field_2</f_name>
<_val>value_2</_val>
</row>
</coll_row>
’
After these declarations you can modify a collection, record, or collection of records by using
XQuery. You may find it useful to write wrapper functions to work with XML, such as GET and
SET functions.
END;
A sample call
DECLARE
@x xml
For more information, see XQuery Functions against the xml Data Type
(http://msdn.microsoft.com/en-us/library/ms189254.aspx) in SQL Server Books Online.
CollectionIndexInt
CollectionIndexString
Record
The CollectionIndexInt type is intended for simulating collections indexed by integer, such as
VARRAYs, nested tables and integer key based associative arrays. The CollectionIndexString
type is used for associative arrays based indexed by character keys. Oracle record functionality
is emulated by the Record type.
All declarations of record or collection types are converted to this Transact-SQL declaration:
Here <type definition> is a descriptive text uniquely identifying the source PL/SQL type. For
example:
Oracle
SQL Server
DECLARE
@Record$TYPE varchar(max) = 'RECORD ( ID INT , NAME STRING , CANFLY INT )',
@CollectionIndexInt$TYPE varchar(max) = 'TABLE INDEX BY INT OF (' +
@Record$TYPE + ')'
Oracle
declare
TYPE <type_name> TABLE OF <element_type> INDEX BY [PLS_INTEGER |
BINARY_INTEGER];
<var_name> <type_name>;
SQL Server
DECLARE
@CollectionIndexInt$TYPE varchar(max) = 'TABLE INDEX BY INT OF <element_type>'
DECLARE
@<var_name> dbo.CollectionIndexInt = = dbo.CollectionIndexInt ::
[Null].SetType(@CollectionIndexInt$TYPE)
Oracle
DECLARE
TYPE nested_type IS TABLE OF VARCHAR2(20);
TYPE varray_type IS VARRAY(5) OF INTEGER;
v1 nested_type;
v2 varray_type;
BEGIN
v1 := nested_type('Arbitrary','number','of','strings');
v2 := varray_type(10, 20, 40, 80, 160);
END;
SQL Server
DECLARE
@CollectionIndexInt$TYPE varchar(max) = ' TABLE OF STRING',
@CollectionIndexInt$TYPE$2 varchar(max) = ' VARRAY OF INT',
@v1 dbo.CollectionIndexInt,
@v2 dbo.CollectionIndexInt
Oracle
a_collection(i) := ’VALUE’;
SQL Server
SET @a_collection = @a_collection.SetString(@i, ’VALUE’);
Oracle
declare
TYPE rec_details IS RECORD (id int,name varchar2(20));
type ntb1 is table of rec_details index by binary_integer;
c ntb1;
begin
c(1).id := 1;
end;
SQL Server
DECLARE
@CollectionIndexInt$TYPE varchar(max) = ' TABLE INDEX BY INT OF ( RECORD
( ID INT , NAME STRING ) )',
@c dbo.CollectionIndexInt = dbo.CollectionIndexInt ::
[Null].SetType(@CollectionIndexInt$TYPE)
SET @c = @c.SetRecord(1, @c.GetOrCreateRecord(1).SetInt(N'ID', 1))
Collection Built-in Methods
SSMA uses the following UDT methods to emulate built-in methods of PL/SQL collections.
sysdb.ssma_oracle.fn_bulk_collect2CollectionSimple
sysdb.ssma_oracle.fn_bulk_collect2CollectionComplex
The choice depends on the type of the target object. These functions return XML values that
can be parsed by CollectionIndexInt, CollectionIndexString and Record types. A special
AssignData function assigns XML-based collection to the UDT.
1. The collection contains elements with scalar types, and the SELECT list contains one
column:
Oracle
SELECT column_name_1
BULK COLLECT INTO <collection_name_1> FROM <data_source>
SQL Server
SET @<collection_name_1> =
@<collection_name_1>.AssignData(sysdb.ssma_oracle.fn_bulk_
collect2CollectionSimple((select column_name_1 from <data_source> for
xml path)))
2. The collection contains elements with record types, and the SELECT list contains one
column:
Oracle
SQL Server
SET @<collection_name_1> =
@<collection_name_1>.AssignData(sysdb.ssma_oracle.fn_bulk_
collect2CollectionComplex((select column_name_1 as
[collection_name_1_element_field_name_1], column_name_2 as
[collection_name_1_element_field_name_2] from <data_source> for xml
path)))
3. The collection contains elements with scalar type, and the SELECT list contains multiple
columns:
Oracle
SQL Server:
DB Best developed migration tools to automate conversion between SQL dialects. In 2005
Microsoft acquired this technology, which later became a family of SQL Server Migration
Assistant (SSMA) products. We continue to develop new versions of SSMA, and support
Microsoft customers who are migrating to SQL Server.
We also provide migration services covering all major steps of a typical migration project:
complexity assessment, schema conversion, data migration, application conversion, testing,
integration, deployment, performance tuning, training, and support.
Did this paper help you? Please give us your feedback. Tell us on a scale of 1 (poor) to 5
(excellent), how would you rate this paper and why have you given it this rating? For example:
Are you rating it high due to having good examples, excellent screenshots, clear writing,
or another reason?
Are you rating it low due to poor examples, fuzzy screenshots, unclear writing?
This feedback will help us improve the quality of the white papers we release.
Send feedback.