ロスト アップデートとその解決方法 その 3

ロスト アップデートとその解決方法 その 1 では SqlCommand クラスを利用したコードを示しましたが、
今回は ADO.NET Entity Framework を利用したコードを示します。
ただし、ステップ 3 (Read Committed & UPDLOCK) の場合のみ示します。

Entity Framework では基本的に LINQ to Entities でクエリを記述しますが、更新ロックを取得するための構文は存在しません。
ObjectContext.ExecuteStoreQuery メソッドにより、データベースに依存する任意の SELECT 文を実行できます。

なお、下記のコードを記述する前に Visual Studio で ADO.NET Entity Data Model を作成しておきます。
名前は NorthwindModel.edmx としておきます。

NorthwindModel


using System;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;

namespace LostUpdate
{
    class Program
    {
        private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };

        static void Main(string[] args)
        {
            Parallel.For(0, 100, i => AddUnitsInStock());
        }

        private static void AddUnitsInStock()
        {
            try
            {
                short oldValue, newValue;

                using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
                using (var context = new NorthwindEntities()) 
                {
                    // 商品を取得します。
                    Product product = context.ExecuteStoreQuery<Product>("select * from Products with (updlock) where ProductID = {0}", 1).Single();
                    context.AttachTo("Products", product);

                    // 在庫個数を 1 だけ増加させます。
                    oldValue = product.UnitsInStock.Value; 
                    newValue = (short)(oldValue + 1);
                    product.UnitsInStock = newValue;

                    // 商品を更新します。
                    context.SaveChanges();

                    // コミットします。
                    scope.Complete(); 
                } 

                Console.WriteLine("{0} → {1}", oldValue, newValue); 
            }
            catch (System.Data.DataException ex) 
            {
                Console.WriteLine(ex.Message); 
            } 
        } 
    }
}


注意点
(1) ObjectContext.ExecuteStoreQuery メソッドでパラメーター化クエリを実行する場合、
     パラメーター名には順に {0}, {1}, {2}, … または @p0, @p1, @p2, … を使用します。
(2) ObjectContext.ExecuteStoreQuery メソッドを使用した場合、
     取得したデータは自動的に ObjectContext にアタッチされないため、ObjectContext.AttachTo メソッドを呼び出します。
     標準的な

          Product product = context.Products.Single(p => p.ProductID == 1);

     などのような場合には呼び出す必要はありません。
(3) UPDATE、INSERT、DELETE などの変更操作を任意の SQL で実行する場合には、
     ObjectContext.ExecuteStoreCommand メソッドを使用します。

バージョン情報
.NET Framework 4
SQL Server 2008, 2008 R2

参照
ObjectContext.ExecuteStoreQuery メソッド
ObjectContext.ExecuteStoreCommand メソッド
ストア コマンドの直接実行

広告

ロスト アップデートとその解決方法 その 2

前回のロスト アップデートとその解決方法 その 1 では自動トランザクション (TransactionScope) を
利用したコードを示しましたが、今回は手動トランザクションを利用したコードを示します。

ただし、特に説明する部分はないので、ステップ 3 (Read Committed & UPDLOCK) の場合のみ示します。
TransactionScope クラスの代わりに SqlTransaction クラスを利用するだけです。


using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;

namespace LostUpdate
{
    class Program
    { 
        private const string NorthwindConnectionString = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True"

        private const string SelectCommandText = "select UnitsInStock from Products with (updlock) where ProductID = 1"
        private const string UpdateCommandText = "update Products set UnitsInStock = @UnitsInStock where ProductID = 1";

        static void Main(string[] args) 
        { 
            Parallel.For(0, 100, i => AddUnitsInStock()); 
        } 

        /// <summary>
        /// Northwind データベースの Products テーブルの UnitsInStock の値を 1 だけ増加させます。
        /// </summary>
        private static void AddUnitsInStock() 
        { 
            try
            { 
                short oldValue, newValue; 

                using (var connection = new SqlConnection(NorthwindConnectionString)) 
                { 
                    // データベース接続を開きます。
                    connection.Open(); 

                    // トランザクションを開始します。
                    SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); 

                    // 在庫個数を取得します。
                    using (var command = new SqlCommand(SelectCommandText, connection) { Transaction = transaction }) 
                    { 
                        oldValue = (short)command.ExecuteScalar(); 
                    } 

                    // 在庫個数を 1 だけ増加させます。
                    newValue = (short)(oldValue + 1); 

                    // 在庫個数を更新します。
                    using (var command = new SqlCommand(UpdateCommandText, connection) { Transaction = transaction }) 
                    { 
                        command.Parameters.AddWithValue("@UnitsInStock", newValue); 
                        command.ExecuteNonQuery(); 
                    } 

                    // コミットします。
                    transaction.Commit(); 
                } 

                Console.WriteLine("{0} → {1}", oldValue, newValue); 
            } 
            catch (SqlException ex) 
            { 
                Console.WriteLine(ex.Message); 
            } 
        } 
    }
}


バージョン情報
.NET Framework 4
SQL Server 2008, 2008 R2

ロスト アップデートとその解決方法 その 1

(目次: トランザクションのサンプル)

ロスト アップデート (Lost Update, 失われた更新) とは、更新したはずのデータが失われてしまう現象のことです。
「後勝ち」と表現されることもあります。
わざと後勝ちルールを採用する業務もあると思いますが、頻繁なアクセスが見込まれる場合には適切な排他制御が必要になります。
今回は、ロスト アップデートの発生と SQL Server における解決方法について、実際のコードを示して説明します。

前提として、SQL Server におけるロック メカニズムやトランザクション分離レベルについての基礎知識が必要になります。
これらは SQL Server のロック管理 (@IT) で確認できます。

以下で示すサンプルでは同時実行制御のために自動トランザクション (TransactionScope クラス) を使用していますが、
手動トランザクションなどを使用しても同様です。
さらに、データ アクセスのために SqlCommand クラスを使用していますが、
型指定された TableAdapter などを使用しても同様です。

■ ステップ 1: Read Committed (→ロスト アップデート発生)

先にコードを示します。
コンソール アプリケーション プロジェクトを作成し、Program.cs に次のように記述します。
System.Transactions.dll への参照も必要です。


using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using System.Transactions;

namespace LostUpdate
{
    class Program
    {
        private const string NorthwindConnectionString = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True";

        private const string SelectCommandText = "select UnitsInStock from Products where ProductID = 1"
        private const string UpdateCommandText = "update Products set UnitsInStock = @UnitsInStock where ProductID = 1";

        private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };

        static void Main(string[] args)
        {
            Parallel.For(0, 100, i => AddUnitsInStock());
        }

        /// <summary>
        /// Northwind データベースの Products テーブルの UnitsInStock の値を 1 だけ増加させます。
        /// </summary>
        private static void AddUnitsInStock()
        {
            try
            {
                short oldValue, newValue;

                using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
                using (var connection = new SqlConnection(NorthwindConnectionString))
                {
                    // データベース接続を開きます。
                    connection.Open();

                    // 在庫個数を取得します。
                    using (var command = new SqlCommand(SelectCommandText, connection))
                    {
                        oldValue = (short)command.ExecuteScalar();
                    }

                    // 在庫個数を 1 だけ増加させます。
                    newValue = (short)(oldValue + 1);

                    // 在庫個数を更新します。
                    using (var command = new SqlCommand(UpdateCommandText, connection))
                    {
                        command.Parameters.AddWithValue("@UnitsInStock", newValue);
                        command.ExecuteNonQuery();
                    }

                    // コミットします。
                    scope.Complete();
                }

                Console.WriteLine("{0} → {1}", oldValue, newValue);
            }
            catch (SqlException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}


AddUnitsInStock メソッドは、Northwind データベース内の特定の商品の在庫個数を 1 だけ増加させます。
これは、「同一トランザクション内で、あるデータを取得して、新たな値で更新する」という特徴を持っています。
トランザクション分離レベルを Read Committed に設定して、一連の処理を TransactionScope で囲みます。
更新処理が完了した場合は「100 → 101」のように値の変化を表示し、例外がスローされた場合はそのメッセージを表示します。

取得および更新の対象となるのはこの値です。
なお、UnitsInStock 列のデータ型は smallint (C# では short) です。

Products テーブルのデータ

AddUnitsInStock メソッドを、並列に 100 回実行します。
なお、並列処理には .NET Framework 4 で追加されたタスク並列ライブラリ (TPL) を使用しています。

ステップ 1 実行結果

実行結果は上の図のようになります。
例外がスローされているわけでもなく、正常に終了したようにも見えますが、
実際には値が 60 ほどしか増加していません (結果は毎回変化します)。
これがロスト アップデートという現象です。

Read Committed の場合には、後発のスレッドが古い値を読み取ることができる上に、
トランザクション内で共有ロックを最後まで取得し続けないためにすべてのスレッドで更新ができてしまいます。

■ ステップ 2: Serializable (→デッドロック発生)

Read Committed がだめなら、ということで今度は Serializable にしてみます。


private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.Serializable };


コードを上記のように 1 行だけ書き換えて実行します。

ステップ 2 実行結果

すると、Read Committed のときには発生していなかったはずの例外が通知されます。
約 20 回は成功し、約 80 回はデッドロックによりロールバックされています。
ただし、ロスト アップデートは発生していません。

デッドロック自体は、あるスレッドの更新時に別のスレッドが共有ロックを取得してしまっていることが原因で発生していますが、
別々のスレッドが更新前の同じ値を読み取ってしまうという根幹の問題は Serializable にしただけでは解決できません。

今回のデッドロックは、先に取得した共有ロックを排他ロックに変換しようとするときに発生することから、
変換デッドロックと呼ばれます。

ところで、Read Committed の場合はすべてのスレッドが正常に終了したと通知されていながら一部のデータが失われているのに対し、
Serializable の場合は正常に終了したと通知された分だけ実際のデータに反映されており、整合性は保たれています。
Serializable にしておけば ACID 属性は保証されるので、Read Committed よりは安全といえるでしょう。

■ ステップ 3: Read Committed & UPDLOCK (→解決)

変換デッドロックを防ぐには、初めの SQL で強めのロックを取得させます。
しかし排他ロックにする必要はなく、更新ロックで十分です。
更新ロックを取得するには、テーブル ヒント「with (updlock)」を使用します。
また、初めの SQL で更新ロックを取得するので、分離レベルは Read Committed で十分となります。


private const string SelectCommandText = "select UnitsInStock from Products with (updlock) where ProductID = 1";

private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };


実行します。

ステップ 3 実行結果

すると、今度は見事に期待通りに動作します。

UPDLOCK により初めの読取りのときに更新ロックを取得することで、
後発のスレッドがデータを読み取ろうとしても自身のトランザクションが終了するまで待機させることができます。

注意点
(1) デバッグする場合には、データベース接続がタイムアウトしないよう、
     TransactionOptions および SqlCommand のタイムアウトの値を長めに設定するとよいです。
(2) 更新用の SQL を

         update Products set UnitsInStock = @UnitsInStock where ProductID = 1 and UnitsInStock = @Original_UnitsInStock

     とする方法もありますが、これは主に取得と更新が別トランザクション (対話型など) となる場合に使用されます。
     楽観的同時実行制御と呼ばれ、頻繁なアクセスが見込まれない場合の実装方法です。
     Visual Studio で型指定された DataTable を自動生成した場合は、既定でこの SQL が利用されます。

ロスト アップデートとその解決方法 その 2 に手動トランザクションを利用したコードを、
ロスト アップデートとその解決方法 その 3 に ADO.NET Entity Framework を利用したコードを載せました。

バージョン情報
.NET Framework 4
SQL Server 2008, 2008 R2

参照
.NET エンタープライズ Web アプリケーション開発技術大全 〈Vol.5〉 トランザクション設計編
エンタープライズ技術大全(トランザクション設計編) – WikiWiki (上記書籍の要約)
SQL Server のロック管理 (@IT)
テーブル ヒント (Transact-SQL)

テーブルやストアド プロシージャの情報を一覧として取得する

SQL Server で、テーブルおよび列の情報や、ストアド プロシージャおよびパラメーターの情報 (メタデータ) を
一覧として取得する方法について記述します。

■ テーブルおよび列の一覧を取得する

sys.tables および sys.columns からテーブルおよび列のメタデータを取得できます。
さらに、型やキーの情報を取得するためには sys.typessys.indexes と結合します。
最大サイズ (max_length) についてはバイト数を取得するので、
Unicode 文字列型 (nchar または nvarchar) の場合には 2 で除算するとよいです。

次のようなクエリでメタデータを取得できます。


SELECT          T.name AS TableName, C.name AS ColumnName, U.name AS TypeName,
                CASE WHEN U.name IN (N’nchar’, N’nvarchar’) THEN C.max_length / 2 ELSE C.max_length END AS [MaxLength],
                IC.index_column_id AS [PrimaryKeyIndex],
                FT.name AS [RefTableName], FC.name AS [RefColumnName],
                C.is_nullable, C.is_identity
FROM            sys.columns AS C
INNER JOIN      sys.tables AS T
ON              C.object_id = T.object_id
INNER JOIN      sys.types AS U
ON              C.user_type_id = U.user_type_id
LEFT OUTER JOIN sys.indexes AS I
ON              C.object_id = I.object_id
AND             I.is_primary_key = 1
LEFT OUTER JOIN sys.index_columns AS IC
ON              C.object_id = IC.object_id
AND             C.column_id = IC.column_id
AND             I.index_id = IC.index_id
LEFT OUTER JOIN sys.foreign_key_columns AS F
ON              C.object_id = F.parent_object_id
AND             C.column_id = F.parent_column_id
LEFT OUTER JOIN sys.tables AS FT
ON              F.referenced_object_id = FT.object_id
LEFT OUTER JOIN sys.columns AS FC
ON              F.referenced_object_id = FC.object_id
AND             F.referenced_column_id = FC.column_id
ORDER BY        TableName, C.column_id


Northwind データベースに対して実行すると、下の図のような結果が得られます。

テーブルおよび列の一覧 (Northwind)

注意点
(1) 上記のクエリでは、列が 0 個のテーブルは取得されません。

■ ストアド プロシージャおよびパラメーターの一覧を取得する

先ほどのテーブル情報と同様にして、sys.procedures および sys.parameters から
ストアド プロシージャおよびパラメーターのメタデータを取得できます。


SELECT     P.name AS ProcedureName, Q.name AS ParamName, U.name AS TypeName,
           CASE WHEN U.name IN (N’nchar’, N’nvarchar’) THEN Q.max_length / 2 ELSE Q.max_length END AS [MaxLength],
           Q.is_output
FROM       sys.parameters AS Q
INNER JOIN sys.procedures AS P
ON         Q.object_id = P.object_id
INNER JOIN sys.types AS U
ON         Q.user_type_id = U.user_type_id
ORDER BY   ProcedureName, Q.parameter_id


Northwind データベースに対して実行すると、下の図のような結果が得られます。

ストアド プロシージャおよびパラメーターの一覧 (Northwind)

注意点
(1) 上記のクエリでは、パラメーターが 0 個のストアド プロシージャは取得されません。

バージョン情報
SQL Server 2008, 2008 R2

参照
sys.tables (Transact-SQL)
sys.columns (Transact-SQL)
sys.procedures (Transact-SQL)
sys.parameters (Transact-SQL)

PowerShell でデータベースのすべてのデータをエクスポートする

PowerShell では .NET のライブラリを呼び出せるとのことなので、その記述方法を学ぶために、
データベースのすべてのデータを CSV ファイルにエクスポートするスクリプトをサンプルとして作成してみました。
テーブル名をあらかじめ指定しておかなければなりませんでしたが、今回はテーブル名を動的に取得します。
 
アプローチとしては、まず C# で作成し、そのコードを PowerShell スクリプトに置き換えました。
テーブル名はデータベースのメタデータから取得します。
 
C# のコードはこちらです。

string instanceName = @".\SQLEXPRESS";
string databaseName = "Tfs_DefaultCollection";
string outDirPath = "Output";

string commandText = "select [name] from sys.tables order by [name]";
string connectionString = string.Format("Data Source={0};Initial Catalog={1};Integrated Security=True",
    instanceName, databaseName);

DataTable dt = new DataTable();  

using (SqlDataAdapter da = new SqlDataAdapter(commandText, connectionString))
{
    da.Fill(dt);
}  

Directory.CreateDirectory(outDirPath);  

foreach (DataRow dr in dt.Rows)
{
    Process.Start("bcp"string.Format(@"[{2}].[dbo].[{0}] out {3}\{0}.csv -S {1} -T -w -t ,",
        (string)dr[0], instanceName, databaseName, outDirPath));
}  


 
これを PowerShell スクリプトに変換すると、次のようになります。
ファイル名を「ExportData.ps1」として保存します。

$instanceName = ".\SQLEXPRESS"
$databaseName = "Tfs_DefaultCollection"
$outDirPath = "Output"
 
$commandText = "select [name] from sys.tables order by [name]"
$connectionString = [String]::Format("Data Source={0};Initial Catalog={1};Integrated Security=True", $instanceName, $databaseName)
 
$dt = New-Object System.Data.DataTable
$da = New-Object System.Data.SqlClient.SqlDataAdapter($commandText, $connectionString)
[void]$da.Fill($dt)
$da.Dispose()
 
if (-! (Test-Path $outDirPath))
{
    New-Item $outDirPath -type directory
}
 
$dt.Rows | ForEach-Object { bcp ([String]::Format("[{0}].[dbo].[{1}]", $databaseName, $_[0])) out ([String]::Format("{0}\{1}.csv", $outDirPath, $_[0])) -S $instanceName -T -w -t "," }

 
ExportData.ps1 を右クリックして [PowerShell で実行] をクリックするとデータがエクスポートされます。
ちなみに、この例では Team Foundation Server 2010 の既定のチーム プロジェクト コレクションである 
Tfs_DefaultCollection データベースを対象としており、全部で 213 テーブルあります。
 
 
 
注意点
(1) 既定では PowerShell の実行ポリシーが Restricted に設定されているため、.ps1 ファイルを PowerShell で実行できません。
     このことは Get-ExecutionPolicy コマンドレットを実行すれば確認できます。
     実行ポリシーを緩める必要があり、そのためには次のように Set-ExecutionPolicy コマンドレットを実行します。
Set-ExecutionPolicy RemoteSigned
(2) 上記の例ではコレクション内でループさせるために ForEach-Object コマンドレットを使用していますが、
     C# の foreach に近い構文を使用することもできます (Windows PowerShell 入門(5)-制御構文)。
 
(3) 逆に、データをインポートする場合は外部参照制約に注意する必要があります。
     親テーブルから先にデータをインポートしなければなりませんが、
     次のようなクエリでテーブルの親子関係を取得すれば何とかできそうです。
select [ct].[name] as [ChildTableName], [pt].[name] as [ParentTableName]
from sys.foreign_keys as [f]
inner join sys.tables as [ct] on [f].[parent_object_id] = [ct].[object_id]
inner join sys.tables as [pt] on [f].[referenced_object_id] = [pt].[object_id]
参照

bcp ユーティリティによるデータのエクスポートおよびインポート

SQL Server 上のテーブルのデータをテキスト形式のファイルにエクスポートしたり、
逆にインポートしたりする方法について記述します。
 
SQL Server 認証を使用して、Table1, Table2 テーブルのデータを Unicode かつカンマ区切り形式のファイルとして
それぞれ Table1.csv, Table2.csv にエクスポートするには、以下のようなコマンドを実行します。

@echo off
 
set dbServer=localhost\SQLEXPRESS
set dbUser=user1
set dbPassword=P@ssw0rd
 
bcp [Database1].[dbo].[Table1] out Table1.csv -S %dbServer% -U %dbUser% -P %dbPassword% -w -t ,
bcp [Database1].[dbo].[Table2] out Table2.csv -S %dbServer% -U %dbUser% -P %dbPassword% -w -t ,
 
pause

 
逆に、Table1.csv, Table2.csv のデータをそれぞれ Table1, Table2 テーブルにインポートするには、out を in に変更するだけです。

@echo off
 
set dbServer=localhost\SQLEXPRESS
set dbUser=user1
set dbPassword=P@ssw0rd
 
bcp [Database1].[dbo].[Table1] in Table1.csv -S %dbServer% -U %dbUser% -P %dbPassword% -w -t ,
bcp [Database1].[dbo].[Table2] in Table2.csv -S %dbServer% -U %dbUser% -P %dbPassword% -w -t ,
 
pause

 
注意点
(1) Windows 認証を使用する場合は、-U および -P の代わりに -T を使用します。
bcp [Database1].[dbo].[Table1] out Table1.csv -S %dbServer% -T -w -t ,
(2) 「-w」の部分は Unicode 形式で出力することを示します。
他の文字エンコーディングで出力する場合は、「-c -C codepage」を指定します。
「-C codepage」を省略した場合は Shift_JIS となります。
例えば、UTF-8 で出力する場合は次のようにします。
bcp [Database1].[dbo].[Table1] out Table1.csv -S %dbServer% -U %dbUser% -P %dbPassword% -c -C 65001 -t ,
ただし、試してみたところ、どの文字エンコーディングでも利用可能というわけではないようです。
利用可: Shift_JIS (932)、JIS (20932)、ISO-2022-JP (50220)、UTF-8 (65001)
利用不可: Unicode (1200)、Unicode ビッグ エンディアン (1201)、EUC-JP (51932)
なお、Excel で CSV ファイルとして開く場合には、Shift_JIS でないと正常に読み込めません。
また、-w、-c の他に、-n (ネイティブ形式) および -N (Unicode ネイティブ形式) があります。
 
(3) 「-t ,」の部分はカンマ区切りであることを示します。省略した場合はタブ区切りとなります。
 
(4) NULL 値は空文字列として出力され、空文字列は \0 (16 進表記で 00 の制御文字) として出力されます。
 
(5) リレーションシップがある場合、親テーブルから先にインポートする必要があります。
 
(6) テーブル名を動的に取得する方法については、PowerShell でデータベースのすべてのデータをエクスポートするをご覧ください。
 
バージョン情報
SQL Server 2005, 2008, 2008 R2
 
 
カテゴリー: データベース. タグ: , . 1 Comment »

データベース初期化スクリプト (SQL Server)

SQL Server 上でデータベースを作成し、ログインを作成し、データベースのユーザーを作成するための一連の Transact-SQL について記述します。
テーブルやインデックスの作成などについては触れません。
SQL Server 認証と Windows 認証では若干異なるので、分割して記述します。
 
■ SQL Server 認証のユーザーを作成する場合
Database1 データベースを作成し、user1 ログインおよび user1 ユーザーを作成するには、以下のようなスクリプトを実行します。

use [master]
go
 
if exists (select name from sys.databases where name = N'Database1')
drop database [Database1]
go
 
create database [Database1]
go
 
if exists (select name from sys.server_principals where name = N'user1')
drop login [user1]
go
 
create login [user1] with password = N'P@ssw0rd', default_database = [Database1]
go
 
use [Database1]
go
 
create user [user1]
go
 
exec sp_addrolemember N'db_owner', N'user1'
go

 
注意点
(1) 同じ名前のデータベースが既に存在するかどうかを調べるには、次のように db_id を使用してもかまいません。
if db_id(N'Database1') is not null
drop database [Database1]
go
(2) exists の逆の判定をするには、次のように is null を使用します。
if (select name from sys.databases where name = N'Database1') is null
(3) 照合順序の指定を省略した場合、日本語版では Japanese_CI_AS が設定されます。
     明示的に指定するには、次のように collate を使用します。
create database [Database1] collate Japanese_CI_AS
go
(4) ユーザーを削除するには次のようにします。
     (今回の場合は、同じ名前のユーザーが既に存在していたとしてもデータベース自体を再作成しているので不要です。)
if exists (select name from sys.database_principals where name = N'user1')
drop user [user1]
go
(5) ログイン名とユーザー名が同じであれば、ユーザーを作成するときに for login を省略できます。
     明示的に指定するには次のようにします。
create user [user1] for login [user1]
go
 
■ Windows 認証のユーザーを作成する場合
Database1 データベースを作成し、XYZPC\ASPNET ログインおよび XYZPC\ASPNET ユーザーを作成するには、
以下のようなスクリプトを実行します。

use [master]
go
 
if exists (select name from sys.databases where name = N'Database1')
drop database [Database1]
go
 
create database [Database1]
go
 
if exists (select name from sys.server_principals where name = N'XYZPC\ASPNET')
drop login [XYZPC\ASPNET]
go
 
create login [XYZPC\ASPNET] from windows with default_database = [master]
go
 
use [Database1]
go
 
create user [XYZPC\ASPNET]
go
 
exec sp_addrolemember N'db_owner', N'XYZPC\ASPNET'
go

 
バージョン情報
SQL Server 2005, 2008, 2008 R2