본문으로 건너뛰기
IntoSQL 홈

SQL Server에서 Oracle로 변환

SSMS의 스크립트 생성으로 스키마와 데이터를 담아 만든 스크립트(.sql 또는 .sql.gz, UTF-16 또는 UTF-8)를 놓으면, 같은 테이블, 키, 인덱스, 데이터를 담은 Oracle 스크립트를 다운로드할 수 있어요. Oracle이(가) 그대로 담을 수 없는 값과 옮기지 않는 객체는 모두 보고서에 나와요.

내 파일
sqlshop_2024_sqlserver.sql샘플 파일, 11kB
내 데이터베이스

18개 행 변환, 수정 2개, 거부 없음

보기

shop_2024_sqlserver.sql의 테이블SQL Server 덤프 · SQL Server Management Objects(으)로 작성됨 · utf-8

  • orders새 이름: ORDERS10개 행1
    11개 열
    IDNUMBER(10) not null
    CUSTOMER_IDNUMBER(10) not null
    STATUSVARCHAR2(10 CHAR) not null
    TOTALNUMBER(19,4) not null
    ITEMSNUMBER(3) not null
    ORDERED_ATTIMESTAMP(0) not null
    SHIPPED_ATTIMESTAMP(7)
    NOTECLOB
    DETAILSCLOB
    ORDER_REFVARCHAR2(36 CHAR) not null
    TOTAL_WITH_TAXNUMBER(12,2)
  • customers새 이름: CUSTOMERS8개 행1
    8개 열
    IDNUMBER(10) not null
    NAMEVARCHAR2(100 CHAR) not null
    EMAILVARCHAR2(200 CHAR) not null
    COUNTRYVARCHAR2(2 CHAR) not null
    IS_ACTIVENUMBER(1) not null
    SIGNED_UPDATE
    CREATED_ATTIMESTAMP(0) WITH TIME ZONE not null
    ROW_VERSIONRAW(8)

변환되지 않음

  • 뷰 paid_orders
  • 트리거 orders_no_negative

데이터베이스마다 이 객체를 자체 언어로 작성해요. Oracle에 맞게 다시 작성하세요.

-- Written by IntoSQL from a SQL Server dump. Load it with: NLS_LANG=.AL32UTF8 sqlplus -S -L <user>/<password>@<database> @<file>
WHENEVER SQLERROR EXIT FAILURE ROLLBACK;
SET DEFINE OFF;
SET SQLBLANKLINES ON;
SET FEEDBACK OFF;
SET SERVEROUTPUT ON;
CREATE OR REPLACE FUNCTION "INTOSQL_CLOB" (p SYS.ODCIVARCHAR2LIST) RETURN CLOB IS
    r CLOB;
BEGIN
    DBMS_LOB.CREATETEMPORARY(r, TRUE);
    FOR i IN 1 .. p.COUNT LOOP
        IF p(i) IS NOT NULL THEN
            DBMS_LOB.WRITEAPPEND(r, LENGTH2(p(i)), p(i));
        END IF;
    END LOOP;
    RETURN r;
END;
/
CREATE OR REPLACE FUNCTION "INTOSQL_BLOB" (p SYS.ODCIVARCHAR2LIST) RETURN BLOB IS
    r BLOB;
    piece RAW(2000);
BEGIN
    DBMS_LOB.CREATETEMPORARY(r, TRUE);
    FOR i IN 1 .. p.COUNT LOOP
        piece := HEXTORAW(p(i));
        DBMS_LOB.WRITEAPPEND(r, UTL_RAW.LENGTH(piece), piece);
    END LOOP;
    RETURN r;
END;
/

CREATE TABLE "ORDERS" (
    "ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
    "CUSTOMER_ID" NUMBER(10) NOT NULL,
    "STATUS" VARCHAR2(10 CHAR) DEFAULT 'pending' NOT NULL,
    "TOTAL" NUMBER(19,4) NOT NULL,
    "ITEMS" NUMBER(3) DEFAULT 1 NOT NULL,
    "ORDERED_AT" TIMESTAMP(0) NOT NULL,
    "SHIPPED_AT" TIMESTAMP(7),
    "NOTE" CLOB,
    "DETAILS" CLOB,
    "ORDER_REF" VARCHAR2(36 CHAR) NOT NULL,
    "TOTAL_WITH_TAX" NUMBER(12,2) GENERATED ALWAYS AS (CAST(CAST("TOTAL" * (1.16) AS NUMBER(12,2)) AS NUMBER(12,2))) VIRTUAL,
    CONSTRAINT "CK_ORDERS_ITEMS" CHECK ("ITEMS" >= 0),
    CONSTRAINT "CHECK2" CHECK ("STATUS" = 'cancelled' OR "STATUS" = 'shipped' OR "STATUS" = 'paid' OR "STATUS" = 'pending'),
    CONSTRAINT "CHECK3" CHECK ("TOTAL" >= (0))
);

CREATE TABLE "CUSTOMERS" (
    "ID" NUMBER(10) GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH 1),
    "NAME" VARCHAR2(100 CHAR) NOT NULL,
    "EMAIL" VARCHAR2(200 CHAR) NOT NULL,
    "COUNTRY" VARCHAR2(2 CHAR) DEFAULT 'US' NOT NULL,
    "IS_ACTIVE" NUMBER(1) DEFAULT 1 NOT NULL,
    "SIGNED_UP" DATE,
    "CREATED_AT" TIMESTAMP(0) WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
    "ROW_VERSION" RAW(8),
    CONSTRAINT "CK_CUSTOMERS_IS_ACTIVE" CHECK ("IS_ACTIVE" IN (0, 1))
);

BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
1,
'Ana Martínez',
'ana@example.com',
'MX',
1,
DATE '2023-01-15',
TIMESTAMP '2023-01-15 04:24:00.0000000 -06:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
2,
'Zoë Müller',
'zoe@example.de',
'DE',
1,
DATE '2023-02-03',
TIMESTAMP '2023-02-03 09:11:42.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
3,
'José Peña',
'jose@example.es',
'ES',
0,
DATE '2023-03-21',
TIMESTAMP '2023-03-21 20:05:17.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
4,
'李雷',
'lilei@example.cn',
'CN',
1,
DATE '2023-05-09',
TIMESTAMP '2023-05-09 10:40:00.0000000 +08:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
5,
'Noah O''Brien',
'noah@example.ie',
'IE',
1,
NULL,
TIMESTAMP '2023-06-30 15:00:00.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
6,
'Amélie Laurent',
'amelie@example.fr',
'FR',
1,
DATE '2024-01-02',
TIMESTAMP '2024-01-02 10:30:00.0000000 +01:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
7,
'Ravi Patel',
'ravi@example.in',
'IN',
0,
DATE '2024-02-29',
TIMESTAMP '2024-03-01 05:29:59.0000000 +05:30'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "CUSTOMERS" (
"ID",
"NAME",
"EMAIL",
"COUNTRY",
"IS_ACTIVE",
"SIGNED_UP",
"CREATED_AT"
) VALUES (
8,
'Sofia Rossi 🌻',
'sofia@example.it',
'IT',
1,
DATE '2024-04-12',
TIMESTAMP '2024-04-12 14:12:12.0000000 +02:00'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
1,
1,
'shipped',
129.9000,
3,
TIMESTAMP '2024-03-01 10:00:00.0000000',
TIMESTAMP '2024-03-02 16:30:00.1234567',
NULL,
'{"gift": false}',
'0b7a4c2e-3f1d-4e8a-9c55-1a2b3c4d5e6f'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
2,
2,
'paid',
54.0000,
1,
TIMESTAMP '2024-03-02 11:15:00.0000000',
NULL,
'Gift wrap 🎁',
'{"gift": true, "wrap": "red"}',
'1c8b5d3f-4a2e-4f9b-8d66-2b3c4d5e6f70'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
3,
1,
'cancelled',
12.5000,
1,
TIMESTAMP '2024-03-03 09:45:00.0000000',
NULL,
'Customer changed their mind',
NULL,
'2d9c6e4a-5b3f-4a0c-9e77-3c4d5e6f7081'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
4,
4,
'shipped',
980.0000,
12,
TIMESTAMP '2024-03-05 02:10:00.0000000',
TIMESTAMP '2024-03-07 08:00:00.0000000',
'请小心轻放',
'{"fragile": true}',
'3e0d7f5b-6c4a-4b1d-8f88-4d5e6f708192'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
5,
5,
'pending',
45.7500,
2,
TIMESTAMP '2024-03-08 17:20:00.0000000',
NULL,
'Leave at the door; ring twice',
NULL,
'4f1e806c-7d5b-4c2e-9a99-5e6f708192a3'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
6,
6,
'shipped',
310.4000,
5,
TIMESTAMP '2024-03-10 13:00:00.0000000',
TIMESTAMP '2024-03-11 09:00:00.0000000',
'Fragile: "glass"',
'{"fragile": true}',
'50f2917d-8e6c-4d3f-8baa-6f708192a3b4'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
7,
8,
'paid',
76.2000,
2,
TIMESTAMP '2024-03-11 18:45:00.0000000',
NULL,
NULL,
NULL,
'6103a28e-9f7d-4e40-9cbb-708192a3b4c5'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
8,
2,
'shipped',
19.9900,
1,
TIMESTAMP '2024-03-12 08:00:00.0000000',
TIMESTAMP '9999-12-31 23:59:59.9999999',
'Shipped date set to the maximum by an old import',
NULL,
'7214b39f-a08e-4f51-8dcc-8192a3b4c5d6'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
9,
3,
'shipped',
64.0000,
2,
TIMESTAMP '2024-03-14 15:30:00.0000000',
TIMESTAMP '2024-03-15 10:00:00.0000000',
'Two parcels',
'{"parcels": [1, 2]}',
'8325c4a0-b19f-4062-9edd-92a3b4c5d6e7'
);
COMMIT;
END;
/
BEGIN
INSERT INTO "ORDERS" (
"ID",
"CUSTOMER_ID",
"STATUS",
"TOTAL",
"ITEMS",
"ORDERED_AT",
"SHIPPED_AT",
"NOTE",
"DETAILS",
"ORDER_REF"
) VALUES (
10,
7,
'paid',
1499.0000,
1,
TIMESTAMP '2024-03-15 21:05:00.0000000',
NULL,
'C:\Users\ravi\invoice.pdf',
NULL,
'9436d5b1-c2a0-4173-8fee-a3b4c5d6e7f8'
);
COMMIT;
END;
/

-- Keys, indexes, foreign keys, counters and triggers, after the rows.
ALTER TABLE "ORDERS" ADD CONSTRAINT "PK_ORDERS" PRIMARY KEY ("ID");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "PK_CUSTOMERS" PRIMARY KEY ("ID");
ALTER TABLE "CUSTOMERS" ADD CONSTRAINT "UQ_CUSTOMERS_EMAIL" UNIQUE ("EMAIL");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_CUSTOMER" ON "ORDERS" ("CUSTOMER_ID");
CREATE INDEX "IX_ORDERS_IDX_ORDERS_STATUS_DATE" ON "ORDERS" ("STATUS", "ORDERED_AT");
ALTER TABLE "ORDERS" ADD CONSTRAINT "FK_ORDERS" FOREIGN KEY ("CUSTOMER_ID") REFERENCES "CUSTOMERS" ("ID") ENABLE NOVALIDATE;
BEGIN
    EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" ENABLE VALIDATE CONSTRAINT "FK_ORDERS"';
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Foreign key ' || '"FK_ORDERS"' || ' has rows without a parent: it stays unvalidated, and new rows are checked.');
END;
/
DECLARE
    next_id NUMBER;
BEGIN
    SELECT NVL(MAX("ID"), 0) + 1 INTO next_id FROM "ORDERS";
    IF next_id < 1 THEN
        next_id := 1;
    END IF;
    EXECUTE IMMEDIATE 'ALTER TABLE "ORDERS" MODIFY ("ID" GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH ' || next_id || '))';
END;
/
DECLARE
    next_id NUMBER;
BEGIN
    SELECT NVL(MAX("ID"), 0) + 1 INTO next_id FROM "CUSTOMERS";
    IF next_id < 1 THEN
        next_id := 1;
    END IF;
    EXECUTE IMMEDIATE 'ALTER TABLE "CUSTOMERS" MODIFY ("ID" GENERATED BY DEFAULT ON NULL AS IDENTITY (START WITH ' || next_id || '))';
END;
/
COMMENT ON TABLE "CUSTOMERS" IS 'People who bought at least once';
DROP FUNCTION "INTOSQL_CLOB";
DROP FUNCTION "INTOSQL_BLOB";
COMMIT;
EXIT;

수정 사항

  1. orders의 "total_with_tax" 열은 원본에서 저장돼요. Oracle에서는 읽을 때 계산해요.
  2. customers의 "row_version" 열은 SQL Server가 자동으로 설정하는 행 버전이에요. 덤프에 그 값이 없어서 RAW(8)(으)로 바꾸고 비워 둬요.

확인 필요

  • 원본에서 6개 텍스트 열은 대소문자를 구분하지 않고 비교해요. Oracle은 정확히 비교하므로 고유 키와 검색 결과가 달라질 수 있어요. 필요한 열에 대소문자를 구분하지 않는 콜레이션이나 열 타입을 쓰세요.
  • 뷰, 트리거, 루틴, 이벤트 2개가 변환되지 않았어요. 보고서에 각각 나와 있어요. Oracle에서 직접 다시 만드세요.
  • orders의 체크 제약 check1을(를) Oracle용으로 쓸 수 없어(function isjson) 만들지 않았어요. 중요하다면 체크 제약을 직접 작성하세요.
  • orders의 인덱스 idx_orders_pending은(는) Oracle용으로 쓸 수 없는 식을 사용해서(filtered index) 만들지 않았어요. 쿼리에 필요하면 인덱스를 직접 만드세요.
  • orders의 "order_ref" 열 기본값을 Oracle용으로 쓸 수 없어(function uuid) 기본값 없이 만들었어요. 새 행에 필요하면 기본값을 직접 추가하세요.

파일은 이 컴퓨터에서 변환되고 업로드되지 않아요.

다른 샘플:

SQL Server에서 Oracle로 바뀌는 점

각 행은 변환기가 적용하는 규칙이며, 규칙이 데이터를 바꿀 때 보고서에 쓰는 줄이 함께 나와요.

  • bit

    0과 1의 CHECK가 붙은 NUMBER(1)

    Oracle 21c에는 boolean 열 타입이 없어요. 1과 0이면 모든 버전에서 값이 유지돼요.

  • char(n), varchar(n), nchar(n), nvarchar(n)

    VARCHAR2(n CHAR), 문자 수로 셈

    길이는 SQL Server의 nvarchar처럼 문자 수로 세요.

  • varchar(max), nvarchar(max), text, ntext

    모든 값이 4000바이트에 들어가면 VARCHAR2(4000 CHAR), 아니면 CLOB

    VARCHAR2는 기본으로 4000바이트까지 담고 Oracle은 CLOB을 인덱싱할 수 없어요. 그래서 텍스트는 값이 들어가면 VARCHAR2, 아니면 CLOB이 돼요.

    보고서 줄: text_as_lob

  • empty strings and empty binary values

    NULL

    Oracle은 빈 문자열을 NULL로 저장하므로, '였던 값은 NULL로 읽혀요. 보고서에 열마다 개수가 나와요.

    보고서 줄: empty_string_null

  • decimal(p,s), money, smallmoney

    NUMBER(p,s), money는 NUMBER(19,4)

    NUMBER는 decimal처럼 38자리를 담으므로 모든 값이 정확해요.

  • datetime, datetime2, smalldatetime

    TIMESTAMP(p)

    datetime은 시계에 표시된 시각으로, 초를 소수 9자리까지 유지하는 Oracle의 TIMESTAMP와 같아요. date는 DATE가 되는데, DATE는 시각도 담지만 자정으로 남아요.

  • datetimeoffset

    TIMESTAMP WITH TIME ZONE, 각 값의 오프셋 유지

    Oracle은 -12:00부터 +14:00까지의 오프셋을 유지해요. -12:00을 넘는 오프셋의 값은 UTC로 써서 같은 시점을 유지해요.

  • time

    자정부터의 INTERVAL DAY TO SECOND

    Oracle에는 시각만 담는 타입이 없어요. 자정부터의 interval이 모든 값을 담아요.

  • dates that aren't valid

    NULL

    날짜가 아닌 값은 NULL이 되고 보고서에 개수가 나와요.

    보고서 줄: date_out_of_range

  • json (SQL Server 2025)

    CLOB

    같은 JSON 값이고, Oracle의 JSON 함수로 읽을 수 있어요.

  • uniqueidentifier

    VARCHAR2(36)

    같은 식별자를 소문자로 써요.

  • binary, varbinary, image

    RAW(n)과 BLOB

    바이트는 바이트 그대로 남아요.

  • geometry, geography

    SQL Server 자체 바이트를 담은 BLOB

    SSMS는 공간 값을 SQL Server의 직렬화 형식으로 쓰고, 이를 바이트 단위로 그대로 유지해요.

    보고서 줄: spatial_as_native

  • rowversion, timestamp

    BLOB, 빈 값

    행 버전은 SQL Server가 자동으로 설정하고, SSMS는 그 값을 스크립트에 넣지 않아요.

    보고서 줄: rowversion_empty

  • xml, sql_variant, hierarchyid, vector

    각 값의 텍스트를 담은 VARCHAR2 또는 CLOB

    Oracle에는 같은 타입이 없어요. xml과 sql_variant는 각 값의 텍스트를, hierarchyid는 /1/2/ 같은 경로를 유지해요.

    보고서 줄: type_replaced

  • collations that ignore case (_CI_)

    정확한 비교

    Oracle은 따로 지정하지 않으면 텍스트를 정확히 비교하므로, SQL Server에서 대소문자를 무시하던 열도 이제 Ana와 ana를 구분해요.

    보고서 줄: case_insensitive_collation

  • IDENTITY

    GENERATED BY DEFAULT ON NULL AS IDENTITY, 가장 큰 id 다음부터 이어짐

    복사한 id는 그대로 불러오고, 카운터는 가장 큰 id 다음부터 이어져요.

  • CREATE SEQUENCE

    같은 옵션의 CREATE SEQUENCE

    Oracle 시퀀스도 같은 방식으로 동작해요. SSMS는 시퀀스가 어디까지 진행했는지 스크립트에 넣지 않으므로, 만들 때의 값부터 시작해요.

  • computed columns

    가상 열

    Oracle은 생성 열을 읽을 때만 계산해요. PERSISTED 열은 같은 값을 가진 가상 열이 돼요.

    보고서 줄: stored_as_virtual

  • computed columns with functions Oracle lacks

    일반 열

    쓸 수 없는 수식은 일반 열로 남고 보고서에 한 줄 기록돼요. SSMS는 계산 열의 값을 스크립트에 넣지 않아요.

    보고서 줄: generated_not_translated

  • defaults with functions Oracle lacks, and NEXT VALUE FOR

    기본값 없음

    쓸 수 없는 기본값은 제외하고 보고해요.

    보고서 줄: default_not_translated

  • CHECK constraints

    변환됨

    같은 체크 제약이고, 각 행을 불러올 때 검사해요.

  • CHECK constraints with functions Oracle lacks

    만들지 않음

    쓸 수 없는 체크 제약은 제외하고 보고해요.

    보고서 줄: check_skipped

  • CHECK constraints added WITH NOCHECK or disabled

    만들지 않음

    SQL Server가 이 체크 제약을 적용하지 않으므로 행이 이를 위반할 수 있어요.

    보고서 줄: check_not_enforced

  • filtered and columnstore indexes, and indexes on CLOB columns

    만들지 않음

    IntoSQL은 열 전체에 대한 b-tree 인덱스를 써요. 다른 인덱스는 불러온 뒤 다시 만드세요.

    보고서 줄: index_skipped

  • foreign keys

    데이터 뒤에 ENABLE NOVALIDATE로 만들고, 고아 행은 경고와 함께 검증

    키는 새 행을 바로 검사하고 마지막에 검증해요. 부모 행이 없는 행이 있으면 불러오기를 멈추지 않고 경고와 함께 검증하지 않은 상태로 남겨요.

  • ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT

    제거

    Oracle의 외래 키는 삭제할 때만, CASCADE 또는 SET NULL로 동작해요.

    보고서 줄: foreign_key_action_unsupported

  • names over 128 bytes

    줄임

    Oracle 12.2 이상의 이름은 128바이트까지이고, SQL Server의 이름은 128자까지예요.

    보고서 줄: name_shortened

  • foreign keys to tables the dump doesn't create

    만들지 않음

    키는 없는 테이블을 가리킬 수 없어요.

    보고서 줄: foreign_key_skipped

  • tables on a partition scheme

    모든 파티션의 행을 담은 하나의 테이블

    행은 같아요. 필요하면 Oracle에서 테이블을 다시 파티션으로 나누세요.

    보고서 줄: partitions_dropped

  • temporal tables (SYSTEM_VERSIONING)

    두 테이블: 현재 행과 기록

    테이블과 그 기록 테이블은 일반 테이블로 복사돼요. Oracle은 기록을 자동으로 유지하지 않아요.

    보고서 줄: system_versioning_dropped

  • schemas

    하나의 스키마, 테이블 이름 앞에 스키마 이름

    스크립트는 실행하는 사용자의 스키마에 불러와지므로, 다른 스키마의 테이블은 이름 앞에 스키마 이름이 붙어요. dbo의 테이블은 이름을 유지해요.

    보고서 줄: schemas_merged

  • NOT NULL columns with values that become NULL

    NOT NULL 제거

    값을 NULL로 바꿔야 했던 열은 NOT NULL을 유지할 수 없어요. 유지하면 불러오기가 멈춰요.

    보고서 줄: not_null_relaxed

  • views, functions, procedures, triggers, synonyms, types and partition functions

    보고서에 나열

    코드가 T-SQL로 작성되어 있어서 직접 다시 만들 수 있도록 나열해요.

    보고서 줄: not_converted_objects

  • scripts without CREATE TABLE (data only)

    값으로 판단한 타입

    데이터만 있는 스크립트에는 행만 있어서 타입을 값으로 판단해요.

    보고서 줄: no_schema

  • expressions in INSERT values

    NULL

    데이터베이스를 실행하지 않는 변환기는 이 식을 계산할 수 없어요.

    보고서 줄: expression_value

  • types IntoSQL doesn't know

    CLOB

    텍스트는 어떤 값이든 담아요. 불러온 뒤 타입을 바꾸세요.

    보고서 줄: unknown_type

  • NUL characters in text

    유지

    VARCHAR2와 CLOB은 NUL 문자를 담을 수 있어서 값을 그대로 복사해요.

  • text that isn't valid in its encoding

    잘못된 바이트를 대체 문자로

    스크립트는 UTF-8이므로 모든 값이 올바른 텍스트여야 해요.

    보고서 줄: invalid_utf8_replaced

  • text decoded twice (ñ)

    그대로 복사

    스크립트에 정확히 이 문자들이 있으므로 그대로 복사하고, 보고서에서 알려 줘요.

    보고서 줄: mojibake_suspected

SQL Server에서Oracle에서보고서 줄
bit0과 1의 CHECK가 붙은 NUMBER(1)Oracle 21c에는 boolean 열 타입이 없어요. 1과 0이면 모든 버전에서 값이 유지돼요.
char(n), varchar(n), nchar(n), nvarchar(n)VARCHAR2(n CHAR), 문자 수로 셈길이는 SQL Server의 nvarchar처럼 문자 수로 세요.
varchar(max), nvarchar(max), text, ntext모든 값이 4000바이트에 들어가면 VARCHAR2(4000 CHAR), 아니면 CLOBVARCHAR2는 기본으로 4000바이트까지 담고 Oracle은 CLOB을 인덱싱할 수 없어요. 그래서 텍스트는 값이 들어가면 VARCHAR2, 아니면 CLOB이 돼요.text_as_lob
empty strings and empty binary valuesNULLOracle은 빈 문자열을 NULL로 저장하므로, '였던 값은 NULL로 읽혀요. 보고서에 열마다 개수가 나와요.empty_string_null
decimal(p,s), money, smallmoneyNUMBER(p,s), money는 NUMBER(19,4)NUMBER는 decimal처럼 38자리를 담으므로 모든 값이 정확해요.
datetime, datetime2, smalldatetimeTIMESTAMP(p)datetime은 시계에 표시된 시각으로, 초를 소수 9자리까지 유지하는 Oracle의 TIMESTAMP와 같아요. date는 DATE가 되는데, DATE는 시각도 담지만 자정으로 남아요.
datetimeoffsetTIMESTAMP WITH TIME ZONE, 각 값의 오프셋 유지Oracle은 -12:00부터 +14:00까지의 오프셋을 유지해요. -12:00을 넘는 오프셋의 값은 UTC로 써서 같은 시점을 유지해요.
time자정부터의 INTERVAL DAY TO SECONDOracle에는 시각만 담는 타입이 없어요. 자정부터의 interval이 모든 값을 담아요.
dates that aren't validNULL날짜가 아닌 값은 NULL이 되고 보고서에 개수가 나와요.date_out_of_range
json (SQL Server 2025)CLOB같은 JSON 값이고, Oracle의 JSON 함수로 읽을 수 있어요.
uniqueidentifierVARCHAR2(36)같은 식별자를 소문자로 써요.
binary, varbinary, imageRAW(n)과 BLOB바이트는 바이트 그대로 남아요.
geometry, geographySQL Server 자체 바이트를 담은 BLOBSSMS는 공간 값을 SQL Server의 직렬화 형식으로 쓰고, 이를 바이트 단위로 그대로 유지해요.spatial_as_native
rowversion, timestampBLOB, 빈 값행 버전은 SQL Server가 자동으로 설정하고, SSMS는 그 값을 스크립트에 넣지 않아요.rowversion_empty
xml, sql_variant, hierarchyid, vector각 값의 텍스트를 담은 VARCHAR2 또는 CLOBOracle에는 같은 타입이 없어요. xml과 sql_variant는 각 값의 텍스트를, hierarchyid는 /1/2/ 같은 경로를 유지해요.type_replaced
collations that ignore case (_CI_)정확한 비교Oracle은 따로 지정하지 않으면 텍스트를 정확히 비교하므로, SQL Server에서 대소문자를 무시하던 열도 이제 Ana와 ana를 구분해요.case_insensitive_collation
IDENTITYGENERATED BY DEFAULT ON NULL AS IDENTITY, 가장 큰 id 다음부터 이어짐복사한 id는 그대로 불러오고, 카운터는 가장 큰 id 다음부터 이어져요.
CREATE SEQUENCE같은 옵션의 CREATE SEQUENCEOracle 시퀀스도 같은 방식으로 동작해요. SSMS는 시퀀스가 어디까지 진행했는지 스크립트에 넣지 않으므로, 만들 때의 값부터 시작해요.
computed columns가상 열Oracle은 생성 열을 읽을 때만 계산해요. PERSISTED 열은 같은 값을 가진 가상 열이 돼요.stored_as_virtual
computed columns with functions Oracle lacks일반 열쓸 수 없는 수식은 일반 열로 남고 보고서에 한 줄 기록돼요. SSMS는 계산 열의 값을 스크립트에 넣지 않아요.generated_not_translated
defaults with functions Oracle lacks, and NEXT VALUE FOR기본값 없음쓸 수 없는 기본값은 제외하고 보고해요.default_not_translated
CHECK constraints변환됨같은 체크 제약이고, 각 행을 불러올 때 검사해요.
CHECK constraints with functions Oracle lacks만들지 않음쓸 수 없는 체크 제약은 제외하고 보고해요.check_skipped
CHECK constraints added WITH NOCHECK or disabled만들지 않음SQL Server가 이 체크 제약을 적용하지 않으므로 행이 이를 위반할 수 있어요.check_not_enforced
filtered and columnstore indexes, and indexes on CLOB columns만들지 않음IntoSQL은 열 전체에 대한 b-tree 인덱스를 써요. 다른 인덱스는 불러온 뒤 다시 만드세요.index_skipped
foreign keys데이터 뒤에 ENABLE NOVALIDATE로 만들고, 고아 행은 경고와 함께 검증키는 새 행을 바로 검사하고 마지막에 검증해요. 부모 행이 없는 행이 있으면 불러오기를 멈추지 않고 경고와 함께 검증하지 않은 상태로 남겨요.
ON UPDATE CASCADE, SET NULL or SET DEFAULT; ON DELETE SET DEFAULT제거Oracle의 외래 키는 삭제할 때만, CASCADE 또는 SET NULL로 동작해요.foreign_key_action_unsupported
names over 128 bytes줄임Oracle 12.2 이상의 이름은 128바이트까지이고, SQL Server의 이름은 128자까지예요.name_shortened
foreign keys to tables the dump doesn't create만들지 않음키는 없는 테이블을 가리킬 수 없어요.foreign_key_skipped
tables on a partition scheme모든 파티션의 행을 담은 하나의 테이블행은 같아요. 필요하면 Oracle에서 테이블을 다시 파티션으로 나누세요.partitions_dropped
temporal tables (SYSTEM_VERSIONING)두 테이블: 현재 행과 기록테이블과 그 기록 테이블은 일반 테이블로 복사돼요. Oracle은 기록을 자동으로 유지하지 않아요.system_versioning_dropped
schemas하나의 스키마, 테이블 이름 앞에 스키마 이름스크립트는 실행하는 사용자의 스키마에 불러와지므로, 다른 스키마의 테이블은 이름 앞에 스키마 이름이 붙어요. dbo의 테이블은 이름을 유지해요.schemas_merged
NOT NULL columns with values that become NULLNOT NULL 제거값을 NULL로 바꿔야 했던 열은 NOT NULL을 유지할 수 없어요. 유지하면 불러오기가 멈춰요.not_null_relaxed
views, functions, procedures, triggers, synonyms, types and partition functions보고서에 나열코드가 T-SQL로 작성되어 있어서 직접 다시 만들 수 있도록 나열해요.not_converted_objects
scripts without CREATE TABLE (data only)값으로 판단한 타입데이터만 있는 스크립트에는 행만 있어서 타입을 값으로 판단해요.no_schema
expressions in INSERT valuesNULL데이터베이스를 실행하지 않는 변환기는 이 식을 계산할 수 없어요.expression_value
types IntoSQL doesn't knowCLOB텍스트는 어떤 값이든 담아요. 불러온 뒤 타입을 바꾸세요.unknown_type
NUL characters in text유지VARCHAR2와 CLOB은 NUL 문자를 담을 수 있어서 값을 그대로 복사해요.
text that isn't valid in its encoding잘못된 바이트를 대체 문자로스크립트는 UTF-8이므로 모든 값이 올바른 텍스트여야 해요.invalid_utf8_replaced
text decoded twice (ñ)그대로 복사스크립트에 정확히 이 문자들이 있으므로 그대로 복사하고, 보고서에서 알려 줘요.mojibake_suspected

스크립트를 Oracle에 불러오기

스크립트는 테이블을 만들고 행을 복사한 다음 키, 인덱스, 외래 키를 추가해요. 그래서 빨리 불러오고 첫 오류에서 멈춰요.

NLS_LANG=.AL32UTF8 sqlplus -S -L user@//localhost/mydb @sql_server.oracle.sql

외래 키는 ENABLE NOVALIDATE로 추가해서 새 행은 바로 검사하고, 그다음 검증해요. 기존 행이 존재하지 않는 행을 가리키면, 중간에 실패하지 않고 어떤 제약인지 알려 주고 그 제약은 검증하지 않은 채로 남겨요.

큰 덤프는 데이터베이스와 같은 컴퓨터에서 SQL*Plus를 실행하세요. 스크립트는 100행마다 커밋해요. 실행하는 사용자에게는 CREATE TABLE, CREATE SEQUENCE, CREATE TRIGGER, CREATE PROCEDURE 권한과 테이블스페이스 할당량이 필요해요.

좋은 결과를 위한 SQL Server 덤프 방법

변환기는 덤프에 담긴 내용을 읽어요. 다음 옵션을 쓰면 더 많은 데이터가 덤프에 남아요:

  • SSMS에서 데이터베이스를 마우스 오른쪽 버튼으로 클릭하고 태스크, 스크립트 생성을 차례로 고른 뒤 테이블을 선택하세요. dbatools, mssql-scripter 등 같은 라이브러리로 만든 도구도 같은 스크립트를 써요.
  • 고급에서 스크립팅할 데이터 형식을 스키마 및 데이터로 설정하세요. 스키마만 담은 스크립트에는 테이블에 행이 없어요.
  • SSMS 기본값인 유니코드나 UTF-8로 저장하세요. 둘 다 같은 방식으로 읽고, gzip도 쓸 수 있어요.

변환되지 않는 것

뷰, 함수, 프로시저, 트리거, 동의어, 규칙, 보안 정책은 T-SQL로 작성돼요. 보고서에 이름으로 나열되고 몰래 빠지는 일은 없으므로, Oracle에 맞게 다시 작성할 수 있어요.

SQL Server에서 Oracle로 변환에 관한 질문

여기에 답이 없나요? 문의 페이지로 연락하시면 담당자가 답장해요. 문의 페이지

덤프가 업로드되나요?

아니요. 변환기는 브라우저에서 실행되고 디스크의 덤프를 조각조각 읽으므로, 덤프가 컴퓨터 밖으로 나가지 않아요. 데스크톱 앱과 명령줄도 컴퓨터에서 변환해요.

덤프는 얼마나 커도 되나요?

변환기는 덤프 전체를 메모리에 올리지 않아요. 파일을 범위별로 두 번 읽고, 읽는 대로 스크립트를 써요. 수 GB 덤프도 브라우저, 데스크톱 앱, 명령줄에서 변환돼요.

SSMS가 저장하는 UTF-16 파일도 읽나요?

네. SSMS는 기본적으로 스크립트를 UTF-16으로 저장하고, 변환기는 UTF-16과 UTF-8을 gzip 여부와 상관없이, 배치 사이의 GO까지 똑같이 읽어요.

어떤 SQL Server 버전의 스크립트를 읽나요?

SQL Server 2017 이상에서 SSMS, dbatools, mssql-scripter로 만든 스크립트예요. 모든 테스트 스크립트는 SQL Server 2017, 2022, 2025에서 만들고 원본과 셀 단위로 비교해요.

IDENTITY 값은 유지되나요?

네. IDENTITY 열은 identity 열이 되고, 모든 행이 id를 유지하며, 다음 id는 가장 큰 id 다음부터 이어져요. 시퀀스는 같은 옵션으로 만들어요.

money, datetimeoffset 등 SQL Server 타입은 어떻게 되나요?

bit는 NUMBER(1), datetime2는 모든 자릿수를 담은 TIMESTAMP, datetimeoffset은 오프셋을 담은 TIMESTAMP WITH TIME ZONE이 되고, nvarchar(max)는 값이 VARCHAR2에 들어가지 않으면 CLOB이 돼요. 빈 문자열은 Oracle이(가) 저장하는 방식대로 NULL이 되고, 보고서에 개수가 나와요.

프로시저, 트리거, 뷰는 어떻게 되나요?

데이터베이스마다 자체 언어로 작성하므로 변환하지 않고 보고서에 이름으로 나열해요.

스크립트는 어떤 Oracle 버전에서 불러올 수 있나요?

Oracle 19c 이상이에요. 모든 테스트 스크립트를 21c와 23ai에서 불러오며, 문제가 생기면 첫 오류에서 멈춰요.