目录
3.建立一个视图,名为PersonBorrowView,SQL已给出:
4.如果往BorrowInfo加一条记录,我原本的SQL会增加一条记录,那么查询视图,数据会增加吗?
1.创建一个存储过程(一键归还非管理员用户2024年借的书),要求做以下几件事情:
3.假设一个存储过程分为4步,第2步发现报错,那么它会执行1、2步,还是都不执行呢?
2.以下SQL装载10万数据,每天被查询1000多万次,建议怎么设置索引?
一、视图
1.视图是什么,有什么作用?
①一个虚拟表
②简化查询
2.视图和存储过程有什么区别?
①视图:用于简化查询和数据展示
②存储过程:用于执行一系列SQL语句,包含增删改查等,即业务逻辑得处理。
3.建立一个视图,名为PersonBorrowView,SQL已给出:
sql
SELECT U.Name,BW.BoTime,BI.BookName FROM UserInfo U
INNER JOIN BorrowInfo BW ON U.UserID=BW.UserID
INNER JOIN BookInfo BI ON BW.BookID=BI.BookID
【解答】建立视图的SQL语句:
sql
CREATE VIEW PersonBorrowView AS
SELECT U.Name, BW.BoTime, BI.BookName
FROM UserInfo U
INNER JOIN BorrowInfo BW ON U.UserID = BW.UserID
INNER JOIN BookInfo BI ON BW.BookID = BI.BookID;
4.如果往BorrowInfo加一条记录,我原本的SQL会增加一条记录,那么查询视图,数据会增加吗?
会,因为查询视图,本质是查询视图保存的SQL
5.写出查询视图的SQL。
sql
SELECT * FROM PersonBorrowView;
6.写出根据视图名获取视图的SQL
sql
SELECT OBJECT_DEFINITION(OBJECT_ID('PersonBorrowView')) AS VIEWSQL;
7.小结:
sql
-- 建立视图:
CREATE VIEW 视图名 AS
SQL-- 写你的查询SQL
-- 查询视图:
SELECT * FROM 视图名
-- 根据视图名查询视图保存的SQL:
SELECT OBJECT_DEFINITION(OBJECT_ID('视图名')) AS VIEWSQL
二、存储过程
1.创建一个存储过程(一键归还非管理员用户2024年借的书),要求做以下几件事情:
sql
-- 筛选出需要一键归还的用户
SELECT UserID,Name FROM UserInfo WHERE Power !=1
-- 筛选出2024年未归还的书
SELECT BorID,UserID,BookID FROM BorrowInfo Where Year(BoTime)='2024' and BackTime is null and UserID in (xxxxx)
-- 将这些书的归还状态更新为今天
Update BorrowInfo set BackTime = GETDATE() Where BorID in (xxxxx)
-- 将一键归还涉及到的用户、书名、还款日期插入到PC_Table中
INSERT INTO PC_TABLE ([PCName],[PCBoTime],[PCBookName]) VALUES(xxxx)
【解答:存储过程的创建SQL】
定义存储过程的名称:ReturnBooksForNonAdmin2024
另外定义一些临时表去推进存储过程的流程
sql
CREATE PROCEDURE ReturnBooksForNonAdmin2024
AS
BEGIN
-- 1. 筛选出需要一键归还的用户
DECLARE @NonAdminUsers TABLE (UserID NVARCHAR(100), Name NVARCHAR(100));
INSERT INTO @NonAdminUsers (UserID, Name)
SELECT UserID, Name
FROM UserInfo
WHERE Power != 1;
-- 2. 筛选出2024年未归还的书
DECLARE @BooksToReturn TABLE (BorID NVARCHAR(100), UserID NVARCHAR(100),BookID NVARCHAR(100));
INSERT INTO @BooksToReturn (BorID, UserID,BookID)
SELECT BorID, UserID,BookID
FROM BorrowInfo
WHERE YEAR(BoTime) = 2024
AND BackTime IS NULL
AND UserID IN (SELECT UserID FROM @NonAdminUsers);
-- 3. 将这些书的归还状态更新为今天
UPDATE BorrowInfo
SET BackTime = GETDATE()
WHERE BorID IN (SELECT BorID FROM @BooksToReturn);
-- 4. 将一键归还涉及到的用户、书名、还款日期插入到PC_TABLE中
INSERT INTO PC_TABLE ([PCName], [PCBoTime], [PCBookName])
SELECT u.Name AS [PCName], GETDATE() AS [PCBoTime], bi.BookName AS [PCBookName]
FROM @BooksToReturn b
JOIN UserInfo u ON b.UserID = u.UserID
JOIN BookInfo bi ON b.BookID= bi.BookID;
END;
2.写出执行存储过程的SQL/方法
sql
EXECUTE ReturnBooksForNonAdmin2024;
【查询某个存储过程的SQL】
3.假设一个存储过程分为4步,第2步发现报错,那么它会执行1、2步,还是都不执行呢?
取决于SQL的事务:
- 如果步骤1和步骤2在同一个事务中执行,并且第2步失败,步骤1也不会生效。
- 如果每一步都在独立的事务中,那么步骤1和步骤2会分别执行,失败的步骤不会影响之前成功的步骤。
(本例的存储过程没有指定事务,因此会被当做是独立的事务,因此会执行第1、2步,而不执行后续的SQL)
4.谈谈临时表和公共表达式的区别
sql
-- 临时表
DECLARE @NonAdminUsers TABLE (UserID NVARCHAR(100), Name NVARCHAR(100));
INSERT INTO @NonAdminUsers (UserID, Name)
SELECT UserID, Name
FROM UserInfo
WHERE Power != 1;
SELECT * FROM @NonAdminUsers
-- 公共表达式CTE
WITH NonAdminUsers AS
(
SELECT UserID, Name
FROM UserInfo
WHERE Power != 1
)
SELECT * FROM NonAdminUsers
【区别和特点】
- 作用域: 临时表通常用于存储过程或复杂逻辑,公共表达式(CTE)更适合单次查询,特别是递归查询。
- 复杂性: 临时表处理复杂的增删改查操作时更具优势。
- 用途: 公共表达式适合在查询中引用和计算临时数据。
5.存储过程的出入参和异常处理
【需求】
传入一个USERID,如果查到下面的SQL则插入,查不到就不插入,且查到且成功插入返回1,查不到返回0,查到了插入失败返回-99
sql
SELECT U.Name,BW.BoTime,BI.BookName FROM UserInfo U
INNER JOIN BorrowInfo BW ON U.UserID=BW.UserID
INNER JOIN BookInfo BI ON BW.BookID=BI.BookID
WHERE U.UserID=''
【创建存储过程】
sql
CREATE PROCEDURE InsertUserData
@UserID NVARCHAR(100), -- 输入的用户ID
@Result INT OUTPUT -- 输出结果
AS
BEGIN
-- 初始化结果为0
SET @Result = 0;
-- 使用事务处理数据插入操作
BEGIN TRANSACTION;
BEGIN TRY
-- 检查是否有记录需要插入
IF EXISTS (
SELECT 1
FROM UserInfo U
INNER JOIN BorrowInfo BW ON U.UserID = BW.UserID
INNER JOIN BookInfo BI ON BW.BookID = BI.BookID
WHERE U.UserID = @UserID
)
BEGIN
-- 将所有相关数据插入目标表
INSERT INTO PC_TABLE ([PCName], [PCBoTime], [PCBookName])
SELECT U.Name, BW.BoTime, BI.BookName
FROM UserInfo U
INNER JOIN BorrowInfo BW ON U.UserID = BW.UserID
INNER JOIN BookInfo BI ON BW.BookID = BI.BookID
WHERE U.UserID = @UserID;
-- 如果插入成功,设置结果为1
SET @Result = 1;
END
ELSE
BEGIN
-- 如果没有记录符合条件,设置结果为0
SET @Result = 0;
END
-- 提交事务
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
-- 如果发生错误,回滚事务
ROLLBACK TRANSACTION;
-- 设置结果为-99
SET @Result = -99;
END CATCH
END
【执行存储过程】
sql
DECLARE @OutputResult INT;
EXEC InsertUserData @UserID = 'some_user_id', @Result = @OutputResult OUTPUT;
-- 查询输出结果
SELECT @OutputResult AS Result;
三、索引
1.索引是什么,创建索引有什么好处
①提高数据库查询效率的数据结构。
②可以让数据库系统快速查找到特定的数据行。
2.以下SQL装载10万数据,每天被查询1000多万次,建议怎么设置索引?
sql
SELECT * FROM [BookManage].[dbo].[BookInfo] WHERE Author = 'XXX'
如果 Author 是主要的查询条件列,则应对 Author 列创建索引。
3.写出创建索引的SQL(单个索引和复合索引各列举一条)
CREATE INDEX IX_Author ON BookManage.dbo.BookInfo (Author);
CREATE INDEX IX_BookName_Author ON BookManage.dbo.BookInfo (BookName, Author);
4.写出根据表名获取索引设置情况的方法
【方法一:SSMS查找】
【方法二:SQL查找】
sql
-- 获取指定表的索引信息
SELECT
t.name AS TableName,
i.name AS IndexName,
i.type_desc AS IndexType,
c.name AS ColumnName,
ic.index_column_id AS ColumnID,
ic.key_ordinal AS KeyOrdinal
FROM
sys.indexes AS i
INNER JOIN sys.index_columns AS ic
ON i.object_id = ic.object_id AND i.index_id = ic.index_id
INNER JOIN sys.columns AS c
ON ic.object_id = c.object_id AND ic.column_id = c.column_id
INNER JOIN sys.tables AS t
ON i.object_id = t.object_id
WHERE
t.name = 'BookInfo' -- 指定表名
ORDER BY
i.name, ic.index_column_id;
说明:
5.为什么主键一般不设置为索引?
因为创建主键时会自动创建一个聚集索引,以确保数据的唯一性和高效的查找。
四、触发器
1.触发器是什么,使用有什么好处
①定义:附加到表或视图上 的一个特殊类型的存储过程。
②好处:在某表(视图)发生增删改时自动化地执行定义好的SQL 。(可选是增删改某表前执行 ,还是增删改某表后执行)
2.触发器有多少种类型
①按状态分:
INSERT 触发器: 在插入操作之前或之后执行。
UPDATE 触发器: 在更新操作之前或之后执行。
DELETE 触发器: 在删除操作之前或之后执行。
INSTEAD OF 触发器: 替代执行触发器所附加的操作,这意味着触发器内的操作会取代原来的操作。
②按时间分:
BEFORE触发器:在数据操作语句(如INSERT、UPDATE、DELETE)执行之前触发。
AFTER触发器:在数据操作语句执行之后触发。
3.请你完成三个触发器SQL的编写:
- LogInfo表:ID,LogTime,Remarks
- UserInfo表作更新(Update)前,将信息插入到LogInfo表中(ID:NEWID())
- UserInfo表作新增(Insert)后,将信息插入到LogInfo表中(ID:NEWID())
- BorrowInfo表作删除(Delete)后,将信息插入到LogInfo表中(ID:NEWID())
sql
-- UserInfo表作更新(Update)前,将信息插入到LogInfo表中(ID:NEWID())
CREATE TRIGGER trg_UserInfo_Update_Before
ON dbo.UserInfo
AFTER UPDATE
AS
BEGIN
INSERT INTO dbo.LogInfo (ID, LogTime, Remarks)
SELECT
NEWID() AS ID,
GETDATE() AS LogTime,
'Update Operation: ID = ' + CAST(i.UserID AS VARCHAR(50)) + ', Updated By = ' + SYSTEM_USER AS Remarks
FROM
inserted i -- 这里的i指的是UserInfo表,下同
END;
-- UserInfo表作新增(Insert)后,将信息插入到LogInfo表中(ID:NEWID())
CREATE TRIGGER trg_UserInfo_Insert_After
ON dbo.UserInfo
AFTER INSERT
AS
BEGIN
INSERT INTO dbo.LogInfo (ID, LogTime, Remarks)
SELECT
NEWID() AS ID,
GETDATE() AS LogTime,
'Insert Operation: ID = ' + CAST(i.UserID AS VARCHAR(50)) + ', Inserted By = ' + SYSTEM_USER AS Remarks
FROM
inserted i
-- 这里也可以使用 WHERE 子句来筛选特定的记录
END;
-- BorrowInfo表作删除(Delete)后,将信息插入到LogInfo表中(ID:NEWID())
CREATE TRIGGER trg_BorrowInfo_Delete_After
ON dbo.BorrowInfo
AFTER DELETE
AS
BEGIN
INSERT INTO dbo.LogInfo (ID, LogTime, Remarks)
SELECT
NEWID() AS ID,
GETDATE() AS LogTime,
'Delete Operation: ID = ' + CAST(d.UserID AS VARCHAR(50)) + ', Deleted By = ' + SYSTEM_USER AS Remarks
FROM
deleted d
-- 这里也可以使用 WHERE 子句来筛选特定的记录
END;
4.查询触发器
【方法一:SSMS】
【方法二:SQL】
sql
SELECT
t.name AS TriggerName,
o.name AS TableName
FROM
sys.triggers t
INNER JOIN
sys.objects o
ON t.parent_id = o.object_id
WHERE
o.name = 'UserInfo'; -- 替换为你的表名
5.触发器可以接收外部入参吗,为什么?
①不可以
②防止注入风险;简化设计,确保其可维护性;
五、事务
1.事务是什么,运用事务有什么好处?
①定义:事务是一个或者多个SQL,一个事务要么全部成功,要么全部失败。(ACID属性)
②好处:事务执行过程中发生错误或故障,事务可以被回滚到其开始前的状态,防止数据损坏。
2.事务的重要操作有哪些?
BEGIN TRANSACTION:开始一个新的事务。
COMMIT:提交事务,保存事务中的所有操作,使它们对其他事务可见。提交操作将所有更改写入数据库。
ROLLBACK:回滚事务,撤销事务中的所有操作,将数据库恢复到事务开始前的状态。
3.演示一个事务SQL
sql
-- 开始事务
BEGIN TRANSACTION;
-- 执行多个数据库操作
INSERT INTO [dbo].[LogInfo]
([ID]
,[LogTime]
,[Remarks])
VALUES
(NEWID()
,GETDATE()
,'执行了插入操作')
-- 提交事务
--COMMIT;
-- 回滚事务
--ROLLBACK;
SELECT * FROM LogInfo
问题:
①如果开始事务后,执行SQL,然后既不执行提交事务,又不执行回滚事务,会怎么样?
对数据的修改都会在事务结束之前保持在事务的上下文中。
事务通常会在数据库连接关闭时自动回滚,或者在数据库服务器重启时也会回滚未提交的事务。
②开始事务后,执行SQL,还没提交,为何就可以查出数据了?
SQLsever默认自动提交事务,且隔离级别默认设置是"读已提交"隔离级别,事务内的查询会看到其他事务已提交的更改。
4.事务隔离性有哪些,特点是啥?
- 读未提交(Read Uncommitted):可读到:开启事务+SQL增删改(未提交)的数据
- 读已提交(Read Committed):默认,可读到:开启事务+SQL增删改后提交的数据(不开启事务默认执行SQL自动提交)
- 可重复读(Repeatable Read):开启事务1读A表,此时A表插入新数据,再读A表,读不到新数据,直到事务1提交后读A表才看得到。
- 串行化(Serializable):开启事务1读A表,此时A表插入新数据,发现插入失败,直到事务1提交后,才可以插入新数据(事务隔离,需排队)。