3

有没有一种方法可以省略存储过程中使用的对象的模式,这些对象不绑定到存储过程的模式,而是以相同的存储过程为不同的用户产生不同结果的方式使用的记录模式模式?

为了更好地理解我正在尝试做的事情,下面我将尝试更好地解释。

假设以下数据库:

CREATE TABLE [dbo].[SampleTable01] (...)

CREATE TABLE [dbo].[SampleTable02] (...)

CREATE TABLE [dbo].[SampleTable03] (...)

CREATE TABLE [dbo].[SampleTable04] (...)

在这个数据库中,表名后面的数字代表表的所有者(它是一个遗留系统,我无法更改)。

为了将其集成到 .net 实体框架中,我提供了在不同模式中创建同义词的解决方案,因此更改连接字符串我可以更改使用的对象,而无需更改我的数据库上下文或我的编程逻辑。

像这样。

CREATE SCHEMA [s01]
CREATE SYNONYM [s01].[SampleTable] FOR [dbo].[SampleTable01]

...

CREATE SCHEMA [s04]
CREATE SYNONYM [s04].[SampleTable] FOR [dbo].[SampleTable04]

此解决方案运行良好,但我需要复制所有使用的存储过程,因为存储过程绑定到特定对象。

当我创建以下存储过程时:

CREATE PROCEDURE [dbo].[usp_SampleProc] AS
SELECT * FROM [SampleTable]

存储过程将产生错误,因为[SampleTable]schema 中不存在[dbo]

我正在做的是复制存储过程以匹配登录用户的架构。

所以我这样做:

CREATE PROCEDURE [s01].[usp_SampleProc] AS
SELECT * FROM [s01].[SampleTable]

...

CREATE PROCEDURE [s04].[usp_SampleProc] AS
SELECT * FROM [s04].[SampleTable]

在不指定架构的情况下执行时,架构中的用户[s01]将从[s01].[SampleTable]中获取值,架构中的用户将从中[s04]获取值,这是我的预期结果。[s04].[SampleTable][usp_SampleProc]

到目前为止一切顺利,这在我的真实场景中并不高效。我有数千个表、数百个程序和十几个模式(我知道这很难看,但我将遗留系统与 .net 集成,到目前为止,这是我来的最佳解决方案)。

那么,问题又来了:

有没有一种方法可以省略存储过程中使用的对象的模式,这些对象不绑定到存储过程的模式,而是登录用户的模式,相同的存储过程产生不同的结果不同模式的用户?

4

3 回答 3

3

这是我知道做我想做的事情的两种方法。

这两种方式对开发人员来说都是透明的,因此他们不需要了解解决方案的复杂性。

下面我创建了一个每个人都可以使用的示例:

原始遗留数据库创建:保持不变,因为遗留应用程序仍在使用数据库。

CREATE TABLE [dbo].[SampleTable01] (
    value varchar(100)
)
INSERT INTO [dbo].[SampleTable01] VALUES ('[dbo].[SampleTable01]')

CREATE TABLE [dbo].[SampleTable02] (
    value varchar(100)
)
INSERT INTO [dbo].[SampleTable02] VALUES ('[dbo].[SampleTable02]')

CREATE TABLE [dbo].[SampleTable03] (
    value varchar(100)
)
INSERT INTO [dbo].[SampleTable03] VALUES ('[dbo].[SampleTable03]')

CREATE TABLE [dbo].[SampleTable04] (
    value varchar(100)
)
INSERT INTO [dbo].[SampleTable04] VALUES ('[dbo].[SampleTable04]')
GO

我的应用程序使用的用户和模式分离:这些是很多重复的代码,但将由应用程序设置生成。

CREATE SCHEMA [S01]
GO

CREATE SCHEMA [S02]
GO

CREATE SCHEMA [S03]
GO

CREATE SCHEMA [S04]
GO

CREATE USER USER_S01 WITHOUT LOGIN WITH DEFAULT_SCHEMA = S01
GO

CREATE USER USER_S02 WITHOUT LOGIN WITH DEFAULT_SCHEMA = S02
GO

CREATE USER USER_S03 WITHOUT LOGIN WITH DEFAULT_SCHEMA = S03
GO

CREATE USER USER_S04 WITHOUT LOGIN WITH DEFAULT_SCHEMA = S04
GO

CREATE SYNONYM [S01].[SampleTable] FOR [dbo].[SampleTable01]
CREATE SYNONYM [S02].[SampleTable] FOR [dbo].[SampleTable02]
CREATE SYNONYM [S03].[SampleTable] FOR [dbo].[SampleTable03]
CREATE SYNONYM [S04].[SampleTable] FOR [dbo].[SampleTable04]
GO

GRANT DELETE     ON SCHEMA::[S01] TO [USER_S01]
GRANT EXECUTE    ON SCHEMA::[S01] TO [USER_S01]
GRANT INSERT     ON SCHEMA::[S01] TO [USER_S01]
GRANT REFERENCES ON SCHEMA::[S01] TO [USER_S01]
GRANT SELECT     ON SCHEMA::[S01] TO [USER_S01]
GRANT UPDATE     ON SCHEMA::[S01] TO [USER_S01]
GRANT EXECUTE    ON SCHEMA::[S01] TO [USER_S01]
GO

GRANT DELETE     ON SCHEMA::[S02] TO [USER_S02]
GRANT EXECUTE    ON SCHEMA::[S02] TO [USER_S02]
GRANT INSERT     ON SCHEMA::[S02] TO [USER_S02]
GRANT REFERENCES ON SCHEMA::[S02] TO [USER_S02]
GRANT SELECT     ON SCHEMA::[S02] TO [USER_S02]
GRANT UPDATE     ON SCHEMA::[S02] TO [USER_S02]
GRANT EXECUTE    ON SCHEMA::[S02] TO [USER_S02]
GO

GRANT DELETE     ON SCHEMA::[S03] TO [USER_S03]
GRANT EXECUTE    ON SCHEMA::[S03] TO [USER_S03]
GRANT INSERT     ON SCHEMA::[S03] TO [USER_S03]
GRANT REFERENCES ON SCHEMA::[S03] TO [USER_S03]
GRANT SELECT     ON SCHEMA::[S03] TO [USER_S03]
GRANT UPDATE     ON SCHEMA::[S03] TO [USER_S03]
GRANT EXECUTE    ON SCHEMA::[S03] TO [USER_S03]
GO

GRANT DELETE     ON SCHEMA::[S04] TO [USER_S04]
GRANT EXECUTE    ON SCHEMA::[S04] TO [USER_S04]
GRANT INSERT     ON SCHEMA::[S04] TO [USER_S04]
GRANT REFERENCES ON SCHEMA::[S04] TO [USER_S04]
GRANT SELECT     ON SCHEMA::[S04] TO [USER_S04]
GRANT UPDATE     ON SCHEMA::[S04] TO [USER_S04]
GRANT EXECUTE    ON SCHEMA::[S04] TO [USER_S04]
GO

解决方案 1(我的选择):包括在不同模式中使用相同的过程名称。每个用户一个程序(有自己的模式)。

CREATE PROCEDURE [S01].[usp_SampleProc]
AS
SELECT * FROM [SampleTable]
GO

CREATE PROCEDURE [S02].[usp_SampleProc]
AS
SELECT * FROM [SampleTable]
GO

CREATE PROCEDURE [S03].[usp_SampleProc]
AS
SELECT * FROM [SampleTable]
GO

CREATE PROCEDURE [S04].[usp_SampleProc]
AS
SELECT * FROM [SampleTable]
GO

解决方案 2:使用动态创建,因为在执行时,表引用将被解析为用户模式内的同义词。

GRANT EXECUTE    ON SCHEMA::[dbo] TO [USER_S01]
GRANT EXECUTE    ON SCHEMA::[dbo] TO [USER_S02]
GRANT EXECUTE    ON SCHEMA::[dbo] TO [USER_S03]
GRANT EXECUTE    ON SCHEMA::[dbo] TO [USER_S04]
GO

CREATE PROCEDURE [dbo].[usp_SampleProc]
AS
    exec(N'SELECT * FROM [SampleTable]')
GO

执行:两种解决方案完全相同。

EXECUTE AS USER = 'USER_S01'
EXEC [usp_SampleProc]
REVERT;

EXECUTE AS USER = 'USER_S02'
EXEC [usp_SampleProc]
REVERT;

EXECUTE AS USER = 'USER_S03'
EXEC [usp_SampleProc]
REVERT;

EXECUTE AS USER = 'USER_S04'
EXEC [usp_SampleProc]
REVERT;

选择的原因:我不希望开发人员简化程序的创建和测试。并解决生产中发生的错误。以我决定使用的方式,该过程对于所有模式都是完全相同的。因此,只需使用该模式记录并解决所有模式的问题,就可以轻松测试该模式中发生的问题。

该解决方案的缺点是我不能将模式放在过程中的表中。所以这将是轻微的性能损失。

于 2012-10-19T17:29:37.003 回答
2

在某些情况下,您实际上不必选择依赖动态 SQL。

不要创建越来越多的对象,而是尝试动态调用您的表。

它可能看起来像这样:

CREATE PROCEDURE [dbo].[usp_SampleProc]
AS
BEGIN
    DECLARE @SQL varchar(MAX)
    set @SQL = 'SELECT col1, col2, ... FROM [SampleTable'+ SUSER_SNAME() +']'
    exec(@SQL)
END

您可能必须转换用户名以适应您的命名约定。

此外,您不应该在这种情况下使用“select *”,因为它没有被编译,如果表结构中有任何修改,您最终可能会感到惊讶。

于 2012-10-19T15:29:31.560 回答
1

如果我是你,我会非常熟悉 SqlCmd.exe(命令行实用程序)和使用变量。

我将尝试将 5 个文件的代码放在下面。我将在代码(文件的内容)之前放置一个标签,文件名如下:

||||||||||||||||||MyFileName.txt||||||||||||||||||

你不会把它放在文件的内容中,但是这个“标记”行下面的所有东西都是文件的内容。您需要完全按照我的方式命名文件。您会将所有文件放在同一目录中。

创建完所有文件后,您需要编辑(一个).bat 文件并更新一些信息。(主要是您的机器上存在 sqlcmd.exe 的位置,以及您有权使用集成身份验证创建数据库的 sqlserver/实例的名称。

以下是常见的位置:

%ProgramFiles%\Microsoft SQL Server\100\Tools\Binn\sqlcmd.exe
%ProgramFiles%\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe
%ProgramFiles% (x86)\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe

我们走吧!

||||||||||||||||||MasterRunMeBatFile.bat|||||||||||||||||||

REM Find the location of your SQLCMD.EXE
set __sqlCmdLocation=c:\Program Files (x86)\Microsoft SQL Server\90\Tools\binn\SQLCMD.EXE

REM Set your servername/instancename here
set __sqlServerNameAndInstance=MyServerName\MyInstanceName



REM Create the database
"%__sqlCmdLocation%" -i .\DatabaseCreate.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_DatabaseCreateOutput.txt" -v DBName="MyFirstCommandLineDB"


REM Create the multiple Schemas
"%__sqlCmdLocation%" -i .\SchemasCreate.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_SchemasCreate_01.txt" -v DBName="MyFirstCommandLineDB" SchemaName="Schema01"
"%__sqlCmdLocation%" -i .\SchemasCreate.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_SchemasCreate_02.txt" -v DBName="MyFirstCommandLineDB" SchemaName="Schema02"
"%__sqlCmdLocation%" -i .\SchemasCreate.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_SchemasCreate_03.txt" -v DBName="MyFirstCommandLineDB" SchemaName="Schema03"


REM Create the DDL (tables)
"%__sqlCmdLocation%" -i .\OrganizationDDL.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_OrganizationDDL_01.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema01" MyUniqueNumber="01" DBUSERNAME="public"
"%__sqlCmdLocation%" -i .\OrganizationDDL.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_OrganizationDDL_02.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema02" MyUniqueNumber="02" DBUSERNAME="public"
"%__sqlCmdLocation%" -i .\OrganizationDDL.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_OrganizationDDL_03.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema03" MyUniqueNumber="03" DBUSERNAME="public"


REM Create some stored procedures against the multiple schemas
"%__sqlCmdLocation%" -i .\TSQL_USP_UDF_TRG.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_TSQL_USP_UDF_TRG_01.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema01" MyUniqueNumber="01" DBUSERNAME="public"
"%__sqlCmdLocation%" -i .\TSQL_USP_UDF_TRG.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_TSQL_USP_UDF_TRG_02.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema02" MyUniqueNumber="02" DBUSERNAME="public"
"%__sqlCmdLocation%" -i .\TSQL_USP_UDF_TRG.sql -b -S "%__sqlServerNameAndInstance%"  -E -o ".\ZZZ_TSQL_USP_UDF_TRG_03.txt" -v DBName="MyFirstCommandLineDB" MySchemaVariable="Schema03" MyUniqueNumber="03" DBUSERNAME="public"



set __sqlCmdLocation=
set __sqlServerNameAndInstance=

|||||||||||||||||||DatabaseCreate.sql||||||||||||||||||

Use [master];
GO


if exists (select * from sysdatabases where name='$(DBName)')
BEGIN
        DROP DATABASE [$(DBName)];
END

GO



Create Database $(DBName)
GO

||||||||||||||||||SchemasCreate.sql||||||||||||||||||

Use [$(DBName)]
GO



IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = '$(SchemaName)')
    BEGIN
        -- The schema must be run in its own batch!
        EXEC( 'CREATE SCHEMA $(SchemaName)' );
    END


IF EXISTS (SELECT 1 FROM sys.schemas WHERE name = '$(SchemaName)')
    BEGIN
            PRINT 'SCHEMA $(SchemaName) Exists!' ;
    END
ELSE
    BEGIN
        PRINT 'Oh My : SCHEMA $(SchemaName) does not exist.' ;
    END

GO

||||||||||||||||||组织DDL.sql||||||||||||||||||

Use [$(DBName)]
GO



IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[$(MySchemaVariable)].[Employee$(MyUniqueNumber)]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
    BEGIN
        DROP TABLE [$(MySchemaVariable)].[Employee$(MyUniqueNumber)]
    END
GO



CREATE TABLE [$(MySchemaVariable)].[Employee$(MyUniqueNumber)]
(
    EmployeeUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID() , 
    SSN varchar(11) , 
    LastName varchar(24) , 
    FirstName varchar(24) , 
    DateOfBirth smalldatetime 

)

ALTER TABLE [$(MySchemaVariable)].[Employee$(MyUniqueNumber)] ADD CONSTRAINT PK_Employee$(MyUniqueNumber)
PRIMARY KEY NONCLUSTERED (EmployeeUUID)

ALTER TABLE [$(MySchemaVariable)].[Employee$(MyUniqueNumber)] ADD CONSTRAINT CK_Employee$(MyUniqueNumber)_SSN_Unique
UNIQUE (SSN)

GRANT SELECT , INSERT, UPDATE, DELETE ON [$(MySchemaVariable)].[Employee$(MyUniqueNumber)] TO $(DBUSERNAME)
GO


PRINT 'Select * from [$(MySchemaVariable)].[Employee$(MyUniqueNumber)]'
Select * from [$(MySchemaVariable)].[Employee$(MyUniqueNumber)]

||||||||||||||||||TSQL_USP_UDF_TRG.sql||||||||||||||||||

Use [$(DBName)]
GO



GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[$(MySchemaVariable)].[uspEmployeeGetAll]') AND type in (N'P', N'PC'))
    DROP PROCEDURE [$(MySchemaVariable)].[uspEmployeeGetAll]
GO


CREATE PROCEDURE [$(MySchemaVariable)].[uspEmployeeGetAll]

AS

SET NOCOUNT ON

SELECT 
    EmployeeUUID , 
    SSN , 
    LastName , 
    FirstName , 
    DateOfBirth
FROM 
    [$(MySchemaVariable)].[Employee$(MyUniqueNumber)] e

SET NOCOUNT OFF

GO

GRANT EXECUTE ON $(MySchemaVariable).[uspEmployeeGetAll] TO $(DBUSERNAME)
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[$(MySchemaVariable)].[uspEmployeeGetAll]') AND type in (N'P', N'PC'))
    PRINT '[$(MySchemaVariable)].[uspEmployeeGetAll] has been created!'
GO


GO



IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[$(MySchemaVariable)].[uspEmployeeGetByUUID]') AND type in (N'P', N'PC'))
    DROP PROCEDURE [$(MySchemaVariable)].[uspEmployeeGetByUUID]
GO

/*
declare @EmployeeUUID uniqueidentifier
select @EmployeeUUID = NEWID() 
exec [$(MySchemaVariable)].[uspEmployeeGetByUUID] @EmployeeUUID
*/

CREATE PROCEDURE [$(MySchemaVariable)].[uspEmployeeGetByUUID]
@EmployeeUUID uniqueidentifier

AS

SET NOCOUNT ON

SELECT 
    EmployeeUUID , 
    SSN , 
    LastName , 
    FirstName , 
    DateOfBirth
FROM 
    [$(MySchemaVariable)].[Employee$(MyUniqueNumber)] e
WHERE 
    e.EmployeeUUID = @EmployeeUUID 

SET NOCOUNT OFF

GO


GRANT EXECUTE ON $(MySchemaVariable).[uspEmployeeGetByUUID] TO $(DBUSERNAME)
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[$(MySchemaVariable)].[uspEmployeeGetByUUID]') AND type in (N'P', N'PC'))
    PRINT '[$(MySchemaVariable)].[uspEmployeeGetByUUID] has been created!'
GO

=============== 结束文件和文件内容======================

行。

在这个练习结束时......你应该有这样的东西。

三表:(在同一个数据库内)

[Schema01].[Employee01] , 
[Schema02].[Employee02] , 
[Schema03].[Employee03] 

并且存储过程类似于下面的一个。(注意,存储过程的模式名称和它从中提取的表。)

ALTER PROCEDURE [Schema01].[uspEmployeeGetAll]

AS

SET NOCOUNT ON

SELECT 
    EmployeeUUID , 
    SSN , 
    LastName , 
    FirstName , 
    DateOfBirth
FROM 
    [Schema01].[Employee01] e

恕我直言。使用带变量的 sqlcmd.exe 是确保不同环境之间完美可重复性的最佳方式。

另一个人的想法:

http://blogs.msdn.com/tomholl/archive/2008/04/29/thoughts-on-being-a-solution-architect.aspx

最大限度地减少开发人员需要编写的代码量 开发人员编写代码是有报酬的,而且他们通常都很擅长。但是,一旦为开发人员分配了一大堆需求或故事,他们就需要着手处理这些特定需求,而且他们很难在任何细节级别上跟上其他人正在做的事情。

这可以包括发现不同需求之间的协同作用或宏观级代码重用和重构的机会。架构师工作的很大一部分是在这些机会出现时抓住它们,并确保开发人员不会在他们自己的世界中重新发明轮子。

理想情况下,这应该导致模式、组件和框架允许开发人员通过专注于那些独特的部分来用更少的代码完成他们的需求。

额外的:

http://www.yaldex.com/sql_server_tutorial_3/ch06lev1sec5.html

这就是您开发脚本的方式。但不要忘记注释掉变量设置(在 .sql 文件中),因为文件内容中的变量优先于通过命令行传入的变量。

请就这个问题投票!

http://connect.microsoft.com/sqlserver/feedback/details/382007/in-sqlcmd

于 2012-10-19T15:18:44.223 回答