Các câu lệnh MySQL nâng cao

Các câu lệnh MySQL nâng cao: Hướng dẫn chi tiết từ A-Z

Khám phá các câu lệnh MySQL nâng cao như JOIN, CTE, Window Function, Transaction, Stored Procedure, Trigger, View, Index, EXPLAIN, JSON và nhiều kỹ thuật tối ưu truy vấn giúp xây dựng hệ thống nhanh, ổn định và dễ mở rộng, chi tiết như nào hãy cùng chúng tôi phân tích:

MySQL không chỉ dừng lại ở những câu lệnh SELECT, INSERT, UPDATE hay DELETE quen thuộc. Khi dữ liệu ngày càng lớn, hệ thống phát triển phức tạp và yêu cầu tối ưu hiệu suất cao hơn, lập trình viên cần khai thác các tính năng nâng cao của MySQL để xử lý dữ liệu nhanh, chính xác và dễ bảo trì hơn.

Bài viết này tập trung vào các câu lệnh MySQL nâng cao được sử dụng phổ biến trong thực tế. Nội dung được trình bày theo hướng từ nền tảng đến chuyên sâu, giúp bạn hiểu bản chất từng tính năng thay vì chỉ ghi nhớ cú pháp.

Các câu lệnh MySQL nâng cao

Vì sao nên học các tính năng nâng cao của MySQL?

Khi làm việc với những cơ sở dữ liệu nhỏ, các câu lệnh cơ bản thường đủ để giải quyết yêu cầu. Tuy nhiên, trong các hệ thống quản lý doanh nghiệp, website thương mại điện tử, ERP, CRM hoặc các ứng dụng có hàng triệu bản ghi, việc chỉ sử dụng truy vấn đơn giản sẽ khiến tốc độ xử lý giảm đáng kể.

Các câu lệnh nâng cao giúp:

  • Tăng hiệu suất truy vấn.
  • Giảm số lượng câu lệnh phải thực hiện.
  • Đảm bảo tính toàn vẹn dữ liệu.
  • Tối ưu khả năng mở rộng của hệ thống.
  • Dễ dàng xây dựng các báo cáo thống kê.
  • Đơn giản hóa logic xử lý ở phía ứng dụng.

Truy vấn nhiều bảng bằng JOIN

JOIN là kỹ thuật quan trọng nhất trong MySQL nâng cao. Thay vì lưu toàn bộ dữ liệu trong một bảng lớn, cơ sở dữ liệu thường được chuẩn hóa thành nhiều bảng khác nhau và liên kết thông qua khóa.

INNER JOIN

INNER JOIN chỉ trả về các bản ghi tồn tại ở cả hai bảng.

SELECT users.name, orders.total
FROM users
INNER JOIN orders
ON users.id = orders.user_id;

Kết quả chỉ chứa các khách hàng đã có đơn hàng.

LEFT JOIN

LEFT JOIN luôn lấy toàn bộ dữ liệu từ bảng bên trái, kể cả khi bảng bên phải không có dữ liệu tương ứng.

SELECT users.name, orders.total
FROM users
LEFT JOIN orders
ON users.id = orders.user_id;

Đây là kiểu JOIN thường dùng để thống kê khách hàng chưa từng mua hàng.

RIGHT JOIN

Ngược lại với LEFT JOIN, RIGHT JOIN ưu tiên toàn bộ dữ liệu của bảng bên phải.

SELECT users.name, orders.total
FROM users
RIGHT JOIN orders
ON users.id = orders.user_id;

CROSS JOIN

CROSS JOIN tạo ra tích Descartes giữa hai bảng. Mỗi dòng của bảng thứ nhất sẽ ghép với mọi dòng của bảng thứ hai.

SELECT *
FROM products
CROSS JOIN colors;

Loại JOIN này chỉ nên sử dụng khi thực sự cần vì số lượng bản ghi có thể tăng rất lớn.

Truy vấn lồng bằng Subquery

Subquery là câu lệnh SELECT nằm bên trong một câu lệnh SQL khác.

Subquery trong WHERE

SELECT *
FROM products
WHERE category_id IN
(
    SELECT id
    FROM categories
    WHERE active = 1
);

Truy vấn trên chỉ lấy sản phẩm thuộc các danh mục đang hoạt động.

Subquery trả về một giá trị

SELECT *
FROM employees
WHERE salary >
(
    SELECT AVG(salary)
    FROM employees
);

Kết quả là danh sách nhân viên có mức lương cao hơn mức trung bình.

Common Table Expression (CTE)

Từ MySQL 8 trở lên, CTE giúp viết truy vấn rõ ràng và dễ bảo trì hơn.

WITH HighSalary AS
(
    SELECT *
    FROM employees
    WHERE salary > 2000
)

SELECT *
FROM HighSalary;

So với Subquery, CTE giúp chia nhỏ truy vấn phức tạp thành nhiều bước logic.

CTE đệ quy

Recursive CTE thường dùng để xử lý dữ liệu dạng cây như danh mục sản phẩm hoặc sơ đồ tổ chức.

WITH RECURSIVE numbers AS
(
    SELECT 1 AS n

    UNION ALL

    SELECT n + 1
    FROM numbers
    WHERE n < 10
)

SELECT *
FROM numbers;

Ví dụ trên tạo ra dãy số từ 1 đến 10 mà không cần tạo bảng tạm.

Sử dụng GROUP BY nâng cao

GROUP BY giúp gom dữ liệu thành từng nhóm để tính toán.

SELECT category_id,
COUNT(*) AS total_products,
AVG(price) AS average_price,
MAX(price) AS highest_price,
MIN(price) AS lowest_price
FROM products
GROUP BY category_id;

Đây là nền tảng để xây dựng các báo cáo thống kê trong doanh nghiệp.

Kết hợp HAVING

HAVING dùng để lọc dữ liệu sau khi đã GROUP BY.

SELECT category_id,
COUNT(*) AS total
FROM products
GROUP BY category_id
HAVING COUNT(*) > 10;

Khác với WHERE, HAVING hoạt động trên kết quả đã được nhóm.

Window Function trong MySQL

Window Function là một trong những tính năng mạnh nhất của MySQL hiện đại. Thay vì gộp dữ liệu như GROUP BY, Window Function vẫn giữ nguyên từng dòng dữ liệu và bổ sung các giá trị tính toán.

ROW_NUMBER()

SELECT
ROW_NUMBER() OVER(ORDER BY salary DESC) AS ranking,
name,
salary
FROM employees;

Hàm này đánh số thứ tự theo điều kiện sắp xếp.

RANK()

SELECT
name,
salary,
RANK() OVER(ORDER BY salary DESC) AS ranking
FROM employees;

Nếu hai nhân viên có cùng lương thì cùng hạng và sẽ có khoảng trống ở thứ hạng tiếp theo.

DENSE_RANK()

DENSE_RANK hoạt động tương tự RANK nhưng không bỏ qua số thứ tự.

LAG()

SELECT
order_date,
revenue,
LAG(revenue) OVER(ORDER BY order_date) AS previous_day
FROM revenues;

Hàm này thường được sử dụng khi so sánh doanh thu giữa các ngày.

LEAD()

LEAD cho phép lấy giá trị của dòng kế tiếp mà không cần JOIN.

CASE WHEN trong truy vấn

CASE giúp tạo điều kiện trực tiếp trong SQL.

SELECT
name,
salary,
CASE
WHEN salary >= 3000 THEN 'Senior'
WHEN salary >= 1500 THEN 'Middle'
ELSE 'Junior'
END AS level
FROM employees;

Việc phân loại dữ liệu ngay trong truy vấn giúp giảm xử lý ở phía ứng dụng.

UNION và UNION ALL

UNION kết hợp kết quả từ nhiều truy vấn.

SELECT name
FROM customers

UNION

SELECT name
FROM suppliers;

UNION tự loại bỏ dữ liệu trùng lặp.

Nếu muốn giữ nguyên toàn bộ dữ liệu, hãy dùng UNION ALL.

SELECT name
FROM customers

UNION ALL

SELECT name
FROM suppliers;

Quản lý giao dịch bằng Transaction

Trong các hệ thống có nhiều thao tác cập nhật dữ liệu liên tiếp, Transaction đóng vai trò đảm bảo tất cả các thao tác hoặc được thực hiện thành công hoàn toàn, hoặc bị hủy hoàn toàn. Điều này giúp tránh tình trạng dữ liệu bị sai lệch nếu xảy ra lỗi giữa chừng.

Ví dụ phổ biến là chuyển tiền giữa hai tài khoản. Nếu chỉ trừ tiền ở tài khoản gửi nhưng chưa cộng vào tài khoản nhận thì dữ liệu sẽ không còn chính xác.

Bắt đầu một Transaction

START TRANSACTION;

Sau khi bắt đầu, mọi thay đổi sẽ chưa được lưu vĩnh viễn cho đến khi thực hiện COMMIT.

Lưu thay đổi bằng COMMIT

START TRANSACTION;

UPDATE accounts
SET balance = balance - 500
WHERE id = 1;

UPDATE accounts
SET balance = balance + 500
WHERE id = 2;

COMMIT;

Khi COMMIT được thực hiện, toàn bộ thay đổi sẽ được ghi xuống cơ sở dữ liệu.

Hủy Transaction bằng ROLLBACK

Nếu phát hiện lỗi trong quá trình xử lý, có thể đưa cơ sở dữ liệu trở về trạng thái trước khi Transaction bắt đầu.

START TRANSACTION;

UPDATE products
SET quantity = quantity - 5
WHERE id = 10;

ROLLBACK;

Sau khi ROLLBACK, mọi thay đổi trước đó trong Transaction sẽ bị hủy.

Sử dụng SAVEPOINT

SAVEPOINT cho phép tạo các điểm đánh dấu trong Transaction để có thể quay lại từng bước thay vì hủy toàn bộ.

START TRANSACTION;

UPDATE products
SET quantity = quantity - 5
WHERE id = 10;

SAVEPOINT step1;

UPDATE orders
SET total = total + 100
WHERE id = 5;

ROLLBACK TO step1;

COMMIT;

Kỹ thuật này hữu ích trong các quy trình xử lý nhiều bước.

Tạo View để đơn giản hóa truy vấn

View là bảng ảo được tạo từ một hoặc nhiều câu lệnh SELECT. Dữ liệu không được lưu riêng mà được lấy trực tiếp từ các bảng gốc.

Tạo View

CREATE VIEW active_users AS

SELECT
id,
name,
email
FROM users
WHERE status = 'active';

Sau khi tạo, có thể truy vấn giống như một bảng thông thường.

SELECT *
FROM active_users;

Cập nhật View

Một số View đơn giản cho phép UPDATE hoặc DELETE trực tiếp.

UPDATE active_users
SET name = 'Nguyen Van A'
WHERE id = 3;

Thực tế dữ liệu sẽ được cập nhật vào bảng gốc.

Xóa View

DROP VIEW active_users;

Stored Procedure giúp tái sử dụng nghiệp vụ

Stored Procedure là tập hợp nhiều câu lệnh SQL được lưu trong máy chủ MySQL. Thay vì gửi nhiều truy vấn từ ứng dụng, lập trình viên chỉ cần gọi Procedure.

Tạo Stored Procedure

DELIMITER //

CREATE PROCEDURE GetAllProducts()

BEGIN

SELECT *
FROM products;

END //

DELIMITER ;

Thực thi Procedure

CALL GetAllProducts();

Việc đóng gói nghiệp vụ giúp giảm lượng dữ liệu truyền qua mạng và tăng khả năng tái sử dụng.

Procedure có tham số

DELIMITER //

CREATE PROCEDURE GetProductByCategory
(
IN categoryId INT
)

BEGIN

SELECT *
FROM products
WHERE category_id = categoryId;

END //

DELIMITER ;

Gọi Procedure:

CALL GetProductByCategory(5);

Sử dụng Function trong MySQL

Khác với Procedure, Function luôn trả về một giá trị và có thể sử dụng trực tiếp trong câu lệnh SELECT.

Tạo Function

DELIMITER //

CREATE FUNCTION TotalPrice
(
price DECIMAL(10,2),
quantity INT
)

RETURNS DECIMAL(10,2)

BEGIN

RETURN price * quantity;

END //

DELIMITER ;

Gọi Function

SELECT
name,
TotalPrice(price, quantity)
FROM products;

Tự động hóa bằng Trigger

Trigger là đoạn mã SQL tự động chạy khi có thao tác INSERT, UPDATE hoặc DELETE trên bảng.

Trigger BEFORE INSERT

DELIMITER //

CREATE TRIGGER before_insert_product

BEFORE INSERT

ON products

FOR EACH ROW

BEGIN

SET NEW.created_at = NOW();

END //

DELIMITER ;

Khi thêm sản phẩm mới, trường created_at sẽ tự động nhận thời gian hiện tại.

Trigger AFTER UPDATE

DELIMITER //

CREATE TRIGGER log_salary

AFTER UPDATE

ON employees

FOR EACH ROW

BEGIN

INSERT INTO employee_logs
(
employee_id,
old_salary,
new_salary
)

VALUES
(
OLD.id,
OLD.salary,
NEW.salary
);

END //

DELIMITER ;

Trigger này giúp lưu lại lịch sử thay đổi lương.

Khóa dữ liệu bằng Lock

Trong môi trường nhiều người dùng cùng thao tác, Lock giúp tránh xung đột dữ liệu.

Khóa để đọc

SELECT *
FROM products
LOCK IN SHARE MODE;

Khóa để cập nhật

SELECT *
FROM products
WHERE id = 5
FOR UPDATE;

Các câu lệnh này thường được sử dụng bên trong Transaction.

Sử dụng EXISTS để tối ưu truy vấn

Trong nhiều trường hợp, EXISTS nhanh hơn IN khi dữ liệu lớn vì chỉ cần tìm thấy một bản ghi phù hợp là dừng.

SELECT *
FROM customers c

WHERE EXISTS
(
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);

Truy vấn trên lấy danh sách khách hàng đã từng phát sinh đơn hàng.

ANY và ALL trong điều kiện so sánh

ANY và ALL cho phép so sánh với tập kết quả từ Subquery.

ANY

SELECT *
FROM products

WHERE price > ANY
(
SELECT price
FROM products
WHERE category_id = 2
);

Điều kiện đúng nếu giá lớn hơn ít nhất một sản phẩm trong danh mục.

ALL

SELECT *
FROM products

WHERE price > ALL
(
SELECT price
FROM products
WHERE category_id = 2
);

Điều kiện chỉ đúng khi giá lớn hơn toàn bộ tập kết quả.

Sử dụng COALESCE để xử lý giá trị NULL

NULL thường gây ra nhiều vấn đề trong tính toán. COALESCE giúp trả về giá trị đầu tiên khác NULL.

SELECT
name,
COALESCE(phone, 'Chưa cập nhật')
FROM users;

Người dùng chưa có số điện thoại sẽ hiển thị dòng chữ "Chưa cập nhật".

IFNULL và NULLIF

IFNULL

SELECT
IFNULL(discount, 0)
FROM products;

Nếu discount bằng NULL thì trả về 0.

NULLIF

SELECT
NULLIF(quantity, 0)
FROM products;

Nếu quantity bằng 0 thì kết quả sẽ là NULL.

Tăng tốc truy vấn bằng Index

Index là một trong những kỹ thuật tối ưu quan trọng nhất trong MySQL. Có thể hình dung Index giống như mục lục của một cuốn sách. Thay vì phải đọc toàn bộ nội dung để tìm một thông tin, MySQL chỉ cần tra cứu trong Index để xác định vị trí dữ liệu.

Tuy nhiên, Index không phải càng nhiều càng tốt. Mỗi Index đều tiêu tốn thêm dung lượng lưu trữ và làm chậm các thao tác INSERT, UPDATE hoặc DELETE vì hệ thống phải cập nhật lại Index sau mỗi lần dữ liệu thay đổi.

Tạo Index

CREATE INDEX idx_email
ON users(email);

Index này giúp tăng tốc các truy vấn tìm kiếm theo trường email.

Xóa Index

DROP INDEX idx_email
ON users;

Khi nào nên tạo Index?

  • Các cột thường xuyên xuất hiện trong điều kiện WHERE.
  • Các cột dùng để JOIN giữa nhiều bảng.
  • Các trường ORDER BY hoặc GROUP BY được sử dụng thường xuyên.
  • Các cột có số lượng giá trị khác nhau lớn.

Không nên tạo Index cho những cột có rất ít giá trị khác nhau, ví dụ giới tính hoặc trạng thái chỉ có hai hoặc ba giá trị.

Composite Index (Index nhiều cột)

Thay vì tạo nhiều Index đơn lẻ, MySQL cho phép tạo Index trên nhiều cột cùng lúc.

CREATE INDEX idx_name_city
ON customers(name, city);

Index trên sẽ phát huy hiệu quả với các truy vấn:

SELECT *
FROM customers
WHERE name = 'An';

Hoặc:

SELECT *
FROM customers
WHERE name = 'An'
AND city = 'Ha Noi';

Trong khi đó, nếu chỉ tìm theo city thì Index này có thể không được sử dụng do nguyên tắc tiền tố bên trái (Leftmost Prefix).

Unique Index đảm bảo dữ liệu không bị trùng

Unique Index không chỉ tăng tốc truy vấn mà còn ngăn việc lưu các giá trị trùng lặp.

CREATE UNIQUE INDEX idx_username
ON users(username);

Nếu đã tồn tại username giống nhau, MySQL sẽ từ chối thao tác INSERT hoặc UPDATE.

Full Text Index phục vụ tìm kiếm văn bản

Khi cần tìm kiếm nội dung bài viết, tiêu đề hoặc mô tả dài, Full Text Index mang lại hiệu quả cao hơn nhiều so với toán tử LIKE.

Tạo Full Text Index

CREATE FULLTEXT INDEX idx_article
ON articles(title, content);

Truy vấn Full Text

SELECT *
FROM articles

WHERE MATCH(title, content)

AGAINST('mysql optimization');

Đây là kỹ thuật được nhiều hệ thống quản lý nội dung sử dụng để xây dựng chức năng tìm kiếm.

Phân tích kế hoạch thực thi bằng EXPLAIN

EXPLAIN giúp lập trình viên biết MySQL đang thực hiện truy vấn theo cách nào, có sử dụng Index hay không và ước lượng số lượng bản ghi cần đọc.

Sử dụng EXPLAIN

EXPLAIN

SELECT *

FROM products

WHERE category_id = 5;

Kết quả trả về nhiều thông tin như:

  • Loại truy vấn.
  • Index đang được sử dụng.
  • Số lượng bản ghi dự kiến phải đọc.
  • Phương thức truy cập dữ liệu.

Đây là công cụ gần như bắt buộc khi tối ưu hệ thống lớn.

EXPLAIN ANALYZE

Trong MySQL 8, EXPLAIN ANALYZE còn cung cấp thời gian thực thi thực tế.

EXPLAIN ANALYZE

SELECT *

FROM orders

WHERE customer_id = 20;

Thông tin này giúp xác định chính xác vị trí gây chậm trong câu lệnh SQL.

Phân vùng dữ liệu bằng Partition

Khi bảng chứa hàng chục triệu hoặc hàng trăm triệu bản ghi, việc chia dữ liệu thành nhiều Partition sẽ giúp tăng hiệu suất truy vấn.

Partition theo phạm vi

CREATE TABLE sales
(
id INT,
sale_date DATE
)

PARTITION BY RANGE(YEAR(sale_date))
(
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION pmax VALUES LESS THAN MAXVALUE
);

MySQL chỉ cần đọc đúng Partition liên quan thay vì quét toàn bộ bảng.

Làm việc với dữ liệu JSON

Từ MySQL 5.7 trở đi, kiểu dữ liệu JSON được hỗ trợ chính thức. Điều này rất hữu ích khi dữ liệu có cấu trúc linh hoạt.

Tạo bảng chứa JSON

CREATE TABLE customers
(
id INT PRIMARY KEY,
profile JSON
);

Đọc dữ liệu JSON

SELECT

JSON_EXTRACT(profile, '$.phone')

FROM customers;

Truy cập bằng toán tử

SELECT

profile -> '$.email'

FROM customers;

Kiểu dữ liệu JSON giúp giảm số lượng cột trong các trường hợp dữ liệu thay đổi thường xuyên.

Quản lý quan hệ bằng Foreign Key

Foreign Key giúp đảm bảo dữ liệu giữa các bảng luôn nhất quán.

Tạo khóa ngoại

CREATE TABLE orders
(
id INT PRIMARY KEY,

customer_id INT,

FOREIGN KEY(customer_id)

REFERENCES customers(id)
);

MySQL sẽ không cho phép tạo đơn hàng nếu khách hàng không tồn tại.

Xóa theo dây chuyền

FOREIGN KEY(customer_id)

REFERENCES customers(id)

ON DELETE CASCADE;

Khi khách hàng bị xóa, toàn bộ đơn hàng liên quan cũng sẽ bị xóa.

Thêm dữ liệu khi chưa tồn tại

Đây là tình huống rất phổ biến trong các hệ thống đồng bộ dữ liệu.

INSERT IGNORE

INSERT IGNORE INTO users
(
id,
name
)

VALUES
(
1,
'Nguyen Van A'
);

Nếu khóa chính đã tồn tại, MySQL sẽ bỏ qua thay vì phát sinh lỗi.

INSERT ... ON DUPLICATE KEY UPDATE

INSERT INTO users
(
id,
name
)

VALUES
(
1,
'Nguyen Van A'
)

ON DUPLICATE KEY UPDATE

name = VALUES(name);

Đây là kỹ thuật thường dùng để đồng bộ dữ liệu hoặc cập nhật thông tin nếu bản ghi đã tồn tại.

Thay thế dữ liệu bằng REPLACE

REPLACE sẽ xóa bản ghi cũ rồi thêm bản ghi mới nếu khóa chính hoặc Unique Key bị trùng.

REPLACE INTO products
(
id,
name,
price
)

VALUES
(
5,
'Laptop',
1500
);

Cần lưu ý rằng REPLACE thực chất thực hiện DELETE rồi INSERT nên có thể kích hoạt Trigger hoặc làm thay đổi khóa ngoại.

Sử dụng Event Scheduler để tự động hóa công việc

Event Scheduler cho phép MySQL thực hiện các tác vụ định kỳ mà không cần ứng dụng bên ngoài.

Tạo Event

CREATE EVENT delete_old_logs

ON SCHEDULE

EVERY 1 DAY

DO

DELETE

FROM logs

WHERE created_at <

NOW() - INTERVAL 30 DAY;

Ví dụ trên sẽ tự động xóa các bản ghi log quá 30 ngày mỗi ngày một lần.

Những sai lầm thường gặp khi sử dụng MySQL nâng cao

  • Tạo quá nhiều Index dẫn đến giảm tốc độ ghi dữ liệu.
  • Sử dụng SELECT * trong các bảng có nhiều cột.
  • Không kiểm tra EXPLAIN trước khi đưa truy vấn vào hệ thống thực tế.
  • Lạm dụng Subquery khi JOIN hoặc CTE sẽ phù hợp hơn.
  • Thiếu Transaction trong các nghiệp vụ cập nhật nhiều bảng.
  • Không xây dựng Foreign Key khiến dữ liệu mất tính nhất quán.
  • Sử dụng LIKE với ký tự % ở đầu chuỗi làm mất khả năng sử dụng Index.
  • Không phân trang dữ liệu khi truy vấn số lượng bản ghi lớn.

Kết luận

Việc thành thạo các câu lệnh MySQL nâng cao không chỉ giúp viết truy vấn ngắn gọn hơn mà còn nâng cao đáng kể hiệu suất và độ ổn định của hệ thống. Các kỹ thuật như JOIN, CTE, Window Function, Transaction, Stored Procedure, Trigger, View, Index, EXPLAIN, JSON, Partition hay Event Scheduler đều giải quyết những bài toán thực tế mà các câu lệnh SQL cơ bản không đáp ứng được.

Thay vì học thuộc cú pháp, bạn nên thực hành trên các bộ dữ liệu lớn và phân tích kế hoạch thực thi của từng truy vấn. Khi hiểu cách MySQL xử lý dữ liệu phía sau, bạn sẽ biết lựa chọn câu lệnh phù hợp cho từng tình huống, từ đó xây dựng những hệ thống có khả năng mở rộng tốt, tốc độ cao và dễ bảo trì trong thời gian dài.

  • 0 Bình luận
Trần Thị Vân | Content Creator Web Mới
Trần Thị Vân
Trần Thị Vân là Content Creator tại Web Mới, phụ trách nghiên cứu, biên soạn nội dung và chia sẻ kiến thức về website, SEO, lập trình cùng các xu hướng công nghệ
Chia sẻ nội dung đánh giá của bạn về Các câu lệnh MySQL nâng cao
Email, Điện thoại của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *
Đánh giá của bạn
Tên *
Email
Số điện thoại *
Bình luận, Hỏi đáp
Đăng ký tư vấn thiết kế website
Tìm hiểu 1 năm không bằng lắng nghe 1 câu tư vấn