Database, bảng và khoá
Nội dung bài · 7 mục
Dữ liệu trong một List<Product> của chương trình C# mất khi chương trình
tắt. Dữ liệu của cửa hàng phải còn nguyên, và nhiều người phải đọc và ghi cùng lúc mà
không giẫm lên nhau. Việc đó do database đảm nhận. Khoá học này dùng Oracle,
loại database phổ biến ở ngân hàng và doanh nghiệp lớn.
Khái niệm
🗄️ Database quan hệ (relational database): nơi lưu dữ liệu thành nhiều bảng, các bảng liên kết với nhau qua khoá.
📋 Bảng (table): tập hợp các dòng cùng cấu trúc, mỗi cột là một loại thông tin, mỗi dòng là một đối tượng.
🔑 Khoá chính (primary key): cột có giá trị không trùng và không được trống, dùng để chỉ đúng một dòng.
💬 SQL: ngôn ngữ dùng để đọc và thay đổi dữ liệu trong database quan hệ.
Bảng giống class, cột giống property, mỗi dòng giống một object.
Ví dụ
Một bảng sản phẩm đơn giản được tạo như sau:
CREATE TABLE product_samples (
product_id NUMBER PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
price NUMBER(12, 2) NOT NULL
);- Mỗi dòng trong ngoặc là một cột: tên cột, kiểu dữ liệu, rồi quy tắc.
PRIMARY KEY:product_idlà khoá chính.NOT NULL: cột này bắt buộc phải có giá trị.
Ba kiểu dữ liệu hay dùng nhất trong Oracle:
| Kiểu | Chứa gì | Ví dụ |
|---|---|---|
NUMBER |
số nguyên hoặc số thực | 120, 5000.50 |
VARCHAR2(n) |
chuỗi tối đa n byte | 'Bút bi' |
DATE |
ngày giờ | DATE '2025-01-05' |
Chuỗi trong SQL đặt trong dấu nháy đơn: 'Bút bi'.
Cài Oracle trên máy
Khoá học này, chương EF Core của khoá ASP.NET Core và khoá WinForms dùng chung một Oracle chạy bằng Docker. Cài Docker Desktop, rồi chạy lệnh sau một lần:
docker run -d --name oracle -p 1521:1521 -e ORACLE_PASSWORD=oracle_pw -e APP_USER=shop -e APP_USER_PASSWORD=shop_pw gvenzl/oracle-free:slim-faststart- Lệnh tải Oracle Database Free và tạo sẵn user
shop, mật khẩushop_pw. ORACLE_PASSWORDlà mật khẩu của user quản trịsystem, khoá ASP.NET Core sẽ dùng đến.- Lần đầu mất vài phút. Chạy
docker logs -f oracle, thấy dòngDATABASE IS READY TO USE!là dùng được. - Tắt máy xong, bật lại bằng
docker start oracle.
Để gõ SQL, cài VS Code và extension Oracle SQL Developer. Tạo kết nối mới
với user shop, mật khẩu shop_pw, host localhost, port 1521,
service name FREEPDB1.
Tạo dữ liệu mẫu
Mọi bài trong khoá dùng chung một database cửa hàng với bốn bảng: khách hàng,
sản phẩm, đơn hàng và dòng hàng (order_lines): một sản phẩm trong đơn kèm số
lượng và đơn giá. Mở kết nối shop, dán toàn bộ script dưới đây rồi chạy cả
script. Chỉ cần chạy một lần.
CREATE TABLE customers (
customer_id NUMBER GENERATED BY DEFAULT AS IDENTITY
PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
email VARCHAR2(100),
city VARCHAR2(50)
);
CREATE TABLE products (
product_id NUMBER GENERATED BY DEFAULT AS IDENTITY
PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
price NUMBER(12, 2) NOT NULL,
stock NUMBER NOT NULL
);
CREATE TABLE orders (
order_id NUMBER GENERATED BY DEFAULT AS IDENTITY
PRIMARY KEY,
customer_id NUMBER NOT NULL
REFERENCES customers (customer_id),
order_date DATE NOT NULL,
status VARCHAR2(20) NOT NULL
);
CREATE TABLE order_lines (
line_id NUMBER GENERATED BY DEFAULT AS IDENTITY
PRIMARY KEY,
order_id NUMBER NOT NULL
REFERENCES orders (order_id),
product_id NUMBER NOT NULL
REFERENCES products (product_id),
quantity NUMBER NOT NULL,
unit_price NUMBER(12, 2) NOT NULL
);
INSERT INTO customers (name, email, city)
VALUES ('An', 'an@shop.vn', 'Hà Nội');
INSERT INTO customers (name, email, city)
VALUES ('Bình', 'binh@shop.vn', 'Đà Nẵng');
INSERT INTO customers (name, email, city)
VALUES ('Chi', NULL, 'Hà Nội');
INSERT INTO customers (name, email, city)
VALUES ('Dũng', 'dung@shop.vn', 'TP.HCM');
INSERT INTO products (name, price, stock)
VALUES ('Bút bi', 5000, 120);
INSERT INTO products (name, price, stock)
VALUES ('Vở', 12000, 0);
INSERT INTO products (name, price, stock)
VALUES ('Balo', 350000, 8);
INSERT INTO products (name, price, stock)
VALUES ('Thước', 7000, 50);
INSERT INTO products (name, price, stock)
VALUES ('Máy tính', 450000, 3);
INSERT INTO orders (customer_id, order_date, status)
VALUES (1, DATE '2025-01-05', 'PAID');
INSERT INTO orders (customer_id, order_date, status)
VALUES (1, DATE '2025-01-20', 'NEW');
INSERT INTO orders (customer_id, order_date, status)
VALUES (2, DATE '2025-02-02', 'PAID');
INSERT INTO orders (customer_id, order_date, status)
VALUES (3, DATE '2025-02-10', 'CANCELLED');
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (1, 1, 10, 5000);
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (1, 2, 5, 12000);
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (2, 3, 1, 350000);
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (3, 4, 2, 7000);
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (3, 1, 3, 5000);
INSERT INTO order_lines
(order_id, product_id, quantity, unit_price)
VALUES (4, 5, 1, 450000);
COMMIT;GENERATED BY DEFAULT AS IDENTITY cho Oracle tự cấp mã tăng dần 1, 2, 3…
khi thêm dòng mới mà không ghi mã.
Một số bài sau sửa dữ liệu mẫu. Nếu kết quả của bạn khác trong bài, hãy chạy
khối này rồi chạy lại script trên. Khối xoá mọi bảng của user shop, kể cả
bảng tạo thử trong các bài:
BEGIN
FOR t IN (SELECT table_name FROM user_tables) LOOP
EXECUTE IMMEDIATE
'DROP TABLE "' || t.table_name || '" CASCADE CONSTRAINTS';
END LOOP;
END;Thử ngay
Chạy câu lệnh sau bằng kết nối shop:
SELECT * FROM products ORDER BY product_id;SELECT * lấy mọi cột. ORDER BY sắp kết quả theo mã, bài Sắp xếp sẽ nói kỹ hơn.
Đoán trước khi chạy: có bao nhiêu dòng, và sản phẩm số 2 là gì?
Xem kết quả
| PRODUCT_ID | NAME | PRICE | STOCK |
|---|---|---|---|
| 1 | Bút bi | 5000 | 120 |
| 2 | Vở | 12000 | 0 |
| 3 | Balo | 350000 | 8 |
| 4 | Thước | 7000 | 50 |
| 5 | Máy tính | 450000 | 3 |
Năm dòng, sản phẩm số 2 là Vở. Mã do Oracle tự cấp theo thứ tự thêm vào. Tên cột hiện chữ hoa vì Oracle lưu những tên không đặt trong nháy kép ở dạng chữ hoa.
Lỗi hay gặp
Thêm dòng trùng khoá chính. INSERT thêm một dòng mới (học kỹ ở chương
Thay đổi dữ liệu). Khoá chính không được trùng, nên Oracle báo lỗi
ORA-00001: vi phạm ràng buộc không trùng.
-- SAI — lỗi: đã có sản phẩm mã 1
INSERT INTO products (product_id, name, price, stock)
VALUES (1, 'Kéo', 15000, 10);-- ĐÚNG — không ghi mã, để Oracle tự cấp
INSERT INTO products (name, price, stock)
VALUES ('Kéo', 15000, 10);Thử xong, chạy ROLLBACK; để bỏ dòng Kéo (bài Transaction sẽ giải thích).
Tóm tắt
- Database quan hệ lưu dữ liệu thành nhiều bảng liên kết với nhau.
- Mỗi bảng có cột (loại thông tin) và dòng (từng đối tượng).
- Khoá chính không trùng, không trống, dùng để chỉ đúng một dòng.
- Oracle hay dùng
NUMBER,VARCHAR2,DATE. Chuỗi đặt trong nháy đơn.
Tự kiểm tra
0/3 câuBảng students có khoá chính student_id. Hai học sinh trùng tên. Có thêm được cả hai không?
Cột lưu số điện thoại như '0901234567' nên dùng kiểu nào trong Oracle?
Cột nào hợp làm khoá chính của bảng customers?