2 sql-server-2008 database-design sql-server sql-server-2012 sql-server-2016
我们有一个遗留应用程序。出于某种原因,有人决定为每个客户创建一个数据库。所以我们有 500 多个具有相同架构的数据库。
我想转换成一个多客户端数据库。原始客户数据库没有 CustomerId。新的单一数据库确实如此。
我们正在考虑用视图替换旧的数据库,因此旧的应用程序插入/更新仍然可以工作。
老客户数据库:
CREATE TABLE [CustomerOne].[dbo].[CustomerTransaction]
(
[CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
[QuantityBought] [int] NULL,
)
Run Code Online (Sandbox Code Playgroud)
新报告数据库具有 CustomerId:
CREATE TABLE ReportingDB.[dbo].[CustomerTransaction]
(
[CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
[Customerid] [int] NOT NULL,
[QuantityBought] [int] NULL,
)
Run Code Online (Sandbox Code Playgroud)
用视图替换旧数据库
create view [CustomerOne].[dbo].[CustomerTransaction]
as
select
[CustomerTransactionid]
,1 as [CustomerId]
,[quantitybought]
from ReportingDB.[dbo].[CustomerTransaction]
where Customerid = 1
with check option
Run Code Online (Sandbox Code Playgroud)
这失败:
-- Attempt inserting into View
insert into CustomerOne.dbo.Customertransaction
(Quantitybought)
values (4)
Msg 515, Level 16, State 2, Line 1
Cannot insert the value NULL into column 'customerid', table 'ReportingDB.dbo.customertransaction'; column does not allow nulls. INSERT fails.
The statement has been terminated.
Run Code Online (Sandbox Code Playgroud)
如何在没有触发器的情况下使最终语句成功?否则我们将不得不使用某种触发器来让它工作。也许没有办法轻松获得统一的数据库。
我们试图避免更改旧的应用程序代码(插入和更新语句)。
这可以使用SESSION_CONTEXT(SQL Server 2016 中的新功能)来实现。在以前的版本中CONTEXT_INFO可以使用,但它不太健壮(不能是只读的,每个会话只有一个插槽)。
首先,DEFAULT在基础表的CustomerId列上设置 a :
CREATE TABLE ReportingDB.[dbo].[CustomerTransaction]
(
[CustomerTransactionid] [int] identity(1,1) primary key NOT NULL,
[Customerid] [int] NOT NULL
DEFAULT TRY_CONVERT(integer, SESSION_CONTEXT(N'CustomerID')),
[QuantityBought] [int] NULL,
);
Run Code Online (Sandbox Code Playgroud)
然后定义视图如下:
CREATE OR ALTER VIEW dbo.CustomerOneTransaction
AS
SELECT
CT.CustomerTransactionid,
CT.Customerid,
CT.QuantityBought
FROM ReportingDB.dbo.CustomerTransaction AS CT
WHERE
CT.Customerid = 1
AND TRY_CONVERT(integer, SESSION_CONTEXT(N'CustomerID')) = 1
WITH CHECK OPTION;
Run Code Online (Sandbox Code Playgroud)
每个会议将需要设置客户ID键一次使用sys.sp_set_session_context:
EXECUTE sys.sp_set_session_context
@key = N'CustomerID',
@value = 1;
Run Code Online (Sandbox Code Playgroud)
如果密钥在连接的生命周期内不可变(通常为 true),请使用以下@read_only选项:
EXECUTE sys.sp_set_session_context
@key = N'CustomerID',
@value = 1,
@read_only = 1;
Run Code Online (Sandbox Code Playgroud)
这种安排允许您的插入查询工作,并确保CustomerID在使用视图插入或更新时始终匹配会话键,否则CHECK_OPTION返回错误。对于删除,非法操作将被忽略而不是引发错误。
这个答案解决了提出的问题,但实际上,出于安全性、可管理性和可用性的原因,我几乎总是更喜欢每个客户的数据库。
演示: db<>小提琴
您还可以将此模式与行级安全性结合使用,以在没有视图的情况下强制执行客户隔离。每个客户的数据库是软件即服务公司(不是银行和金融公司 - 客户数据库隔离的好处对许多其他行业没有真正意义)最常用的模式。