# Declare, View, Function, Stored Procedures in Microsoft SQL Server ## Declare :::info :bulb: **Định nghĩa**: declare là khai báo 1 biến chứa giá trị đặc biệt. Mục đích để có thể dùng lại giá trị đó nhiều lần. Như ta thường sử dụng tên $$\pi$$ thay cho giá trị 3.14159265358979323846... của pi ::: cú pháp cơ bản ```sql Declare @<variable_name> <data_type> = <value> -- gán giá trị inline; Set @<variable_name> = <value> or (subquery) -- gán giá trị gián tiếp ``` > variable_name: bắt đầu bằng '@' > [data_type: click vào đây](https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver15) > gán giá trị inline dùng để gán cứng 1 giá trị đã biết trước. Trong trường hợp này phải gián giá trị có kiểu dữ liệu giống với khai báo hoặc convert được vd thử chạy các câu sau đây vd1 : khai báo đúng ```sql= declare @a varchar(10) = 'hello' declare @b varchar(10) = 3 declare @c date = '2021/12/21' select @a, @b, @c -- trong trường hợp hày giá trị số 3 của @b có thể chuyển sang dạng chuỗi. -- giá trị chuỗi của @c đúng format có thể convert sang ngày ``` vd2 : khai báo sai ```sql= declare @d date = '2021/21/12' declare @e int = '3' select @d, @e -- giá trị chuỗi của @d không đúng format để có thể convert sang ngày -- giá trị chuỗi '3 'của @e không thể chuyển sang dạng số. ``` > gán giá trị gián tiếp dùng để gán giá trị được lấy từ câu query Xem các ví dụ bên dưới ```sql DECLARE @max_rate MONEY SET @max_rate =( select MAX([Rate]) from [HumanResources].[EmployeePayHistory]) select @max_rate ``` khai báo và dùng lại trong câu truy vấn ```sql DECLARE @find VARCHAR(30) = 'Man%'; /* Also allowed: SET @find = 'Man%'; */ SELECT p.LastName, p.FirstName, ph.PhoneNumber FROM Person.Person AS p JOIN Person.PersonPhone AS ph ON p.BusinessEntityID = ph.BusinessEntityID WHERE LastName LIKE @find; ``` <i class="fa fa-book fa-fw"></i> ngoài chứa các giá trị độc lập Declare có thể chứa giá trị cả table. Nhưng có các kĩ thuật khác tiện hơn để làm chuyện đó như With hay @view ## VIEW :::info :bulb: **Định nghĩa**: là một bảng ảo trong cơ sở dữ liệu có nội dung được định nghĩa thông qua một câu lệnh SQL. > _ Thông thường dữ liệu sẽ được lưu trong các bảng chứa trong bộ nhớ của data. > _ View sẽ chỉ chứa câu lệnh SQL, khi nào ta dùng thì nó mới thực thi để lấy dữ liệu ra => ít tốn bộ nhớ nhưng hao performance > _ Số cột tối đa của view là 1,024 columns. ::: cú pháp ```sql= CREATE [ OR ALTER ] VIEW [ schema_name . ] view_name AS (SELECT... FROM... WHERE...) -- Không được dùng order by ``` ví dụ ta tạo 1 bảng lấy ra tên họ và ngày tuyển dụng của nhân viên ```sql= CREATE VIEW [HumanResources].hiredate_view -- view này thuộc [HumanResources] schema AS SELECT p.FirstName, p.LastName, e.BusinessEntityID, e.HireDate FROM HumanResources.Employee e JOIN Person.Person AS p ON e.BusinessEntityID = p.BusinessEntityID ; GO select * from [HumanResources].hiredate_view ``` View có thể được update với các điều kiện nhất định được đưa ra dưới đây: * Mệnh đề SELECT không được chứa từ khoá DISTINCT. * Mệnh đề SELECT không được chứa các hàm tổng. * Mệnh đề SELECT có thể không chứa các hàm tập hợp. * Mệnh đề SELECT không được chứa các toán tử tập hợp. * Mệnh đề SELECT không được chứa mệnh đề ORDER BY. * Mệnh đề FROM không được chứa nhiều bảng. * Mệnh đề WHERE không được chứa các truy vấn con. * Truy vấn không chứa GROUP BY hoặc HAVING. * Các cột được ứng lượng không thể được update. * Tất cả các cột NOT NULL từ bảng cơ sở phải được select trong view để truy vấn INSERT hoạt động. SỬA 1 dòng cho view ```sql= UPDATE [HumanResources].hiredate_view SET FirstName = 'A' WHERE BusinessEntityID =285; ``` Ở đây ta chưa thêm được 1 dòng cho view vì các cột NOT NULL của 2 bảng ta chưa thêm hết vào view. Để thêm được 1 cột cho view ta thử tạo câu view sau ```sql CREATE VIEW [Person].phone_person_777 AS SELECT [BusinessEntityID], [PhoneNumber],[PhoneNumberTypeID],[ModifiedDate] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' --lấy kết quả ra thử select * from [Person].phone_person_777 -- thêm 1 cột cho view INSERT INTO [Person].phone_person_777 VALUES(911, '999-999-9999', 1,GETDATE()) --hãy tự kiểm tra lại kết quả ``` # Function :::info :bulb: **Định nghĩa**: Function trong T-SQL có thể trả về 1 giá trị (Scalar-value). Hoặc một table. Có thể dùng lại nhiều lần thường được sử dụng cho các mục đích > Lưu lại câu query > Lưu lại quy trình xử lý phức tạp (xử lý ngày tháng tính năm nhuận, xử lý chuỗi chuẩn hóa viết hoa viết thường,...) ::: cú pháp cho Scalar Function ```sql= CREATE FUNCTION [schema_name.]function_name (parameter_list) RETURNS data_type AS BEGIN statements RETURN value END; ``` ví dụ ```sql CREATE | ALTER FUNCTION fnSum ( @So1 int, @So2 int ) RETURNS int AS begin declare @result int; set @result = @So1 + @So2 return @result end; ``` chạy function ```sql select [dbo].[fnSum](1,2) ``` Function trả về table: ```sql= CREATE | ALTER FUNCTION [ schema_name. ] function_name (parameter_list) RETURNS TABLE [ WITH <clr_function_option> [ ,...n ] ] [ ORDER ( <order_clause> ) ] [ AS ] EXTERNAL NAME <method_specifier> [ ; ] ``` ```sql= CREATE FUNCTION fnFindPhoneNumber ( @number varchar(10) ) RETURNS TABLE AS return SELECT [BusinessEntityID], [PhoneNumber],[PhoneNumberTypeID],[ModifiedDate] FROM [Person].[PersonPhone] where [PhoneNumber] like '%'+@number+'%' ``` ```sql= select * from [dbo].[fnFindPhoneNumber]('777') ``` # Stored Procedures :::info :bulb: **Định nghĩa**: Stored Procedures dùng để lưu trữ các câu query để có thể dùng lại nhiều lần. Cho phép truyền tham số vào để xử lý. Có thể thao tác các lệnh như Create, Update, Delete, Query, thậm chí là thực thi các câu Stored khác ::: Cú pháp ```sql CREATE PROCEDURE producer_name @parameter1 data_type; @parameter2 data_type;...-- các tham số truyển vào nếu có AS BEGIN CÂU LỆNH LOGIC HOẶC QUERY END ``` VÍ DỤ: Tạo store tính tổng 2 số ```sql= CREATE PROCEDURE spSum @So1 int, @So2 int as declare @Tong int; set @Tong = @So1 + @So2; return @Tong ``` chạy bằng cách ấn F5 Sau khi chạy thành công Store sẽ được lưu trong thư mục ![](https://i.imgur.com/eUsRokH.png) nếu không thấy thì ra chọn chuột phải Refresh lại mục Stored Procedures sau đó ta cùng thực thi store ```sql= declare @Ket_qua int; exec @Ket_qua = [dbo].[spSum] @So1 =2, @So2 =3 select @Ket_qua ``` ví dụ 2 tạo bảng bởi store ```sql= CREATE PROCEDURE [Person].[spPhonePerson777] AS BEGIN SELECT [BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate] into [Person].[PhonePerson777] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' END ``` Bấm F5 để tạo stored và sau khi tạo ta kiểm tra trong mục Procedure như trên Để thực thi store ta chạy lệnh ```sql= exec [Person].[spPhonePerson777] ``` Sau khi chạy lệnh ta Refresh lại table xem đã có table tên [Person].[PhonePerson777] đã được tạo chưa? ### vấn đề Trong ví dụ 2 ta chỉ thực thi Stored được 1 lần không thực thi lần 2 được. Vì bảng [Person].[PhonePerson777] đã được tạo nên khi chạy lần 2 bảng mới tạo sẽ bị trùng tên bảng cũ. => giải quyết trong store bằng cách xóa đi bảng cũ tạo thêm bảng mới ta sửa lại câu query tạo stored như sau ```sql= ALTER PROCEDURE [Person].[spPhonePerson777] AS BEGIN IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Person].[PhonePerson777]')) DROP TABLE [Person].[PhonePerson777] SELECT [BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate] into [Person].[PhonePerson777] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' END ``` sau đó ta chạy lại sẽ chạy được nhiều lần vì ta đã xóa bảng trước đó ```sql USE [AdventureWorks2017] GO /****** Object: StoredProcedure [Person].[spPhonePerson777] Script Date: 6/10/2021 2:12:19 AM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [Person].[spPhonePerson777] AS BEGIN SELECT [BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate] into [Person].[PhonePerson777_temp] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' ---------------------------------------------- if exists (select* from sys.objects where object_id = object_id(N'[Person].[PhonePerson777]')) begin print N'xuat hien table' drop table [Person].[PhonePerson777]; print N'xoa table roi nha' end ------------------------------------------- else begin RAISERROR ('no found this table',10,1) end ------------------------------------------ EXEC sp_rename '[Person].[PhonePerson777_temp]', 'PhonePerson777', 'object'; ALTER TABLE [Person].[PhonePerson777] ADD PRIMARY KEY ([BusinessEntityID]); END ``` ví dụ 3 viết store tìm ra Rate lương của một nhân viên dựa vào BusinessEntityID ```sql= create procedure spFindRatePayment @BusinessEntityID int, @Result int out as begin select @Result = max(Rate) from HumanResources.EmployeePayHistory E where E.BusinessEntityID = @BusinessEntityID end if @Result is null begin RAISERROR ('No record found',10,1) end else print @Result return ``` thực thi câu sau ```sql= declare @a int; declare @ID int = 1; exec spFindRatePayment @ID, @Result = @a out; ``` thay đổi giá trị @ID =10000 và xem kết quả. ### pipeline tạo stored #### pipline 1 ```sql= ALTER PROCEDURE [Person].[spPhonePerson777] AS BEGIN IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Person].[PhonePerson777_temp]')) DROP TABLE [Person].[PhonePerson777_temp] SELECT [BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate] into [Person].[PhonePerson777_temp] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Person].[PhonePerson777]')) DROP TABLE [Person].[PhonePerson777] EXEC sp_rename '[Person].[PhonePerson777_temp]', 'PhonePerson777', 'object'; END ``` #### pipline 2 ```sql= ALTER PROCEDURE [Person].[spPhonePerson777] AS BEGIN -- tạo bảng tạm IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Person].[PhonePerson777_temp]')) DROP TABLE [Person].[PhonePerson777_temp] SELECT [BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate] into [Person].[PhonePerson777_temp] FROM [Person].[PersonPhone] where [PhoneNumber] like '%777%' -- select từ bảng tạm k có trong bảng gốc insert into [Person].[PhonePerson777] select * from [Person].[PhonePerson777_temp] temp where temp.BusinessEntityID not in (select BusinessEntityID from [Person].[PhonePerson777]) -- xóa bảng tạm IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Person].[PhonePerson777_temp]')) DROP TABLE [Person].PhonePerson777_temp END ``` ## Trả giá trị về trong stored procedure _ Việc trả về giá trị trong STORED là optional, có cũng được không có cũng không sao _ Stored Procedures có 2 cách trả về giá trị dùng **OUT/OUTPUT** parameter hoặc dùng câu lệnh **RETURN**. Ta cùng tìm hiểu 2 cách này ### RETURN Với câu lệnh Return ta chỉ trả về được **1 kết quả kiểu INT**. Thường chỉ dùng để thông báo việc thực thi STORED PROCEDURE có thành công hay không Ta cùng kham khảo đoạn code sau ```sql= CREATE procedure [Person].sp_insert_PhonePerson777 @BusinessEntityID INT, @PhoneNumber INT, @PhoneNumberTypeID INT as begin TRAN declare @returnvalue int insert into [Person].[PhonePerson777] ([BusinessEntityID], [PhoneNumber], [PhoneNumberTypeID], [ModifiedDate]) values( @BusinessEntityID, @PhoneNumber, @PhoneNumberTypeID, GETDATE()) set @returnvalue = @@ERROR IF @returnvalue <> 0 BEGIN PRINT @returnvalue ROLLBACK TRAN END ELSE BEGIN COMMIT TRAN END return @returnvalue declare @x int exec @x = [Person].sp_insert_PhonePerson777 13,12345,123 select @x 'Return_value' ``` ### OUT/OUTPUT parameter Có thể trả về **nhiều kết quả** với **nhiều kiểu dữ liệu**. Dùng để ```sql= create procedure Out_test3 ( @OutValue1 int output ,@OutValue2 datetime output ,@outValue3 varchar(10) output) as begin set @OutValue1 = 10 set @OutValue2=GETDATE() set @outValue3='test' end ``` ```sql= declare @x int,@y datetime,@z varchar(10); exec Out_test3 @OutValue1=@x output,@OutValue2=@y output ,@outValue3=@z output select @x 'interger',@y 'datetime',@z 'varchar' ```