Oracle Database

Cảm ơn bạn đã đăng ký khóa học!

Tài liệu về Oracle Database vẫn đang được cập nhật.
Để tôn trọng công sức của tác giả, vui lòng không chia sẻ tài liệu.
Chúc bạn học tập hiệu quả!

Cài Đặt Oracle Database và Công cụ Quản lý

1. Cài Đặt Oracle Database

Bạn có thể tải Oracle Database từ trang chính thức của Oracle:

Oracle Database có nhiều phiên bản phù hợp với từng nhu cầu:

Phiên bảnĐặc điểm
Enterprise EditionDành cho doanh nghiệp lớn, hỗ trợ đầy đủ tính năng như RAC, Data Guard.
Standard EditionDành cho doanh nghiệp vừa và nhỏ, tính năng bị giới hạn so với Enterprise.
Express EditionMiễn phí, nhẹ, phù hợp cho học tập và phát triển nhỏ (hạn chế tài nguyên).
Personal EditionDành cho cá nhân, đầy đủ tính năng như Enterprise nhưng chỉ dùng trên một máy.

Lưu ý: Oracle Express Edition (XE) thường được khuyến khích cho người mới bắt đầu vì dễ cài đặt và miễn phí.

Các bước cài đặt cơ bản:

  1. Tải file cài đặt từ trang Oracle (ví dụ: Oracle Database 21c Express Edition).
  2. Giải nén và chạy file cài đặt (trên Windows là setup.exe, trên Linux là runInstaller).
  3. Chọn chế độ cài đặt: Desktop Class (dành cho máy cá nhân) hoặc Server Class (dành cho hệ thống lớn).
  4. Cấu hình Global Database Name (tên cơ sở dữ liệu toàn cục) và SYSDBA Password (mật khẩu cho tài khoản quản trị SYSSYSTEM).
  5. Hoàn tất cài đặt và kiểm tra bằng cách kết nối.

2. Cài Đặt Công cụ Quản lý

Oracle cung cấp nhiều công cụ để quản lý cơ sở dữ liệu:

  • SQL*Plus: Công cụ dòng lệnh mặc định đi kèm Oracle Database.
  • Oracle SQL Developer: Công cụ GUI miễn phí, dễ sử dụng, tải tại:
  • Enterprise Manager: Công cụ quản lý web dành cho quản trị viên (thường đi kèm Enterprise Edition).

Cài đặt SQL Developer:

  1. Tải file ZIP từ trang Oracle.
  2. Giải nén và chạy file sqldeveloper.exe (không cần cài đặt).
  3. Cấu hình kết nối đến cơ sở dữ liệu trong phần Connections.

3. Kết Nối và Làm Việc Với Oracle Database

  1. Mở SQL*Plus hoặc SQL Developer.

  2. Kết nối bằng tài khoản quản trị:

    • Trong SQL*Plus:
      CONNECT SYS/your_password AS SYSDBA;
      
    • Trong SQL Developer: Tạo kết nối mới với thông tin:
      • Username: SYS hoặc SYSTEM
      • Password: Mật khẩu được đặt khi cài đặt
      • Connection Type: Basic
      • Hostname: localhost
      • Port: 1521 (mặc định)
      • SID: xe (mặc định cho Express Edition) hoặc tên cơ sở dữ liệu bạn đặt.
  3. Kiểm tra kết nối:

    SELECT * FROM v$version;
    

    Kết quả sẽ hiển thị phiên bản Oracle Database đang chạy.

4. So sánh với SQL Server

  1. Cài đặt:

    • Thay SQL Server Management Studio (SSMS) bằng SQL*PlusSQL Developer, là các công cụ phổ biến của Oracle.
    • Oracle không có phiên bản "Developer" như SQL Server mà thay bằng Personal Edition hoặc Express Edition.
  2. Kết nối:

    • Oracle sử dụng SID hoặc Service Name thay vì Server Name như SQL Server.
    • Quyền quản trị trong Oracle là SYSDBA, khác với Windows Authentication hoặc SQL Server Authentication.

Tạo Database StudentManagementSystem

Chúng ta có thể sử dụng câu lệnh sau để tạo Database StudentManagementSystem trong Oracle:

-- Kết nối với quyền SYSDBA để tạo user/schema (thực hiện một lần)
CONN SYS/your_password AS SYSDBA;

-- Xóa schema nếu đã tồn tại
DECLARE
  v_count NUMBER;
BEGIN
  SELECT COUNT(*) INTO v_count FROM dba_users WHERE username = 'STUDENTMANAGEMENTSYSTEM';
  IF v_count > 0 THEN
    EXECUTE IMMEDIATE 'DROP USER STUDENTMANAGEMENTSYSTEM CASCADE';
  END IF;
END;
/

-- Tạo user/schema mới
CREATE USER STUDENTMANAGEMENTSYSTEM IDENTIFIED BY sms_password
  DEFAULT TABLESPACE USERS
  QUOTA UNLIMITED ON USERS;

-- Gán quyền cơ bản
GRANT CONNECT, RESOURCE TO STUDENTMANAGEMENTSYSTEM;

-- Kết nối với schema STUDENTMANAGEMENTSYSTEM
CONN STUDENTMANAGEMENTSYSTEM/sms_password;

-- Xóa các bảng nếu tồn tại
BEGIN
  FOR t IN (SELECT TABLE_NAME FROM USER_TABLES) LOOP
    EXECUTE IMMEDIATE 'DROP TABLE ' || t.TABLE_NAME || ' PURGE';
  END LOOP;
END;
/

-- Tạo bảng Students
CREATE TABLE Students (
    StudentID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name NVARCHAR2(50) NOT NULL,
    Birthdate DATE
);

-- Tạo bảng Courses
CREATE TABLE Courses (
    CourseID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name NVARCHAR2(50) NOT NULL,
    Description NVARCHAR2(255) NOT NULL
);

-- Tạo bảng Enrollments
CREATE TABLE Enrollments (
    EnrollmentID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    StudentID NUMBER NOT NULL,
    CourseID NUMBER NOT NULL,
    EnrollmentDate DATE DEFAULT SYSDATE NOT NULL,
    CONSTRAINT fk_enrollments_students FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
    CONSTRAINT fk_enrollments_courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
);

-- Chèn dữ liệu vào bảng Courses
INSERT INTO Courses (Name, Description)
VALUES (N'Toán học', N'Khóa học về toán học cơ bản và nâng cao'),
       (N'Lập trình', N'Khóa học lập trình và thuật toán'),
       (N'Tiếng Nhật', N'Khóa học tiếng Nhật từ sơ cấp đến trung cấp'),
       (N'Tiếng Anh', N'Khóa học tiếng Anh giao tiếp');

-- Chèn dữ liệu vào bảng Students
INSERT INTO Students (Name, Birthdate)
VALUES (N'Trần Nhật Anh', TO_DATE('2002-01-01', 'YYYY-MM-DD')),
       (N'Phạm Thanh Hương', TO_DATE('2000-05-15', 'YYYY-MM-DD')),
       (N'Đặng Hoàng Nam', TO_DATE('2003-03-07', 'YYYY-MM-DD')),
       (N'Vũ Khánh Linh', TO_DATE('2005-11-25', 'YYYY-MM-DD'));

-- Chèn dữ liệu vào bảng Enrollments (không chỉ định EnrollmentDate, dùng mặc định SYSDATE)
INSERT INTO Enrollments (StudentID, CourseID)
VALUES (1, 1),
       (1, 2);

-- Chèn dữ liệu vào bảng Enrollments với EnrollmentDate cụ thể
INSERT INTO Enrollments (StudentID, CourseID, EnrollmentDate)
VALUES (2, 3, TO_DATE('2025-01-01', 'YYYY-MM-DD')),
       (3, 4, TO_DATE('2025-02-28', 'YYYY-MM-DD'));

-- Commit dữ liệu
COMMIT;

So sánh với SQL Server

  1. Tạo Database:

    • SQL Server dùng CREATE DATABASEUSE. Oracle không có khái niệm database độc lập như vậy; thay vào đó, bạn tạo một schema (user) bằng CREATE USER.
    • Xóa database trong Oracle là xóa user với DROP USER ... CASCADE.
    • Quyền truy cập được gán qua GRANT CONNECT, RESOURCE.
  2. Xóa bảng:

    • SQL Server dùng ALTER DATABASE ... SET SINGLE_USER để đóng kết nối trước khi xóa. Oracle không cần bước này; bạn chỉ cần xóa các bảng trong schema bằng vòng lặp PL/SQL với DROP TABLE ... PURGE.
  3. Tạo bảng:

    • Kiểu dữ liệu:
      • INT thay bằng NUMBER.
      • NVARCHAR thay bằng NVARCHAR2 (hỗ trợ Unicode).
    • IDENTITY: Oracle dùng GENERATED ALWAYS AS IDENTITY thay IDENTITY(1,1).
    • DEFAULT: GETDATE() trong SQL Server thay bằng SYSDATE trong Oracle.
    • Khóa ngoại: Oracle yêu cầu đặt tên rõ ràng (CONSTRAINT fk_name) thay vì để mặc định như SQL Server.
  4. Chèn dữ liệu:

    • Ngày tháng: SQL Server chấp nhận định dạng ngày trực tiếp (như '2002-01-01'). Oracle yêu cầu TO_DATE để chuyển chuỗi thành kiểu DATE.
    • Transaction: SQL Server không cần COMMIT rõ ràng trong script này vì mặc định autocommit trong một số trường hợp. Oracle yêu cầu COMMIT để lưu dữ liệu vĩnh viễn.
    • Cú pháp INSERT INTO ... VALUES giữ nguyên.
  5. Phân tách lệnh:

    • SQL Server dùng GO để phân tách các batch. Oracle không cần GO, nhưng dùng / để kết thúc khối PL/SQL hoặc chạy script trong SQL*Plus/SQL Developer.

Quản lý Cơ sở dữ liệu (Database)

Trong Oracle, một Database là một tập hợp các tệp dữ liệu vật lý (data files, control files, redo log files). Một Instance là tập hợp các tiến trình và bộ nhớ để quản lý database. Oracle thường sử dụng khái niệm Schema để quản lý các đối tượng (bảng, view,...) thuộc về một người dùng.

Tạo cơ sở dữ liệu (CREATE DATABASE)

Oracle không khuyến khích tạo database mới bằng lệnh SQL thủ công vì quá trình này phức tạp. Thay vào đó, bạn sử dụng công cụ Database Configuration Assistant (DBCA):

  1. Chạy DBCA từ menu Start (Windows) hoặc lệnh dbca (Linux).
  2. Chọn Create Database > Typical Configuration.
  3. Đặt tên database (ví dụ: StudentManagementSystemTest) và mật khẩu quản trị.
  4. Hoàn tất để tạo database.

Tuy nhiên, nếu muốn tạo thủ công (dành cho quản trị viên nâng cao):

CREATE DATABASE StudentManagementSystemTest
   DATAFILE '/u01/app/oracle/oradata/StudentManagementSystemTest/system01.dbf' SIZE 100M
   SYSAUX DATAFILE '/u01/app/oracle/oradata/StudentManagementSystemTest/sysaux01.dbf' SIZE 100M
   LOGFILE GROUP 1 ('/u01/app/oracle/oradata/StudentManagementSystemTest/redo01.log') SIZE 50M,
           GROUP 2 ('/u01/app/oracle/oradata/StudentManagementSystemTest/redo02.log') SIZE 50M
   CHARACTER SET AL32UTF8
   NATIONAL CHARACTER SET AL16UTF16;

Xóa cơ sở dữ liệu (DROP DATABASE)

Xóa database bằng DBCA hoặc lệnh SQL:

DROP DATABASE;

Lưu ý: Phải kết nối với quyền SYSDBA và database phải ở trạng thái MOUNT.

Sao lưu cơ sở dữ liệu

Oracle sử dụng Recovery Manager (RMAN) để sao lưu:

RMAN> BACKUP DATABASE;

Lưu bản sao lưu vào thư mục mặc định hoặc chỉ định:

RMAN> BACKUP DATABASE TO DESTINATION '/u01/backup';

Khôi phục cơ sở dữ liệu

Khôi phục bằng RMAN:

RMAN> RESTORE DATABASE;
RMAN> RECOVER DATABASE;

Lưu ý: Cần cấu hình trước các tệp sao lưu và đảm bảo instance đang chạy.

Chọn schema để làm việc

Trong Oracle, bạn làm việc trong một schema (thường gắn với một user):

ALTER SESSION SET CURRENT_SCHEMA = StudentManagementSystem;

Hoặc kết nối trực tiếp với user sở hữu schema:

CONNECT StudentManagementSystem/password;

Liệt kê cơ sở dữ liệu

Oracle không có lệnh trực tiếp liệt kê tất cả database như SQL Server. Thay vào đó, bạn kiểm tra các instance hoặc schema:

  • Liệt kê các schema (người dùng):
SELECT USERNAME FROM ALL_USERS;
  • Kiểm tra database hiện tại:
SELECT NAME FROM v$database;

Quản lý bảng (Table)

Trong Oracle, bảng được tạo trong một schema (thường gắn với một user). Mỗi bảng thuộc về schema của user tạo ra nó, và bạn cần quyền cụ thể để truy cập bảng từ schema khác.

Tạo bảng (CREATE TABLE)

CREATE TABLE Teachers (
    TeacherID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name VARCHAR2(50) NOT NULL
);
  • NUMBER GENERATED ALWAYS AS IDENTITY: Tạo cột tự tăng giống IDENTITY trong SQL Server.
  • VARCHAR2: Kiểu chuỗi phổ biến trong Oracle, khác với VARCHAR của SQL Server.

Xóa bảng (DROP TABLE)

DROP TABLE Teachers;
  • Để xóa bảng và các ràng buộc liên quan:
DROP TABLE Teachers PURGE;

Lưu ý: PURGE xóa vĩnh viễn, không thể khôi phục từ Recycle Bin của Oracle.

Thay đổi cấu trúc bảng (ALTER TABLE)

Thêm cột mới

ALTER TABLE Students ADD (Email VARCHAR2(100));

Thay đổi kiểu dữ liệu của cột

ALTER TABLE Students MODIFY (Email VARCHAR2(150));

Lưu ý: Oracle không cho phép thay đổi kiểu dữ liệu nếu cột đã chứa dữ liệu không tương thích.

Đổi tên cột

ALTER TABLE Students RENAME COLUMN Email TO StudentEmail;
  • Oracle không dùng sp_rename như SQL Server mà dùng cú pháp RENAME COLUMN.

Xóa cột

ALTER TABLE Students DROP COLUMN StudentEmail;

Đổi tên bảng

ALTER TABLE Students RENAME TO Pupils;

Liệt kê bảng

Liệt kê các bảng trong schema hiện tại:

SELECT TABLE_NAME 
FROM USER_TABLES;
  • Nếu muốn liệt kê bảng từ tất cả schema mà user có quyền truy cập:
SELECT TABLE_NAME 
FROM ALL_TABLES;

Kiểu dữ liệu (Data Types)

Oracle có hệ thống kiểu dữ liệu riêng, khác với SQL Server ở một số điểm.

Kiểu số (Numeric Data Types)

Kiểu dữ liệuMô tảVí dụ
NUMBERKiểu số linh hoạt, có thể lưu số nguyên hoặc thập phân (tối đa 38 chữ số).10, 12345.67
NUMBER(p,s)Số thập phân với độ chính xác p (tổng chữ số) và s (số chữ số sau dấu chấm).NUMBER(10,2)12345.67
INTEGERSố nguyên, tương đương NUMBER(38) không thập phân.100, -50
FLOATSố thực dấu phẩy động, độ chính xác cao, có thể có lỗi làm tròn.3.14159
BINARY_FLOATSố thực 32-bit, nhanh hơn FLOAT nhưng ít chính xác hơn.3.14
BINARY_DOUBLESố thực 64-bit, tương tự BINARY_FLOAT nhưng chính xác hơn.3.14159

Lưu ý: Oracle không có kiểu TINYINT, SMALLINT, hay BIGINT riêng biệt như SQL Server; tất cả đều dùng NUMBER với độ chính xác phù hợp.

Kiểu chuỗi (String Data Types)

Kiểu dữ liệuMô tảVí dụ
CHAR(n)Chuỗi cố định độ dài n ký tự (mặc định byte), luôn chiếm n byte/ký tự.'ABCD ' (5 ký tự)
VARCHAR2(n)Chuỗi độ dài thay đổi, tối đa n byte (hoặc ký tự nếu khai báo CHAR).'John Doe'
NCHAR(n)Chuỗi Unicode cố định độ dài, hỗ trợ đa ngôn ngữ, mỗi ký tự 2 byte.N'Hoàng Hiệp'
NVARCHAR2(n)Chuỗi Unicode độ dài thay đổi, tối đa n ký tự.N'Hoàng Hiệp'
CLOBLưu chuỗi dài (Character Large Object), tối đa 4GB.'Lorem ipsum...'

Dùng CHAR(n), NCHAR(n) khi:

  • Chuỗi có độ dài cố định (mã số, mã bưu chính).
  • Hiệu suất cao hơn khi không cần kiểm tra độ dài.

Dùng VARCHAR2(n), NVARCHAR2(n) khi:

  • Chuỗi độ dài thay đổi (tên, mô tả).
  • Tiết kiệm bộ nhớ.

Dùng CLOB khi:

  • Chuỗi rất dài (bài viết, JSON/XML).

Lưu ý: Oracle không khuyến khích dùng VARCHAR; luôn dùng VARCHAR2.

Kiểu ngày và thời gian (Date & Time Data Types)

Kiểu dữ liệuMô tảVí dụ
DATELưu ngày và giờ, chính xác đến giây, từ 4712 BC đến 9999 AD.'2025-03-01 12:30:45'
TIMESTAMPLưu ngày giờ với độ chính xác đến phần nhỏ của giây (mặc định 6 chữ số).'2025-03-01 12:30:45.123456'
TIMESTAMP WITH TIME ZONENhư TIMESTAMP, nhưng kèm múi giờ.'2025-03-01 12:30:45.123456 +07:00'
INTERVAL YEAR TO MONTHLưu khoảng thời gian tính bằng năm và tháng.'2-6' (2 năm 6 tháng)
INTERVAL DAY TO SECONDLưu khoảng thời gian tính bằng ngày, giờ, phút, giây.'3 12:30:45.123'

Lưu ý: Oracle không có kiểu TIME riêng như SQL Server; dùng TIMESTAMP hoặc DATE.

Kiểu dữ liệu nhị phân và định danh (Binary & Unique Identifier)

Kiểu dữ liệuMô tảVí dụ
RAW(n)Dữ liệu nhị phân độ dài tối đa n byte (tối đa 2000 byte).0x1A2B3C
BLOBLưu dữ liệu nhị phân lớn (Binary Large Object), tối đa 4GB.Ảnh, file PDF

Lưu ý: Oracle không có kiểu UNIQUEIDENTIFIER như SQL Server; dùng RAW(16) hoặc chuỗi GUID với VARCHAR2(36).

Ví dụ tạo bảng với kiểu dữ liệu phổ biến

CREATE TABLE Employees (
    EmployeeID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name NVARCHAR2(100) NOT NULL,
    Birthdate DATE,
    WorkStartTime TIMESTAMP,
    Salary NUMBER(10,2),
    ProfilePicture BLOB,
    IsActive NUMBER(1) DEFAULT 1 -- 1: True, 0: False
);

Ví dụ tạo bảng với tất cả kiểu dữ liệu

CREATE TABLE AllDataTypesExample (
    ID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    IntNumber NUMBER(10) NOT NULL,
    DecimalNumber NUMBER(10,2),
    FloatNumber FLOAT,
    FixedText CHAR(10),
    VariableText VARCHAR2(255),
    UnicodeText NVARCHAR2(255),
    LongText CLOB,
    BirthDate DATE,
    FullTimestamp TIMESTAMP,
    ProfilePicture BLOB,
    CreatedAt DATE DEFAULT SYSDATE
);

Biến (Variable)

Trong Oracle, biến được sử dụng trong các khối PL/SQL (ngôn ngữ lập trình của Oracle) để lưu trữ dữ liệu tạm thời. Biến thường xuất hiện trong anonymous blocks, stored procedures, hoặc functions.

Khai báo biến (DECLARE)

Biến được khai báo trong khối DECLARE của PL/SQL:

DECLARE
  v_StudentName VARCHAR2(50);
  v_BirthYear NUMBER(4);
BEGIN
  NULL; -- Thân khối PL/SQL
END;
/
  • / là ký hiệu kết thúc khối PL/SQL trong SQL*Plus hoặc SQL Developer.

Gán giá trị cho biến

Oracle hỗ trợ gán giá trị bằng toán tử := hoặc lệnh SELECT ... INTO.

Gán giá trị bằng :=

DECLARE
  v_StudentName VARCHAR2(50);
BEGIN
  v_StudentName := 'Hoàng Hiệp';
  DBMS_OUTPUT.PUT_LINE(v_StudentName);
END;
/
  • DBMS_OUTPUT.PUT_LINE: Hàm in giá trị ra màn hình (tương tự PRINT trong SQL Server).

Gán giá trị bằng SELECT ... INTO

DECLARE
  v_BirthYear NUMBER(4);
BEGIN
  SELECT EXTRACT(YEAR FROM Birthdate) INTO v_BirthYear
  FROM Students 
  WHERE StudentID = 1;
  DBMS_OUTPUT.PUT_LINE('Năm sinh: ' || v_BirthYear);
END;
/
  • EXTRACT(YEAR FROM ...): Lấy năm từ ngày, thay cho YEAR() trong SQL Server.
  • ||: Toán tử nối chuỗi trong Oracle.

Sử dụng biến

Biến có thể dùng trong các câu lệnh SQL trong khối PL/SQL:

DECLARE
  v_Year NUMBER(4) := 2000;
BEGIN
  FOR rec IN (SELECT * FROM Students WHERE EXTRACT(YEAR FROM Birthdate) = v_Year) LOOP
    DBMS_OUTPUT.PUT_LINE(rec.Name);
  END LOOP;
END;
/

Biến bảng (Collection)

Oracle không có "biến bảng" như SQL Server mà dùng collections (ARRAY-like structures) như VARRAY hoặc TABLE OF.

Khai báo collection

DECLARE
  TYPE StudentTab IS TABLE OF Students%ROWTYPE;
  v_StudentTable StudentTab;
BEGIN
  SELECT * BULK COLLECT INTO v_StudentTable
  FROM Students
  WHERE ROWNUM <= 5; -- Giới hạn 5 dòng
  FOR i IN 1..v_StudentTable.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(v_StudentTable(i).Name);
  END LOOP;
END;
/
  • %ROWTYPE: Kiểu dữ liệu dựa trên cấu trúc bảng.
  • BULK COLLECT: Thu thập nhiều dòng vào collection.

Biến toàn cục (System Variables)

Oracle có các biến hệ thống bắt đầu bằng : hoặc thông qua gói SYS:

  • SQL%ROWCOUNT: Số dòng bị ảnh hưởng bởi câu lệnh trước đó.
  • USER: Tên schema hiện tại.
BEGIN
  UPDATE Students SET Name = 'New Name' WHERE StudentID = 1;
  DBMS_OUTPUT.PUT_LINE('Số dòng cập nhật: ' || SQL%ROWCOUNT);
  DBMS_OUTPUT.PUT_LINE('Người dùng: ' || USER);
END;
/

Hàm (Functions)

Oracle hỗ trợ các hàm tích hợp và cho phép tạo hàm người dùng trong PL/SQL.

Hàm xử lý chuỗi (String Functions)

HàmChức năngVí dụ
LENGTH(string)Trả về độ dài chuỗiLENGTH('Hello')5
SUBSTR(string, start, length)Cắt chuỗi conSUBSTR('SQL Server', 5, 6)'Server'
INSTR(string, substring)Tìm vị trí chuỗi conINSTR('Learn SQL', 'SQL')7
UPPER(string)Chuyển thành chữ in hoaUPPER('sql')'SQL'
LOWER(string)Chuyển thành chữ thườngLOWER('SQL')'sql'
TRIM(string)Xóa khoảng trắng 2 bên chuỗiTRIM(' SQL ')'SQL'
REPLACE(string, old, new)Thay thế chuỗi conREPLACE('Hello World', 'World', 'SQL')'Hello SQL'
CONCAT(string1, string2)Nối chuỗiCONCAT('Hello', ' SQL')'Hello SQL'

Hàm xử lý số (Numeric Functions)

HàmChức năngVí dụ
ABS(number)Trị tuyệt đốiABS(-5)5
CEIL(number)Làm tròn lênCEIL(4.2)5
FLOOR(number)Làm tròn xuốngFLOOR(4.9)4
ROUND(number, decimals)Làm tròn sốROUND(4.567, 2)4.57
POWER(base, exponent)Lũy thừaPOWER(2, 3)8
SQRT(number)Căn bậc haiSQRT(9)3
MOD(number, divisor)Chia lấy dưMOD(10, 3)1

Hàm xử lý ngày tháng (Date Functions)

HàmChức năngVí dụ
SYSDATETrả về ngày giờ hiện tạiSYSDATE'2025-03-15 10:15:30'
SYSTIMESTAMPNgày giờ với múi giờSYSTIMESTAMP'2025-03-15 10:15:30.123456 +07:00'
EXTRACT(part FROM date)Lấy phần từ ngàyEXTRACT(YEAR FROM DATE '2025-03-15')2025
ADD_MONTHS(date, months)Cộng thángADD_MONTHS('2025-03-15', 10)'2026-01-15'
MONTHS_BETWEEN(date1, date2)Khoảng cách giữa hai ngày (tháng)MONTHS_BETWEEN('2025-03-15', '2025-01-01')2.45
TO_CHAR(date, format)Định dạng ngàyTO_CHAR(SYSDATE, 'DD/MM/YYYY')'15/03/2025'
LAST_DAY(date)Ngày cuối thángLAST_DAY('2025-03-15')'2025-03-31'

Hàm nâng cao (Advanced Functions)

Hàm chuyển đổi dữ liệu

HàmChức năngVí dụ
TO_CHAR(expression, format)Chuyển sang chuỗiTO_CHAR(123, '999')'123'
TO_NUMBER(string)Chuyển chuỗi thành sốTO_NUMBER('123.45')123.45
TO_DATE(string, format)Chuyển chuỗi thành ngàyTO_DATE('15/03/2025', 'DD/MM/YYYY')'2025-03-15'

Hàm xử lý giá trị NULL

HàmChức năngVí dụ
NVL(expr1, expr2)Thay thế NULL bằng giá trị khácNVL(NULL, 'N/A')'N/A'
NULLIF(val1, val2)Trả về NULL nếu hai giá trị bằng nhauNULLIF(10, 10)NULL
COALESCE(val1, val2, …)Giá trị không NULL đầu tiênCOALESCE(NULL, 'SQL', 'Oracle')'SQL'

Ràng buộc dữ liệu

Ràng buộc đảm bảo tính toàn vẹn dữ liệu trong Oracle.

Tổng quan các ràng buộc

Ràng buộcChức năng
NOT NULLKhông cho phép giá trị NULL.
UNIQUEĐảm bảo giá trị trong cột là duy nhất.
PRIMARY KEYKết hợp NOT NULLUNIQUE, định danh duy nhất mỗi dòng.
FOREIGN KEYTạo mối quan hệ giữa các bảng, đảm bảo dữ liệu hợp lệ.
CHECKKiểm tra giá trị nhập vào thỏa mãn điều kiện cụ thể.

NOT NULL

CREATE TABLE Teachers (
    TeacherID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name VARCHAR2(50) NOT NULL
);

UNIQUE

CREATE TABLE Doctors (
    DoctorID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Email VARCHAR2(100) UNIQUE
);

PRIMARY KEY

CREATE TABLE Departments (
    DeptID NUMBER PRIMARY KEY,
    DeptName VARCHAR2(50) NOT NULL
);

FOREIGN KEY

CREATE TABLE Enrollments (
    StudentID NUMBER,
    CourseID NUMBER,
    FOREIGN KEY (StudentID) REFERENCES Students(StudentID) 
        ON DELETE CASCADE,
    FOREIGN KEY (CourseID) REFERENCES Courses(CourseID) 
        ON DELETE SET NULL
);
  • ON DELETE CASCADE: Xóa bản ghi cha sẽ xóa các bản ghi con.
  • ON DELETE SET NULL: Xóa bản ghi cha sẽ đặt khóa ngoại thành NULL.

CHECK

CREATE TABLE Employees (
    EmployeeID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Age NUMBER CHECK (Age >= 18)
);

Ví dụ tạo bảng có đầy đủ ràng buộc

CREATE TABLE Departments (
    DeptID NUMBER PRIMARY KEY,
    DeptName VARCHAR2(100) NOT NULL UNIQUE
);

CREATE TABLE Employees (
    EmployeeID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    Name VARCHAR2(100) NOT NULL,
    Email VARCHAR2(100) UNIQUE,
    Birthdate DATE CHECK (Birthdate >= '1990-01-01'),
    DepartmentID NUMBER,
    FOREIGN KEY (DepartmentID) REFERENCES Departments(DeptID)
);

Thao tác dữ liệu

Thêm dữ liệu (INSERT INTO)

Dùng để thêm dữ liệu vào bảng.

Thêm một sinh viên mới:

INSERT INTO Students (Name, Birthdate) 
VALUES ('Hoàng Hiệp', TO_DATE('2001-05-20', 'YYYY-MM-DD'));
  • TO_DATE: Chuyển chuỗi thành kiểu DATE theo định dạng.

Cập nhật dữ liệu (UPDATE)

Dùng để cập nhật dữ liệu trong bảng.

Cập nhật ngày sinh cho sinh viên có StudentID = 4:

UPDATE Students 
SET Birthdate = TO_DATE('2000-06-15', 'YYYY-MM-DD') 
WHERE StudentID = 4;

Xóa dữ liệu (DELETE)

Dùng để xóa dữ liệu trong bảng.

Xóa sinh viên có StudentID = 2:

DELETE FROM Students 
WHERE StudentID = 2;

Lưu ý:

  • Nếu không có WHERE, toàn bộ dữ liệu trong bảng sẽ bị xóa.
  • Dữ liệu xóa sẽ vào Recycle Bin (trừ khi dùng PURGE), có thể khôi phục bằng:
FLASHBACK TABLE Students TO BEFORE DROP;

Xóa toàn bộ dữ liệu (TRUNCATE TABLE)

Dùng để xóa toàn bộ dữ liệu trong bảng, nhanh hơn DELETE:

TRUNCATE TABLE Students;

Lưu ý:

  • Không ghi log từng dòng, không khôi phục được.
  • Reset giá trị IDENTITY (sequence nếu dùng).

Sao chép dữ liệu vào bảng mới (CREATE TABLE AS SELECT)

Tương đương SELECT INTO trong SQL Server:

CREATE TABLE StudentsBackup 
AS 
SELECT * FROM Students;

Chèn dữ liệu từ bảng khác (INSERT INTO ... SELECT)

INSERT INTO StudentsBackup (Name, Birthdate)
SELECT Name, Birthdate 
FROM Students;

Hợp nhất dữ liệu (MERGE)

Dùng để thực hiện INSERT, UPDATE, hoặc DELETE dựa trên điều kiện:

MERGE INTO StudentsBackup tgt
USING Students src
ON (tgt.StudentID = src.StudentID)
WHEN MATCHED THEN 
    UPDATE SET tgt.Name = src.Name, tgt.Birthdate = src.Birthdate
WHEN NOT MATCHED THEN 
    INSERT (StudentID, Name, Birthdate) 
    VALUES (src.StudentID, src.Name, src.Birthdate);
  • Oracle không có WHEN NOT MATCHED BY SOURCE THEN DELETE như SQL Server; cần xử lý riêng nếu muốn xóa.

Kiểm tra tồn tại (EXISTS)

Kiểm tra dữ liệu trước khi thao tác:

BEGIN
  IF EXISTS (SELECT 1 FROM Students WHERE Name = 'Hoàng Hiệp') THEN
    DBMS_OUTPUT.PUT_LINE('Sinh viên đã tồn tại');
  ELSE
    INSERT INTO Students (Name, Birthdate) 
    VALUES ('Hoàng Hiệp', TO_DATE('2001-05-20', 'YYYY-MM-DD'));
  END IF;
END;
/

Lấy dữ liệu bị ảnh hưởng (RETURNING)

Tương đương OUTPUT trong SQL Server:

DECLARE
  v_StudentID NUMBER;
  v_Name VARCHAR2(50);
BEGIN
  DELETE FROM Students
  WHERE Birthdate < TO_DATE('2000-01-01', 'YYYY-MM-DD')
  RETURNING StudentID, Name INTO v_StudentID, v_Name;
  DBMS_OUTPUT.PUT_LINE('Xóa: ' || v_StudentID || ' - ' || v_Name);
END;
/
  • RETURNING trả về dữ liệu bị ảnh hưởng vào biến trong PL/SQL.

Toán tử và điều kiện lọc

Toán tử so sánh

Các toán tử phổ biến trong Oracle:

Toán tửChức năng
=So sánh bằng
!= hoặc <>So sánh khác
>Lớn hơn
<Nhỏ hơn
>=Lớn hơn hoặc bằng
<=Nhỏ hơn hoặc bằng
BETWEENTrong khoảng
INTrong danh sách
LIKEKhớp mẫu ký tự
AND
ORHoặc
NOTPhủ định

AND

Lấy sinh viên có tên bắt đầu bằng 'P' và sinh sau 01/01/2000:

SELECT * FROM Students
WHERE Name LIKE 'P%' AND Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD');

OR

Lấy sinh viên có tên là “Trần Nhật Anh” hoặc “Vũ Khánh Linh”:

SELECT * FROM Students
WHERE Name = 'Trần Nhật Anh' OR Name = 'Vũ Khánh Linh';

NOT

Lấy tất cả sinh viên trừ “Trần Nhật Anh”:

SELECT * FROM Students
WHERE NOT Name = 'Trần Nhật Anh';

LIKE

Tìm sinh viên có tên bắt đầu bằng “T”:

SELECT * FROM Students
WHERE Name LIKE 'T%';

Wildcards (Ký tự đại diện)

  • %: Đại diện cho 0 hoặc nhiều ký tự.
  • _: Đại diện cho một ký tự. Tìm sinh viên có tên chứa “ng”:
SELECT * FROM Students
WHERE Name LIKE '%ng%';

IN

Lấy sinh viên có tên trong danh sách:

SELECT * FROM Students
WHERE Name IN ('Vũ Khánh Linh', 'Trần Nhật Anh', 'Lê Thu Hà');

BETWEEN

Lấy sinh viên sinh từ 2000 đến 2003:

SELECT * FROM Students
WHERE Birthdate BETWEEN TO_DATE('2000-01-01', 'YYYY-MM-DD') 
                    AND TO_DATE('2003-12-31', 'YYYY-MM-DD');

IS NULL và IS NOT NULL

Lấy sinh viên chưa có ngày sinh:

SELECT * FROM Students 
WHERE Birthdate IS NULL;

ANY và ALL

  • ANY: So sánh với bất kỳ giá trị nào trong tập con.
  • ALL: So sánh với tất cả giá trị trong tập con. Lấy sinh viên sinh muộn hơn ít nhất một người trong danh sách:
SELECT * FROM Students
WHERE Birthdate > ANY (
    SELECT Birthdate 
    FROM Students 
    WHERE Name IN ('Vũ Khánh Linh', 'Trần Nhật Anh')
);

EXISTS

Lấy sinh viên đã đăng ký khóa học:

SELECT Name 
FROM Students s
WHERE EXISTS (
    SELECT 1 
    FROM Enrollments e 
    WHERE e.StudentID = s.StudentID
);

IF ELSE

Dùng trong PL/SQL:

DECLARE
  v_TotalStudents NUMBER;
BEGIN
  SELECT COUNT(*) INTO v_TotalStudents FROM Students;
  IF v_TotalStudents > 100 THEN
    DBMS_OUTPUT.PUT_LINE('Có hơn 100 sinh viên.');
  ELSE
    DBMS_OUTPUT.PUT_LINE('Số lượng sinh viên <= 100.');
  END IF;
END;
/

CASE WHEN

Dùng trong SQL hoặc PL/SQL:

SELECT Name,
       CASE 
           WHEN EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM Birthdate) < 18 THEN 'Teen'
           WHEN EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM Birthdate) BETWEEN 18 AND 24 THEN 'Young Adult'
           ELSE 'Adult'
       END AS AgeGroup
FROM Students;

Truy vấn dữ liệu

Lệnh SELECT cơ bản

Lấy tất cả dữ liệu từ bảng Students:

SELECT * FROM Students;

Lấy cột cụ thể

Lấy NameBirthdate từ bảng Students:

SELECT Name, Birthdate FROM Students;

Đặt bí danh (Alias)

Sử dụng AS để đặt tên khác cho cột hoặc bảng:

SELECT Name AS StudentName, Birthdate AS DOB FROM Students;
  • Oracle cho phép bỏ AS khi đặt alias cho cột:
SELECT Name StudentName, Birthdate DOB FROM Students;

Sắp xếp dữ liệu (ORDER BY)

Sắp xếp sinh viên theo tên:

SELECT Name, Birthdate 
FROM Students 
ORDER BY Name ASC;
  • ASC: Tăng dần (mặc định).
  • DESC: Giảm dần.

Sắp xếp theo nhiều cột:

SELECT Name, Birthdate 
FROM Students 
ORDER BY Birthdate DESC, Name ASC;

Lấy một số dòng nhất định (FETCH)

Oracle dùng FETCH thay cho TOP trong SQL Server (từ Oracle 12c trở lên):

SELECT Name, Birthdate 
FROM Students 
ORDER BY Name 
FETCH FIRST 5 ROWS ONLY;
  • Lấy 5 dòng đầu tiên.
  • FETCH FIRST 5 ROWS WITH TIES: Lấy thêm nếu có giá trị trùng lặp ở dòng thứ 5.

Trước Oracle 12c, dùng WHERE ROWNUM:

SELECT Name, Birthdate 
FROM Students 
WHERE ROWNUM <= 5 
ORDER BY Name;

Lưu ý: ROWNUM yêu cầu sắp xếp sau khi lọc, nên cần subquery nếu muốn chính xác:

SELECT Name, Birthdate 
FROM (SELECT Name, Birthdate FROM Students ORDER BY Name)
WHERE ROWNUM <= 5;

Giới hạn dòng với OFFSET

Lấy dòng 6 đến 10:

SELECT Name, Birthdate 
FROM Students 
ORDER BY Name 
OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY;

Lọc dữ liệu với WHERE

Lấy sinh viên sinh sau năm 2000:

SELECT Name, Birthdate 
FROM Students 
WHERE Birthdate > TO_DATE('2000-12-31', 'YYYY-MM-DD');

Hàm tổng hợp (Aggregate Functions)

HàmChức năngVí dụ
COUNT(*)Đếm số dòngSELECT COUNT(*) FROM Students;
SUM(column)Tổng giá trị cộtSELECT SUM(Salary) FROM Employees;
AVG(column)Trung bình cộngSELECT AVG(Salary) FROM Employees;
MAX(column)Giá trị lớn nhấtSELECT MAX(Birthdate) FROM Students;
MIN(column)Giá trị nhỏ nhấtSELECT MIN(Birthdate) FROM Students;

Ví dụ: Đếm số sinh viên sinh năm 2001:

SELECT COUNT(*) AS Total 
FROM Students 
WHERE EXTRACT(YEAR FROM Birthdate) = 2001;

Nhóm dữ liệu (GROUP BY)

Đếm số sinh viên theo năm sinh:

SELECT EXTRACT(YEAR FROM Birthdate) AS BirthYear, COUNT(*) AS Total 
FROM Students 
GROUP BY EXTRACT(YEAR FROM Birthdate);

Lọc nhóm (HAVING)

Lấy các năm có hơn 10 sinh viên:

SELECT EXTRACT(YEAR FROM Birthdate) AS BirthYear, COUNT(*) AS Total 
FROM Students 
GROUP BY EXTRACT(YEAR FROM Birthdate)
HAVING COUNT(*) > 10;

Truy vấn con (Subquery)

Lấy sinh viên có ngày sinh nhỏ nhất:

SELECT Name, Birthdate 
FROM Students 
WHERE Birthdate = (SELECT MIN(Birthdate) FROM Students);

WITH Clause (Common Table Expression - CTE)

Tương đương CTE trong SQL Server:

WITH YoungStudents AS (
    SELECT Name, Birthdate 
    FROM Students 
    WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD')
)
SELECT * FROM YoungStudents 
ORDER BY Birthdate;

Kết hợp bảng (Joins)

INNER JOIN

Kết hợp bảng StudentsEnrollments để lấy thông tin đăng ký:

SELECT s.Name, e.CourseID 
FROM Students s 
INNER JOIN Enrollments e ON s.StudentID = e.StudentID;

LEFT OUTER JOIN

Lấy tất cả sinh viên, kể cả chưa đăng ký khóa học:

SELECT s.Name, e.CourseID 
FROM Students s 
LEFT OUTER JOIN Enrollments e ON s.StudentID = e.StudentID;

RIGHT OUTER JOIN

Lấy tất cả đăng ký, kể cả sinh viên không tồn tại:

SELECT s.Name, e.CourseID 
FROM Students s 
RIGHT OUTER JOIN Enrollments e ON s.StudentID = e.StudentID;

FULL OUTER JOIN

Lấy tất cả sinh viên và đăng ký, kể cả không khớp:

SELECT s.Name, e.CourseID 
FROM Students s 
FULL OUTER JOIN Enrollments e ON s.StudentID = e.StudentID;

CROSS JOIN

Kết hợp tất cả dòng từ hai bảng (tích Descartes):

SELECT s.Name, c.CourseName 
FROM Students s 
CROSS JOIN Courses c;

SELF JOIN

Kết hợp bảng với chính nó để tìm sinh viên cùng ngày sinh:

SELECT s1.Name AS Student1, s2.Name AS Student2, s1.Birthdate 
FROM Students s1 
INNER JOIN Students s2 ON s1.Birthdate = s2.Birthdate 
WHERE s1.StudentID != s2.StudentID;

Kết hợp nhiều bảng

Lấy thông tin sinh viên, khóa học và giáo viên:

SELECT s.Name AS StudentName, c.CourseName, t.Name AS TeacherName 
FROM Students s 
INNER JOIN Enrollments e ON s.StudentID = e.StudentID 
INNER JOIN Courses c ON e.CourseID = c.CourseID 
INNER JOIN Teachers t ON c.TeacherID = t.TeacherID;

UNION, UNION ALL, INTERSECT, MINUS

  • UNION: Hợp các tập hợp, loại bỏ trùng lặp.
  • UNION ALL: Hợp các tập hợp, giữ trùng lặp.
  • INTERSECT: Lấy giao của các tập hợp.
  • MINUS: Lấy hiệu của các tập hợp (tương đương EXCEPT trong SQL Server).

Ví dụ:

SELECT Name FROM Students 
WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD')
UNION
SELECT Name FROM Teachers 
WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD');

Lấy sinh viên không đăng ký khóa học nào:

SELECT StudentID, Name 
FROM Students 
MINUS
SELECT s.StudentID, s.Name 
FROM Students s 
INNER JOIN Enrollments e ON s.StudentID = e.StudentID;

View

View là một bảng ảo được tạo từ một hoặc nhiều bảng cơ sở, dùng để đơn giản hóa truy vấn hoặc bảo mật dữ liệu.

Tạo View (CREATE VIEW)

Tạo view chứa thông tin sinh viên sinh sau năm 2000:

CREATE VIEW YoungStudents AS
SELECT StudentID, Name, Birthdate 
FROM Students 
WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD');

Tạo hoặc thay thế View (CREATE OR REPLACE VIEW)

Cập nhật view mà không cần xóa:

CREATE OR REPLACE VIEW YoungStudents AS
SELECT StudentID, Name, Birthdate 
FROM Students 
WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD')
WITH READ ONLY;
  • WITH READ ONLY: Ngăn chỉnh sửa dữ liệu qua view.

Sử dụng View

Truy vấn từ view như bảng thông thường:

SELECT * FROM YoungStudents 
ORDER BY Birthdate;

Xóa View (DROP VIEW)

DROP VIEW YoungStudents;

View có thể cập nhật (Updatable View)

View đơn giản (không dùng hàm tổng hợp, join phức tạp) có thể được cập nhật:

CREATE VIEW SimpleStudents AS
SELECT StudentID, Name 
FROM Students 
WHERE Birthdate > TO_DATE('2000-01-01', 'YYYY-MM-DD');

UPDATE SimpleStudents 
SET Name = 'New Name' 
WHERE StudentID = 1;

Lưu ý: View phức tạp hơn cần trigger hoặc rule để cập nhật.

Liệt kê View

Xem tất cả view trong schema:

SELECT VIEW_NAME 
FROM USER_VIEWS;

Index

Index tăng tốc độ truy vấn bằng cách tạo cấu trúc dữ liệu để tìm kiếm nhanh hơn.

Tạo Index (CREATE INDEX)

Tạo index trên cột Name của bảng Students:

CREATE INDEX idx_students_name 
ON Students(Name);

Index duy nhất (UNIQUE INDEX)

Đảm bảo giá trị trong cột là duy nhất:

CREATE UNIQUE INDEX idx_students_email 
ON Students(Email);

Index trên nhiều cột (Composite Index)

Tạo index trên BirthdateName:

CREATE INDEX idx_students_birthdate_name 
ON Students(Birthdate, Name);

Xóa Index (DROP INDEX)

DROP INDEX idx_students_name;

Index Bitmap

Dùng cho cột có ít giá trị khác nhau (ví dụ: giới tính):

CREATE BITMAP INDEX idx_students_gender 
ON Students(Gender);

Tự động tạo Index

Khi tạo PRIMARY KEY hoặc UNIQUE, Oracle tự động tạo index:

CREATE TABLE Students (
    StudentID NUMBER PRIMARY KEY,
    Name VARCHAR2(50)
);
  • Index tự động được tạo cho StudentID.

Liệt kê Index

Xem tất cả index trong schema:

SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME 
FROM USER_IND_COLUMNS;

Khi nào nên dùng Index?

Dùng khi:

  • Cột thường xuyên xuất hiện trong WHERE, JOIN, hoặc ORDER BY.
  • Bảng lớn và truy vấn chọn lọc ít dòng.

Không dùng khi:

  • Bảng nhỏ (chi phí duy trì index cao hơn lợi ích).
  • Cột thường xuyên cập nhật (index làm chậm INSERT, UPDATE, DELETE).

Trigger

Trigger là đoạn mã PL/SQL tự động chạy khi có sự kiện (INSERT, UPDATE, DELETE) trên bảng.

Tạo Trigger (CREATE TRIGGER)

Trigger ghi log khi thêm sinh viên mới:

CREATE TABLE StudentsLog (
    LogID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    StudentID NUMBER,
    Action VARCHAR2(50),
    ActionDate DATE
);

CREATE OR REPLACE TRIGGER trg_students_insert
AFTER INSERT ON Students
FOR EACH ROW
BEGIN
    INSERT INTO StudentsLog (StudentID, Action, ActionDate)
    VALUES (:NEW.StudentID, 'INSERT', SYSDATE);
END;
/
  • :NEW: Truy cập giá trị mới được chèn.
  • FOR EACH ROW: Trigger chạy cho từng dòng.

Trigger trước sự kiện (BEFORE Trigger)

Kiểm tra tuổi trước khi thêm sinh viên:

CREATE OR REPLACE TRIGGER trg_students_before_insert
BEFORE INSERT ON Students
FOR EACH ROW
DECLARE
    v_Age NUMBER;
BEGIN
    v_Age := EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM :NEW.Birthdate);
    IF v_Age < 16 THEN
        RAISE_APPLICATION_ERROR(-20001, 'Sinh viên phải từ 16 tuổi trở lên.');
    END IF;
END;
/
  • RAISE_APPLICATION_ERROR: Báo lỗi tùy chỉnh.

Trigger cập nhật (UPDATE Trigger)

Ghi log khi cập nhật tên sinh viên:

CREATE OR REPLACE TRIGGER trg_students_update
AFTER UPDATE OF Name ON Students
FOR EACH ROW
BEGIN
    INSERT INTO StudentsLog (StudentID, Action, ActionDate)
    VALUES (:OLD.StudentID, 'UPDATE: ' || :OLD.Name || ' to ' || :NEW.Name, SYSDATE);
END;
/
  • :OLD: Giá trị cũ trước khi cập nhật.
  • :NEW: Giá trị mới sau khi cập nhật.

Trigger xóa (DELETE Trigger)

Ghi log khi xóa sinh viên:

CREATE OR REPLACE TRIGGER trg_students_delete
BEFORE DELETE ON Students
FOR EACH ROW
BEGIN
    INSERT INTO StudentsLog (StudentID, Action, ActionDate)
    VALUES (:OLD.StudentID, 'DELETE', SYSDATE);
END;
/

Trigger cấp bảng (Statement Trigger)

Chạy một lần cho toàn bộ sự kiện, không theo dòng:

CREATE OR REPLACE TRIGGER trg_students_statement
AFTER DELETE ON Students
BEGIN
    INSERT INTO StudentsLog (StudentID, Action, ActionDate)
    VALUES (NULL, 'Xóa nhiều sinh viên', SYSDATE);
END;
/

Vô hiệu hóa/Kích hoạt Trigger

  • Vô hiệu hóa:
ALTER TRIGGER trg_students_insert DISABLE;
  • Kích hoạt:
ALTER TRIGGER trg_students_insert ENABLE;

Xóa Trigger (DROP TRIGGER)

DROP TRIGGER trg_students_insert;

Liệt kê Trigger

Xem tất cả trigger trong schema:

SELECT TRIGGER_NAME, TABLE_NAME, TRIGGERING_EVENT 
FROM USER_TRIGGERS;

Stored Procedure

Stored Procedure là tập hợp các câu lệnh PL/SQL được lưu trữ trong database để tái sử dụng.

Tạo Stored Procedure (CREATE PROCEDURE)

Tạo procedure thêm sinh viên mới:

CREATE OR REPLACE PROCEDURE AddStudent (
    p_Name IN VARCHAR2,
    p_Birthdate IN DATE
) AS
BEGIN
    INSERT INTO Students (Name, Birthdate)
    VALUES (p_Name, p_Birthdate);
    DBMS_OUTPUT.PUT_LINE('Thêm sinh viên thành công: ' || p_Name);
END AddStudent;
/
  • IN: Tham số đầu vào (mặc định).
  • AS thay cho IS cũng được chấp nhận.

Thực thi Stored Procedure (EXECUTE)

BEGIN
    AddStudent('Hoàng Hiệp', TO_DATE('2001-05-20', 'YYYY-MM-DD'));
END;
/

Hoặc dùng cú pháp ngắn:

EXECUTE AddStudent('Hoàng Hiệp', TO_DATE('2001-05-20', 'YYYY-MM-DD'));

Stored Procedure với tham số đầu ra (OUT Parameter)

Tạo procedure trả về số lượng sinh viên:

CREATE OR REPLACE PROCEDURE GetStudentCount (
    p_Year IN NUMBER,
    p_Count OUT NUMBER
) AS
BEGIN
    SELECT COUNT(*) INTO p_Count
    FROM Students 
    WHERE EXTRACT(YEAR FROM Birthdate) = p_Year;
END GetStudentCount;
/

Thực thi:

DECLARE
    v_Count NUMBER;
BEGIN
    GetStudentCount(2001, v_Count);
    DBMS_OUTPUT.PUT_LINE('Số sinh viên sinh năm 2001: ' || v_Count);
END;
/

Xóa Stored Procedure (DROP PROCEDURE)

DROP PROCEDURE AddStudent;

Liệt kê Stored Procedure

Xem tất cả procedure trong schema:

SELECT OBJECT_NAME 
FROM USER_OBJECTS 
WHERE OBJECT_TYPE = 'PROCEDURE';

Function

Function là một khối PL/SQL trả về một giá trị duy nhất.

Tạo Function (CREATE FUNCTION)

Tạo hàm tính tuổi từ ngày sinh:

CREATE OR REPLACE FUNCTION CalculateAge (
    p_Birthdate IN DATE
) RETURN NUMBER AS
    v_Age NUMBER;
BEGIN
    v_Age := EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM p_Birthdate);
    RETURN v_Age;
END CalculateAge;
/

Sử dụng Function

Dùng trong truy vấn SQL:

SELECT Name, CalculateAge(Birthdate) AS Age 
FROM Students;

Function với logic phức tạp

Tạo hàm kiểm tra sinh viên có đủ tuổi:

CREATE OR REPLACE FUNCTION IsAdult (
    p_StudentID IN NUMBER
) RETURN VARCHAR2 AS
    v_Age NUMBER;
BEGIN
    SELECT CalculateAge(Birthdate) INTO v_Age
    FROM Students 
    WHERE StudentID = p_StudentID;
    
    IF v_Age >= 18 THEN
        RETURN 'Đủ tuổi';
    ELSE
        RETURN 'Chưa đủ tuổi';
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN 'Không tìm thấy sinh viên';
END IsAdult;
/

Thực thi:

SELECT Name, IsAdult(StudentID) AS Status 
FROM Students;

Xóa Function (DROP FUNCTION)

DROP FUNCTION CalculateAge;

Liệt kê Function

Xem tất cả function trong schema:

SELECT OBJECT_NAME 
FROM USER_OBJECTS 
WHERE OBJECT_TYPE = 'FUNCTION';

Transaction

Transaction là tập hợp các thao tác được thực hiện như một đơn vị, đảm bảo tính toàn vẹn dữ liệu.

Bắt đầu Transaction

Oracle tự động bắt đầu transaction khi thực hiện lệnh INSERT, UPDATE, DELETE. Không cần BEGIN TRANSACTION như SQL Server.

Ví dụ:

INSERT INTO Students (Name, Birthdate) 
VALUES ('Nguyễn Văn A', TO_DATE('2002-03-15', 'YYYY-MM-DD'));
UPDATE Students 
SET Name = 'Nguyễn Văn B' 
WHERE Name = 'Nguyễn Văn A';

Kết thúc Transaction

  • COMMIT: Lưu vĩnh viễn các thay đổi.
COMMIT;
  • ROLLBACK: Hủy bỏ các thay đổi.
ROLLBACK;

Ví dụ:

BEGIN
    INSERT INTO Students (Name, Birthdate) 
    VALUES ('Trần Văn C', TO_DATE('2001-07-10', 'YYYY-MM-DD'));
    -- Kiểm tra lỗi
    IF SQL%ROWCOUNT = 1 THEN
        COMMIT;
        DBMS_OUTPUT.PUT_LINE('Thành công');
    ELSE
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Thất bại');
    END IF;
END;
/

Điểm lưu (SAVEPOINT)

Đặt điểm lưu để rollback một phần:

BEGIN
    INSERT INTO Students (Name, Birthdate) 
    VALUES ('Lê Thị D', TO_DATE('2000-12-25', 'YYYY-MM-DD'));
    SAVEPOINT AfterInsert;
    
    UPDATE Students 
    SET Name = 'Lê Thị E' 
    WHERE Name = 'Lê Thị D';
    
    ROLLBACK TO AfterInsert; -- Quay lại sau khi insert
    COMMIT;
END;
/

Transaction với exception

Xử lý lỗi trong transaction:

BEGIN
    INSERT INTO Students (Name, Birthdate) 
    VALUES ('Phạm Văn F', TO_DATE('2003-01-01', 'YYYY-MM-DD'));
    
    -- Giả lập lỗi
    UPDATE Students 
    SET Name = NULL 
    WHERE StudentID = 1; -- Vi phạm NOT NULL
    
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Lỗi: ' || SQLERRM);
END;
/
  • SQLERRM: Trả về thông báo lỗi.

Kiểm tra Transaction

Xem trạng thái transaction hiện tại không trực tiếp như SQL Server; dùng logic PL/SQL:

BEGIN
    IF DBMS_TRANSACTION.STEP_ID IS NOT NULL THEN
        DBMS_OUTPUT.PUT_LINE('Có transaction đang chạy');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Không có transaction');
    END IF;
END;
/

Autocommit

Mặc định, các công cụ như SQL*Plus hoặc SQL Developer không tự commit. Để bật autocommit:

SET AUTOCOMMIT ON;

Tắt:

SET AUTOCOMMIT OFF;

Cursor

Cursor là con trỏ dùng để xử lý từng dòng dữ liệu từ kết quả truy vấn trong PL/SQL.

Cursor ẩn (Implicit Cursor)

Oracle tự động tạo cursor cho các lệnh DML hoặc SELECT ... INTO:

BEGIN
    UPDATE Students 
    SET Name = 'Updated Name' 
    WHERE StudentID = 1;
    
    IF SQL%ROWCOUNT > 0 THEN
        DBMS_OUTPUT.PUT_LINE('Đã cập nhật ' || SQL%ROWCOUNT || ' dòng');
    ELSE
        DBMS_OUTPUT.PUT_LINE('Không có dòng nào được cập nhật');
    END IF;
END;
/
  • SQL%ROWCOUNT: Số dòng bị ảnh hưởng.
  • SQL%FOUND: True nếu có dòng bị ảnh hưởng.

Cursor hiển nhiên (Explicit Cursor)

Dùng khi cần duyệt qua nhiều dòng.

Khai báo và sử dụng Cursor

DECLARE
    CURSOR student_cursor IS
        SELECT StudentID, Name, Birthdate 
        FROM Students 
        WHERE EXTRACT(YEAR FROM Birthdate) = 2001;
    v_StudentID NUMBER;
    v_Name VARCHAR2(50);
    v_Birthdate DATE;
BEGIN
    OPEN student_cursor;
    LOOP
        FETCH student_cursor INTO v_StudentID, v_Name, v_Birthdate;
        EXIT WHEN student_cursor%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_StudentID || ' - ' || v_Name || ' - ' || TO_CHAR(v_Birthdate, 'DD/MM/YYYY'));
    END LOOP;
    CLOSE student_cursor;
END;
/
  • OPEN: Mở cursor.
  • FETCH: Lấy dữ liệu vào biến.
  • CLOSE: Đóng cursor.
  • %NOTFOUND: Kiểm tra hết dữ liệu.

Cursor với vòng lặp FOR ( đơn giản hơn )

BEGIN
    FOR rec IN (SELECT StudentID, Name, Birthdate 
                FROM Students 
                WHERE EXTRACT(YEAR FROM Birthdate) = 2001)
    LOOP
        DBMS_OUTPUT.PUT_LINE(rec.StudentID || ' - ' || rec.Name || ' - ' || TO_CHAR(rec.Birthdate, 'DD/MM/YYYY'));
    END LOOP;
END;
/
  • Không cần OPEN, FETCH, CLOSE thủ công.

Cursor với tham số

DECLARE
    CURSOR student_cursor (p_Year NUMBER) IS
        SELECT StudentID, Name 
        FROM Students 
        WHERE EXTRACT(YEAR FROM Birthdate) = p_Year;
BEGIN
    FOR rec IN student_cursor(2001) 
    LOOP
        DBMS_OUTPUT.PUT_LINE(rec.StudentID || ' - ' || rec.Name);
    END LOOP;
END;
/

Cursor cập nhật (FOR UPDATE)

Dùng để khóa dòng khi cập nhật:

DECLARE
    CURSOR student_cursor IS
        SELECT StudentID, Name 
        FROM Students 
        WHERE EXTRACT(YEAR FROM Birthdate) = 2001
        FOR UPDATE;
BEGIN
    FOR rec IN student_cursor 
    LOOP
        UPDATE Students 
        SET Name = rec.Name || '_Updated'
        WHERE CURRENT OF student_cursor;
    END LOOP;
    COMMIT;
END;
/
  • FOR UPDATE: Khóa dòng để tránh xung đột.
  • WHERE CURRENT OF: Cập nhật dòng hiện tại của cursor.

Exception Handling

Exception Handling xử lý lỗi trong PL/SQL để tránh chương trình dừng đột ngột.

Cấu trúc cơ bản

BEGIN
    -- Code có thể gây lỗi
    INSERT INTO Students (Name, Birthdate) 
    VALUES (NULL, TO_DATE('2001-05-20', 'YYYY-MM-DD')); -- Vi phạm NOT NULL
    
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Lỗi: ' || SQLERRM);
END;
/
  • WHEN OTHERS: Bắt mọi lỗi không xác định.

Các exception thường gặp

ExceptionÝ nghĩa
NO_DATA_FOUNDKhông tìm thấy dữ liệu (SELECT ... INTO không trả về dòng nào)
TOO_MANY_ROWSSELECT ... INTO trả về nhiều dòng
DUP_VAL_ON_INDEXVi phạm ràng buộc UNIQUE hoặc PRIMARY KEY
VALUE_ERRORLỗi chuyển đổi dữ liệu (ví dụ: chuỗi thành số)
OTHERSBắt mọi lỗi khác

Ví dụ xử lý exception cụ thể

DECLARE
    v_Name VARCHAR2(50);
BEGIN
    SELECT Name INTO v_Name 
    FROM Students 
    WHERE StudentID = 999; -- Không tồn tại
    
    DBMS_OUTPUT.PUT_LINE('Tên: ' || v_Name);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('Không tìm thấy sinh viên');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('Trả về quá nhiều dòng');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Lỗi không xác định: ' || SQLERRM);
END;
/

Tự tạo exception

DECLARE
    e_AgeTooLow EXCEPTION;
    v_Age NUMBER;
BEGIN
    SELECT EXTRACT(YEAR FROM SYSDATE) - EXTRACT(YEAR FROM Birthdate) INTO v_Age
    FROM Students 
    WHERE StudentID = 1;
    
    IF v_Age < 16 THEN
        RAISE e_AgeTooLow;
    END IF;
EXCEPTION
    WHEN e_AgeTooLow THEN
        DBMS_OUTPUT.PUT_LINE('Tuổi quá thấp!');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Lỗi: ' || SQLERRM);
END;
/

PL/SQL Block

PL/SQL Block là khối mã cơ bản trong Oracle, gồm 3 phần: DECLARE, BEGIN, EXCEPTION.

Anonymous Block (Khối vô danh)

DECLARE
    v_Count NUMBER;
BEGIN
    SELECT COUNT(*) INTO v_Count 
    FROM Students;
    DBMS_OUTPUT.PUT_LINE('Tổng số sinh viên: ' || v_Count);
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Lỗi: ' || SQLERRM);
END;
/

Nested Block (Khối lồng nhau)

BEGIN
    DBMS_OUTPUT.PUT_LINE('Khối ngoài bắt đầu');
    DECLARE
        v_Name VARCHAR2(50);
    BEGIN
        SELECT Name INTO v_Name 
        FROM Students 
        WHERE StudentID = 1;
        DBMS_OUTPUT.PUT_LINE('Tên trong khối lồng: ' || v_Name);
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('Không tìm thấy trong khối lồng');
    END;
    DBMS_OUTPUT.PUT_LINE('Khối ngoài kết thúc');
END;
/

Debugging với DBMS_OUTPUT

Bật đầu ra trong SQL*Plus hoặc SQL Developer:

SET SERVEROUTPUT ON;

In thông tin:

BEGIN
    DBMS_OUTPUT.PUT_LINE('Dòng 1');
    DBMS_OUTPUT.PUT_LINE('Dòng 2');
END;
/

Ghi log lỗi với DBMS_UTILITY

BEGIN
    INSERT INTO Students (Name) VALUES (NULL); -- Vi phạm NOT NULL
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
        DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/
  • FORMAT_ERROR_STACK: Thông tin lỗi.
  • FORMAT_ERROR_BACKTRACE: Dấu vết lỗi (stack trace).

So sánh Oracle Database với SQL Server

  1. Quản lý cơ sở dữ liệu:

    • Oracle không dùng CREATE DATABASE đơn giản như SQL Server mà yêu cầu cấu hình tệp dữ liệu (data files, redo logs). Tuy nhiên, DBCA giúp đơn giản hóa quá trình này.
    • DROP DATABASE trong Oracle xóa toàn bộ database, không chỉ là một schema.
    • Sao lưu và khôi phục dùng RMAN thay vì lệnh BACKUPRESTORE.
    • Oracle không có lệnh USE như SQL Server mà dùng ALTER SESSION SET CURRENT_SCHEMA.
    • Liệt kê database trong Oracle phức tạp hơn, tập trung vào schema hoặc instance thay vì danh sách database.
  2. Quản lý bảng:

    • Oracle dùng NUMBER GENERATED ALWAYS AS IDENTITY thay cho INT IDENTITY(1,1) để tạo cột tự tăng.
    • DROP TABLE PURGE là tùy chọn đặc trưng của Oracle để xóa vĩnh viễn.
    • Cú pháp ALTER TABLE trong Oracle khác biệt: dùng MODIFY thay ALTER COLUMN, RENAME COLUMN thay sp_rename.
    • Liệt kê bảng dùng USER_TABLES hoặc ALL_TABLES thay cho INFORMATION_SCHEMA.TABLES.
  3. Kiểu dữ liệu:

    • Số: Oracle dùng NUMBER thay cho INT, DECIMAL, BIGINT,... Không có MONEY hay BIT (dùng NUMBER(1) cho Boolean).
    • Chuỗi: Oracle dùng VARCHAR2 thay VARCHAR, và CLOB thay TEXT (khác với VARCHAR(MAX)). TEXT không tồn tại trong Oracle.
    • Ngày giờ: Oracle không có DATETIME2 hay TIME, thay bằng TIMESTAMP với độ chính xác cao hơn.
    • Nhị phân: Oracle dùng BLOB thay VARBINARY(MAX), không có UNIQUEIDENTIFIER (thay bằng RAW hoặc chuỗi GUID).
  4. Biến:

    • Oracle không có DECLARE trực tiếp trong SQL mà dùng trong khối PL/SQL.
    • Không có SET như SQL Server; dùng := hoặc SELECT ... INTO.
    • Biến bảng thay bằng collections (VARRAY, TABLE OF) với BULK COLLECT.
    • Biến toàn cục như @@ROWCOUNT thay bằng SQL%ROWCOUNT.
  5. Hàm:

    • Oracle dùng LENGTH thay LEN, SUBSTR thay SUBSTRING, INSTR thay CHARINDEX.
    • Không có TRIM riêng cho từng bên (LTRIM, RTRIM); chỉ có TRIM chung.
    • Hàm ngày giờ khác biệt: SYSDATE thay GETDATE, EXTRACT thay YEAR/MONTH/DAY.
    • Hàm chuyển đổi dùng TO_CHAR, TO_NUMBER, TO_DATE thay CAST/CONVERT.
    • NVL thay ISNULL, nhưng tương tự COALESCE.
  6. Ràng buộc dữ liệu:

    • Oracle không có IDENTITY mà dùng GENERATED ALWAYS AS IDENTITY.
    • Không có DEFAULT như một ràng buộc riêng; giá trị mặc định khai báo trong định nghĩa cột.
    • Các ràng buộc như FOREIGN KEY, CHECK tương tự, nhưng cú pháp hơi khác.
  7. Thao tác dữ liệu:

    • Oracle dùng TO_DATE để xử lý ngày giờ, thay vì định dạng trực tiếp như SQL Server.
    • DELETE trong Oracle có thể khôi phục từ Recycle Bin, không như SQL Server.
    • TRUNCATE TABLE tương tự nhưng không có log chi tiết.
    • CREATE TABLE AS SELECT thay cho SELECT INTO.
    • MERGE trong Oracle không hỗ trợ WHEN NOT MATCHED BY SOURCE THEN DELETE trực tiếp.
    • RETURNING thay cho OUTPUT, nhưng phải dùng trong PL/SQL.
  8. Toán tử và điều kiện lọc:

    • Toán tử cơ bản (=, >, LIKE,...) giống nhau.
    • Oracle không có IIF; chỉ dùng CASE WHEN hoặc logic trong PL/SQL.
    • IF ELSE phải nằm trong khối PL/SQL, không dùng trực tiếp trong SQL như SQL Server.
    • Hàm ngày giờ như EXTRACT thay cho YEAR, MONTH, DAY.
  9. Truy vấn dữ liệu:

    • Oracle không có TOP; dùng FETCH FIRST (12c+) hoặc ROWNUM (phiên bản cũ).
    • Alias cột trong Oracle có thể bỏ AS, khác với SQL Server yêu cầu rõ ràng.
    • Hàm ngày giờ dùng EXTRACT thay YEAR, MONTH, DAY.
    • WITH clause tương đương CTE, nhưng cú pháp giống nhau.
    • Oracle không có HAVING mà không cần GROUP BY như một số trường hợp trong SQL Server; luôn cần GROUP BY trước HAVING.
  10. Kết hợp bảng:

    • Các loại JOIN (INNER, LEFT, RIGHT, FULL, CROSS) tương tự SQL Server.
    • Oracle dùng MINUS thay EXCEPT trong SQL Server.
    • Cú pháp JOIN giống nhau, nhưng Oracle cũng hỗ trợ cú pháp cũ (+), ví dụ:
      SELECT s.Name, e.CourseID 
      FROM Students s, Enrollments e 
      WHERE s.StudentID = e.StudentID(+); -- LEFT OUTER JOIN
      
      Tuy nhiên, nên dùng cú pháp ANSI (JOIN) hiện đại.
  11. View:

    • Cú pháp CREATE VIEW giống nhau, nhưng Oracle có WITH READ ONLY để bảo vệ dữ liệu.
    • Oracle không có SCHEMABINDING như SQL Server; dùng WITH CHECK OPTION nếu cần kiểm tra điều kiện khi cập nhật.
    • Liệt kê view dùng USER_VIEWS thay INFORMATION_SCHEMA.VIEWS.
  12. Index:

    • Oracle tự động tạo index cho PRIMARY KEYUNIQUE, giống SQL Server.
    • Có thêm BITMAP INDEX (dành cho cột ít giá trị khác nhau), không có trong SQL Server.
    • Không có INCLUDE như SQL Server để thêm cột không lập chỉ mục vào index.
  13. Trigger:

    • Oracle dùng :NEW:OLD thay @INSERTED@DELETED trong SQL Server.
    • Trigger trong Oracle phân biệt rõ FOR EACH ROW (dòng) và statement-level (bảng), trong khi SQL Server mặc định là statement-level.
    • RAISE_APPLICATION_ERROR thay RAISERROR để báo lỗi.
    • Oracle không có INSTEAD OF trigger trực tiếp như SQL Server; cần logic thay thế trong BEFORE trigger.
  14. Stored Procedure:

    • Oracle dùng CREATE OR REPLACE PROCEDURE thay CREATE PROCEDURE (thay thế trực tiếp nếu tồn tại).
    • Tham số IN, OUT, IN OUT rõ ràng hơn SQL Server (SQL Server chỉ có @param OUTPUT).
    • Không có EXEC; dùng EXECUTE hoặc khối BEGIN ... END.
  15. Function:

    • Oracle yêu cầu RETURN kiểu dữ liệu rõ ràng trong khai báo hàm.
    • Function có thể dùng trực tiếp trong SQL, tương tự SQL Server, nhưng cú pháp khai báo khác.
    • Xử lý ngoại lệ (EXCEPTION) tích hợp trong PL/SQL, mạnh mẽ hơn T-SQL.
  16. Transaction:

    • Oracle không cần BEGIN TRANSACTION; transaction tự động bắt đầu với DML.
    • COMMITROLLBACK tương tự, nhưng Oracle có SAVEPOINT rõ ràng hơn SQL Server.
    • Xử lý lỗi dùng EXCEPTION WHEN OTHERS thay TRY...CATCH.
    • Không có @@TRANCOUNT; dùng gói DBMS_TRANSACTION để kiểm tra trạng thái.
  17. Cursor:

    • Oracle phân biệt implicit (ẩn) và explicit (hiển nhiên) rõ ràng hơn SQL Server.
    • Không có FETCH NEXT như SQL Server; dùng FETCH INTO trong vòng lặp hoặc vòng FOR.
    • FOR UPDATEWHERE CURRENT OF là cách khóa và cập nhật đặc trưng của Oracle.
  18. Exception Handling:

    • Oracle dùng EXCEPTION thay TRY...CATCH.
    • Các exception như NO_DATA_FOUND, TOO_MANY_ROWS cụ thể hơn ERROR trong SQL Server.
    • RAISERAISE_APPLICATION_ERROR thay THROW để tạo lỗi tùy chỉnh.
  19. PL/SQL Block:

    • Oracle yêu cầu / để kết thúc khối trong công cụ như SQL*Plus, không có trong SQL Server.
    • Khối lồng nhau và DBMS_OUTPUT thay thế PRINT trong T-SQL.
    • Không có GO như SQL Server; mọi thứ trong một khối được xử lý liền mạch.

Bài tập thực hành

Hệ thống quản lý sinh viên tại trường đại học EZSE

Cơ sở dữ liệu dưới đây được thiết kế để quản lý thông tin về sinh viên, giảng viên, ngành học, môn học, và các lớp học tại trường đại học EZSE. Hệ thống hỗ trợ các chức năng như đăng ký môn học, theo dõi điểm số, và quản lý các lớp học.

Câu lệnh SQL tạo Database và dữ liệu

Trong Oracle, không có lệnh USE để chuyển database như SQL Server. Thay vào đó, bạn làm việc trong một schema (thường gắn với user). Tôi sẽ tạo schema EZSE và các bảng trong đó.

-- Kết nối với quyền SYSDBA để tạo user/schema (thực hiện một lần)
CONN SYS/your_password AS SYSDBA;

-- Xóa schema nếu đã tồn tại
DECLARE
  v_count NUMBER;
BEGIN
  SELECT COUNT(*) INTO v_count FROM dba_users WHERE username = 'EZSE';
  IF v_count > 0 THEN
    EXECUTE IMMEDIATE 'DROP USER EZSE CASCADE';
  END IF;
END;
/

-- Tạo user/schema mới
CREATE USER EZSE IDENTIFIED BY ezse_password
  DEFAULT TABLESPACE USERS
  QUOTA UNLIMITED ON USERS;

-- Gán quyền cơ bản
GRANT CONNECT, RESOURCE TO EZSE;

-- Kết nối với schema EZSE
CONN EZSE/ezse_password;

-- Xóa các bảng nếu tồn tại (không cần kiểm tra khóa ngoại phức tạp như SQL Server)
BEGIN
  FOR t IN (SELECT TABLE_NAME FROM USER_TABLES) LOOP
    EXECUTE IMMEDIATE 'DROP TABLE ' || t.TABLE_NAME || ' PURGE';
  END LOOP;
END;
/

---------------------------- Tạo bảng ----------------------------------

-- Bảng Ngành học (Majors)
CREATE TABLE Majors (
    MajorID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    MajorName NVARCHAR2(100) NOT NULL
);

-- Bảng Sinh viên (Students)
CREATE TABLE Students (
    StudentID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    FirstName NVARCHAR2(50) NOT NULL,
    LastName NVARCHAR2(50) NOT NULL,
    DateOfBirth DATE NOT NULL,
    Gender NVARCHAR2(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
    Email NVARCHAR2(100) UNIQUE NOT NULL,
    Phone NVARCHAR2(15) UNIQUE,
    Address NVARCHAR2(255),
    EnrollmentDate DATE NOT NULL,
    MajorID NUMBER,
    CONSTRAINT fk_students_majors FOREIGN KEY (MajorID) REFERENCES Majors(MajorID)
);

-- Bảng Giảng viên (Teachers)
CREATE TABLE Teachers (
    TeacherID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    FirstName NVARCHAR2(50) NOT NULL,
    LastName NVARCHAR2(50) NOT NULL,
    Email NVARCHAR2(100) UNIQUE NOT NULL,
    Phone NVARCHAR2(15) UNIQUE,
    Address NVARCHAR2(255)
);

-- Bảng Môn học (Courses)
CREATE TABLE Courses (
    CourseID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    CourseName NVARCHAR2(100) NOT NULL,
    Credits NUMBER CHECK (Credits > 0)
);

-- Bảng Môn học theo ngành (MajorCourses)
CREATE TABLE MajorCourses (
    MajorID NUMBER,
    CourseID NUMBER,
    PRIMARY KEY (MajorID, CourseID),
    CONSTRAINT fk_majorcourses_majors FOREIGN KEY (MajorID) REFERENCES Majors(MajorID),
    CONSTRAINT fk_majorcourses_courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
);

-- Bảng Lớp học (Classes)
CREATE TABLE Classes (
    ClassID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    CourseID NUMBER,
    TeacherID NUMBER,
    Semester NVARCHAR2(20) NOT NULL,
    Year NUMBER NOT NULL,
    CONSTRAINT fk_classes_courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID),
    CONSTRAINT fk_classes_teachers FOREIGN KEY (TeacherID) REFERENCES Teachers(TeacherID)
);

-- Bảng Đăng ký môn học (Enrollments)
CREATE TABLE Enrollments (
    StudentID NUMBER,
    ClassID NUMBER,
    Attempt NUMBER DEFAULT 1 NOT NULL,
    Grade NUMBER(4,2) CHECK (Grade >= 0 AND Grade <= 10),
    PRIMARY KEY (StudentID, ClassID, Attempt),
    CONSTRAINT fk_enrollments_students FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
    CONSTRAINT fk_enrollments_classes FOREIGN KEY (ClassID) REFERENCES Classes(ClassID)
);

#### Chèn dữ liệu vào các bảng

```sql
-- Chèn dữ liệu vào bảng Majors
INSERT INTO Majors (MajorName) VALUES
(N'Computer Science'),
(N'Software Engineering'),
(N'Information Security');

-- Chèn dữ liệu vào bảng Courses
INSERT INTO Courses (CourseName, Credits) VALUES
(N'Data Structures & Algorithms', 3),
(N'Object-Oriented Programming', 3),
(N'Database Systems', 3),
(N'Computer Networks', 3),
(N'Web Development', 4),
(N'Agile Software Development', 3),
(N'Cyber Security', 3),
(N'Mobile Application Development', 3);

-- Chèn dữ liệu vào bảng Students
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID) VALUES
(N'Phạm', N'Gia Bảo', TO_DATE('2002-03-10', 'YYYY-MM-DD'), 'Male', '[email protected]', '0912345678', N'12 Trần Phú, Hà Nội', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 1),
(N'Nguyễn', N'Thảo Linh', TO_DATE('2003-07-22', 'YYYY-MM-DD'), 'Female', '[email protected]', '0987654321', N'34 Lê Lợi, TP.HCM', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 2),
(N'Trần', N'Quang Vinh', TO_DATE('2001-05-15', 'YYYY-MM-DD'), 'Male', '[email protected]', '0933123456', N'56 Nguyễn Trãi, Đà Nẵng', TO_DATE('2020-09-01', 'YYYY-MM-DD'), 3),
(N'Lê', N'Minh Nhật', TO_DATE('2002-09-25', 'YYYY-MM-DD'), 'Male', '[email protected]', '0977890123', N'78 Hai Bà Trưng, Hải Phòng', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 1),
(N'Vũ', N'Hồng Ngọc', TO_DATE('2003-01-30', 'YYYY-MM-DD'), 'Female', '[email protected]', '0966543210', N'90 Quang Trung, Cần Thơ', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 2),
(N'Đỗ', N'Thanh Huyền', TO_DATE('2004-02-14', 'YYYY-MM-DD'), 'Female', '[email protected]', '0913456789', N'15 Hùng Vương, Huế', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 3),
(N'Hoàng', N'Đức Anh', TO_DATE('2003-11-05', 'YYYY-MM-DD'), 'Male', '[email protected]', '0934567890', N'27 Nguyễn Huệ, Quy Nhơn', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 1),
(N'Ngô', N'Khánh Duy', TO_DATE('2002-08-19', 'YYYY-MM-DD'), 'Male', '[email protected]', '0945678901', N'39 Lê Đại Hành, Vinh', TO_DATE('2020-09-01', 'YYYY-MM-DD'), 2),
(N'Bùi', N'Thục Đoan', TO_DATE('2004-06-25', 'YYYY-MM-DD'), 'Female', '[email protected]', '0956789012', N'51 Phạm Ngũ Lão, Nha Trang', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 3),
(N'Phan', N'Tiến Đạt', TO_DATE('2003-04-12', 'YYYY-MM-DD'), 'Male', '[email protected]', '0967890123', N'63 Nguyễn Văn Cừ, Đà Lạt', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 1),
(N'Đặng', N'Mỹ Linh', TO_DATE('2004-09-08', 'YYYY-MM-DD'), 'Female', '[email protected]', '0978901234', N'75 Trần Hưng Đạo, Hà Nội', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 2),
(N'Võ', N'Hoàng Phúc', TO_DATE('2002-12-30', 'YYYY-MM-DD'), 'Male', '[email protected]', '0989012345', N'87 Bạch Đằng, TP.HCM', TO_DATE('2020-09-01', 'YYYY-MM-DD'), 3),
(N'Trương', N'Ngọc Ánh', TO_DATE('2003-03-17', 'YYYY-MM-DD'), 'Female', '[email protected]', '0990123456', N'99 Nguyễn Thị Minh Khai, Đà Nẵng', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 1),
(N'Lý', N'Quốc Hưng', TO_DATE('2004-07-21', 'YYYY-MM-DD'), 'Male', '[email protected]', '0901234567', N'111 Lê Lai, Hải Phòng', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 2),
(N'Nguyễn', N'Văn Hùng', TO_DATE('2003-05-10', 'YYYY-MM-DD'), 'Male', '[email protected]', '0912345689', N'123 Nguyễn Huệ, Hà Nội', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 1),
(N'Trần', N'Thị Mai', TO_DATE('2004-08-15', 'YYYY-MM-DD'), 'Female', '[email protected]', '0987654332', N'45 Lê Lợi, TP.HCM', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 1),
(N'Lê', N'Quang Minh', TO_DATE('2002-11-20', 'YYYY-MM-DD'), 'Male', '[email protected]', '0933123467', N'67 Trần Phú, Đà Nẵng', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 1),
(N'Phạm', N'Hồng Phúc', TO_DATE('2003-02-25', 'YYYY-MM-DD'), 'Male', '[email protected]', '0977890134', N'89 Hai Bà Trưng, Hải Phòng', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 1),
(N'Vũ', N'Thị Lan', TO_DATE('2004-04-12', 'YYYY-MM-DD'), 'Female', '[email protected]', '0966543221', N'101 Quang Trung, Cần Thơ', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 2),
(N'Đỗ', N'Minh Tuấn', TO_DATE('2003-09-30', 'YYYY-MM-DD'), 'Male', '[email protected]', '0913456790', N'23 Hùng Vương, Huế', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 2),
(N'Hoàng', N'Ngọc Ánh', TO_DATE('2002-07-18', 'YYYY-MM-DD'), 'Female', '[email protected]', '0934567901', N'34 Nguyễn Huệ, Quy Nhơn', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 2),
(N'Ngô', N'Văn Long', TO_DATE('2003-12-05', 'YYYY-MM-DD'), 'Male', '[email protected]', '0945678912', N'56 Lê Đại Hành, Vinh', TO_DATE('2022-09-01', 'YYYY-MM-DD'), 3),
(N'Bùi', N'Thị Hương', TO_DATE('2004-03-22', 'YYYY-MM-DD'), 'Female', '[email protected]', '0956789023', N'78 Phạm Ngũ Lão, Nha Trang', TO_DATE('2023-09-01', 'YYYY-MM-DD'), 3),
(N'Phan', N'Quốc Anh', TO_DATE('2002-06-15', 'YYYY-MM-DD'), 'Male', '[email protected]', '0967890134', N'90 Nguyễn Văn Cừ, Đà Lạt', TO_DATE('2021-09-01', 'YYYY-MM-DD'), 3);

-- Chèn dữ liệu vào bảng Teachers
INSERT INTO Teachers (FirstName, LastName, Email, Phone, Address) VALUES
(N'Hoàng', N'Văn Hiệp', '[email protected]', '0944123456', N'101 Lý Thường Kiệt, Hà Nội'),
(N'Lê', N'Thu Hà', '[email protected]', '0933221122', N'202 Phạm Văn Đồng, TP.HCM'),
(N'Hoàng', N'Việt Thắng', '[email protected]', '0922334455', N'303 Trường Chinh, Đà Nẵng'),
(N'Nguyễn', N'Minh Tuấn', '[email protected]', '0911445566', N'404 Nguyễn Văn Linh, Hải Phòng'),
(N'Phạm', N'Quốc Khánh', '[email protected]', '0902345678', N'505 Điện Biên Phủ, Huế'),
(N'Vũ', N'Thị Duyên', '[email protected]', '0913567890', N'606 Nguyễn Đình Chiểu, TP.HCM'),
(N'Trần', N'Đình Phong', '[email protected]', '0924678901', N'707 Trần Phú, Hà Nội'),
(N'Đỗ', N'Ngọc Mai', '[email protected]', '0935789012', N'808 Lê Hồng Phong, Đà Nẵng'),
(N'Bùi', N'Gia Bảo', '[email protected]', '0946890123', N'909 Nguyễn Trãi, Hải Phòng'),
(N'Ngô', N'Thị Lan Anh', '[email protected]', '0957901234', N'1010 Phạm Hùng, Cần Thơ');

-- Chèn dữ liệu vào bảng MajorCourses
INSERT INTO MajorCourses (MajorID, CourseID) VALUES
(1, 1), (1, 3), (1, 4), (1, 6),
(2, 2), (2, 5), (2, 6),
(3, 4), (3, 7), (3, 8);

-- Chèn dữ liệu vào bảng Classes
INSERT INTO Classes (CourseID, TeacherID, Semester, Year) VALUES
(1, 1, 'Spring', 2023),
(2, 2, 'Spring', 2023),
(3, 1, 'Summer', 2023),
(4, 3, 'Fall', 2023),
(5, 2, 'Spring', 2023),
(6, 4, 'Summer', 2023),
(7, 3, 'Fall', 2023),
(8, 5, 'Spring', 2023),
(1, 1, 'Spring', 2024),
(2, 2, 'Fall', 2024),
(3, 1, 'Spring', 2024),
(4, 3, 'Fall', 2024),
(5, 2, 'Summer', 2024),
(6, 4, 'Spring', 2024),
(7, 3, 'Fall', 2024),
(8, 5, 'Summer', 2024),
(1, 1, 'Fall', 2024),
(3, 6, 'Fall', 2024),
(1, 1, 'Spring', 2025),
(2, 2, 'Spring', 2025),
(3, 1, 'Summer', 2025),
(4, 3, 'Fall', 2025),
(5, 2, 'Spring', 2025),
(6, 4, 'Summer', 2025),
(7, 3, 'Fall', 2025),
(8, 5, 'Spring', 2025),
(2, 2, 'Fall', 2025),
(4, 3, 'Spring', 2025);

-- Chèn dữ liệu vào bảng Enrollments
INSERT INTO Enrollments (StudentID, ClassID, Attempt, Grade) VALUES
(1, 1, 1, 8.5),
(1, 9, 1, 7.2),
(1, 3, 1, 6.8),
(1, 4, 1, 5.5),
(1, 12, 2, 7.5),
(2, 2, 1, 8.0),
(2, 5, 1, 9.0),
(2, 6, 1, 6.0),
(2, 14, 1, 4.8),
(3, 4, 1, 7.5),
(3, 7, 1, 8.8),
(3, 8, 1, 9.2),
(4, 1, 1, 5.0),
(4, 9, 2, 7.2),
(4, 5, 1, 8.5),
(4, 6, 1, 9.1),
(5, 2, 1, 6.8),
(5, 3, 1, 5.5),
(5, 11, 2, 7.5),
(5, 8, 1, 8.3),
(6, 12, 1, 8.0),
(6, 15, 1, 7.8),
(6, 16, 1, 9.0),
(7, 9, 1, 7.5),
(7, 11, 1, 8.2),
(7, 12, 1, 6.5),
(8, 2, 1, 8.8),
(8, 5, 1, 7.0),
(8, 6, 1, 6.2),
(9, 20, 1, 8.5),
(9, 23, 1, 9.0),
(9, 16, 1, 7.8),
(10, 9, 1, 6.5),
(10, 11, 1, 7.0),
(10, 14, 1, 8.5),
(15, 10, 1, 7.8),
(19, 13, 1, 8.2),
(16, 17, 1, 6.5),
(20, 19, 1, 7.5),
(22, 24, 1, 8.5),
(21, 25, 1, 6.8),
(23, 26, 1, 7.2),
(24, 27, 1, 8.8);

-- Commit dữ liệu
COMMIT;

Các bảng trong Database

1. Bảng Majors (Ngành học)

Bảng này lưu thông tin về các ngành học tại trường.

CREATE TABLE Majors (
    MajorID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    MajorName NVARCHAR2(100) NOT NULL
);
  • MajorID: Mã ngành học, là khóa chính, tự động tăng.
  • MajorName: Tên ngành học.
2. Bảng Students (Sinh viên)

Bảng này lưu trữ thông tin về sinh viên trong hệ thống.

CREATE TABLE Students (
    StudentID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    FirstName NVARCHAR2(50) NOT NULL,
    LastName NVARCHAR2(50) NOT NULL,
    DateOfBirth DATE NOT NULL,
    Gender NVARCHAR2(15) CHECK (Gender IN ('Male', 'Female', 'Other')),
    Email NVARCHAR2(100) UNIQUE NOT NULL,
    Phone NVARCHAR2(15) UNIQUE,
    Address NVARCHAR2(255),
    EnrollmentDate DATE NOT NULL,
    MajorID NUMBER,
    CONSTRAINT fk_students_majors FOREIGN KEY (MajorID) REFERENCES Majors(MajorID)
);
  • StudentID: Mã sinh viên, là khóa chính, tự động tăng.
  • FirstName, LastName: Tên và họ của sinh viên.
  • DateOfBirth: Ngày sinh.
  • Gender: Giới tính (Male, Female, Other).
  • Email: Email của sinh viên, duy nhất.
  • Phone: Số điện thoại, duy nhất (cho phép NULL).
  • Address: Địa chỉ của sinh viên (cho phép NULL).
  • EnrollmentDate: Ngày nhập học.
  • MajorID: Mã ngành học của sinh viên, tham chiếu đến bảng Majors.
3. Bảng Teachers (Giảng viên)

Bảng này lưu trữ thông tin về các giảng viên.

CREATE TABLE Teachers (
    TeacherID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    FirstName NVARCHAR2(50) NOT NULL,
    LastName NVARCHAR2(50) NOT NULL,
    Email NVARCHAR2(100) UNIQUE NOT NULL,
    Phone NVARCHAR2(15) UNIQUE,
    Address NVARCHAR2(255)
);
  • TeacherID: Mã giảng viên, là khóa chính, tự động tăng.
  • FirstName, LastName: Tên và họ của giảng viên.
  • Email: Email của giảng viên, duy nhất.
  • Phone: Số điện thoại, duy nhất (cho phép NULL).
  • Address: Địa chỉ của giảng viên (cho phép NULL).
4. Bảng Courses (Môn học)

Bảng này lưu thông tin về các môn học.

CREATE TABLE Courses (
    CourseID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    CourseName NVARCHAR2(100) NOT NULL,
    Credits NUMBER CHECK (Credits > 0)
);
  • CourseID: Mã môn học, là khóa chính, tự động tăng.
  • CourseName: Tên môn học.
  • Credits: Số tín chỉ của môn học, lớn hơn 0.
5. Bảng MajorCourses (Môn học theo ngành)

Bảng này liên kết các ngành học với các môn học bắt buộc.

CREATE TABLE MajorCourses (
    MajorID NUMBER,
    CourseID NUMBER,
    PRIMARY KEY (MajorID, CourseID),
    CONSTRAINT fk_majorcourses_majors FOREIGN KEY (MajorID) REFERENCES Majors(MajorID),
    CONSTRAINT fk_majorcourses_courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
);
  • MajorID: Mã ngành học, tham chiếu đến bảng Majors.
  • CourseID: Mã môn học, tham chiếu đến bảng Courses.
  • Khóa chính là tổ hợp (MajorID, CourseID).
6. Bảng Classes (Lớp học)

Bảng này lưu thông tin về các lớp học.

CREATE TABLE Classes (
    ClassID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    CourseID NUMBER,
    TeacherID NUMBER,
    Semester NVARCHAR2(20) NOT NULL,
    Year NUMBER NOT NULL,
    CONSTRAINT fk_classes_courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID),
    CONSTRAINT fk_classes_teachers FOREIGN KEY (TeacherID) REFERENCES Teachers(TeacherID)
);
  • ClassID: Mã lớp học, là khóa chính, tự động tăng.
  • CourseID: Mã môn học, tham chiếu đến bảng Courses.
  • TeacherID: Mã giảng viên, tham chiếu đến bảng Teachers.
  • Semester: Học kỳ.
  • Year: Năm học.
7. Bảng Enrollments (Đăng ký môn học)

Bảng này lưu trữ thông tin về việc sinh viên đăng ký lớp học.

CREATE TABLE Enrollments (
    StudentID NUMBER,
    ClassID NUMBER,
    Attempt NUMBER DEFAULT 1 NOT NULL,
    Grade NUMBER(4,2) CHECK (Grade >= 0 AND Grade <= 10),
    PRIMARY KEY (StudentID, ClassID, Attempt),
    CONSTRAINT fk_enrollments_students FOREIGN KEY (StudentID) REFERENCES Students(StudentID),
    CONSTRAINT fk_enrollments_classes FOREIGN KEY (ClassID) REFERENCES Classes(ClassID)
);
  • StudentID: Mã sinh viên, tham chiếu đến bảng Students.
  • ClassID: Mã lớp học, tham chiếu đến bảng Classes.
  • Attempt: Số lần đăng ký (mặc định là 1).
  • Grade: Điểm số, từ 0 đến 10.

Đề thực hành (Practical Exam)

Câu hỏi 1

Viết truy vấn để hiển thị tất cả các giảng viên.

Question 1

Đáp án:

SELECT * FROM Teachers;

Câu hỏi 2

Viết truy vấn để hiển thị tất cả các sinh viên thuộc các ngành có MajorID = 2 hoặc MajorID = 3.

Question 2

Đáp án:

SELECT * FROM Students
WHERE MajorID IN (2, 3);

Câu hỏi 3

Viết truy vấn để lấy thông tin các lớp học mà giảng viên có họ là "Hoàng" và tên là "Văn Hiệp" phụ trách.

Hiển thị các cột như hình dưới.

Question 3

Đáp án:

SELECT C.ClassID, C.CourseID, CO.CourseName, C.Semester, C.Year, T.TeacherID, T.FirstName, T.LastName
FROM Classes C
JOIN Teachers T ON C.TeacherID = T.TeacherID
JOIN Courses CO ON C.CourseID = CO.CourseID
WHERE T.FirstName = N'Hoàng' AND T.LastName = N'Văn Hiệp';

Câu hỏi 4

Viết truy vấn để lấy danh sách sinh viên đã đăng ký ít nhất một trong các lớp thuộc các môn học "Data Structures & Algorithms", "Database Systems", hoặc "Web Development".

Hiển thị các cột như hình dưới, không hiển thị kết quả trùng nhau.
Kết quả sắp xếp theo CourseID tăng dần, sau đó theo StudentID tăng dần.

Question 4

Đáp án:

SELECT DISTINCT CO.CourseID, CO.CourseName, S.StudentID, S.FirstName || ' ' || S.LastName AS StudentFullName, S.DateOfBirth
FROM Students S
JOIN Enrollments E ON S.StudentID = E.StudentID
JOIN Classes C ON E.ClassID = C.ClassID
JOIN Courses CO ON C.CourseID = CO.CourseID
WHERE CO.CourseName IN (N'Data Structures & Algorithms', N'Database Systems', N'Web Development')
ORDER BY CO.CourseID, S.StudentID;

Câu hỏi 5

Viết truy vấn để hiển thị số lượng sinh viên đã đăng ký vào các khóa học trong năm 2025.

Hiển thị các cột như hình dưới.
Kết quả sắp xếp theo NumberOfEnrollments giảm dần, sau đó theo CourseID tăng dần.

Question 5

Đáp án:

SELECT C.CourseID, C.CourseName, 
       COUNT(E.StudentID) AS NumberOfEnrollments
FROM Courses C
LEFT JOIN Classes CL ON C.CourseID = CL.CourseID
LEFT JOIN Enrollments E ON CL.ClassID = E.ClassID AND CL.Year = 2025
GROUP BY C.CourseID, C.CourseName
ORDER BY NumberOfEnrollments DESC, C.CourseID ASC;

Câu hỏi 6

Viết truy vấn để tìm các giảng viên có số lượng lớp dạy nhiều nhất trong từng năm.

Hiển thị các cột như hình dưới.
Kết quả sắp xếp theo Year tăng dần, sau đó theo TeacherID tăng dần.

Question 6

Đáp án:

WITH TeacherClasses AS (
    SELECT 
        c.Year,
        t.TeacherID,
        t.FirstName || ' ' || t.LastName AS TeacherFullName,
        COUNT(c.ClassID) AS NumberOfClasses
    FROM Teachers t
    LEFT JOIN Classes c ON t.TeacherID = c.TeacherID
    GROUP BY c.Year, t.TeacherID, t.FirstName, t.LastName
),
MaxClasses AS (
    SELECT 
        Year,
        MAX(NumberOfClasses) AS MaxClasses
    FROM TeacherClasses
    GROUP BY Year
)
SELECT 
    tc.Year,
    tc.TeacherID,
    tc.TeacherFullName,
    tc.NumberOfClasses
FROM TeacherClasses tc
JOIN MaxClasses mc ON tc.Year = mc.Year AND tc.NumberOfClasses = mc.MaxClasses
ORDER BY tc.Year, tc.TeacherID;

Câu hỏi 7

Viết truy vấn để đếm số sinh viên duy nhất đã đăng ký môn học theo từng năm của ngành "Computer Science""Software Engineering".

Hiển thị các cột như hình dưới.

Question 7

Đáp án:

WITH StudentEnrollments AS (
    SELECT 
        m.MajorID,
        m.MajorName,
        c.Year,
        COUNT(DISTINCT e.StudentID) AS TotalStudents
    FROM Enrollments e
    JOIN Classes c ON e.ClassID = c.ClassID
    JOIN Students s ON e.StudentID = s.StudentID
    JOIN Majors m ON s.MajorID = m.MajorID
    WHERE m.MajorName IN (N'Computer Science', N'Software Engineering')
    GROUP BY m.MajorID, m.MajorName, c.Year
),
MajorsCTE AS (
    SELECT MajorID, MajorName 
    FROM Majors 
    WHERE MajorName IN (N'Computer Science', N'Software Engineering')
),
Years AS (
    SELECT DISTINCT Year FROM Classes
),
AllCombinations AS (
    SELECT y.Year, m.MajorID, m.MajorName
    FROM MajorsCTE m
    CROSS JOIN Years y
)
SELECT 
    ac.Year,
    ac.MajorID,
    ac.MajorName,
    NVL(se.TotalStudents, 0) AS TotalStudents
FROM AllCombinations ac
LEFT JOIN StudentEnrollments se 
    ON ac.MajorID = se.MajorID 
    AND ac.Year = se.Year
ORDER BY ac.Year, ac.MajorID;

Câu hỏi 8

Tạo function GetNumberOfStudentsByCourseID để tính số lượng sinh viên duy nhất đã đăng ký một môn học cụ thể theo CourseID.

Sau khi viết xong, kiểm tra lại function bằng câu lệnh SQL dưới:

SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4);

Question 8

Đáp án:

CREATE OR REPLACE FUNCTION GetNumberOfStudentsByCourseID (p_CourseID IN NUMBER)
RETURN NUMBER AS
    v_NumberOfStudents NUMBER;
BEGIN
    SELECT COUNT(DISTINCT e.StudentID) INTO v_NumberOfStudents
    FROM Enrollments e
    JOIN Classes c ON e.ClassID = c.ClassID
    WHERE c.CourseID = p_CourseID;
    
    RETURN v_NumberOfStudents;
EXCEPTION
    WHEN OTHERS THEN
        RETURN 0; -- Trả về 0 nếu có lỗi
END GetNumberOfStudentsByCourseID;
/

-- Kiểm tra function
SELECT CourseID, CourseName, GetNumberOfStudentsByCourseID(CourseID) AS NumberOfStudents
FROM Courses
WHERE CourseID IN (1, 2, 3, 4);

Câu hỏi 9

Tạo trigger để xử lý dữ liệu khi thêm sinh viên mới vào bảng Students có tên trg_InsteadOfInsertStudent đảm bảo hai điều kiện:

  1. Nếu Email không chứa ký tự "@", tự động thêm @ezse.net vào cuối.
  2. Nếu MajorID bị NULL, tự động đặt MajorID thành ID của ngành "Software Engineering".

Sau khi viết xong, kiểm tra lại trigger bằng câu lệnh SQL dưới:

-- Trường hợp thiếu @ -> Tự động thêm @ezse.net, MajorID có giá trị nên giữ nguyên
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (N'Nguyễn', N'Văn A', TO_DATE('2002-05-10', 'YYYY-MM-DD'), 'Male', 'nguyenvana', '0734567890', N'Hà Nội', TO_DATE('2024-04-02', 'YYYY-MM-DD'), 3);

-- Trường hợp thiếu MajorID -> Tự động gán MajorID của ngành Software Engineering
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (N'Trần', N'Thị B', TO_DATE('2001-08-20', 'YYYY-MM-DD'), 'Female', '[email protected]', '0678123456', N'HCM', TO_DATE('2024-04-02', 'YYYY-MM-DD'), NULL);

-- Kiểm tra kết quả
SELECT StudentID, FirstName, LastName, Email, MajorID FROM Students;

-- Chú ý: Vì Phone là UNIQUE, khi test nhiều lần, hãy thay đổi số điện thoại.

Question 9

Đáp án:

CREATE OR REPLACE TRIGGER trg_InsteadOfInsertStudent
BEFORE INSERT ON Students
FOR EACH ROW
DECLARE
    v_SoftwareEngineeringID NUMBER;
BEGIN
    -- Lấy MajorID của ngành "Software Engineering"
    SELECT MajorID INTO v_SoftwareEngineeringID
    FROM Majors
    WHERE MajorName = N'Software Engineering';
    
    -- Xử lý Email
    IF INSTR(:NEW.Email, '@') = 0 THEN
        :NEW.Email := :NEW.Email || '@ezse.net';
    END IF;
    
    -- Xử lý MajorID
    IF :NEW.MajorID IS NULL THEN
        :NEW.MajorID := v_SoftwareEngineeringID;
    END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20001, 'Không tìm thấy ngành Software Engineering');
END trg_InsteadOfInsertStudent;
/

-- Kiểm tra trigger
INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (N'Nguyễn', N'Văn A', TO_DATE('2002-05-10', 'YYYY-MM-DD'), 'Male', 'nguyenvana', '0734567890', N'Hà Nội', TO_DATE('2024-04-02', 'YYYY-MM-DD'), 3);

INSERT INTO Students (FirstName, LastName, DateOfBirth, Gender, Email, Phone, Address, EnrollmentDate, MajorID)
VALUES (N'Trần', N'Thị B', TO_DATE('2001-08-20', 'YYYY-MM-DD'), 'Female', '[email protected]', '0678123456', N'HCM', TO_DATE('2024-04-02', 'YYYY-MM-DD'), NULL);

SELECT StudentID, FirstName, LastName, Email, MajorID FROM Students;

Câu hỏi 10

Xóa sinh viên có StudentID = 3StudentID = 5 khỏi bảng Students, đồng thời xóa các hàng liên quan trong bảng Enrollments.

Đáp án:

-- Xóa dữ liệu liên quan trong bảng Enrollments trước
DELETE FROM Enrollments
WHERE StudentID IN (3, 5);

-- Xóa sinh viên trong bảng Students
DELETE FROM Students
WHERE StudentID IN (3, 5);

-- Commit thay đổi
COMMIT;

Giải thích:

  • Vì bảng Enrollments trong script gốc không có ON DELETE CASCADE, ta phải xóa dữ liệu từ Enrollments trước, sau đó xóa từ Students.
  • Nếu muốn tự động xóa, cần sửa bảng Enrollments khi tạo:
CREATE TABLE Enrollments (
    StudentID NUMBER,
    ClassID NUMBER,
    Attempt NUMBER DEFAULT 1 NOT NULL,
    Grade NUMBER(4,2) CHECK (Grade >= 0 AND Grade <= 10),
    PRIMARY KEY (StudentID, ClassID, Attempt),
    CONSTRAINT fk_enrollments_students FOREIGN KEY (StudentID) REFERENCES Students(StudentID) ON DELETE CASCADE,
    CONSTRAINT fk_enrollments_classes FOREIGN KEY (ClassID) REFERENCES Classes(ClassID)
);

Sau đó chỉ cần:

DELETE FROM Students WHERE StudentID IN (3, 5);
COMMIT;

So sánh với SQL Server

  1. Tạo Database:

    • SQL Server dùng CREATE DATABASEUSE. Oracle không có khái niệm database riêng như vậy trong ngữ cảnh người dùng; thay vào đó, bạn tạo một schema (user) bằng CREATE USER.
    • Xóa database trong Oracle là xóa user với DROP USER ... CASCADE.
  2. Xóa bảng:

    • SQL Server dùng INFORMATION_SCHEMA để kiểm tra và xóa khóa ngoại trước. Oracle đơn giản hóa bằng cách xóa tất cả bảng trong schema với DROP TABLE ... PURGE (xóa vĩnh viễn, không vào Recycle Bin).
  3. Tạo bảng:

    • Kiểu dữ liệu:
      • INT thay bằng NUMBER.
      • NVARCHAR thay bằng NVARCHAR2 (hỗ trợ Unicode).
      • DECIMAL(4,2) thay bằng NUMBER(4,2).
    • IDENTITY: Oracle dùng GENERATED ALWAYS AS IDENTITY thay IDENTITY(1,1).
    • Khóa ngoại: Oracle yêu cầu đặt tên rõ ràng (CONSTRAINT fk_name) thay vì để mặc định như SQL Server.
    • CHECK: Giữ nguyên logic, nhưng cú pháp giống nhau.
  4. Ngày tháng:

    • SQL Server chấp nhận định dạng ngày trực tiếp (như '2002-03-10'). Oracle yêu cầu dùng TO_DATE để chuyển chuỗi thành kiểu DATE.
  5. Kiểu dữ liệu:

    • NVARCHAR trong SQL Server thay bằng NVARCHAR2 trong Oracle để hỗ trợ Unicode.
    • DECIMAL(4,2) thay bằng NUMBER(4,2).
  6. Transaction:

    • SQL Server không cần COMMIT rõ ràng trong script này vì mặc định autocommit. Oracle yêu cầu COMMIT để lưu dữ liệu vĩnh viễn.
  7. Cú pháp INSERT:

    • Giữ nguyên cú pháp INSERT INTO ... VALUES, nhưng Oracle không cần GO để phân tách lệnh.
  8. Mô tả bảng:

    • Thay INT IDENTITY(1,1) bằng NUMBER GENERATED ALWAYS AS IDENTITY.
    • NVARCHAR thay bằng NVARCHAR2.
    • DECIMAL(4,2) thay bằng NUMBER(4,2).
    • Khóa ngoại đặt tên rõ ràng bằng CONSTRAINT.
  9. Truy vấn:

    • Câu 1-2: Giữ nguyên cú pháp.
    • Câu 3: JOIN giống nhau.
    • Câu 4:
      • CONCAT(FirstName, ' ', LastName) trong SQL Server thay bằng FirstName || ' ' || LastName trong Oracle.
      • DISTINCTORDER BY giống nhau.
    • Câu 5: LEFT JOINGROUP BY giống nhau.
    • Câu 6:
      • CONCAT thay bằng || để nối chuỗi.
      • WITH clause (CTE) giống nhau.
    • Câu 7:
      • COALESCE thay bằng NVL (tương tự nhưng Oracle dùng NVL phổ biến hơn).
      • Logic CTE giữ nguyên.
    • Câu 8:
      • Function trong Oracle dùng CREATE OR REPLACE FUNCTION, trả về kiểu NUMBER.
      • Không cần DECLARE riêng cho biến trong hàm; khai báo trực tiếp trong phần AS.
      • Thêm EXCEPTION để xử lý lỗi cơ bản.
    • Câu 9:
      • SQL Server dùng INSTEAD OF INSERT; Oracle không có INSTEAD OF trực tiếp, thay bằng BEFORE INSERT và chỉnh sửa :NEW.
      • NOT LIKE '%@%' thay bằng INSTR(:NEW.Email, '@') = 0.
      • ISNULL thay bằng IS NULL.
      • Ngày dùng TO_DATE thay vì định dạng trực tiếp.
    • Câu 10:
      • Oracle yêu cầu xóa thủ công từ bảng con (Enrollments) trước nếu không có ON DELETE CASCADE.
      • Thêm COMMIT để lưu thay đổi (SQL Server mặc định autocommit trong một số trường hợp).