Views trong SQL Server
1. View là gì?
View là một "virtual table" được định nghĩa bởi một SELECT query. View không lưu trữ data mà lưu trữ query definition.
Tại sao sử dụng Views?
Benefits of Views
View Benefits
Simplification
Hide complex JOINs
Reusable queries
Security
Hide sensitive columns
Row-level filtering
Abstraction
Hide table structure
Backward compatibility
Calculations
Pre-computed columns
Aggregations
| Benefit | Description |
|---|---|
| Simplification | Ẩn các JOINs phức tạp |
| Security | Giới hạn columns/rows users có thể thấy |
| Abstraction | Thay đổi base tables mà không ảnh hưởng app |
| Reusability | Tái sử dụng queries |
2. CREATE VIEW - Basic Syntax
2.1 Simple View
SQL
1-- Basic view syntax2CREATE VIEW ViewName3AS4SELECT columns5FROM tables6WHERE conditions;7GO89-- Example: View khách hàng active10CREATE VIEW Customer.vw_ActiveCustomers11AS12SELECT 13 CustomerID,14 FirstName,15 LastName,16 Email,17 Phone18FROM Customer.Customer19WHERE IsActive = 1;20GO2122-- Use the view23SELECT * FROM Customer.vw_ActiveCustomers;2.2 View với JOINs
SQL
1-- View ẩn complex JOINs2CREATE VIEW Sales.vw_OrderSummary3AS4SELECT 5 o.OrderID,6 o.OrderNumber,7 o.OrderDate,8 c.FirstName + ' ' + c.LastName AS CustomerName,9 c.Email AS CustomerEmail,10 o.Status,11 o.TotalAmount,12 COUNT(od.OrderDetailID) AS ItemCount13FROM Sales.[Order] o14JOIN Customer.Customer c ON o.CustomerID = c.CustomerID15LEFT JOIN Sales.OrderDetail od ON o.OrderID = od.OrderID16GROUP BY 17 o.OrderID, o.OrderNumber, o.OrderDate,18 c.FirstName, c.LastName, c.Email,19 o.Status, o.TotalAmount;20GO2122-- Simple query từ view23SELECT * 24FROM Sales.vw_OrderSummary25WHERE Status = 'Delivered'26ORDER BY OrderDate DESC;2.3 AdventureWorks Views
SQL
1-- AdventureWorks có nhiều Views sẵn2-- Xem danh sách Views3SELECT 4 SCHEMA_NAME(schema_id) AS SchemaName,5 name AS ViewName6FROM sys.views7ORDER BY SchemaName, ViewName;89-- Ví dụ: Sales.vSalesPerson10SELECT * FROM Sales.vSalesPerson;1112-- Ví dụ: HumanResources.vEmployee13SELECT 14 FirstName,15 LastName,16 JobTitle,17 Department,18 HireDate19FROM HumanResources.vEmployee20WHERE Department = 'Sales';3. View Options
3.1 WITH SCHEMABINDING
SQL
1-- SCHEMABINDING ngăn base tables bị modify/drop2CREATE VIEW Sales.vw_ProductSales3WITH SCHEMABINDING -- Lock base tables4AS5SELECT 6 p.ProductID,7 p.ProductName,8 SUM(od.Quantity) AS TotalQuantitySold,9 SUM(od.LineTotal) AS TotalRevenue10FROM Inventory.Product p11JOIN Sales.OrderDetail od ON p.ProductID = od.ProductID12GROUP BY p.ProductID, p.ProductName;13GO1415-- Giờ không thể:16-- DROP TABLE Inventory.Product -- Error!17-- ALTER TABLE Inventory.Product DROP COLUMN ProductName -- Error!⚠️ Lưu ý: SCHEMABINDING yêu cầu phải dùng two-part names (Schema.Table)
3.2 WITH ENCRYPTION
SQL
1-- Ẩn view definition2CREATE VIEW Sales.vw_SensitiveData3WITH ENCRYPTION4AS5SELECT 6 CustomerID,7 Email,8 Phone9FROM Customer.Customer;10GO1112-- Không thể xem definition13SELECT OBJECT_DEFINITION(OBJECT_ID('Sales.vw_SensitiveData'));14-- Returns NULL3.3 WITH CHECK OPTION
SQL
1-- Ngăn INSERT/UPDATE vi phạm WHERE clause2CREATE VIEW Customer.vw_ActiveCustomers_Protected3AS4SELECT 5 CustomerID,6 FirstName,7 LastName,8 Email,9 IsActive10FROM Customer.Customer11WHERE IsActive = 112WITH CHECK OPTION;13GO1415-- Không thể insert inactive customer qua view này16INSERT INTO Customer.vw_ActiveCustomers_Protected 17 (FirstName, LastName, Email, IsActive)18VALUES ('Test', 'User', 'test@email.com', 0); -- Error!4. Indexed Views (Materialized Views)
4.1 Tạo Indexed View
SQL
1-- Indexed View lưu trữ data (materialized)2-- Yêu cầu: SCHEMABINDING, deterministic functions34CREATE VIEW Sales.vw_ProductSalesIndexed5WITH SCHEMABINDING6AS7SELECT 8 p.ProductID,9 p.ProductName,10 COUNT_BIG(*) AS SaleCount,11 SUM(od.Quantity) AS TotalQuantity,12 SUM(od.LineTotal) AS TotalRevenue13FROM Inventory.Product p14JOIN Sales.OrderDetail od ON p.ProductID = od.ProductID15GROUP BY p.ProductID, p.ProductName;16GO1718-- Tạo unique clustered index19CREATE UNIQUE CLUSTERED INDEX IX_vw_ProductSalesIndexed20ON Sales.vw_ProductSalesIndexed(ProductID);21GO4.2 Khi nào dùng Indexed Views?
| Use Indexed View | Don't Use |
|---|---|
| Complex aggregations | Frequently updated tables |
| Infrequently updated data | Simple queries |
| Reporting queries | OLTP workloads |
| Heavy read workloads | Small result sets |
SQL
1-- Indexed View tự động được dùng với Enterprise Edition2-- Standard Edition cần hint3SELECT * FROM Sales.vw_ProductSalesIndexed WITH (NOEXPAND);5. Updatable Views
5.1 Rules for Updatable Views
View có thể UPDATE/INSERT/DELETE nếu:
- Không có aggregate functions (SUM, COUNT, AVG...)
- Không có GROUP BY, HAVING
- Không có DISTINCT
- Không có TOP với ORDER BY
- Không có UNION
- Modify chỉ 1 base table
SQL
1-- Updatable View2CREATE VIEW Customer.vw_CustomerBasic3AS4SELECT 5 CustomerID,6 FirstName,7 LastName,8 Email,9 Phone,10 IsActive11FROM Customer.Customer;12GO1314-- Có thể UPDATE qua View15UPDATE Customer.vw_CustomerBasic16SET Phone = '0123456789'17WHERE CustomerID = 1;1819-- Có thể INSERT qua View20INSERT INTO Customer.vw_CustomerBasic (FirstName, LastName, Email)21VALUES ('New', 'Customer', 'new@email.com');5.2 INSTEAD OF Triggers
SQL
1-- View với JOINs không updatable by default2-- Dùng INSTEAD OF trigger để enable34CREATE VIEW Sales.vw_OrderWithCustomer5AS6SELECT 7 o.OrderID,8 o.OrderNumber,9 o.OrderDate,10 o.CustomerID,11 c.FirstName AS CustomerFirstName,12 c.LastName AS CustomerLastName13FROM Sales.[Order] o14JOIN Customer.Customer c ON o.CustomerID = c.CustomerID;15GO1617-- INSTEAD OF INSERT trigger18CREATE TRIGGER trg_vw_OrderWithCustomer_Insert19ON Sales.vw_OrderWithCustomer20INSTEAD OF INSERT21AS22BEGIN23 INSERT INTO Sales.[Order] (OrderNumber, OrderDate, CustomerID)24 SELECT OrderNumber, OrderDate, CustomerID25 FROM inserted;26END;27GO6. ALTER & DROP View
6.1 ALTER VIEW
SQL
1-- Modify existing view2ALTER VIEW Customer.vw_ActiveCustomers3AS4SELECT 5 CustomerID,6 FirstName,7 LastName,8 Email,9 Phone,10 CreatedDate -- Added column11FROM Customer.Customer12WHERE IsActive = 1;13GO1415-- Best practice: CREATE OR ALTER (SQL Server 2016+)16CREATE OR ALTER VIEW Customer.vw_ActiveCustomers17AS18SELECT 19 CustomerID,20 FirstName,21 LastName,22 Email,23 Phone,24 CreatedDate25FROM Customer.Customer26WHERE IsActive = 1;27GO6.2 DROP VIEW
SQL
1-- Drop single view2DROP VIEW Customer.vw_ActiveCustomers;34-- Drop if exists5DROP VIEW IF EXISTS Customer.vw_ActiveCustomers;67-- Drop multiple views8DROP VIEW 9 Customer.vw_View1,10 Customer.vw_View2;7. View Best Practices
7.1 Naming Conventions
SQL
1-- Prefix với vw_ hoặc V_2vw_ActiveCustomers3vw_OrderSummary4vw_ProductSales56-- Hoặc không prefix nhưng dùng schema7Reporting.CustomerSummary8Reporting.SalesAnalysis7.2 Performance Considerations
SQL
1-- ❌ KHÔNG nên: Nested views (view gọi view)2CREATE VIEW vw_Level1 AS SELECT * FROM Table1;3CREATE VIEW vw_Level2 AS SELECT * FROM vw_Level1;4CREATE VIEW vw_Level3 AS SELECT * FROM vw_Level2;5-- Query optimizer khó optimize67-- ✅ NÊN: Flat views8CREATE VIEW vw_Final AS 9SELECT * FROM Table110JOIN Table2...1112-- ❌ KHÔNG nên: SELECT *13CREATE VIEW vw_Bad AS SELECT * FROM BigTable;1415-- ✅ NÊN: Explicit columns16CREATE VIEW vw_Good AS 17SELECT Col1, Col2, Col3 FROM BigTable;7.3 Security Views
SQL
1-- Row-level security2CREATE VIEW HR.vw_MyDepartmentEmployees3AS4SELECT 5 EmployeeID,6 FirstName,7 LastName,8 Salary9FROM HR.Employee10WHERE DepartmentID = (11 SELECT DepartmentID 12 FROM HR.Employee 13 WHERE Email = SUSER_SNAME()14);15GO1617-- Column-level security (hide sensitive data)18CREATE VIEW Customer.vw_CustomerPublic19AS20SELECT 21 CustomerID,22 FirstName,23 LastName,24 -- Email masked25 LEFT(Email, 3) + '***@***' AS MaskedEmail,26 -- Phone hidden27 'XXX-XXX-' + RIGHT(Phone, 4) AS MaskedPhone28FROM Customer.Customer;29GO8. AdventureWorks Views Analysis
8.1 Study Existing Views
SQL
1-- Xem definition của View2SELECT OBJECT_DEFINITION(OBJECT_ID('Sales.vSalesPerson')) AS ViewDefinition;34-- Hoặc dùng sp_helptext5EXEC sp_helptext 'Sales.vSalesPerson';67-- Xem View với dependencies8SELECT 9 v.name AS ViewName,10 OBJECT_NAME(d.referenced_id) AS DependsOn,11 d.referenced_entity_name12FROM sys.views v13JOIN sys.sql_expression_dependencies d ON v.object_id = d.referencing_id14WHERE SCHEMA_NAME(v.schema_id) = 'Sales';8.2 Common AdventureWorks Views
SQL
1-- HumanResources.vEmployee - Employee info với Person data2SELECT TOP 5 * FROM HumanResources.vEmployee;34-- Sales.vSalesPerson - Sales person với quota và YTD sales5SELECT TOP 5 * FROM Sales.vSalesPerson;67-- Production.vProductAndDescription - Product với description8SELECT TOP 5 * FROM Production.vProductAndDescription;910-- Sales.vIndividualCustomer - Customer info11SELECT TOP 5 * FROM Sales.vIndividualCustomer;9. Hands-on Exercise
Exercise: Create Views for Reporting
SQL
1-- 1. View: Top Products by Revenue2CREATE OR ALTER VIEW Reporting.vw_TopProductsByRevenue3AS4SELECT TOP 1005 p.ProductID,6 p.Name AS ProductName,7 pc.Name AS Category,8 SUM(sod.OrderQty) AS TotalQuantitySold,9 SUM(sod.LineTotal) AS TotalRevenue,10 AVG(sod.UnitPrice) AS AvgSellingPrice11FROM Production.Product p12JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID13JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID14JOIN Sales.SalesOrderDetail sod ON p.ProductID = sod.ProductID15GROUP BY p.ProductID, p.Name, pc.Name16ORDER BY TotalRevenue DESC;17GO1819-- 2. View: Monthly Sales Summary20CREATE OR ALTER VIEW Reporting.vw_MonthlySalesSummary21AS22SELECT 23 YEAR(soh.OrderDate) AS SalesYear,24 MONTH(soh.OrderDate) AS SalesMonth,25 COUNT(DISTINCT soh.SalesOrderID) AS OrderCount,26 COUNT(DISTINCT soh.CustomerID) AS CustomerCount,27 SUM(soh.SubTotal) AS TotalSubTotal,28 SUM(soh.TaxAmt) AS TotalTax,29 SUM(soh.Freight) AS TotalFreight,30 SUM(soh.TotalDue) AS TotalRevenue31FROM Sales.SalesOrderHeader soh32GROUP BY YEAR(soh.OrderDate), MONTH(soh.OrderDate);33GO3435-- 3. View: Customer Purchase History36CREATE OR ALTER VIEW Customer.vw_CustomerPurchaseHistory37AS38SELECT 39 c.CustomerID,40 p.FirstName + ' ' + p.LastName AS CustomerName,41 COALESCE(p.EmailPromotion, 0) AS EmailPromotion,42 COUNT(DISTINCT soh.SalesOrderID) AS TotalOrders,43 MIN(soh.OrderDate) AS FirstOrderDate,44 MAX(soh.OrderDate) AS LastOrderDate,45 SUM(soh.TotalDue) AS LifetimeValue,46 AVG(soh.TotalDue) AS AvgOrderValue47FROM Sales.Customer c48LEFT JOIN Person.Person p ON c.PersonID = p.BusinessEntityID49LEFT JOIN Sales.SalesOrderHeader soh ON c.CustomerID = soh.CustomerID50GROUP BY c.CustomerID, p.FirstName, p.LastName, p.EmailPromotion;51GO5253-- Test views54SELECT * FROM Reporting.vw_TopProductsByRevenue;55SELECT * FROM Reporting.vw_MonthlySalesSummary ORDER BY SalesYear DESC, SalesMonth DESC;56SELECT TOP 10 * FROM Customer.vw_CustomerPurchaseHistory ORDER BY LifetimeValue DESC;10. Summary
markdown
1## View Checklist2 3### When to Create Views4- ✅ Complex JOINs used repeatedly5- ✅ Security requirements (hide columns/rows)6- ✅ Reporting and analytics7- ✅ Data abstraction layer8- ✅ Backward compatibility9 10### Best Practices11- ✅ Use SCHEMABINDING for indexed views12- ✅ Avoid nested views13- ✅ Select specific columns, not SELECT *14- ✅ Use WITH CHECK OPTION for updatable views15- ✅ Document view purpose16 17### Performance Tips18- Use indexed views for heavy aggregations19- Avoid views on views20- Check execution plans21- Consider materialized tables for complex reportsTiếp theo
Bài tiếp theo: Stored Procedures - học cách tạo và sử dụng Stored Procedures với T-SQL!
