SQL là một kỹ năng quan trọng trong ngành công nghệ thông tin, đặc biệt khi làm việc với cơ sở dữ liệu. Việc chuẩn bị các câu hỏi phỏng vấn SQL là yếu tố then chốt giúp bạn thành công trong các buổi phỏng vấn. Trong bài viết này, ITviec sẽ giới thiệu các câu hỏi phỏng vấn SQL phổ biến, kèm theo lời giải thích chi tiết và ví dụ thực tế để giúp bạn nắm vững kiến thức và gây ấn tượng với nhà tuyển dụng.

Đọc bài viết này để hiểu rõ hơn về:

  • Những vị trí nghề nghiệp yêu cầu SQL
  • Câu hỏi phỏng vấn SQL thường gặp dành cho Fresher
  • Câu hỏi phỏng vấn SQL thường gặp dành cho Intermediate
  • Câu hỏi phỏng vấn SQL thường gặp dành cho Advanced

Tổng quan về SQL

SQL (Structured Query Language) là ngôn ngữ truy vấn được thiết kế đặc biệt để làm việc với các hệ quản trị cơ sở dữ liệu quan hệ (Relational Database Management Systems – RDBMS). SQL được sử dụng để lưu trữ, truy xuất, quản lý và thao tác dữ liệu trong các hệ thống cơ sở dữ liệu.

Bên cạnh đó, SQL còn hỗ trợ quản trị cơ sở dữ liệu như cấp quyền, tạo bảng và định nghĩa cấu trúc dữ liệu dưới dạng các bảng (table). Mỗi bảng trong SQL bao gồm các cột (column) biểu diễn thuộc tính của dữ liệu và các hàng (row) chứa dữ liệu cụ thể.

SQL được phát triển lần đầu tiên vào những năm 1970 tại IBM với mục đích tạo ra một ngôn ngữ tiêu chuẩn để tương tác với cơ sở dữ liệu. Sau đó, nó đã được chuẩn hóa bởi ANSI (American National Standards Institute) và ISO (International Organization for Standardization), và trở thành ngôn ngữ chuẩn quốc tế cho cơ sở dữ liệu quan hệ.

Những ai cần biết câu hỏi phỏng vấn SQL?

  • Lập trình viên (Developers): Các lập trình viên, đặc biệt là backend developers và full-stack developers, cần có kiến thức về SQL để làm việc với cơ sở dữ liệu khi xây dựng và phát triển ứng dụng. Họ thường được yêu cầu viết các truy vấn SQL để thêm, xóa, hoặc cập nhật dữ liệu.
  • Chuyên viên phân tích dữ liệu (Data Analysts): Những người làm công việc phân tích dữ liệu cần sử dụng SQL để truy xuất dữ liệu từ các cơ sở dữ liệu lớn và tạo báo cáo phục vụ cho việc ra quyết định của doanh nghiệp.
  • Kỹ sư dữ liệu (Data Engineers): Các kỹ sư dữ liệu phải thao tác với khối lượng lớn dữ liệu, thiết kế và tối ưu hóa cấu trúc cơ sở dữ liệu. Họ cần thành thạo SQL để đảm bảo dữ liệu được xử lý và lưu trữ hiệu quả.
  • Chuyên viên khoa học dữ liệu (Data Scientists): Trong quá trình thu thập và chuẩn bị dữ liệu để xây dựng mô hình phân tích, các nhà khoa học dữ liệu cần viết truy vấn SQL để lọc, nhóm, hoặc kết hợp dữ liệu từ nhiều bảng khác nhau.
  • Quản trị viên cơ sở dữ liệu (Database Administrators – DBAs):
    Các DBA cần nắm vững SQL để quản lý, bảo trì và tối ưu hóa hiệu suất của cơ sở dữ liệu, đồng thời đảm bảo tính toàn vẹn và bảo mật của dữ liệu.
  • Nhà quản lý dự án và kiểm thử viên (Project Managers & Testers): Đối với các nhà quản lý dự án phần mềm và kiểm thử viên, hiểu SQL giúp họ kiểm tra, xác thực dữ liệu và đảm bảo hệ thống hoạt động đúng yêu cầu.

Tóm lại, bất kỳ ai làm việc trong môi trường liên quan đến dữ liệu và phát triển phần mềm đều cần có hiểu biết về SQL.

Các câu hỏi phỏng vấn SQL dành cho Fresher

Điểm khác biệt giữa SQL và NoSQL?

Tiêu chí SQL NoSQL
Loại cơ sở dữ liệu Quan hệ (Relational Database). Cơ sở dữ liệu linh hoạt (Flexible Database), không bị giới hạn bởi mô hình quan hệ.
Mô hình dữ liệu Dữ liệu được lưu trữ dưới dạng bảng có hàng và cột. Lưu trữ dữ liệu dưới dạng Key-Value, Document, Column-Family, Graph.
Cấu trúc dữ liệu Yêu cầu lược đồ (schema) cố định, chặt chẽ. Linh hoạt, không yêu cầu lược đồ rõ ràng hoặc có lược đồ động.
Ngôn ngữ truy vấn Dùng ngôn ngữ truy vấn chuẩn SQL. Dùng API hoặc ngôn ngữ truy vấn riêng biệt của CSDL.
Khả năng mở rộng Mở rộng theo chiều dọc (Vertical Scaling), nhưng một số hệ thống cũng hỗ trợ mở rộng chiều ngang (ví dụ sharding trong MySQL). Mở rộng theo chiều ngang (Horizontal Scaling).
Tính nhất quán Tuân thủ ACID (đảm bảo tính toàn vẹn dữ liệu). Tuân thủ BASE (đảm bảo tính sẵn sàng cao), một số loại cung cấp tùy chọn để hỗ trợ tính nhất quán mạnh mẽ hơn.
Tính linh hoạt Ít linh hoạt hơn khi thay đổi cấu trúc dữ liệu. Linh hoạt hơn trong việc thay đổi và mở rộng dữ liệu.
Ví dụ MySQL, PostgreSQL, SQL Server, Oracle. MongoDB, Cassandra, Redis, CouchDB.

Câu lệnh SELECT trong SQL dùng để làm gì? Cú pháp cơ bản của nó?

Câu lệnh SELECT trong SQL được sử dụng để truy vấn và lấy dữ liệu từ một hoặc nhiều bảng trong cơ sở dữ liệu. Đây là câu lệnh phổ biến và quan trọng nhất trong SQL, cho phép người dùng truy xuất các cột cụ thể trong bảng, tính toán giá trị, đồng thời có thể kết hợp với các mệnh đề như WHERE để lọc dữ liệu theo điều kiện, ORDER BY để sắp xếp dữ liệu, các phép JOIN để kết hợp nhiều bảng dữ liệu lại với nhau.

Cú pháp cơ bản:

SELECT column1, column2, ...

FROM table_name

[WHERE condition]

[ORDER BY column_name ASC|DESC];

Trong đó:

  • SELECT: Xác định các cột cần lấy dữ liệu. Có thể dùng SELECT * để chọn tất cả các cột trong bảng hoặc kết hợp các hàm tính toán dữ liệu
  • FROM: Chỉ định tên bảng chứa dữ liệu cần truy vấn.
  • WHERE (tùy chọn): Điều kiện để lọc các hàng phù hợp.
  • ORDER BY (tùy chọn): Sắp xếp kết quả theo một hoặc nhiều cột. Có thể sắp xếp tăng dần (ASC) hoặc giảm dần (DESC).

Ví dụ: Giả sử có một bảng tên là employees với các cột ID, Name, Salary. Để lấy tất cả dữ liệu từ bảng employees ta sử dụng cú pháp sau:

SELECT * FROM Employees;

Ví dụ để lấy tên và lương của tất cả nhân viên, ta sử dụng cú pháp:

SELECT Name, Salary FROM Employees;

Ví dụ để lấy dữ liệu nhân viên có mức lương lớn hơn 5000, ta sử dụng cú pháp:

SELECT Name, Salary 

FROM Employees

WHERE Salary > 5000;

Ví dụ để sắp xếp kết quả theo lương giảm dần, ta sử dụng cú pháp:

SELECT Name, Salary 

FROM Employees 

ORDER BY Salary DESC;

Subset trong SQL là gì?

Subset trong SQL đề cập đến việc lấy một phần dữ liệu con từ bảng dữ liệu lớn hơn bằng cách sử dụng các điều kiện lọc hoặc các công cụ truy vấn khác nhau. Việc tạo subset giúp người dùng làm việc với các tập dữ liệu nhỏ hơn, phù hợp với mục đích phân tích, báo cáo hoặc tính toán cụ thể.

Một số ví dụ tạo subset trong SQL:

Ví dụ để lấy những nhân viên có lương lớn hơn 5000, ta lọc dữ liệu bằng WHERE để chọn các hàng thỏa mãn điều kiện như sau:

SELECT Name, Salary 

FROM Employees 

WHERE Salary > 5000;

Ví dụ lấy ra các phòng ban có hơn 3 nhân viên, ta lọc dữ liệu theo nhóm với GROUP BYHAVING dựa trên điều kiện như sau:

SELECT Department, COUNT(*) AS SoLuongNhanVien

FROM Employees

GROUP BY Department

HAVING COUNT(*) > 3;

Trong đó:

  • GROUP BY Department nhóm dữ liệu theo phòng ban.
  • HAVING COUNT(*) > 3 là bộ lọc chỉ được áp dụng sau khi các nhóm đã được tạo bởi GROUP BY, giúp chỉ giữ lại các nhóm có số lượng nhân viên lớn hơn 3.

Phân biệt Primary Key và Foreign Key

Tiêu chí Primary Key Foreign Key
Định nghĩa Là khóa chính dùng để xác định tính duy nhất của mỗi hàng trong bảng. Là khóa ngoại dùng để tạo mối quan hệ giữa các bảng.
Tính duy nhất Các giá trị trong cột Primary Key không được trùng lặp và không được để trống (NOT NULL). Giá trị trong Foreign Key có thể trùng lặp và có thể chứa giá trị NULL.
Bảng áp dụng Được khai báo trong bảng chính để xác định danh tính duy nhất của dữ liệu. Được khai báo trong bảng con để tham chiếu đến Primary Key của bảng chính.
Mục đích Đảm bảo tính duy nhất và toàn vẹn của dữ liệu trong bảng. Tạo mối quan hệ ràng buộc giữa hai bảng. Đảm bảo ràng buộc toàn vẹn dữ liệu giữa hai bảng.
Số lượng Mỗi bảng chỉ có một Primary Key. Một bảng có thể có nhiều Foreign Key.

Sự khác biệt giữa WHERE và HAVING là gì?

WHEREHAVING đều được sử dụng để lọc dữ liệu trong SQL, nhưng chúng có sự khác biệt quan trọng về thời điểm áp dụng và cách sử dụng trong câu lệnh truy vấn.

Tiêu chí WHERE HAVING
Mục đích Lọc dữ liệu trước khi thực hiện các phép tính hoặc nhóm dữ liệu. Lọc dữ liệu sau khi đã thực hiện phép nhóm (GROUP BY).
Áp dụng cho Dữ liệu dòng (hàng) trong bảng. Kết quả của các nhóm được tạo bởi GROUP BY hoặc sử dụng trực tiếp với các hàm tổng hợp
Dùng với hàm tổng hợp Không thể dùng với các hàm tổng hợp (SUM, AVG…). Có thể dùng với các hàm tổng hợp.

Ví dụ: Giả sử có bảng Employees với các cột: Name (tên), Department (phòng ban), và Salary (lương), để lọc ra các nhân viên có lương ít hơn 5000, ta sử dụng WHERE như sau:

SELECT Name, Salary 

FROM Employees

WHERE Salary > 5000;

Ví dụ để lấy các phòng ban có tổng lương lớn hơn 20000, ta sử dụng HAVING như sau:

SELECT Department, SUM(Salary) AS TotalSalary

FROM Employees

GROUP BY Department

HAVING SUM(Salary) > 20000;

Làm sao để sắp xếp dữ liệu trong SQL?

Trong SQL, để sắp xếp dữ liệu theo thứ tự tăng dần hoặc giảm dần, ta sử dụng mệnh đề ORDER BY. Mệnh đề này cho phép sắp xếp kết quả của truy vấn dựa trên một hoặc nhiều cột cụ thể.

Cú pháp cơ bản:

SELECT column1, column2, ...

FROM table_name

ORDER BY column_name [ASC | DESC];

Ví dụ để xuất ra tên và lương của nhân viên theo thứ tự lương giảm dần, ta sử dụng cú pháp:

SELECT Name, Salary 

FROM Employees

ORDER BY Salary DESC;

Nếu có nhiều điều kiện, SQL sẽ sắp xếp theo cột đầu tiên, sau đó đến cột thứ hai nếu cần. Ví dụ như sau:

SELECT Name, Salary, Department 

FROM Employees

ORDER BY Department ASC, Salary DESC;

Trong đó:

  • Sắp xếp Department theo thứ tự tăng dần (A → Z).
  • Trong cùng một Department, sắp xếp Salary theo thứ tự giảm dần.

Ví dụ để xuất ra danh sách nhân viên có tổng lương và thưởng theo thứ tự giảm dần ta sử dụng cú pháp như sau:

SELECT Name, Salary, Bonus 

FROM Employees 

ORDER BY (Salary + Bonus) DESC;

Nếu có giá trị NULL thì sẽ được xử lý đặc biệt khi sắp xếp như sau:

  • Khi sử dụng ORDER BY ASC (thứ tự tăng dần), giá trị NULL thường được xếp ở đầu danh sách.
  • Khi sử dụng ORDER BY DESC (thứ tự giảm dần), giá trị NULL thường được xếp ở cuối danh sách.

Tuy nhiên, hành vi này có thể thay đổi tùy theo hệ quản trị cơ sở dữ liệu (DBMS). Một số hệ quản trị như PostgreSQL hỗ trợ cú pháp tùy chỉnh NULLS FIRST hoặc NULLS LAST để chỉ định rõ cách sắp xếp giá trị NULL.

Ví dụ để sắp xếp danh sách nhân viên theo lương giảm dần, đồng thời các giá trị NULL được đưa xuống cuối, ta sử dụng cú pháp như sau trong MySQL:

SELECT Name, Salary 

FROM Employees

ORDER BY Salary DESC NULLS LAST;

Thế nào là NULL trong SQL? NULL khác gì với 0?

Tiêu chí NULL 0
Định nghĩa Đại diện cho giá trị không xác định hoặc không có dữ liệu. Là một giá trị số cụ thể, nghĩa là “không”.
Kiểu dữ liệu Có thể áp dụng cho mọi kiểu dữ liệu (chuỗi, số, ngày…). Chỉ áp dụng cho kiểu số.
Kết quả phép toán Phép toán với NULL sẽ trả về NULL (vì không xác định). Phép toán với 0 sẽ thực hiện như bình thường.
So sánh NULL không thể so sánh trực tiếp với NULL khác (NULL = NULL là FALSE). Để kiểm tra NULL, phải dùng IS NULL hoặc IS NOT NULL. 0 có thể so sánh với 0 (0 = 0 là TRUE).
Ví dụ Một cột Salary không có giá trị nào được nhập vào (NULL). Một cột Salary có giá trị nhập vào là 0.

Ví dụ ta có bảng sau:

Name Salary
An NULL
Bình 0
Châu 5000

Trong đó:

  • NULL: Với nhân viên “An”, Salary không có giá trị, tức là chưa được xác định.
  • 0: Với nhân viên “Bình”, Salary có giá trị “0”, nghĩa là lương bằng 0.

Câu lệnh JOIN trong SQL là gì? Kể tên các loại JOIN.

Trong SQL, JOIN là câu lệnh được sử dụng để kết hợp dữ liệu từ hai hoặc nhiều bảng dựa trên một điều kiện chung, thường là mối quan hệ giữa các cột trong các bảng. Câu lệnh JOIN giúp bạn truy vấn và lấy dữ liệu từ nhiều bảng một cách linh hoạt và hiệu quả.

Cú pháp tổng quát:

SELECT column1, column2, ...

FROM table1

[JOIN_TYPE] table2

ON table1.column_name = table2.column_name;

Các loại JOIN:

  • INNER JOIN: Chỉ trả về các hàng có giá trị khớp nhau trong cả hai bảng.
  • LEFT JOIN (LEFT OUTER JOIN): Trả về tất cả các hàng từ bảng bên trái và các hàng khớp từ bảng bên phải. Nếu không có giá trị khớp, kết quả sẽ hiển thị NULL cho bảng bên phải.
  • RIGHT JOIN (RIGHT OUTER JOIN): Trả về tất cả các hàng từ bảng bên phải và các hàng khớp từ bảng bên trái. Nếu không có giá trị khớp, kết quả sẽ hiển thị NULL cho bảng bên trái.
  • FULL JOIN (FULL OUTER JOIN): Trả về tất cả các hàng từ cả hai bảng. Các hàng không khớp sẽ hiển thị NULL ở bảng không có dữ liệu tương ứng.
  • CROSS JOIN: Trả về tích Descartes của hai bảng (kết hợp tất cả các hàng của bảng này với tất cả các hàng của bảng kia).
  • SELF JOIN: Là một JOIN đặc biệt, trong đó một bảng được kết hợp với chính nó như thể đó là hai bảng khác nhau.

Thế nào là Subquery (truy vấn con)? Cho ví dụ.

Subquery, hay còn gọi là truy vấn con, là một truy vấn SQL được lồng bên trong một truy vấn SQL khác. Subquery thường được đặt trong mệnh đề SELECT, FROM, WHERE hoặc các mệnh đề khác của truy vấn chính. Mục đích của subquery là trả về kết quả tạm thời để cung cấp dữ liệu cho truy vấn bên ngoài (truy vấn chính).

Đặc điểm của Subquery

  • Luôn được đặt trong cặp dấu ngoặc đơn () để phân biệt với truy vấn chính.
  • Có thể trả về một giá trị (scalar), một cột, hoặc một bảng kết quả.
  • Truy vấn con sẽ được thực thi trước truy vấn chính.

Giả sử ta có bảng employees:

Name Salary Department
An 4000 Kỹ thuật
Bình 7000 Kỹ thuật
Châu 3000 Kinh doanh
Dũng 5000 Kinh doanh

Ví dụ để tìm các nhân viên có lương cao hơn lương trung bình của tất cả nhân viên, ta sử dụng cú pháp:

SELECT Name, Salary

FROM Employees

WHERE Salary > (SELECT AVG(Salary) FROM Employees);

Trong đó:

  • Subquery: (SELECT AVG(Salary) FROM Employees) tính lương trung bình của tất cả nhân viên (kết quả là 4750).
  • Truy vấn chính: Lọc các nhân viên có Salary lớn hơn 4750.

Các câu hỏi phỏng vấn SQL dành cho Trung cấp

Có những loại index nào?

Index trong SQL là một cấu trúc dữ liệu được sử dụng để tăng tốc độ truy vấn dữ liệu từ các bảng trong cơ sở dữ liệu. Index giúp tìm kiếm và truy xuất dữ liệu nhanh hơn bằng cách tổ chức dữ liệu theo cách dễ truy cập. Tuy nhiên, cần lưu ý rằng cú pháp tạo index có thể khác nhau tùy vào hệ quản trị cơ sở dữ liệu (RDBMS) mà bạn đang sử dụng, chẳng hạn như MySQL, SQL Server, PostgreSQL hoặc Oracle. Các ví dụ sau được sử dụng trong MySQL.

Các loại Index phổ biến trong SQL gồm:

Primary Index (Clustered Index)

Primary Index, hay còn gọi là Clustered Index, là loại index mặc định được tạo ra khi bạn khai báo một Primary Key trong bảng. Nó sắp xếp dữ liệu trong bảng theo thứ tự vật lý dựa trên cột được đánh index, có nghĩa là dữ liệu trong bảng thực sự được lưu trữ theo thứ tự của Clustered Index, giúp việc truy vấn dữ liệu theo cột đó nhanh chóng hơn. Một bảng chỉ có thể có một Clustered Index duy nhất vì nó quyết định thứ tự lưu trữ vật lý của dữ liệu.

Ví dụ trong MySQL để tạo một Clustered index giúp sắp xếp dữ liệu trong bảng employees theo thứ tự của cột EmployeeID, giúp truy vấn theo EmployeeID nhanh hơn, ta sử dụng cú pháp:

ALTER TABLE employees

ADD PRIMARY KEY (EmployeeID);

Secondary Index (Non-Clustered Index):

Secondary Index, hay Non-Clustered Index, là loại index được tạo ra để tăng tốc độ truy vấn mà không thay đổi thứ tự vật lý của dữ liệu trong bảng. Thay vì sắp xếp dữ liệu, Non-Clustered Index tạo ra một bảng riêng biệt chứa các giá trị được đánh index và các con trỏ trỏ đến vị trí dữ liệu thực sự trong bảng chính. Vì vậy, một bảng có nhiều Non-Clustered Index, hỗ trợ tối ưu hóa nhiều truy vấn khác nhau.

Ví dụ trong MySQL để tạo một non-clustered index giúp hỗ trợ tăng tốc các truy vấn tìm kiếm theo tên (Name) trong bảng employees, ta sử dụng cú pháp:

CREATE INDEX idx_employee_name ON employees(Name);

Unique Index:

Unique Index là loại index được thiết kế để đảm bảo rằng tất cả các giá trị trong cột được đánh index là duy nhất, có nghĩa là không thể chèn hai hàng dữ liệu có cùng một giá trị trong cột được đánh index. Unique Index thường được tạo tự động khi bạn khai báo Unique Constraint hoặc Primary Key hoặc cũng có thể được tạo thủ công, giúp bảo vệ tính toàn vẹn của dữ liệu và tăng tốc độ truy vấn dựa trên cột đó.

Ví dụ trong MySQL để tạo một Unique Index giúp đảm bảo rằng không có hai nhân viên nào trong bảng employees có cùng địa chỉ email (Email, ta sử dụng cú pháp:

CREATE UNIQUE INDEX idx_unique_email ON employees(Email);

Composite Index:

Composite Index là một loại index được tạo ra trên nhiều cột trong bảng. Mục đích của Composite Index là tối ưu hóa các truy vấn phức tạp liên quan đến nhiều cột bằng cách kết hợp chúng trong một index duy nhất. Loại index này đặc biệt hữu ích khi truy vấn dữ liệu thường xuyên sử dụng các điều kiện trên nhiều cột, giúp giảm thời gian xử lý dữ liệu so với việc sử dụng index riêng lẻ cho từng cột.

Ví dụ trong MySQL để tạo một Composite Index này giúp tối ưu hóa các truy vấn sử dụng cả hai cột NameDepartment cùng một lúc, ta sử dụng cú pháp:

CREATE INDEX idx_composite_name_department ON employees(Name, Department);

Full-Text Index:

Full-Text Index được thiết kế dành riêng cho việc tìm kiếm văn bản trong các cột kiểu chuỗi, chẳng hạn như CHAR, VARCHAR hoặc TEXT. Loại index này giúp tăng tốc các truy vấn tìm kiếm phức tạp liên quan đến từ khóa, cụm từ, hoặc mẫu văn bản trong một bảng lớn. Full-Text Index thường được sử dụng trong các ứng dụng cần tìm kiếm văn bản như công cụ tìm kiếm, trang web hoặc các hệ thống quản lý nội dung.

Lưu ý:

  • Full-Text Index không hỗ trợ trên tất cả các hệ quản trị cơ sở dữ liệu và cú pháp có thể khác nhau.
  • Trong MySQL, Full-Text Index chỉ hỗ trợ trên các cột kiểu CHAR, VARCHAR hoặc TEXT.

Ví dụ để tạo một Full-Text Index giúp tăng tốc các truy vấn tìm kiếm từ khóa hoặc cụm từ trong nội dung (Content) của bảng Articles, ta sử dụng cú pháp:

CREATE FULLTEXT INDEX idx_fulltext_content ON Articles(Content);

Làm sao để tối ưu hóa truy vấn SQL?

  • Sử dụng Index hiệu quả: Index giúp truy vấn dữ liệu nhanh hơn bằng cách tạo cấu trúc tìm kiếm. Hãy tạo các index trên các cột được sử dụng thường xuyên trong mệnh đề WHERE, JOIN hoặc ORDER BY. Tuy nhiên, cần tránh sử dụng quá nhiều index, vì chúng có thể làm chậm các thao tác chèn, cập nhật, hoặc xóa.
  • Chỉ truy vấn các cột cần thiết: Tránh sử dụng SELECT * vì nó truy vấn toàn bộ các cột, gây lãng phí tài nguyên. Thay vào đó, chỉ chọn các cột cần thiết.
  • Ưu tiên JOIN thay vì Subquery: JOIN thường nhanh hơn Subquery vì tối ưu hóa cách xử lý dữ liệu
  • Kiểm tra kế hoạch thực thi (Execution Plan): Dùng EXPLAIN để xem cơ sở dữ liệu xử lý truy vấn thế nào, từ đó điều chỉnh câu lệnh.
  • Tránh sử dụng hàm trong điều kiện WHERE: Khi sử dụng các hàm (như LOWER(), UPPER(), DATE(),…) trong điều kiện WHERE, cơ sở dữ liệu sẽ không thể sử dụng Index để tăng tốc truy vấn. Thay vào đó, hãy sử dụng các điều kiện so sánh trực tiếp nếu có thể.
  • Sử dụng LIMIT để giảm số lượng bản ghi trả về: Nếu không cần tất cả dữ liệu, hãy sử dụng LIMIT để giới hạn số lượng bản ghi trả về, giảm tải cho cơ sở dữ liệu và tăng tốc độ phản hồi.

Phân biệt SQL và PL/SQL

PL/SQL là một ngôn ngữ lập trình mở rộng của SQL, được Oracle phát triển. Nó tích hợp các cấu trúc lập trình như vòng lặp, điều kiện, và hàm. PL/SQL được thiết kế để xử lý logic phức tạp trực tiếp trên máy chủ cơ sở dữ liệu Oracle, giúp tối ưu hóa hiệu suất và giảm tương tác giữa ứng dụng và cơ sở dữ liệu.

Chức năng chính:

  • Thực hiện các thao tác lập trình phức tạp bằng cách kết hợp SQL với logic lập trình.
  • Hỗ trợ khối lệnh, hàm, thủ tục, và trigger để tự động hóa và tối ưu hóa tác vụ.

Sự khác biệt giữa SQL và PL/SQL gồm:

Tiêu chí SQL PL/SQL
Mục đích Dùng để truy vấn và thao tác dữ liệu trong cơ sở dữ liệu. Dùng để lập trình logic phức tạp kết hợp SQL và các cấu trúc lập trình.
Loại ngôn ngữ Khai báo (Declarative): Mô tả dữ liệu cần lấy hoặc thao tác. Thủ tục (Procedural): Mô tả từng bước xử lý logic.
Khả năng xử lý Thực thi từng câu lệnh một cách độc lập. Xử lý nhiều câu lệnh trong một khối mã hoặc chương trình.
Ứng dụng Thêm, sửa, xóa, truy vấn dữ liệu. Tự động hóa, lập trình trigger, tạo hàm và thủ tục trong cơ sở dữ liệu.
Cú pháp hỗ trợ Chỉ hỗ trợ các câu lệnh SQL đơn giản, cùng với các hàm tích hợp như SUM, AVG, COUNT mà không có vòng lặp hoặc điều kiện. Hỗ trợ cấu trúc lập trình như vòng lặp, điều kiện, hàm, thủ tục.
Tính năng bổ sung Không hỗ trợ logic lập trình. Có thể xử lý logic phức tạp và tích hợp hàm hoặc trigger.
Phạm vi hỗ trợ Hỗ trợ bởi hầu hết các hệ quản trị cơ sở dữ liệu (MySQL, SQL Server, PostgreSQL…). Chỉ được hỗ trợ trong Oracle Database.
Hỗ trợ logic lập trình Không hỗ trợ. Có hỗ trợ với các khối mã lập trình logic.

Stored Procedure và Function khác nhau như thế nào?

Thủ tục (Stored Procedure) là một tập hợp các câu lệnh SQL và logic lập trình được lưu trữ trong cơ sở dữ liệu để thực thi một tác vụ cụ thể. Thủ tục có thể chứa nhiều câu lệnh và cho phép thực hiện các thao tác phức tạp.

Đặc điểm của thủ tục:

  • Có thể trả về nhiều giá trị thông qua các tham số đầu ra (OUT parameters).
  • Hỗ trợ các câu lệnh INSERT, UPDATE, DELETESELECT.
  • Được sử dụng để thực hiện các tác vụ hoặc quy trình.
  • Không bắt buộc trả về giá trị.

Function là một khối mã lệnh được sử dụng để thực hiện một tác vụ cụ thể và trả về một giá trị duy nhất. Functions thường được sử dụng trong các biểu thức và câu lệnh SQL.

Đặc điểm của Function:

  • Bắt buộc phải trả về một giá trị (sử dụng RETURN).
  • Chỉ có thể thực hiện các thao tác đọc dữ liệu (không hỗ trợ INSERT, UPDATE, DELETE).
  • Thường được sử dụng trong các phép tính hoặc biểu thức.

Sự khác biệt chính giữa Stored Procedure và Function:

Tiêu chí Stored Procedure Function
Mục đích Thực hiện các tác vụ phức tạp, có thể bao gồm thay đổi dữ liệu. Tính toán hoặc trả về một giá trị duy nhất dựa trên đầu vào.
Trả về giá trị Không bắt buộc giá trị trả về, có thể trả về nhiều giá trị thông qua tham số OUT. Bắt buộc trả về một giá trị duy nhất.
Hỗ trợ DML Hỗ trợ các thao tác dữ liệu như INSERT, UPDATE, DELETE, SELECT. Chỉ hỗ trợ đọc dữ liệu (SELECT).
Sử dụng trong SQL Không thể sử dụng trong các câu lệnh SQL như SELECT, WHERE. Có thể sử dụng trực tiếp trong các câu lệnh SQL.
Tham số Hỗ trợ cả tham số IN, OUTINOUT. Chỉ hỗ trợ tham số đầu vào (IN).
Phạm vi sử dụng Được sử dụng để xử lý logic và tác vụ phức tạp. Được sử dụng chủ yếu cho các phép toán hoặc truy vấn đơn giản.

CTE (Common Table Expression) là gì? Khi nào nên dùng?

CTE (Common Table Expression) là một biểu thức tạm thời trong SQL, cho phép định nghĩa một tập hợp kết quả truy vấn có thể được tham chiếu nhiều lần trong một câu lệnh SQL. CTE được định nghĩa bằng từ khóa WITH và thường được sử dụng để làm cho truy vấn phức tạp trở nên dễ đọc và dễ duy trì hơn. CTE không lưu trữ dữ liệu vật lý, mà chỉ tồn tại trong phạm vi của câu lệnh SQL mà nó được định nghĩa.

Cú pháp:

WITH CTE_Name (Column1, Column2, ...)

AS (

    SELECT Column1, Column2, ...

    FROM Table_Name

    WHERE Condition

)

SELECT *

FROM CTE_Name

WHERE Another_Condition;

Trong đó:

  • WITH CTE_Name: Định nghĩa tên của CTE và danh sách cột (tùy chọn).
  • AS: Mở đầu khối truy vấn cho CTE.
  • Truy vấn bên trong: Truy vấn trả về tập kết quả tạm thời.
  • Truy vấn bên ngoài: Sử dụng CTE như một bảng tạm trong các truy vấn tiếp theo.

Ví dụ giả sử bạn muốn tìm ra các phòng ban có tổng lương lớn hơn 50,000 và hiển thị danh sách nhân viên trong các phòng ban đó, bạn có thể sử dụng cú pháp sau:

WITH DepartmentSalary AS (

    SELECT DepartmentID, SUM(Salary) AS TotalSalary

    FROM Employees

    GROUP BY DepartmentID

    HAVING SUM(Salary) > 50000

)

SELECT E.Name, E.Salary, D.TotalSalary

FROM Employees E

INNER JOIN DepartmentSalary D

ON E.DepartmentID = D.DepartmentID;

Trong đó:

  • DepartmentSalary là CTE tính tổng lương theo từng phòng ban.
  • Truy vấn bên ngoài sử dụng CTE để tìm nhân viên thuộc các phòng ban có tổng lương lớn hơn 50,000.

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

  • Khi cần làm rõ truy vấn phức tạp: Nếu truy vấn của bạn quá phức tạp với nhiều phép nối (JOIN), nhóm (GROUP BY), hoặc điều kiện, bạn có thể sử dụng CTE để chia nhỏ từng bước.
  • Khi cần sử dụng lại kết quả tạm thời: Nếu một tập kết quả cần được sử dụng nhiều lần trong cùng một truy vấn, CTE giúp bạn tái sử dụng kết quả tạm thời mà không phải lặp lại cùng một mã truy vấn.
  • Khi cần truy vấn đệ quy: CTE hỗ trợ truy vấn đệ quy, thường được dùng để xử lý dữ liệu có cấu trúc cây (hierarchical data), như cây thư mục hoặc mối quan hệ cha-con.

Khi nào nên tránh dùng CTE?

  • Khi truy vấn đơn giản và không cần chia nhỏ.
  • Với các bảng lớn, nếu hiệu suất là ưu tiên, hãy kiểm tra vì CTE không luôn tối ưu so với việc dùng Temporary Table hoặc Subquery.

Explain Plan/Query Execution Plan là gì?

Explain Plan hoặc Query Execution Plan là một công cụ trong SQL được sử dụng để phân tích và hiểu cách cơ sở dữ liệu thực thi một truy vấn SQL. Nó cung cấp thông tin chi tiết về các bước mà hệ quản trị cơ sở dữ liệu (DBMS) sẽ thực hiện để trả về kết quả, giúp người dùng tối ưu hóa truy vấn và cải thiện hiệu suất.

Cách hoạt động của Explain Plan

Khi một truy vấn được gửi đến cơ sở dữ liệu, DBMS sẽ tạo ra một “kế hoạch thực thi” (Execution Plan). Kế hoạch này bao gồm:

  • Thứ tự các bước thực hiện truy vấn.
  • Phương pháp truy xuất dữ liệu (quét bảng, sử dụng index hoặc join).
  • Ước tính về số hàng được xử lý ở mỗi bước.
  • Chi phí (cost) của từng bước, biểu thị tài nguyên sử dụng (CPU, bộ nhớ, I/O).

Thông tin chính trong Explain Plan gồm

  • Operation/Step: Các thao tác mà DBMS sẽ thực hiện (quét bảng, sử dụng index, hash join…).
  • Cost: Chi phí ước tính của mỗi bước, giúp xác định bước nào tốn nhiều tài nguyên nhất.
  • Rows: Số hàng dự kiến sẽ được xử lý trong mỗi bước.
  • Key/Index Used: Chỉ ra có sử dụng index không và index nào được sử dụng.
  • Filter: Điều kiện lọc dữ liệu trong từng bước.

Lệnh để xem kế hoạch thực thi:

Trong MySQL:

EXPLAIN SELECT * FROM Employees WHERE Salary > 5000;

Trong PostgreSQL:

EXPLAIN SELECT * FROM Employees WHERE Salary > 5000;

Trong SQL Server: Sử dụng “Display Estimated Execution Plan” trong giao diện.

Ví dụ trong MySQL ta có truy vấn mẫu sau:

EXPLAIN SELECT Name, Salary 

FROM Employees 

WHERE DepartmentID = 2 AND Salary > 5000;

Kết quả sẽ trả về:

ID Select Type Table Type Possible Keys Key Rows Extra
1 SIMPLE Employees range DepartmentID DepartmentID 100 Using where

Trong đó:

  • Type = range: Sử dụng index trên cột DepartmentID để tìm các hàng phù hợp.
  • Rows = 100: Ước tính sẽ xử lý 100 hàng.
  • Extra = Using where: Điều kiện lọc Salary > 5000 được áp dụng.

Thế nào là View trong SQL? Lợi ích và hạn chế của View.

View trong SQL là một bảng ảo (virtual table) được tạo dựa trên kết quả của một truy vấn SQL. View không lưu trữ dữ liệu thực tế, mà chỉ lưu trữ câu lệnh truy vấn SQL được sử dụng để tạo nó. Khi truy vấn View, cơ sở dữ liệu sẽ thực thi câu lệnh SQL gốc để trả về kết quả. Một số hệ quản trị cơ sở dữ liệu (như SQL Server) hỗ trợ Indexed View hoặc Materialized View, nơi View có thể lưu trữ dữ liệu thực tế để cải thiện hiệu suất.

Cú pháp tổng quát:

CREATE VIEW ViewName AS

SELECT column1, column2, ...

FROM table_name

WHERE condition;

Ví dụ để tạo một View để hiển thị các nhân viên có lương cao hơn 5000, ta dùng cú pháp sau:

CREATE VIEW HighSalaryEmployees AS

SELECT Name, Salary

FROM Employees

WHERE Salary > 5000;

Để truy vấn tất cả các nhân viên có lương cao hơn 5000 từ View HighSalaryEmployees ta sử dụng cú pháp:

SELECT * 

FROM HighSalaryEmployees;

Để truy vấn chỉ tên của các nhân viên có lương cao hơn 7000: 

SELECT Name 

FROM HighSalaryEmployees

WHERE Salary > 7000;

Ví dụ: Giả sử bạn có thêm bảng Departments lưu thông tin phòng ban, bạn có thể kết hợp View HighSalaryEmployees với bảng này để lấy thông tin phòng ban của các nhân viên lương cao với cú pháp:

SELECT e.Name, e.Salary, d.DepartmentName

FROM HighSalaryEmployees e

JOIN Departments d ON e.DepartmentID = d.ID;

Ví dụ: Để tính tổng lương của tất cả nhân viên có lương cao hơn 5000 (dựa trên View HighSalaryEmployees) ta sử dụng cú pháp:

SELECT SUM(Salary) AS TotalHighSalary

FROM HighSalaryEmployees;

Lợi ích của View

  • Đơn giản hóa truy vấn phức tạp: View giúp lưu trữ các truy vấn SQL phức tạp, giúp người dùng không cần viết lại các câu lệnh dài dòng khi cần sử dụng lại.
  • Tăng tính bảo mật dữ liệu: View cho phép người dùng truy cập vào các dữ liệu cụ thể mà không cần quyền truy cập trực tiếp vào bảng gốc. Ví dụ, chỉ hiển thị một số cột trong bảng mà không để lộ tất cả dữ liệu.
  • Tính tái sử dụng cao: View có thể được sử dụng nhiều lần trong các truy vấn khác, tiết kiệm thời gian và giảm lỗi khi lặp lại cùng một logic truy vấn.
  • Tính độc lập với dữ liệu: Nếu cấu trúc bảng gốc thay đổi, chỉ cần cập nhật câu lệnh truy vấn trong View mà không cần sửa đổi các truy vấn liên quan khác.

Hạn chế của view

  • Hiệu suất có thể bị ảnh hưởng: Vì View không lưu trữ dữ liệu thực tế, mỗi khi truy vấn View, cơ sở dữ liệu phải thực thi lại câu lệnh gốc, điều này có thể làm chậm hiệu suất với các View phức tạp hoặc khi truy vấn trên tập dữ liệu lớn.
  • Không thể sửa đổi dữ liệu trực tiếp trong một số trường hợp: Nếu View được tạo từ nhiều bảng hoặc sử dụng các hàm tổng hợp (SUM, AVG, …), việc chèn, xóa hoặc cập nhật dữ liệu qua View sẽ không được hỗ trợ.
  • Phụ thuộc vào bảng gốc: Nếu bảng gốc bị thay đổi hoặc xóa, View cũng sẽ không hoạt động đúng hoặc bị mất.
  • Hạn chế trong việc áp dụng Index: View không hỗ trợ trực tiếp Index, điều này có thể làm giảm hiệu suất nếu cần truy cập nhanh vào dữ liệu.
  • Không phù hợp với dữ liệu động: View không tự động phản ánh các thay đổi trong logic hoặc cấu trúc dữ liệu mới. Nếu bảng gốc thay đổi (ví dụ: thêm hoặc xóa cột), View sẽ không tự cập nhật để phản ánh những thay đổi này. Khi đó, cần chỉnh sửa hoặc tạo lại View để đảm bảo hoạt động đúng.

Giải thích Normalize trong SQL là gì?

Normalize (chuẩn hóa) trong SQL là một quy trình tổ chức dữ liệu trong cơ sở dữ liệu để giảm thiểu sự dư thừa và tránh các bất thường (anomalies) trong việc thêm, xóa hoặc cập nhật dữ liệu. Chuẩn hóa chia dữ liệu thành các bảng nhỏ hơn và thiết lập các mối quan hệ giữa chúng để đảm bảo tính toàn vẹn và hiệu quả.

Lợi ích của Normalize: 

  • Loại bỏ sự dư thừa dữ liệu: Đảm bảo rằng mỗi dữ liệu chỉ được lưu trữ ở một nơi duy nhất.
  • Đảm bảo tính toàn vẹn dữ liệu: Tránh tình trạng dữ liệu không nhất quán khi thay đổi.
  • Tăng cường hiệu quả lưu trữ: Tối ưu hóa việc sử dụng bộ nhớ bằng cách loại bỏ các dữ liệu thừa.
  • Tăng khả năng mở rộng: Dễ dàng thêm hoặc thay đổi dữ liệu mà không cần chỉnh sửa nhiều bảng.

Các dạng chuẩn hóa

Dạng chuẩn hoá Mô tả Điều kiện cần thiết
1NF (First Normal Form) Đảm bảo mỗi ô chỉ chứa một giá trị (atomic values) và không có nhóm giá trị lặp (repeating groups). – Mỗi cột chứa dữ liệu dạng nguyên tử. 

– Không có hàng hoặc nhóm dữ liệu lặp lại.

2NF (Second Normal Form) Đảm bảo bảng ở dạng 1NF và loại bỏ các phụ thuộc không đầy đủ (partial dependency). – Dữ liệu đã ở dạng 1NF. 

– Các cột không phải khóa cần phải phụ thuộc hoàn toàn vào toàn bộ khóa chính.

3NF (Third Normal Form) Đảm bảo bảng ở dạng 2NF và loại bỏ các phụ thuộc bắc cầu (transitive dependency). – Dữ liệu đã ở dạng 2NF. 

– Không có cột không phải khóa phụ thuộc vào một cột không phải khóa khác.

BCNF (Boyce-Codd Normal Form) Là một dạng chuẩn hóa nâng cao của 3NF, đảm bảo rằng mỗi phụ thuộc hàm đều dựa vào một siêu khóa. – Dữ liệu đã ở dạng 3NF. 

– Mỗi phụ thuộc hàm phải liên quan đến một siêu khóa của bảng.

4NF (Fourth Normal Form) Loại bỏ các phụ thuộc đa trị (multi-valued dependency). – Dữ liệu đã ở dạng BCNF. 

– Không có phụ thuộc đa trị trong bảng.

5NF (Fifth Normal Form) Loại bỏ các phụ thuộc kết hợp (join dependency), đảm bảo rằng dữ liệu không bị phân mảnh không cần thiết. – Dữ liệu đã ở dạng 4NF. 

– Mỗi phụ thuộc kết hợp đều có thể phân rã mà không làm mất dữ liệu.

Giải thích Denormalize trong SQL là gì?

Denormalize (phi chuẩn hóa) trong SQL là quá trình kết hợp lại các bảng đã được chuẩn hóa để giảm thiểu các phép nối (JOIN) trong truy vấn và tăng tốc độ đọc dữ liệu. Quá trình này thường bổ sung thêm sự dư thừa có kiểm soát bằng cách sao chép hoặc tổng hợp dữ liệu từ các bảng khác nhau vào một bảng duy nhất.

Mục tiêu của Denormalize

  • Tăng hiệu suất truy vấn: Các phép nối (JOIN) giữa nhiều bảng chuẩn hóa có thể làm giảm tốc độ truy vấn, đặc biệt trên tập dữ liệu lớn. Denormalize giúp giảm số lượng phép nối.
  • Đơn giản hóa truy vấn: Dữ liệu được lưu trữ dưới dạng phi chuẩn hóa dễ dàng truy vấn hơn vì mọi thông tin liên quan đều nằm trong một bảng duy nhất.
  • Tối ưu hóa cho đọc dữ liệu: Denormalize đặc biệt hữu ích trong các hệ thống tập trung vào đọc dữ liệu, chẳng hạn như hệ thống báo cáo hoặc kho dữ liệu.

Hạn chế của Denormalize

  • Sự dư thừa dữ liệu: Denormalize có thể dẫn đến sự trùng lặp dữ liệu, làm tăng kích thước lưu trữ.
  • Tốn kém khi cập nhật: Vì dữ liệu bị trùng lặp, cập nhật một thông tin có thể cần thực hiện trên nhiều bản sao.
  • Mất tính toàn vẹn dữ liệu: Dễ xảy ra lỗi không nhất quán nếu dữ liệu dư thừa không được đồng bộ hóa.

User-defined function là gì?

User-defined Function (UDF) trong SQL là một hàm do người dùng định nghĩa để thực hiện một tác vụ cụ thể và có thể tái sử dụng trong các truy vấn. UDF được tạo ra để mở rộng khả năng của SQL, giúp thực hiện các thao tác tính toán hoặc xử lý phức tạp mà các hàm tích hợp sẵn (built-in functions) không đáp ứng được.

Các loại User-defined Function:

Scalar Function (Hàm trả về giá trị đơn)

Scalar Function là loại UDF trả về một giá trị duy nhất, có thể là số, chuỗi, ngày giờ, hoặc bất kỳ kiểu dữ liệu nào khác được hỗ trợ trong SQL. Scalar Function thường được sử dụng để thực hiện các tác vụ tính toán hoặc xử lý dữ liệu đơn giản mà không yêu cầu truy vấn nhiều hàng dữ liệu.

Hàm này có thể được sử dụng trong các mệnh đề SELECT, WHERE, ORDER BY hoặc bất kỳ nơi nào cần một giá trị đơn lẻ. 

Table-Valued Function (Hàm trả về bảng)

Table-Valued Function là loại UDF trả về một tập hợp kết quả dưới dạng một bảng, tương tự như khi bạn thực hiện một truy vấn SELECT. Hàm này được sử dụng khi bạn cần tái sử dụng một truy vấn phức tạp nhiều lần hoặc khi cần xử lý dữ liệu trong bảng một cách linh hoạt.

Table-Valued Function đặc biệt hữu ích trong việc tổ chức truy vấn, làm cho mã SQL trở nên dễ đọc hơn và dễ bảo trì. Có thể sử dụng Table-Valued Function như một bảng tạm thời trong truy vấn SQL, giúp tối ưu hóa các tác vụ như lọc dữ liệu, nhóm dữ liệu hoặc thực hiện các phép toán trên nhiều hàng dữ liệu.

Multi-Statement Table-Valued Function

Đây là một dạng mở rộng của Table-Valued Function, cho phép thực hiện nhiều câu lệnh SQL bên trong hàm trước khi trả về bảng kết quả. Khác với Table-Valued Function thông thường, loại UDF này thường yêu cầu khai báo một biến bảng (@TableVariable) để lưu trữ tạm dữ liệu trong quá trình xử lý. Sau đó, dữ liệu này được trả về dưới dạng bảng.

Loại hàm này hữu ích khi cần thực hiện các tác vụ phức tạp như kết hợp nhiều nguồn dữ liệu, tính toán giá trị trung gian hoặc áp dụng logic phức tạp trước khi trả về kết quả cuối cùng. Tuy nhiên, Multi-Statement Table-Valued Function có thể ảnh hưởng đến hiệu suất vì nó thực hiện nhiều thao tác hơn so với Table-Valued Function thông thường.

Cú pháp tổng quát trong SQL Server:

CREATE FUNCTION FunctionName (@Parameter DataType)

RETURNS DataType

AS

BEGIN

    -- Logic của hàm

    RETURN Value;

END;

Ví dụ 1 với UDF đơn giản (Trả về giá trị): Tạo một hàm để tính lương sau thuế với thuế suất cố định 10%:

CREATE FUNCTION CalculateNetSalary (@GrossSalary DECIMAL(10,2))

RETURNS DECIMAL(10,2)

AS

BEGIN

    RETURN @GrossSalary * 0.9; -- Trừ 10% thuế

END;

Sử dụng UDF trên trong truy vấn:

SELECT Name, Salary, dbo.CalculateNetSalary(Salary) AS NetSalary

FROM Employees;

Ví dụ 2 với UDF phức tạp hơn (Trả về bảng): Tạo một hàm trả về danh sách nhân viên có mức lương cao hơn một giá trị nhất định:

CREATE FUNCTION GetHighSalaryEmployees (@MinSalary DECIMAL(10,2))

RETURNS TABLE

AS

RETURN

(

    SELECT Name, Salary

    FROM Employees

    WHERE Salary > @MinSalary

);

Cách thực thi truy vấn trên:

SELECT * FROM dbo.GetHighSalaryEmployees(5000);

Đặc điểm của User-defined Function

  • Trả về kết quả: UDF luôn trả về một giá trị duy nhất hoặc một tập hợp kết quả (tuỳ thuộc vào loại UDF).
  • Được tái sử dụng: Một khi được tạo, UDF có thể được gọi trong nhiều truy vấn khác nhau, giảm thiểu việc lặp lại mã lệnh.
  • Dùng trong các câu lệnh SQL: UDF có thể được sử dụng trong mệnh đề SELECT, WHERE, GROUP BY, và nhiều mệnh đề khác.
  • Hạn chế thao tác dữ liệu: UDF chỉ có thể đọc dữ liệu (qua truy vấn SELECT), không thể thực hiện các thao tác thay đổi dữ liệu (INSERT, UPDATE, DELETE). Tuy nhiên một số hệ quản trị (như PostgreSQL) cho phép UDF thực hiện các thao tác này thông qua Stored Procedures hoặc Functions với quyền đặc biệt.

Hạn chế của UDF

  • Giới hạn thao tác: Không thể thực hiện các thao tác thay đổi dữ liệu như INSERT, UPDATE hoặc DELETE.
  • Giảm hiệu suất: Trong một số trường hợp, UDF có thể chậm hơn khi áp dụng trên tập dữ liệu lớn vì mỗi lần gọi hàm có thể gây thêm chi phí xử lý.
  • Khả năng tương thích: UDF được triển khai khác nhau trong các hệ quản trị cơ sở dữ liệu (MySQL, SQL Server, PostgreSQL…).

T-SQL là gì?

T-SQL (Transact-SQL) là một phần mở rộng của ngôn ngữ SQL tiêu chuẩn, được phát triển bởi Microsoft. T-SQL được sử dụng trong Microsoft SQL Server và Azure SQL Database để thực hiện các tác vụ liên quan đến cơ sở dữ liệu, bao gồm quản lý, thao tác dữ liệu, và lập trình.

T-SQL bổ sung các tính năng mà SQL tiêu chuẩn không có, như:

  • Các cấu trúc lập trình: Hỗ trợ các khối mã lệnh như vòng lặp, điều kiện, và biến.
  • Tích hợp mạnh mẽ với cơ sở dữ liệu: Cho phép tạo và quản lý thủ tục lưu trữ (stored procedures), hàm (functions) và trigger.
  • Quản lý giao dịch: Cung cấp các lệnh để kiểm soát giao dịch (BEGIN TRANSACTION, COMMIT, ROLLBACK).
  • Tính năng xử lý dữ liệu nâng cao như CROSS APPLY, MERGE và TRY…CATCH để xử lý lỗi.
  • Hỗ trợ các hàm phân tích (analytic functions) như ROW_NUMBER, RANK, và PARTITION BY.

Ứng dụng của T-SQL

  • Xử lý dữ liệu: Truy vấn, lọc và phân tích dữ liệu.
  • Lập trình logic: Xây dựng các hệ thống tự động hóa như báo cáo, hệ thống kế toán hoặc quản lý kho hàng.
  • Tối ưu hóa: T-SQL cho phép thực hiện các truy vấn phức tạp với hiệu suất cao.

Làm sao để hạn chế quyền truy cập cho user trong SQL?

Để hạn chế quyền truy cập cho user trong SQL, bạn có thể sử dụng các cơ chế quản lý quyền (permissions) và vai trò người dùng (roles) được cung cấp bởi hệ quản trị cơ sở dữ liệu. Quản lý quyền truy cập là một phần quan trọng để đảm bảo bảo mật dữ liệu và chỉ cho phép người dùng thực hiện các hành động cần thiết. Tùy thuộc vào hệ quản trị cơ sở dữ liệu như MySQL, SQL Server, hoặc PostgreSQL, cách áp dụng cụ thể có thể khác nhau.

Các cách hạn chế quyền truy cập cho user

Tạo user với quyền hạn cụ thể

Khi thêm một user mới, bạn có thể chỉ định quyền cụ thể mà user được phép thực hiện, chẳng hạn như chỉ đọc dữ liệu (SELECT) hoặc chỉ ghi dữ liệu (INSERT, UPDATE). Ví dụ (MySQL):

CREATE USER 'read_user'@'localhost' IDENTIFIED BY 'password123';

GRANT SELECT ON database_name.* TO 'read_user'@'localhost';

Sử dụng lệnh GRANT và REVOKE

  • GRANT: Cấp quyền cho user hoặc các đối tượng khác như thủ tục (procedures) và hàm (functions) thực hiện các hành động cụ thể.
  • REVOKE: Thu hồi quyền của đối tượng nếu không còn cần thiết.

Ví dụ:

-- Cấp quyền chỉ đọc

GRANT SELECT ON Employees TO 'user1';

-- Thu hồi quyền sửa dữ liệu

REVOKE UPDATE ON Employees FROM 'user1';

Sử dụng vai trò (Roles)

Tạo vai trò (role) với một tập hợp quyền cụ thể, sau đó gán vai trò này cho user, giúp quản lý quyền dễ dàng hơn khi có nhiều user. Ví dụ (SQL Server):

CREATE ROLE ReadOnlyRole;

GRANT SELECT ON Employees TO ReadOnlyRole;

EXEC sp_addrolemember 'ReadOnlyRole', 'user1';

Giới hạn quyền truy cập vào các cột hoặc hàng cụ thể

Giới hạn quyền truy cập cột: Cho phép user chỉ xem hoặc thao tác trên một số cột nhất định. Ví dụ (MySQL):

GRANT SELECT (Name, Salary) ON Employees TO 'user1';

Giới hạn quyền truy cập hàng: Sử dụng View để tạo lớp dữ liệu ảo chứa các hàng được phép truy cập. Ví dụ:

CREATE VIEW LimitedView AS

SELECT Name, Salary FROM Employees WHERE Department = 'IT';

GRANT SELECT ON LimitedView TO 'user1';

Hạn chế quyền truy cập qua mạng

Quy định cụ thể user chỉ được truy cập từ một địa chỉ IP hoặc máy chủ nhất định. Ví dụ (MySQL):

CREATE USER 'user1'@'192.168.1.10' IDENTIFIED BY 'password123';

GRANT SELECT ON database_name.* TO 'user1'@'192.168.1.10';

Các câu hỏi phỏng vấn SQL dành cho Senior

Cách thực hiện giao dịch (Transaction) trong SQL? Các thuộc tính của ACID là gì?

Transaction (Giao dịch) trong SQL là một chuỗi các câu lệnh SQL được thực thi như một đơn vị duy nhất. Nếu một trong các câu lệnh không thành công, toàn bộ giao dịch sẽ bị hủy bỏ, đảm bảo rằng cơ sở dữ liệu không bị rơi vào trạng thái không nhất quán.

Các bước thực hiện giao dịch trong SQL

  1. Bắt đầu giao dịch: Sử dụng câu lệnh BEGIN TRANSACTION hoặc cú pháp tương tự (phụ thuộc vào hệ quản trị cơ sở dữ liệu).
  2. Thực hiện các thao tác SQL: Thêm (INSERT), cập nhật (UPDATE), hoặc xóa (DELETE) dữ liệu.
  3. Xác nhận giao dịch: Sử dụng câu lệnh COMMIT để lưu vĩnh viễn các thay đổi vào cơ sở dữ liệu.
  4. Hủy bỏ giao dịch (nếu cần): Nếu xảy ra lỗi hoặc cần hoàn tác các thay đổi, sử dụng câu lệnh ROLLBACK để khôi phục trạng thái ban đầu của cơ sở dữ liệu.

Ví dụ: Với quá trình chuyển tiền giữa hai tài khoản. Nếu bất kỳ bước nào thất bại, toàn bộ giao dịch sẽ bị hoàn tác với cú pháp như sau (trong SQL Server):

BEGIN TRANSACTION;

-- Trừ tiền từ tài khoản người gửi

UPDATE Accounts

SET Balance = Balance - 1000

WHERE AccountID = 1;

-- Cộng tiền vào tài khoản người nhận

UPDATE Accounts

SET Balance = Balance + 1000

WHERE AccountID = 2;

-- Xác nhận giao dịch nếu không có lỗi

IF @@ERROR = 0

    COMMIT;

ELSE

    ROLLBACK;

Các thuộc tính của ACID trong giao dịch SQL

ACID là các thuộc tính cốt lõi đảm bảo tính toàn vẹn dữ liệu trong một giao dịch.

  • Atomicity (Tính nguyên tử): Giao dịch được thực thi như một đơn vị duy nhất. Nếu bất kỳ phần nào của giao dịch thất bại, toàn bộ giao dịch sẽ bị hủy bỏ. Ví dụ: Trong ví dụ chuyển tiền, nếu không thể trừ tiền từ tài khoản người gửi, giao dịch sẽ không thực hiện cộng tiền cho tài khoản người nhận.
  • Consistency (Tính nhất quán): Sau khi giao dịch hoàn tất, cơ sở dữ liệu sẽ ở trạng thái hợp lệ, tuân thủ các ràng buộc và quy tắc dữ liệu. Ví dụ: Số tiền tổng trong cả hai tài khoản phải không đổi trước và sau giao dịch.
  • Isolation (Tính cô lập): Các giao dịch đồng thời không ảnh hưởng lẫn nhau. Mỗi giao dịch được thực hiện như thể nó là duy nhất. Ví dụ: Nếu hai giao dịch cùng thay đổi số dư tài khoản, kết quả sẽ giống như từng giao dịch được thực hiện lần lượt.
  • Durability (Tính bền vững): Sau khi giao dịch được xác nhận (COMMIT), các thay đổi sẽ được lưu vĩnh viễn, ngay cả khi xảy ra lỗi hệ thống. Ví dụ: Sau khi giao dịch chuyển tiền hoàn tất, số dư mới của các tài khoản sẽ được ghi lại vĩnh viễn.

Phân biệt OLAP và OLTP

OLAP (Online Analytical Processing) và OLTP (Online Transaction Processing) là hai loại hệ thống cơ sở dữ liệu phục vụ các mục đích khác nhau trong quản lý và xử lý dữ liệu.

Tiêu chí OLAP (Online Analytical Processing) OLTP (Online Transaction Processing)
Mục đích Hỗ trợ phân tích dữ liệu và ra quyết định dựa trên dữ liệu lịch sử. Hỗ trợ xử lý giao dịch trực tuyến theo thời gian thực.
Tính chất dữ liệu Dữ liệu được tổng hợp từ nhiều nguồn và thường mang tính lịch sử. Dữ liệu chi tiết, thời gian thực, tập trung vào các giao dịch hàng ngày.
Kiểu truy vấn Truy vấn phức tạp, thường là các phép tính tổng hợp, nhóm, và phân tích. Truy vấn đơn giản, tập trung vào thêm, sửa, xóa, hoặc truy vấn nhanh.
Thiết kế cơ sở dữ liệu Được tối ưu hóa cho truy vấn phân tích với các cấu trúc như kho dữ liệu hoặc schema dạng sao. Được tối ưu hóa cho xử lý giao dịch với thiết kế chuẩn hóa (Normalization).
Người sử dụng chính Các nhà phân tích dữ liệu, quản lý cấp cao hoặc chuyên gia ra quyết định. Người dùng cuối, nhân viên vận hành hoặc các ứng dụng giao dịch.
Khối lượng dữ liệu Xử lý dữ liệu lớn từ nhiều năm hoặc nhiều nguồn. Xử lý dữ liệu nhỏ hơn, tập trung vào giao dịch hiện tại.
Ứng dụng Hệ thống báo cáo, kho dữ liệu, phân tích tài chính. Hệ thống quản lý bán hàng, ngân hàng, hoặc quản lý tồn kho.

Partitioning trong SQL là gì?

Partitioning trong SQL là kỹ thuật chia nhỏ một bảng lớn thành các phần nhỏ hơn (gọi là phân vùng) dựa trên một số điều kiện hoặc cột cụ thể. Mỗi phân vùng chứa một tập hợp con dữ liệu của bảng gốc, nhưng toàn bộ các phân vùng cùng nhau vẫn đại diện cho dữ liệu đầy đủ của bảng.

Partitioning được hỗ trợ bởi một số hệ quản trị cơ sở dữ liệu như MySQL, PostgreSQL và SQL Server, với cú pháp và khả năng khác nhau. Partitioning giúp cải thiện hiệu suất truy vấn, quản lý dữ liệu dễ dàng hơn và tối ưu hóa việc lưu trữ khi làm việc với lượng dữ liệu lớn.

Lợi ích của Partitioning

  • Cải thiện hiệu suất truy vấn: Truy vấn chỉ cần quét một phân vùng cụ thể thay vì toàn bộ bảng, giảm thời gian xử lý. Phù hợp với dữ liệu lớn, như kho dữ liệu hoặc hệ thống báo cáo.
  • Quản lý dữ liệu dễ dàng: Có thể thêm, xóa hoặc lưu trữ dữ liệu riêng lẻ ở cấp phân vùng mà không ảnh hưởng đến toàn bộ bảng.
  • Tối ưu hóa lưu trữ: Các phân vùng khác nhau có thể được lưu trên các ổ đĩa khác nhau để cân bằng tải.
  • Tăng tính khả dụng: Nếu một phân vùng bị lỗi, các phân vùng khác vẫn hoạt động bình thường (chủ yếu áp dụng trong các hệ thống phân tán hoặc lưu trữ phân vùng trên nhiều ổ đĩa).

Hạn chế của Partitioning

  • Độ phức tạp cao hơn: Thiết kế và quản lý bảng phân vùng đòi hỏi kiến thức kỹ thuật cao hơn so với bảng thường.
  • Không phù hợp với bảng nhỏ: Partitioning thường không cần thiết cho các bảng có kích thước nhỏ.
  • Hạn chế trong một số hệ quản trị: Một số hệ quản trị cơ sở dữ liệu không hỗ trợ partitioning hoặc chỉ hỗ trợ ở mức cơ bản.
  • Hiệu suất kém nếu không được thiết kế phù hợp: Nếu các phân vùng không được thiết kế tốt (chẳng hạn số lượng phân vùng quá lớn hoặc điều kiện phân vùng không tối ưu), các truy vấn có thể phải quét qua nhiều phân vùng, dẫn đến hiệu suất kém hơn so với bảng không phân vùng.

Các loại Partitioning

Range Partitioning (Phân vùng theo khoảng giá trị)

Range Partitioning phân chia dữ liệu thành các phân vùng dựa trên khoảng giá trị trong một cột cụ thể. Loại này thường được sử dụng khi dữ liệu có sự phân bổ theo thời gian, số lượng, hoặc các khoảng giá trị rõ ràng.

Ví dụ để chia bảng Orders thành các phân vùng theo năm của cột OrderDate, ta làm như sau (MySQL):

CREATE TABLE Orders (

    OrderID INT,

    OrderDate DATE,

    TotalAmount DECIMAL

) PARTITION BY RANGE (YEAR(OrderDate)) (

    PARTITION p_2020 VALUES LESS THAN (2021),

    PARTITION p_2021 VALUES LESS THAN (2022),

    PARTITION p_2022 VALUES LESS THAN (2023)

);

List Partitioning (Phân vùng theo danh sách giá trị)

List Partitioning phân chia dữ liệu dựa trên danh sách các giá trị cụ thể trong một cột. Loại này phù hợp với dữ liệu phân loại rõ ràng như khu vực, loại sản phẩm hoặc trạng thái.

Ví dụ để chia bảng Employees thành các phân vùng theo khu vực (Region), ta làm như sau (MySQL):

CREATE TABLE Employees (

    EmployeeID INT,

    Name VARCHAR(50),

    Region VARCHAR(20)

) PARTITION BY LIST (Region) (

    PARTITION p_north VALUES IN ('North', 'Northeast'),

    PARTITION p_south VALUES IN ('South', 'Southeast')

);

Hash Partitioning (Phân vùng băm)

Hash Partitioning sử dụng hàm băm để phân chia dữ liệu dựa trên giá trị của một cột, thường áp dụng khi dữ liệu không có quy luật rõ ràng hoặc cần phân bổ đều.

Ví dụ để chia bảng Customers thành 4 phân vùng dựa trên cột CustomerID, ta làm như sau (MySQL):

CREATE TABLE Customers (

    CustomerID INT,

    Name VARCHAR(50)

) PARTITION BY HASH (CustomerID) PARTITIONS 4;

Composite Partitioning (Phân vùng kết hợp)

Composite Partitioning là sự kết hợp của nhiều loại phân vùng, như Range + Hash hoặc List + Hash, để tận dụng lợi thế của cả hai cách phân vùng (MySQL không hỗ trợ composite partitioning trực tiếp).

Ví dụ để chia bảng Sales thành phân vùng theo năm (Range) và tiếp tục phân chia trong từng năm dựa trên Region(Hash), ta làm như sau (MySQL):

CREATE TABLE Sales (

    SaleID INT,

    SaleDate DATE,

    Region VARCHAR(20),

    Amount DECIMAL

) PARTITION BY RANGE (YEAR(SaleDate)) SUBPARTITION BY HASH (Region) (

    PARTITION p_2020 VALUES LESS THAN (2021),

    PARTITION p_2021 VALUES LESS THAN (2022)

) SUBPARTITIONS 4;

ETL trong SQL là gì?

ETL (Extract, Transform, Load) là một quy trình quan trọng trong quản lý dữ liệu, sử dụng để trích xuất (Extract) dữ liệu từ nhiều nguồn, biến đổi (Transform) dữ liệu theo các quy tắc kinh doanh hoặc yêu cầu cụ thể và tải (Load) dữ liệu vào một cơ sở dữ liệu hoặc kho dữ liệu (Data Warehouse).

ETL thường được sử dụng trong các hệ thống phân tích và báo cáo dữ liệu.

Các bước chính trong ETL

  1. Extract (Trích xuất dữ liệu): Giai đoạn này trích xuất dữ liệu từ các nguồn khác nhau, có thể là cơ sở dữ liệu quan hệ, tệp CSV, XML, API, hoặc hệ thống ERP. Dữ liệu thu thập có thể không đồng nhất về định dạng hoặc cấu trúc, tuy nhiên cần đảm bảo không mất mát dữ liệu và giữ được tính toàn vẹn dữ liệu.
  2. Transform (Biến đổi dữ liệu): Trong bước này, dữ liệu được làm sạch, chuẩn hóa hoặc biến đổi để đáp ứng yêu cầu kinh doanh. Các thao tác phổ biến bao gồm đổi định dạng, tính toán giá trị mới, hoặc hợp nhất dữ liệu.

Ví dụ để tính toán giá trị mới “Tổng tiền đã điều chỉnh” (với mức tăng 10%) và chuyển đổi định dạng ngày trong bảng Orders, ta làm như sau:

SELECT 

    OrderID, 

    CustomerID, 

    CONVERT(DATE, OrderDate, 101) AS OrderDate, 

    TotalAmount * 1.1 AS AdjustedTotalAmount

FROM StagingDatabase.Orders;

Trong đó:

  • Cột OrderDate được chuyển sang định dạng DATE để đồng nhất.
  • Cột TotalAmount được nhân với 1.1 để tính “Tổng tiền đã điều chỉnh”.
  • Dữ liệu này sẽ được lưu tạm thời trong giai đoạn staging để chuẩn bị tải vào kho dữ liệu.

Load (Tải dữ liệu)

Đây là giai đoạn cuối cùng, nơi dữ liệu đã được xử lý sẽ được đưa vào hệ thống đích, như kho dữ liệu (Data Warehouse).

Nested trigger là gì?

Nested Trigger trong SQL là các trigger (kích hoạt) được thực thi lồng nhau, tức là một trigger có thể kích hoạt một hoặc nhiều trigger khác. Quá trình này xảy ra khi một trigger thực hiện một thao tác như INSERT, UPDATE hoặc DELETE, và thao tác này tiếp tục kích hoạt các trigger khác trên bảng liên quan.

Cách hoạt động của Nested Trigger

  • Trigger chính (Primary Trigger): Được kích hoạt bởi hành động trên bảng, chẳng hạn như INSERT, UPDATE hoặc DELETE.
  • Trigger phụ (Nested Trigger): Kích hoạt do các thao tác trong trigger chính. Trigger phụ có thể tiếp tục kích hoạt các trigger khác, dẫn đến chuỗi các trigger lồng nhau.

Ví dụ

Giả sử có hai bảng:

  • Orders: Lưu thông tin đơn hàng.
  • Inventory: Lưu thông tin tồn kho sản phẩm.

Yêu cầu:

  • Khi thêm một đơn hàng vào bảng Orders, trigger sẽ giảm số lượng sản phẩm tồn kho trong bảng Inventory.
  • Nếu số lượng tồn kho dưới mức tối thiểu, một trigger khác sẽ ghi thông báo cảnh báo vào bảng Alerts.

Cách thực hiện:

Bước 1: Tạo trigger chính trên bảng Orders. Trigger này giảm số lượng tồn kho sản phẩm khi thêm đơn hàng:

CREATE TRIGGER trg_UpdateInventory

ON Orders

AFTER INSERT

AS

BEGIN

    UPDATE Inventory

    SET Quantity = Quantity - i.Quantity

    FROM Inventory inv

    INNER JOIN Inserted i ON inv.ProductID = i.ProductID;

END;

Bước 2: Tạo trigger phụ trên bảng Inventory. Trigger này ghi thông báo cảnh báo nếu tồn kho dưới mức tối thiểu:

CREATE TRIGGER trg_InventoryAlert

ON Inventory

AFTER UPDATE

AS

BEGIN

    INSERT INTO Alerts (ProductID, AlertMessage, AlertDate)

    SELECT ProductID, 'Low stock alert', GETDATE()

    FROM Inserted

    WHERE Quantity < 10;

END;

Giải thích:

  • Khi thêm một đơn hàng vào Orders, trigger trg_UpdateInventory sẽ giảm số lượng tồn kho trong Inventory.
  • Nếu tồn kho dưới 10, trigger trg_InventoryAlert sẽ thêm thông báo vào bảng Alerts.

Tránh vòng lặp vô hạn do Nested Trigger

Nếu một trigger thực hiện hành động kích hoạt lại chính nó (hoặc một trigger khác có quan hệ tuần hoàn), vòng lặp vô hạn sẽ xảy ra, dẫn đến lỗi hệ thống hoặc hiệu suất suy giảm nghiêm trọng.

Cách tránh vòng lặp vô hạn:

  • Sử dụng NESTED_TRIGGERS: NESTED_TRIGGERS là tùy chọn trong SQL Server giúp bật/tắt việc kích hoạt trigger lồng nhau. Tùy chọn này ngăn trigger phụ được kích hoạt bởi trigger chính, giúp loại bỏ vòng lặp vô hạn. Ví dụ, nếu trigger A cập nhật bảng B, từ đó kích hoạt lại A, tùy chọn này sẽ chặn trigger A thực thi lần hai. Tuy nhiên, nhược điểm là bạn không thể sử dụng các trigger lồng nhau hữu ích trong một số trường hợp cần xử lý phức tạp.
  • Thêm điều kiện kiểm tra: Thêm điều kiện kiểm tra trong trigger, ví dụ sử dụng cờ trạng thái (flag) hoặc kiểm tra dữ liệu đã được xử lý, giúp ngăn trigger tự kích hoạt lại trên dữ liệu cũ và đảm bảo trigger chỉ chạy trên dữ liệu mới hoặc chưa được xử lý. Tuy nhiên, cách này đòi hỏi bổ sung logic và đôi khi thay đổi cấu trúc bảng, làm tăng độ phức tạp.
  • Hạn chế thao tác kích hoạt: Thiết kế trigger sao cho không thực hiện các thao tác có thể kích hoạt lại chính nó, ví dụ di chuyển logic xử lý sang Stored Procedure hoặc ứng dụng bên ngoài sẽ giúp giảm nguy cơ vòng lặp nhưng làm tăng sự phụ thuộc vào logic bên ngoài, làm giảm tính tự động hóa.
  • Sử dụng biến hoặc bảng tạm: Dùng biến hoặc bảng tạm để ghi nhận trạng thái trigger, giúp kiểm soát việc kích hoạt lặp lại trong một giao dịch. Biến trạng thái đảm bảo trigger không chạy lại trên cùng dữ liệu, nhưng cách này có thể phức tạp và ảnh hưởng hiệu suất nếu không được tối ưu.

Giải thích các mức độ cô lập (Isolation level)

Mức độ cô lập (Isolation Level) là một khía cạnh quan trọng trong quản lý giao dịch trong SQL. Nó xác định mức độ mà một giao dịch bị cách ly khỏi các giao dịch khác đang diễn ra, giúp đảm bảo tính toàn vẹn của dữ liệu khi có nhiều giao dịch đồng thời.

Các mức độ cô lập (Isolation Levels):

SQL chuẩn định nghĩa 4 mức độ cô lập chính, mỗi mức độ cân bằng giữa hiệu suất và tính nhất quán dữ liệu:

Read Uncommitted (Đọc dữ liệu chưa cam kết)

Là mức độ cô lập thấp nhất, cho phép một giao dịch đọc dữ liệu chưa được cam kết từ giao dịch khác. Điều này có thể dẫn đến rủi ro khi dữ liệu được đọc có thể bị thay đổi hoặc hoàn tác.

Hạn chế: có thể gây ra Dirty Read, có nghĩa là một giao dịch đọc dữ liệu chưa được cam kết từ giao dịch khác, nếu giao dịch kia bị hoàn tác, dữ liệu đọc được sẽ không hợp lệ.

Ví dụ: Giả sử Giao dịch A cập nhật giá trị Salary = 10000 cho EmployeeID = 1 nhưng chưa cam kết. Trong khi đó, Giao dịch B đọc giá trị này. Nếu Giao dịch A hủy (ROLLBACK), dữ liệu đọc của Giao dịch B sẽ sai.

Read Committed (Đọc dữ liệu đã cam kết)

Giao dịch chỉ đọc dữ liệu đã được cam kết. Đây là mức độ cô lập mặc định trong nhiều hệ quản trị cơ sở dữ liệu, giảm nguy cơ Dirty Read.

Hạn chế: có thể gây ra Non-Repeatable Read, có nghĩa là nếu một giao dịch đọc dữ liệu nhiều lần, giá trị có thể thay đổi giữa các lần đọc do giao dịch khác cam kết thay đổi dữ liệu.

Ví dụ: Giao dịch A đọc giá trị Salary = 5000 từ EmployeeID = 1. Trong lúc đó, Giao dịch B cập nhật Salary = 6000 và cam kết. Khi Giao dịch A đọc lại, giá trị thay đổi thành 6000.

Repeatable Read (Đọc có thể lặp lại)

Giao dịch đảm bảo rằng các giá trị đọc không thay đổi trong suốt quá trình giao dịch, bất kể giao dịch khác cập nhật dữ liệu. Tuy nhiên, dữ liệu mới có thể được thêm vào bảng và không bị khóa.

Hạn chế: có thể gây ra Phantom Read, nghĩa là nếu một giao dịch đọc một tập hợp hàng, giao dịch khác có thể thêm hàng mới vào tập hợp đó, dẫn đến kết quả khác khi truy vấn lại.

Ví dụ: Giao dịch A đọc tất cả nhân viên trong phòng IT. Trong lúc đó, Giao dịch B thêm một nhân viên mới vào phòng IT. Khi Giao dịch A truy vấn lại, nhân viên mới xuất hiện.

Serializable (Tuần tự hóa)

Mức độ cô lập cao nhất, đảm bảo toàn bộ giao dịch được thực hiện như thể chúng tuần tự hóa (không đồng thời). Ngăn chặn mọi vấn đề, bao gồm Dirty Read, Non-Repeatable Read và Phantom Read.

Hạn chế: có thể gây ra hiệu suất thấp do sử dụng khóa toàn diện trên bảng hoặc phạm vi dữ liệu, ngăn chặn giao dịch khác truy cập cùng dữ liệu.

Ví dụ: Giao dịch A đọc tất cả nhân viên trong phòng HR. Trong lúc đó, Giao dịch B không thể thêm, xóa, hoặc cập nhật nhân viên nào trong phòng HR cho đến khi Giao dịch A hoàn tất.

Làm thế nào để thực hiện kiểm soát đồng thời (Concurrency Control)?

Kiểm soát đồng thời (Concurrency Control) trong SQL là kỹ thuật được sử dụng để quản lý việc thực thi đồng thời nhiều giao dịch trong cơ sở dữ liệu, nhằm đảm bảo tính nhất quán dữ liệu và tính toàn vẹn khi có nhiều người dùng hoặc ứng dụng truy cập cùng một lúc.

Công dụng của Concurrency Control: 

  • ​​Tránh xung đột dữ liệu: Đảm bảo rằng các giao dịch không gây lỗi hoặc làm sai lệch dữ liệu khi thực thi đồng thời.
  • Đảm bảo tính toàn vẹn: Các kỹ thuật kiểm soát đồng thời như khóa (Locking), timestamp ordering hoặc multiversion concurrency control (MVCC) đảm bảo rằng dữ liệu vẫn đúng và hợp lệ ngay cả khi có nhiều giao dịch cùng thực thi. Mặc dù có thể giảm hiệu suất trong một số trường hợp, đặc biệt với các phương pháp như Serializable Isolation Level, tính toàn vẹn dữ liệu vẫn luôn được ưu tiên.
  • Đảm bảo ACID: Giữ vững các thuộc tính Atomicity, Consistency, IsolationDurability của giao dịch.

Các phương pháp kiểm soát đồng thời

Khóa (Locking)

Sử dụng khóa để ngăn chặn giao dịch khác truy cập dữ liệu đang được thao tác. Khóa có thể áp dụng ở cấp độ bảng, dòng, hoặc trang, tùy thuộc vào hệ quản trị cơ sở dữ liệu và yêu cầu cụ thể. Có hai loại khóa phổ biến:

  • Shared Lock (Khóa chia sẻ): Cho phép nhiều giao dịch đọc cùng lúc, nhưng không cho phép ghi.
  • Exclusive Lock (Khóa độc quyền): Chỉ cho phép một giao dịch truy cập dữ liệu để đọc hoặc ghi.

T​​imestamp Ordering (Xếp thứ tự theo dấu thời gian)

Giao dịch được thực thi theo thứ tự thời gian bắt đầu. Giao dịch sau không thể ghi đè lên kết quả của giao dịch trước. Ví dụ giao dịch A bắt đầu trước giao dịch B, nếu B muốn ghi dữ liệu mà A đang đọc, B phải chờ cho đến khi A hoàn thành.

Kiểm soát phiên bản (MVCC – Multi -Version Concurrency Control)

MVCC cho phép các giao dịch đồng thời bằng cách giữ nhiều phiên bản dữ liệu. Giao dịch đọc sẽ luôn thấy dữ liệu tại thời điểm bắt đầu giao dịch, bất kể các giao dịch khác đã thay đổi dữ liệu. Tuy nhiên, MVCC có chi phí lưu trữ cao hơn do phải lưu nhiều phiên bản dữ liệu. Điều này có thể ảnh hưởng hiệu suất trong trường hợp bảng lớn và giao dịch dài.

Ví dụ: PostgreSQL và MySQL (InnoDB) sử dụng MVCC để cho phép các giao dịch đồng thời mà không cần khóa.

Serializable Isolation Level

Áp dụng mức cô lập cao nhất để tuần tự hóa các giao dịch, đảm bảo không có xung đột. Tuy nhiên, phương pháp này làm giảm hiệu suất do sử dụng khóa toàn diện.

Optimistic Concurrency Control (Kiểm soát đồng thời lạc quan)

Cho phép các giao dịch thực thi mà không cần khóa. Trước khi ghi dữ liệu, hệ thống kiểm tra nếu dữ liệu không thay đổi trong quá trình thực hiện giao dịch. Nếu có thay đổi, giao dịch bị hủy (ROLLBACK).

Sự khác biệt giữa Authentication và Authorization trong SQL

Tiêu chí Authentication Authorization
Mục đích Xác minh danh tính người dùng hoặc ứng dụng truy cập cơ sở dữ liệu. Xác định quyền mà người dùng hoặc ứng dụng được phép thực hiện.
Thực hiện khi nào Diễn ra trước khi truy cập vào cơ sở dữ liệu. Diễn ra sau khi người dùng đã được xác thực.
Kết quả Cho phép hoặc từ chối truy cập vào hệ thống. Cho phép hoặc từ chối thực hiện hành động cụ thể (như đọc, ghi).
Công cụ chính Username, password, MFA, token, chứng chỉ, hoặc tích hợp hệ điều hành. Quyền (Permissions) và vai trò (Roles) trong hệ thống, chính sách bảo mật (Security Policies), hoặc ràng buộc điều kiện (Row-Level Security).
Cấp độ Hệ thống xác thực (Hệ điều hành hoặc cơ sở dữ liệu). Dữ liệu hoặc thao tác trong hệ thống cơ sở dữ liệu, bao gồm cấp bảng, cột và hàng.
Ví dụ Người dùng đăng nhập vào SQL Server bằng tài khoản admin. Cấp quyền SELECT trên bảng Employees cho người dùng User1.

Full-text search trong SQL là gì?

Full-Text Search (Tìm kiếm toàn văn) là một tính năng trong SQL giúp thực hiện các truy vấn tìm kiếm trên dữ liệu văn bản lớn (như các cột chứa đoạn văn, tài liệu, hoặc văn bản mô tả).

Full-Text Search không chỉ tìm kiếm chính xác từ khóa mà còn hỗ trợ các kiểu tìm kiếm nâng cao như tìm kiếm gần đúng, tìm kiếm theo cụm từ, hoặc tìm kiếm theo thứ tự ưu tiên.

Cách hoạt động của Full-text search

Full-Text Search hoạt động dựa trên việc tạo và sử dụng chỉ mục toàn văn (Full-Text Index) để tối ưu hóa quá trình tìm kiếm trong các cột văn bản (Full-Text Index không lưu trữ toàn bộ nội dung văn bản, mà lưu trữ danh sách từ khóa, cụm từ, cùng với vị trí và thông tin metadata cần thiết để tìm kiếm). Quá trình này bao gồm hai bước chính:

Bước 1: Tạo chỉ mục toàn văn (Full-Text Index)

Chỉ mục toàn văn là một cấu trúc đặc biệt được tạo trên các cột chứa văn bản. Nó không lưu trữ toàn bộ nội dung mà phân tách văn bản thành các từ hoặc cụm từ, sau đó lưu trữ thông tin về chúng, bao gồm:

  • Từ xuất hiện trong văn bản.
  • Vị trí của từ trong văn bản.

Ví dụ: Một cột Content chứa văn bản:

“SQL is a powerful database language.”

Chỉ mục toàn văn sẽ lưu trữ các từ:

SQL, is, a, powerful, database, language

Bước 2: Sử dụng công cụ tìm kiếm (Search Engine)

Hệ quản trị cơ sở dữ liệu sẽ sử dụng các hàm như CONTAINS hoặc FREETEXT để tìm kiếm trong chỉ mục toàn văn thay vì quét trực tiếp bảng dữ liệu.

  • CONTAINS: Tìm kiếm từ khóa cụ thể trong văn bản. Ví dụ nếu bạn tìm kiếm “database”, chỉ mục sẽ xác định tất cả các bản ghi chứa từ “database” và trả kết quả nhanh hơn so với việc quét toàn bộ dữ liệu.
  • FREETEXT: Tìm kiếm linh hoạt hơn bằng cách tìm các từ hoặc cụm từ có ý nghĩa tương tự. Ví dụ nếu bạn tìm kiếm “database”, kết quả có thể bao gồm cả “databases”, “relational database” hoặc “SQL database”.

Hạn chế của Full-text search

  • Hiệu suất kém trên dữ liệu lớn: Khi làm việc với cơ sở dữ liệu chứa lượng lớn dữ liệu văn bản, việc tìm kiếm có thể trở nên chậm nếu chỉ mục full-text không được tối ưu hóa. Ngoài ra, việc duy trì chỉ mục full-text (đặc biệt khi dữ liệu được thêm, sửa hoặc xóa thường xuyên) có thể tiêu tốn tài nguyên và ảnh hưởng đến hiệu suất chung của hệ thống.
  • Không phù hợp cho tìm kiếm real-time: Chỉ mục full-text không cập nhật ngay lập tức khi dữ liệu trong bảng thay đổi. Việc đồng bộ hóa chỉ mục thường xảy ra định kỳ hoặc thủ công, có thể dẫn đến tình trạng kết quả tìm kiếm không phản ánh chính xác trạng thái hiện tại của dữ liệu.
  • Hạn chế trong ngôn ngữ phức tạp: Full-Text Search không hỗ trợ tốt cho các ngôn ngữ phức tạp như tiếng Trung, Nhật hoặc Hàn khi các ký tự thường không có dấu phân cách rõ ràng, dẫn đến việc tìm kiếm không chính xác hoặc bỏ sót dữ liệu.
  • Độ chính xác thấp với các trường hợp ngữ nghĩa: Full-Text Search chỉ tìm kiếm dựa trên từ khóa và cụm từ trong chỉ mục, không xử lý tốt ngữ nghĩa hoặc các trường hợp đồng nghĩa, từ viết tắt hoặc ngữ cảnh phức tạp. Ví dụ: nếu tìm kiếm từ “car”, Full-Text Search có thể không trả về kết quả liên quan đến “vehicle” hoặc “automobile.”
  • Không hỗ trợ tốt dữ liệu phi văn bản: Full-Text Search chỉ hoạt động tốt với dữ liệu dạng văn bản (CHAR, VARCHAR, TEXT) và không áp dụng được với dữ liệu dạng số, ngày tháng hoặc các kiểu dữ liệu phi văn bản khác.
  • Phụ thuộc vào RDBMS: Không phải tất cả các hệ quản trị cơ sở dữ liệu đều hỗ trợ Full-Text Search, và các tính năng này thường khác nhau giữa các RDBMS.

Tổng kết

SQL không chỉ dừng lại ở việc thao tác với cơ sở dữ liệu mà còn là công cụ mạnh mẽ giúp bạn giải quyết các bài toán kinh doanh phức tạp. Việc chuẩn bị cho các câu hỏi phỏng vấn không nên chỉ về kiến thức lý thuyết mà còn kinh nghiệm và khả năng ứng dụng linh hoạt vào các bài toán thực tế sẽ giúp bạn ghi điểm trong mắt nhà tuyển dụng hơn. 

ITviec hy vọng bài viết trên sẽ trở thành nguồn tài liệu hữu ích, giúp bạn tự tin hơn trong quá trình chuẩn bị và sẵn sàng chinh phục mọi thử thách trong buổi phỏng vấn SQL.