3

ストアド プロシージャで使用されるオブジェクトのスキーマを省略できる方法はありますか。これらのオブジェクトはストアド プロシージャのスキーマにバインドされませんが、ログに記録されたスキーマは、同じストアド プロシージャが異なるユーザーに対して異なる結果を生成するように使用されます。スキーマ?

私がやろうとしていることをよりよく理解するために、以下でよりよく説明しようとします。

次のデータベースを想定します。

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

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

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

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

このデータベースでは、テーブルの名前に続く番号はテーブルの所有者を表します (これはレガシー システムであり、変更できません)。

それを .net Entity Framework に統合するために、さまざまなスキーマでシノニムを作成するソリューションが用意されているため、接続文字列を変更すると、データベース コンテキストやプログラミング ロジックを変更せずに、使用するオブジェクトを変更できます。

このような。

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]

A user in [s01] schema will get values from [s01].[SampleTable] and a user in [s04] schema will get values from [s04].[SampleTable], when executing [usp_SampleProc] without specifying the schema, which is my expected result.

So far so good, this isn't productive in my real scenario. I have thousand of tables, hundreds of procedures and dozen of schemas (I know this is ugly, but I integrating a legacy system with .net, and so far, it is the best solution which I come).

So, the question again:

Is there a way which I can omit the schema of objects used in the stored procedure and these objects aren't bound to the schema of the stored procedure but the schema of the logged in user in manner that the same stored procedure produces different results for users in different schemas?

4

3 に答える 3

3

これらは、私がやろうとしていることを行うために私が知っている2つの方法です.

どちらの方法も開発者にとって透過的であるため、ソリューションの複雑さを理解する必要はありません。

以下に、誰もが使用できるサンプルを作成しました。

元のレガシー データベースの作成:レガシー アプリケーションがまだデータベースを使用しているため、変更されません。

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 (私の選択):異なるスキーマで同じプロシージャ名を使用することにあります。ユーザー (独自のスキーマを持つ) ごとに 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||||||||||||||||||

これをファイルの内容に入れることはありませんが、この「マーカー」行の下のすべてがファイルの内容になります。私が持っているのとまったく同じようにファイルに名前を付ける必要があります。そして、すべてのファイルを同じディレクトリに配置します。

すべてのファイルを作成したら、(1 つの) .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

=============== 終了ファイルとファイル内容======================

Ok。

この演習の最後には....このようなものが得られるはずです。

3 つのテーブル: (同じデータベース内)

[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 に答える