【问题标题】:Dynamic use of T-SQL schemasT-SQL 模式的动态使用
【发布时间】:2023-04-09 06:25:01
【问题描述】:

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

为了更好地理解我想要做什么,下面我将尝试更好地解释。

假设如下数据库:

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]

存储过程会产生错误,因为架构[dbo]中不存在[SampleTable]

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

所以我正在这样做:

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 集成,到目前为止,这是我得到的最佳解决方案)。

那么,问题又来了:

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

    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;
    

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

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

    【讨论】:

      【解决方案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 *”,因为它未编译,如果表结构有任何修改,您最终可能会感到意外。

      【讨论】:

      • 我不惜一切代价避免使用动态生成的 sql。很难调试、维护。在这种情况下,我更喜欢每个模式的重复过程。
      • 这就是我自己不喜欢使用 D-SQL 的原因,但有时我更愿意走这条路,而不是创建一个从长远来看需要更多维护的高度复杂的逻辑。
      【解决方案3】:

      如果我是你,我会非常熟悉 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
      

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

      恕我直言。 使用带变量的 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

      【讨论】:

      • 完整的 sqlcmd.exe 文档位于(用于 Sql Server 2008 R2):msdn.microsoft.com/en-us/library/ms162773(v=sql.105).aspx
      • 我正在做与它非常相似的事情,生成所有重复的信息。事实上,我担心只在数据库中进行更改,而不是在生成脚本中复制。纪律很难建立。
      • 你是说你的文件中没有你的脚本......(在源代码管理中)并且你只手动编辑程序的内容(等)?优。你有比前缀模式名称更大的问题。祝一切顺利。
      • 我正在为每个对象使用一个文件来实现版本控制。任何解决方案都必须出现。我在项目中不到一个月,我必须像往常一样“解决”所有问题。我正在使用 SVN 对文件进行版本控制,并使用自动化脚本来创建用于部署(测试、生产和开发)的数据库。
      猜你喜欢
      • 2018-03-21
      • 2015-09-27
      • 2011-05-22
      • 1970-01-01
      • 1970-01-01
      • 2010-11-05
      • 2011-04-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多