SQL Server Management Studioから複数台のSQL Serverに同じクエリを実行する方法

# はじめに

負荷分散のためにスケールアウト可能な構成としているケースや、開発が並行で走っているケースといったような、複数台のSQL Serverを運用しているケースではDBの確認や修正のために各サーバーに同じクエリを流したいことがあります。
本記事ではSSMS(SQL Server Management Studio) からどうすれば各サーバーに同じクエリを流せるのか、3種類の方法を紹介します。

- 使用した環境
  - SSMS(SQL Server Management Studio) :18.8
  - Database: SQL Database 
  - データは[AdventureWorks](https://docs.microsoft.com/ja-jp/sql/samples/adventureworks-install-configure?view=sql-server-ver15&tabs=ssms)を使用


# 方法1: サーバーグループを使用する方法

ローカルサーバーグループあるいは中央管理サーバーグループを作成してサーバーを登録し、クエリを実行することで登録したサーバー全てに同じクエリを実行できます。

ローカルサーバーグループと中央管理サーバーグループの使い分けは主には個人で使用するか複数人かであり、情報の保存先が異なります。
また、中央管理サーバーグループはWindows認証を使用してのみ登録が可能です。
https://docs.microsoft.com/en-us/sql/ssms/register-servers/create-a-new-registered-server-sql-server-management-studio?view=sql-server-ver15


以下詳細手順となります。

1.  SSMSの登録済みサーバーを表示します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/394a2722-9ced-1f79-8509-71a57ad1fe3f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/ca7480a0-a3cc-321e-ad18-9691211f7274.png)


1. ローカルサーバーグループor中央管理サーバーグループにサーバーを登録します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/8b017c34-90c2-90e2-eb90-cdabb80a3cb4.png)
サーバーグループを作成するとフォルダ分けのようにまとめることができます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/daa40bf8-1e1d-53a6-e216-676eac36b577.png)
サーバー登録画面でサーバー名や認証情報などを入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/49eade59-05d4-778e-a725-6922e90adf44.png)
1. 作成が完了すると次のようになります。サーバーグループ側を選択して新しいクエリを実行するとクエリエディタが開き、接続数が下のバーに表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/631a69f6-be4b-64f2-e557-d7b091c3a65b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/01be3dc5-2e5a-b15b-479e-0b3d264538a9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/5f7dd987-7c6d-e7e9-1a3e-0bc0e0f2c934.png)
1. このクエリエディタでクエリを書いて実行すると各サーバーに対して実行されます。
SELECTクエリを書いて実行したのが下記図であり、１列目にサーバー名が表示され、2列目以降にSELECTした情報が表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/68923ec3-b4c3-87b7-6bf7-a7a40c90dc65.png)


# 方法2: リンクサーバーを使用する方法

[リンクサーバー](https://docs.microsoft.com/ja-jp/sql/relational-databases/linked-servers/linked-servers-database-engine?view=sql-server-ver15)を使用すると、`SELECT * FROM [サーバー名].[データベース名].[スキーマ名].[テーブル名]` のようにサーバーを指定してクエリを実行することができるようになります。これを利用してサーバー名を変えながらループさせれば各サーバーに同じクエリを実行できます。

以下詳細手順となります。

1. リンクサーバーは「サーバーオブジェクト」の「リンクサーバー」から追加することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/3ed443c4-21fc-f901-2d0c-aa8d87e29a74.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/df503e4b-d687-2e68-7348-ec665311e73d.png)
2. 設定完了後サーバー名を指定してクエリが実行可能となります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/bbf5a70e-ac32-098c-263a-4eb59d8e441c.png)
3. リンクサーバー名の一覧を `sys.servers` から(あるいは`sp_linkedservers`から)取得して、サーバー名を変更しながらループ処理を行うことで同じクエリを各サーバーに流すことが可能となります。

```sql
DECLARE @LinkedServerName varchar(100);

-- リンクサーバーの一覧を取得してカーソル定義
DECLARE CurServer CURSOR FOR SELECT name FROM sys.servers WHERE is_linked = 1;
OPEN CurServer;

FETCH NEXT FROM CurServer INTO @LinkedServerName;
WHILE @@FETCH_STATUS = 0
BEGIN
	SELECT @LinkedServerName;
	EXEC('SELECT * FROM [' + @LinkedServerName + '].[AdventureWorksLT].[SalesLT].[Address]');
FETCH NEXT FROM CurServer INTO @LinkedServerName;
END

CLOSE CurServer;
DEALLOCATE CurServer;
```

# 方法3: SQLCMDを使用してサーバーを切り替える方法

SSMSのメニューからクエリ-> SQLCMDモードを選択するとクエリエディタでSQLCMDの変数やコマンドが使用できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/ddb6e05e-8f50-1114-81a3-964fe3b2aa92.png)
SQLCMDには接続先サーバーを変更するコマンド `:Connect` があるのでそちらを使用すれば１つのSQLの中で接続先を切り替えることが可能です。
[sqlcmd コマンド](https://docs.microsoft.com/ja-jp/sql/tools/sqlcmd-utility?view=sql-server-ver15#sqlcmd-commands)

実際に書いてみたのが下記のSQLです。
SQLCMD変数ExecuteSQL に実行するSQLを変数として持ち、サーバー1, サーバー2, サーバー3に対して接続してそのSQLを実行する構造となっています。

```sql
-- 各サーバーで同じUserName, Passwordを使用しているので一時変数定義
:Setvar UserName {ユーザーネーム}
:Setvar Password {パスワード}
 
-- 各サーバーで実行するSQL
-- Connectでは接続するDBを選択できないことと、サンプル用に用意したDBがSQL Databaseであり、
-- USE句でDBを切り替えられなかったためmasterからアクセス可能な情報のみをSELECTするサンプルとしています
:Setvar ExecuteSQL "SELECT @@ServerName as serverName, name, create_date FROM sys.databases"

:Setvar ServerName {サーバー名1}
:Connect $(ServerName) -U $(UserName) -P $(Password)
EXECUTE ('$(ExecuteSQL)')
GO

:Setvar ServerName {サーバー名2}
:Connect $(ServerName) -U $(UserName) -P $(Password)
EXECUTE ('$(ExecuteSQL)')
GO

:Setvar ServerName {サーバー名3}
:Connect $(ServerName) -U $(UserName) -P $(Password)
EXECUTE ('$(ExecuteSQL)')
GO

```

実際にユーザー名やサーバー名などを入力して実行してみたのが下記画像であり、各サーバーでいつDBが作成されたかという情報が取得できています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299916/5b0817cb-4d73-14ed-eba6-aebff3604a26.png)