动态使用T-SQL模式

时间:2012-10-19 14:13:04

标签: sql sql-server database tsql stored-procedures

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

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

假设以下数据库:

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]中不存在[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集成,到目前为止,这是我来的最佳解决方案。)

那么,问题又来了:

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

3 个答案:

答案 0 :(得分: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;

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

解决方案的缺点是,我不能在程序里面的表中放置模式。所以这将是一个轻微的性能损失。

答案 1 :(得分: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 *”,因为它没有编译,如果表结构中有任何修改,你最终会有一些惊喜。

答案 2 :(得分:1)

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

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

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

您不会将其放在文件的内容中,但此“marker”行下方的所有内容都将是文件的内容。您需要完全按照我们的名称命名文件。并且您将所有文件放在同一目录中。

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

以下是常见位置:

%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

||||||||||||||||||| OrganizationDDL.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

IMHO。 将sqlcmd.exe与变量一起使用是确保不同环境之间完美重复性的最佳方法。

另一个人的想法:

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

最大限度地减少开发人员需要编写的代码量 开发人员付费编写代码,他们通常都很擅长。然而,一旦开发人员被分配了大量的需求或故事,他们就需要开始处理这些特定需求,并且他们不容易跟上其他人在任何细节层面上所做的事情。

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

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

EXTRA:

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

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

请就此问题投票!

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