SP(ストアドプロシージャ) Tips
MENU
■TOPへ戻る - BLUE-TRANSPARENCY〜限りなく透明に近いブルー〜
■決まりごと
SPの頭に必ずつける決まり文句
■ファイル
ログを書く
■メール
メール設定
メールの送信
複数送信のメールセッション複数開始の回避
■バルクコピー
その1.テーブルの内容をファイルに書き出す
その2.テーブルの内容をファイルに書き出す
その3.ファイルからテーブルに読み込み
その4."文字のある文字列をバルクインサートする
■カーソル
使い方
■テーブル
テーブルロック
レコードセットを返す際の一時テーブル
一時テーブルに中身があるときだけ検索条件に加えるSELECT文
テーブルを縦に繋ぐ(UNION)
■動的クエリ
動的クエリの実行(変数に文字列を入れてそれをクエリとして実行)
■存在確認
DBの存在確認
テーブルの存在確認
一時テーブルの存在確認
プロセスの存在確認
■ヌル(NULL)
SET @ChvTmp = 'abc' + NULL はNULL
■日時
datetime型から年月日を取得
今週の月曜日の取得
秒を何時間:何分:何秒の形式にする
今週の月曜日の日付を求める
月曜日かどうか調べる
1週間前の月曜日を求める
2つの日付の差が1週間か調べる
日時が23時59分59秒か調べる
前の週か調べる
13ヶ月前を求める
時分秒をカットする
日付がたくさんあるテーブルから最大の日付を取得する
■Excel(エクセル)
SP(ストアドプロシージャ)でExcel(エクセル)ファイルを扱うには
ループ内でExcel(エクセル)に書き出す際の注意点
sp_OACreate
sp_OADestroy
sp_OAGetErrorInfo
sp_OAGetProperty
sp_OAMethod
sp_OASetProperty
sp_OAStop
ループ内でExcel(エクセル)に書き出す際の注意点
その1.指定セルへの「シート数指定」、「書込」、「罫線」、「変色」、「幅・高さ」、「結合」、「画像」、「寄せ」、「ハイパーリンク」、「シート名変更」
その2.指定セルへの指定したテーブル名の結果を書込み
考察
■リンクサーバ
リンクサーバ
サーバまたぎ処理でリンクサーバANSINULLS
■クロス集計
行と列を入れ替える
■上位レコード取得
Topと SET ROWCOUNT
■ランキング
振り方
■ソート
カンマ区切り文字列のソート
■待機
一定時間待機(WAIT FOR)
■サーバ名
[-]がある場合
■拡張SP(ストアドプロシージャ)
拡張SP(ストアドプロシージャ)について
"文字のある文字列をバルクインサートする
■エラー
エラー処理
エラー対処
SPが何らかの原因で落ちたとき
■その他
思ったこと
参考文献

■決まりごと
SPの頭に必ずつける決まり文句
SET NOCOUNT ON ( x 件処理されました ) を出力しないようにする。
SET ARITHABORT OFF オーバーフロー、0除算時にエラーにしない。(NULLを返す。)
SET ARITHIGNORE ON 上記時警告メッセージを出さない。

■ファイル
ログを書く
xp_cmdshell と dosコマンド[echo]を使う。

例)
--コマンドの設定
DECLARE @ChvCmdStr varchar(255) --コマンド
DECLARE @ChvOutFile varchar(255) -- ログファイル

SET @ChvOutFile = 'c:\TestTestTest.txt' --ログファイル
SET @ChvCmdStr = 'echo "ログ書き込みテスト: ' + CONVERT(char(10),GETDATE(),120) + ' " >> ' + @ChvOutFile

-- ログファイル出力実行
EXEC @IntErrorCode = master.dbo.xp_cmdshell @ChvTemp, no_OUTPUT

※ 文字列内に "<" などが入らないように注意が必要。その場合は、""で囲む。
(ログには""が出てしまいます。)
※ 文字列内に " がある場合は、"" のようにダブルクォート2つに変える。

■メール
メール設定

コマンドからメールを送るプログラムを xp_cmdshellで実行する方法と、
MAPIを使う方法があります。ここでは、MAPIを使う方法を説明します。

--メールの設定
0:Microsoft OutLook2000 をインストール(Outlook Expressじゃない。)

1:メール送信のための設定(メールプロファイルの設定)
コントロールパネル → メール → ダブルクリックで開く
プロファイルの表示(S) → 追加(D)
インターネット電子メール(チェックボックス)にチェックを入れる → 次へ
プロファイル名を入力 → 次へ
メールアカウントの設定

以下の項目に入力
全般タブ メールアカウント
ユーザ情報(名前、電子メールアドレス、返信アドレス)
サーバタブ 受信メール(POP3)
返信メール(SMTP)
アカウント名
パスワード
OK → 次へ
個人用フォルダ → 次へ → 終了

1.1:サービスの設定:
管理ツール→サービス→MSSQLSERVER 右クリックプロパティ→ログオンタブ アカウントラジオボタンを選択 adminIStratorに設定。

以上で作成したメールプロファイルがSQLServerで使えるようになる。(はず。)

--確認方法
SQLServer EnterprISe manager 起動
サポートサービス → SQLMail 右クリック → プロパティ
1で設定したプロファイルを選択 → テスト
エラーが出なければOK

メールの送信
以下のクエリでメールの送信が出来ます。

EXEC master.dbo.xp_startmaill --メールセッションスタート
--メール送信
EXEC master.dbo.xp_sENDmail @recipients='************'
 ,@subject='test2'
 ,@message='test2'
EXEC master.dbo.xp_stopmail --メールセッションストップ

※ ********* にはメールアドレスを入力。

指定したアドレスにメールが送信されればOK

メール受信時にメッセージの最後に変な文字が入ってしまう場合は、スペースを入れたりして回避してください。
複数送信のメールセッション複数開始の回避

メールセッションがスタートしているときに、xp_startmailでメールセッションをスタートさせようとすると、エラーが生じます。それを回避する方法を示します。

方法1:SQLサーバ起動時に自動実行されるSPを作り、そのなかで「EXEC master.dbo.xp_startmail」を実行しておく方法。SQLServerが動いているときは、
メールセッションを止めないようにして開きっぱなしにしておき、xp_sENDmailでメールを送信する。

方法2:メールを送るたびに、メールセッションを開始し、送信が終わったらメールセッションを止めたい場合。
メール送信SPと送信用のダミーテーブルを作ります。
メールの送信前に、トランザクションをはり、必ずダミーテーブルをロックしてメール送信が終わるまで、他の人は待たせる。

■バルクコピー
その1.テーブルの内容をファイルに書き出す
DECLARE @BCPSTR varchar(4000)
-- FactDB.dbo.ContentsFactTABLEを丸々ファイルに落とす場合
SET @BCPSTR = 'bcp FactDB.dbo.ContentsFactTABLE out C:\FactDB_ContentsFactTABLE.txt -e C:\SQLWORK\FactDB_ContentsFactTABLEerror.txt -c -t, -U sa -P -o C:\SQLWORK\FactDB_ContentsFactTABLE.out'
EXECUTE master.dbo.xp_cmdshell @BCPSTR
その2.テーブルの内容をファイルに書き出す
DECLARE @BCPSTR varchar(4000)
SET @BCPSTR = 'bcp "SELECT A.ContentsFactID, A.AccessTime, A.RefSec, A.DayOfWeekID, A.HourID, A.StoredTime, A.TermID, A.UserID, A.ExeNameID, A.FileVersionID, '
+' A.ProtocolID, A.ClickedDomainID, A.ClickedSubDomainID, A.ClickedURLID, A.MetaFileDomainID, A.MetaFileSubDomainID, A.MetaFileURLID, '
+' A.MetaFileExtensionID, A.ContentsDomainID, A.ContentsSubDomainID, A.ContentsURLID, A.ContentsExtensionID, A.MetaTitleID, A.MetaAuthorID, '
+' A.MetaCopyrightID, A.AreaCode1ID, A.PrefectureCodeID, A.SexAgeCode1ID, A.SexAgeCode2ID, A.SexAgeCode3ID, A.Age, A.Sex, A.BusinessCode1ID, '
+' A.BusinessCode '
+' FROM '+@ChvPreServerName+'.FactDB.dbo.ContentsFactTABLE AS A, '+@ChvPreServerName+'.WebReportDB.dbo.WebPanelInUserInfoTABLE AS B '
+' WHERE B.UserID = A.UserID and '+CAST(@IntMaxSELECTedContentsFactID AS CHAR)+' < A.ContentsFactID " queryout C:\FactDB_ContentsFactTABLE.txt -e C:\FactDB_ContentsFactTABLEerror.txt -c -t , -S '+@ChvOrigenalPreServerName+' -U '+@ChvPreID+' -P '+@ChvPrePass+' -o C:\FactDB_ContentsFactTABLE.out'
EXECUTE master.dbo.xp_cmdshell @BCPSTR
その3.ファイルからテーブルに読み込み
DECLARE @BCPSTR varchar(4000)
SET @BCPSTR = 'bcp FactDB.dbo.SELECTedContentsFactTABLE in C:\FactDB_ContentsFactTABLE.txt -c -t, -S '+@ChvOrigenalPreServerName+' -U '+@ChvPreID+' -P '+@ChvPrePass+' -o C:\FactDB_ContentsFactTABLE.out'
EXECUTE master.dbo.xp_cmdshell @BCPSTR
その4."文字のある文字列をバルクインサートする
出来ません。

と、言うのも冷たいので以下を参照して下さい。
頑張りまくれば可能です。
拡張SP(ストアドプロシージャ):"文字のある文字列をバルクインサートする

■カーソル
使い方
カーソルはSELECT分を1レコードずつ回したいときに使います。(レコードセットで一気にとらずに、レコード一つずつとりたいとき)

例)#tmpSamplesという一時テーブルに日付が入っているとします。

DECLARE @CrsrTmpSample cursor --サンプル数計算時に使用するカーソル
DECLARE @DtmTmpDateID --カーソルでとった値を入れる

--カーソルをセットします。
SET @CrsrTmpSamples = cursor for
 SELECT DateID
 FROM #tmpSamples

--カーソルを開きます。
open @CrsrTmpSamples

--レコードセットから1レコード取得します。
fetch next FROM @CrsrTmpSamples
INTO @DtmTmpDateID

WHILE( @@FETCH_STATUS = 0 ) --レコードセットの最後までループ
BEGIN
 --ここで加工などを行う。
 print @DtmTmpDateID

 --次のレコードを取得します。
 fetch next FROM @CrsrTmpSamples
 INTO @DtmTmpDateID

END --END WHILE

-- カーソルを閉じる
close @CrsrTmpSamples
deallocate @CrsrTmpSamples

■テーブル
テーブルロック
[WITH]オプションを使うことによってテーブルのロックができます。

例1)
以下の例では、このSELECT文が終了するまで、他の人はTestTABLEの参照、更新が出来なくなります。

SELECT * FROM TestTABLE WITH (TABLOCKX)



例2)
トランザクションをはってその中でロックを行った場合は、TestTABLEはトランザクションが終了するまで、分離レベルを無視してロックされたままになります。
(トランザクション、分離レベルについては、SQLServerのBooksOnlineを参照。)

start tran

SELECT * FROM TestTABLE WITH (TABLOCKX)

commit tran --コミットする。
レコードセットを返す際の一時テーブル

ASPからストアドを実行し、レコードセットを取得する際などにおいて。
ストアド内で、一時テーブルを使っている場合、一時テーブルの中身をレコードセットとして受け取ることはできません。
一時テーブルをWHERE句の条件として使うことは可能です。

例1) ストアドで返すレコードセットが以下のような場合は、レコードセットを受け取ることができません。

SELECT * FROM #tmpTest



例2) 以下のようにWHERE句の条件として使う場合は、レコードセットを受け取ることが出来ます。

SELECT Test.a,Test.b FROM TestTABLE,#tmpTest WHERE Test.c = #tmpTest.c

一時テーブルに中身があるときだけ検索条件に加えるSELECT文
曜日番号の入った一時テーブル #tmpWeekID があります。
このテーブルで指定した曜日のデータを取りたい場合、以下のようなクエリを書きます。

 SELECT * FROM MainTABLE M
 WHERE M.WeekID IN (SELECT WeekID FROM #tmpWeekID))

しかし、この場合 #tmpWeekID の中身が空の場合は、レコードが1件も取れません。中身が空の場合は全曜日を対象としたい場合は、以下のようなクエリを書きます。

 SELECT *
 FROM MainTABLE M
 WHERE ( not exISts(SELECT * FROM #tmpWeekID )
 or (M.WeekID IN (SELECT WeekID FROM #tmpWeekID)) )

これで、#tmpWeekIDの中身が空のときは全曜日が対象になり、中身がある場合はその曜日が対象になります。
テーブルを縦に繋ぐ(UNION)
UNION を使ってください。

例)
SELECT A,B FROM TestATABLE
union
SELECT A,B FROM TestBTABLE
ORDER BY A

※ただし ORDER BYは最後にしか付けられないので注意!!

■動的クエリ
動的クエリの実行(変数に文字列を入れてそれをクエリとして実行)

sp_EXECutesql を使います。これによって通常のクエリでは動的に指定できないDB名,テーブル名,列名なども可変にできます。
また、引数を渡すことも可能です。(引数として使えるのは、通常のクエリと同じもののみです。DB名等を引数として使うことは出来ません。)

例)
--------------------------------------------------------------
-- 変数宣言
--------------------------------------------------------------
DECLARE @ChnSqlString nvarchar(4000) --動的クエリ用変数
DECLARE @ChnSqlParam nvarchar(4000) --動的クエリのパラメータ用変数
DECLARE @ChvDBName -- DB名
 SET @ChvDBName = 'TESTDB'
DECLARE @IntParam1 int --パラメータ1
 SET @IntParam1 = 10
DECLARE @IntParam2 int --パラメータ2 OUTPUT
--------------------------------------------------------------
-- クエリ作成
--------------------------------------------------------------
SET @ChnSqlString = N'SELECT @IntParam2 = ranking FROM [' + @ChvDBName + '].[dbo].[testTABLE] WHERE id = @IntParam1'

SET @ChnSqlParam = N'@IntParam1 int,@IntPram2 int OUTPUT'

--------------------------------------------------------------
-- クエリ実行
--------------------------------------------------------------
EXEC sp_EXECutesql @ChnSqlString,@ChnSqlParam,@IntParam1,@IntParam2 OUTPUT

■存在確認
DBの存在確認法
--master..sysdatabasesテーブルを見ることで確認できます。
IF exISts( SELECT * FROM master.dbo.sysdatabases WHERE name = 'TESTDB' )
BEGIN
 print 'DBあり'
END
テーブルの存在確認法
--調べたいDBのsysobjectsテーブルを見ることで確認できます。
IF exISts(SELECT * FROM TESTDB.dbo.sysobjects WHERE id = object_id ( N'[TESTDB].[dbo].[TESTTABLE]') and xtype ='U')
BEGIN
 print 'テーブルあり'
END
一時テーブルの存在確認法
-- tmpdb..sysobjects を見ることで確認できます。
-- その際に OBJECT_IDを使ってIDを取得する必要があります。
IF exISts(SELECT * FROM tempdb.dbo.sysobjects WHERE id = OBJECT_ID('tempdb.dbo.#tmpTESTTABLE') and xtype = 'U')
BEGIN
 print '一時テーブルあり'
END
プロセスの存在確認

自分のプロセスID(spid)は @@SPID で取得できます。
スレッドID(kpid)は master.dbo.sysprocesses からSPIDをキーにして検索し取得できます。

プロセスがあるかどうかは、このようにチェックできます。sysprocessedテーブルに該当するspidとkpidのレコードが存在すれば、そのプロセスが存在していることになります。

例)
IF ExISts(SELECT * FROM master.dbo.sysprocesses WHERE spid = 53 and kpid = 114 )
BEGIN
 print 'プロセスあり'
END

フォルダの存在確認
xp_cmdshell と DOSコマンド "dir" を使います。

例)
DECLARE @ChvCmd varchar(8000) -- DOSコマンド文字列
DECLARE @IntRet int -- DOSコマンドの戻り値
SET @ChvCmd = 'dir ' + @ChvLogFilePath + ' /b'
EXEC @IntRet = master.dbo.xp_cmdshell @ChvCmd, no_OUTPUT
IF @@ERROR <> 0
BEGIN
 print '指定したフォルダを見つけられません。'
END

■ヌル(NULL)
SET @ChvTmp = 'abc' + NULL はNULL
NULL を足すと NULL になります。数値も同じです。

■日時
datetime型から年月日を取得
DECLARE @DtmTmp datetime
SET @DtmTmp = GETDATE()

SELECT CONVERT(char(10),@DtmTmp,111)
今週の月曜日の取得
指定年月日の週の月曜日の日付を取得するサンプルです。
指定年月日が日曜の時は、前の週の月曜日を取得します。

DECLARE @DtmTargetDate datetime -- 指定年月日
DECLARE @DtmMonday datetime --指定年月日の週の月曜日の日付
DECLARE @INTOffSET int --指定年月日の週の月曜日を求める為に使用

-------------------------------------------------------
-- 指定年月日の週の月曜日を取得
-------------------------------------------------------
-- DATEPARTの戻り値(日曜日:1,土曜日:7)
SET @INTOffSET = DATEPART(weekday, @DtmTargetDate)
-- 日曜日だけ特別に[6]に、他の日は[曜日-2]にすることで、前週の月曜日を求める。
IF @INTOffSET = 1 BEGIN
SET @INTOffSET = 6
END
ELSE BEGIN
SET @INTOffSET = @INTOffSET - 2
END
SET @DtmMonday = DATEADD(DAY, -1 * @INTOffSET, @DtmTargetDate)

SELECT @DtmMonday
秒を何時間:何分:何秒の形式にする
50000秒 → 13:53:20(13時間53分20秒) に変換したいとき。
下記のようなクエリを使います。(ユーザ定義関数にしてもいい。)

DECLARE @IntMinute -- 秒
SET @IntMinute = 5000
DECLARE @IntTmp
SET @IntTmp = DATEADD(second,@IntMinute,0) --1900/1/1 **:**:**の形にする。

SELECT
CONVERT(varchar,DATEDIFF( hour,0,@IntTmp)) --[時] 1900/1/1 00:00:00 との差
+ ':' + RIGHT('00' + CONVERT(varchar,DATEPART( minute,@IntTmp)),2) --[分]
+ ':' + RIGHT('00' +CONVERT(varchar,DATEPART( second,@IntTmp)),2)  --[秒]
今週の月曜日の日付を求める
DECLARE @DtmToday DATETIME
SET @DtmToday = GETDATE()
-- DATEPART:1日 2月 3火 4水 5木 6金 7土
IF DATEPART( WEEKDAY, @DtmToday ) = 1
 SET @DtmToday = DATEADD( DD, 1, @DtmToday )
ELSE IF DATEPART( WEEKDAY, @DtmToday ) = 3
 SET @DtmToday = DATEADD( DD, -1, @DtmToday )
ELSE IF DATEPART( WEEKDAY, @DtmToday ) = 4
 SET @DtmToday = DATEADD( DD, -2, @DtmToday )
ELSE IF DATEPART( WEEKDAY, @DtmToday ) = 5
 SET @DtmToday = DATEADD( DD, -3, @DtmToday )
ELSE IF DATEPART( WEEKDAY, @DtmToday ) = 6
 SET @DtmToday = DATEADD( DD, -4, @DtmToday )
ELSE IF DATEPART( WEEKDAY, @DtmToday ) = 7
 SET @DtmToday = DATEADD( DD, -5, @DtmToday )
PRINT @DtmToday
月曜日かどうか調べる
DECLARE @DtmToday DATETIME
SET @DtmToday = GETDATE()
IF DATEPART( WEEKDAY, @DtmToday ) = 2
BEGIN
 PRINT '月曜日です。'
END
ELSE
BEGIN
 PRINT '月曜日ではありません。'
END
1週間前の月曜日を求める
DECLARE @DtmDate DATETIME
SET @DtmDate = GETDATE()
IF DATEPART( WEEKDAY, @DtmDate ) != 1 -- 日曜以外
BEGIN
 SET @DtmDate = ( DATEADD( DD, -( 5 + DATEPART( WEEKDAY,@DtmDate ) ), @DtmDate ) )
END
ELSE
IF DATEPART( WEEKDAY, @DtmDate ) = 1 -- 日曜
BEGIN
 SET @DtmDate = DATEADD( dd, -13, @DtmDate )
END
2つの日付の差が1週間か調べる
DECLARE @DtmFIStDate DATETIME
DECLARE @DtmSecondDate DATETIME
IF DATEDIFF( DAY, @DtmFIStDate, @DtmSecondDate ) = 6
BEGIN
 PRINT '差は1週間です。'
END
ELSE
BEGIN
 PRINT '差は1週間ではありません。'
END
日時が23時59分59秒か調べる
DECLARE @DtmDate DATETIME
SET @DtmDate = GETDATE()
IF DATEPART( HOUR, @DtmDate ) <> 23 or
DATEPART( MINUTE, @DtmDate ) <> 59 or
DATEPART( SECOND, @DtmDate ) < 59
BEGIN
 PRINT '23時59分59秒ではありません。'
END
ELSE
BEGIN
 PRINT '23時59分59秒です。'
END
前の週か調べる
SET @DtmDate = DATEADD( DAY, -1, @DtmDate )

DECLARE @DtmTmpToday DATETIME
SET @DtmTmpToday = DATEADD( DAY, -1, GETDATE() )

IF DATEDIFF( WEEK, @DtmDate, @DtmTmpToday ) <> 1
BEGIN
 PRINT '先週の日付ではありません。'
END
ELSE
BEGIN
 PRINT '先週の日付です。'
END
13ヶ月前を求める
DECLARE @DtmDate DATETIME
SET @DtmDate = GETDATE()
SET @DtmDate = ( DATEADD( MM ,-13, @DtmDate ) )
時分秒をカットする
DECLARE @DtmDate DATETIME
SET @DtmDate = CONVERT( CHAR(10), @DtmDate, 111 )
日付がたくさんあるテーブルから最大の日付を取得する
DECLARE @DtmUsableDate DATETIME
DECLARE @ChvUsableDBName VARCHAR (255)
SELECT UsableDate, UsableDBName
FROM ASPDB.dbo.CategoryChangeTABLE
WHERE ( UsableDate = ( SELECT MAX( UsableDate ) FROM ASPDB.dbo.CategoryChangeTABLE ) )

例:テーブルレイアウト
ASPDB.dbo.CategoryChangeTABLE
UsableDate:Datetime
UsableCategoryName:VARCHAR

■Excel(エクセル)
SP(ストアドプロシージャ)でExcel(エクセル)ファイルを扱うには
SP(ストアドプロシージャ)内でExcel(エクセル)ファイルを扱うには、COMを使用します。
COMといってもC++、VC++などのCOMと違い、予め用意されたExcelのCOMを操作するSPを使用します。
これはExcel(エクセル)だけに特化したことではなく、COMで作成されたアプリケーションはこのようなCOMを扱うSPをコールして操作します。
COMを扱うSP(ストアドプロシージャ)には以下のものが存在します。
・sp_OACreate
・sp_OADestroy
・sp_OAGetErrorInfo
・sp_OAGetProperty
・sp_OAMethod
・sp_OAStop
・sp_OASetChvProperty
sp_OACreate
OLE オブジェクトのインスタンスを作成します。

■構文
sp_OACreate { progid | clsid } , objecttoken OUTPUT [ , context ]



■引数
progid
作成する OLE オブジェクトのプログラム ID (ProgID) を指定します。これは OLE オブジェクトのクラスを説明する文字列で、'OLEComponent.Object' の形式で指定します。

OLEComponent は OLE オートメーション サーバーのコンポーネント名で、Object は OLE オブジェクト名です。指定した OLE オブジェクトは有効かつ IDispatch インターフェイスをサポートする必要があります。

たとえば、SQLDMO.SQLServer は SQL-DMO SQLServer オブジェクトの ProgID です。SQL-DMO は SQLDMO のコンポーネント名であり、SQLServer オブジェクトは有効かつすべての SQL-DMO オブジェクトと同様に IDispatch インターフェイスをサポートしています。

clsid
作成する OLE オブジェクトのクラス ID (CLSID) を指定します。これは OLE オブジェクトのクラスを説明する文字列で、
'{nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}' の形式で指定します。
指定した OLE オブジェクトは有効かつ IDispatch インターフェイスをサポートする必要があります。

objecttoken
返されるオブジェクト トークンです。int 型 (整数型) のローカル変数になります。このオブジェクト トークンは、作成する OLE オブジェクトを識別するもので、その他の OLE オートメーション ストアド プロシージャの呼び出しに使用されます。

context
新しく作成した OLE オブジェクトを実行するときの実行条件を指定します。指定する場合は、次のいずれかの値を指定する必要があります。

1 = 組み込み (.dll) OLE サーバーのみ。

4 = ローカル (.exe) OLE サーバーのみ。

5 = 組み込みおよびローカル OLE サーバーの両方が有効。

値を指定しない場合は、既定値 5 が使用されます。この値は、CoCreateInstance への呼び出しの dwClsContext パラメータとして渡されます。

context 値に 1 または 5 を使用するか、context 値を指定しないで、組み込み OLE サーバーを有効にした場合は、SQL Server が所有するメモリおよびその他のリソースにアクセスできます。組み込み OLE サーバーは、SQL Server のメモリやリソースに損傷を与え、SQL Server のアクセス違反など予期しない結果を招く場合があります。

context 値に 4 を指定した場合、ローカル OLE サーバーは SQL Server のどのリソースにもアクセスすることなく、SQL Server のメモリやリソースに損傷を与えることはありません。



■戻り値
成功した場合は 0、失敗した場合は OLE オートメーション オブジェクトによって返される HRESULT の 0 以外の整数値を返します。
sp_OADestroy
作成した OLE オブジェクトを破棄します。

■構文
sp_OADestroy objecttoken



■引数
objecttoken
以前に sp_OACreate で作成した OLE オブジェクトのオブジェクト トークンを指定します



■戻り値
成功した場合は 0 を、失敗した場合は OLE オートメーションが返す HRESULT の 0 以外の整数値をそれぞれ返します。
sp_OAGetErrorInfo
OLE オートメーションのエラー情報を取得します。

■構文
sp_OAGetErrorInfo [ objecttoken ] [ ,source OUTPUT ] [ ,description OUTPUT ] [ ,helpfile OUTPUT ] [ ,helpid OUTPUT ]



■引数
objecttoken
以前に sp_OACreate で作成した OLE オブジェクトのオブジェクト トークン、または NULL を指定します。
objecttoken を指定した場合、そのオブジェクトのエラー情報が返されます。
NULL を指定した場合は、そのバッチ全体のエラー情報が返されます。

source
エラー情報のソースです。指定する場合、値はローカルの char、nchar、varchar、または nvarchar 型の変数にする必要があります。
戻り値は必要に応じてローカル変数のサイズに切り捨てられます。

description
エラーの説明です。指定する場合、値はローカルの char、nchar、varchar、または nvarchar 型の変数にする必要があります。
戻り値は必要に応じてローカル変数のサイズに切り捨てられます。

helpfile
OLE オブジェクトのヘルプ ファイルです。指定する場合、値はローカルの char、nchar、varchar、または nvarchar 型の変数にする必要があります。
戻り値は必要に応じてローカル変数のサイズに切り捨てられます。

helpid
ヘルプ ファイルのコンテキスト ID です。指定する場合、値はローカルの int 型の変数にする必要があります。



■戻り値
成功した場合は 0、失敗した場合は OLE オートメーション オブジェクトによって返される HRESULT の 0 以外の整数値を返します。
sp_OAGetProperty
OLE オブジェクトのプロパティ値を取得します。

■構文

sp_OAGetProperty objecttoken , propertyname [ ,propertyvalue OUTPUT ] [ , index...]



■引数
objecttoken
以前に sp_OACreate で作成した OLE オブジェクトのオブジェクト トークンです

propertyname
返される OLE オブジェクトのプロパティ名です。

propertyvalue
返されるプロパティ値です。指定する場合は、適切なデータ型のローカル変数でなければなりません。

プロパティが OLE オブジェクトを返す場合は、propertyvalue は int 型のローカル変数であることが必要です。
オブジェクト トークンがローカル変数に格納され、このオブジェクト トークンを他の OLE オートメーション ストアド プロシージャで使用できます。

プロパティが単一の値を返す場合、propertyvalue にローカル変数を指定すると、プロパティ値がローカル変数で返されます。
propertyvalue を指定しないと、プロパティ値が、単一列、単一行の結果セットとしてクライアントに返されます。

プロパティが配列を返すときに、propertyvalue を指定する場合は、NULL を設定します。

propertyvalue が指定され、プロパティが値を返さない場合は、エラーが発生します。
プロパティが 3 次元以上の配列を返す場合は、エラーが発生します。

index
インデックス パラメータです。指定する場合、index は適切なデータ型の値にする必要があります。

プロパティの一部はパラメータを持っています。
このようなプロパティをインデックス付きプロパティ、パラメータをインデックス パラメータと呼びます。
1 つのプロパティが複数のインデックス パラメータを持つことができます。



■戻り値
成功した場合は 0 を、失敗した場合は OLE オートメーションが返す HRESULT の 0 以外の整数値をそれぞれ返します。
sp_OAMethod
OLE オブジェクトのメソッドを呼び出します。

■構文
sp_OAMethod objecttoken , methodname [ , returnvalue OUTPUT ]     [ , [ @parametername = ] parameter [ OUTPUT ] [ ...n ] ]



■引数
objecttoken
以前に sp_OACreate で作成した OLE オブジェクトのオブジェクト トークンです。

methodname
呼び出す OLE オブジェクトのメソッド名です。

returnvalue
OLE オブジェクトのメソッドの戻り値です。指定する場合は、適切なデータ型のローカル変数でなければなりません。

メソッドが単一の値を返す場合は、returnvalue にローカル変数を指定すると、メソッドの戻り値をローカル変数に返します。
returnvalue を指定しない場合は、メソッドの戻り値を単一列、単一行の結果セットとしてクライアントに返します。

メソッドの戻り値が OLE オブジェクトである場合、returnvalue のローカル変数は int 型 (整数型) であることが必要です。
オブジェクト トークンがローカル変数に格納され、このオブジェクト トークンを他の OLE オートメーション ストアド プロシージャで使用できます。

メソッドの戻り値が配列の場合に returnvalue を指定すると、NULL に設定されます。

次のいずれかの状況になると、エラーが発生します。
returnvalue が指定され、メソッドが値を返さない場合
・メソッドが 3 次元以上の配列を返す場合
・メソッドが出力パラメータとして配列を返す場合

[ @parametername = ] parameter
メソッド パラメータです。指定する場合、parameter は適切なデータ型の値にする必要があります。

出力パラメータの戻り値を取得するには、parameter は適切なデータ型のローカル変数で、OUTPUT を指定する必要があります。
定数 parameter を指定する場合、または OUTPUT を指定しない場合は、出力パラメータからの戻り値は無視されます。

指定した場合、parametername は、Microsoft Visual Basic の名前付きパラメータの名前にする必要があります。
@parametername は、Transact-SQL のローカル変数ではありません。
アット マーク (@) は削除され、parametername はパラメータ名として OLE オブジェクトに渡されます。
すべての名前付きのパラメータは、位置で決まるパラメータをすべて指定した後で指定する必要があります。

n
複数のパラメータを指定できることを示すプレースホルダです。



■戻り値
成功した場合は 0 を、失敗した場合は OLE オートメーションが返す HRESULT の 0 以外の整数値をそれぞれ返します。
sp_OASetProperty
OLE オブジェクトのプロパティ値に新しい値を設定します。

■構文
sp_OASetProperty objecttoken , propertyname , newvalue [ , index... ]



■引数
objecttoken

以前に sp_OACreate で作成した OLE オブジェクトのオブジェクト トークンを指定します。

propertyname
新しい値を設定する OLE オブジェクトのプロパティ名を指定します。

newvalue
プロパティの新しい値を指定します。指定する場合は、適切なデータ型の値にする必要があります。

index
インデックス パラメータです。
指定する場合、index は適切なデータ型の値にする必要があります。

プロパティの一部はパラメータを持っています。
このようなプロパティをインデックス付きプロパティと呼び、そのパラメータをインデックス パラメータと呼びます。
1 つのプロパティが複数のインデックス パラメータを持つことができます。



■戻り値
成功した場合は 0、失敗した場合は OLE オートメーション オブジェクトによって返される HRESULT の 0 以外の整数値を返します。
sp_OAStop
サーバー全体の OLE オートメーション ストアド プロシージャの実行環境を停止します。

■構文
sp_OAStop



■引数
なし。



■戻り値
成功した場合は 0 を、失敗した場合は OLE オートメーション オブジェクトによって返される HRESULT の 0 以外の整数値をそれぞれ返します。
ループ内でExcel(エクセル)に書き出す際の注意点
ループ内でExcel(エクセル)に書き込む場合は注意が必要です。
VC++などではCOMはプログラムの初めに一度だけ初期化し、必要のなくなって時点で終了させますが、SP(ストアドプロシージャ)内ではこれが通じません。

例)
COMの初期化

ループ開始
 Excel(エクセル)操作
ループ終了

COMの終了



上記のようなコーディングではOAエラーが発生します。(なぜだかは不明。)
以下のようなコーディングでこれを回避可能です。

例)
・・・

ループ開始
 COMの初期化
 Excel(エクセル)操作
 COMの終了
ループ終了

・・・
その1.指定セルへの「シート数指定」、「書込」、「罫線」、「変色」、「幅・高さ」、「結合」、「画像」、「寄せ」、「ハイパーリンク」、「シート名変更」
--/////////////////////////////////////////////////////////////////////////////////////////
-- ファイル名 :ExcelTest1
-- 概要 :
-- 指定セルへの
-- 「シート数指定」、「書込」、「罫線」、「変色」、「幅・高さ」、「結合」、「画像」、「寄せ」、「ハイパーリンク」、「シート名変更」
-- を行う。
--
-- 戻り値 :
-- :0:正常終了
-- :0以外:異常終了
--
-- SP存在サーバ名:BLUE-TRANSPARENCY
-- 参照テーブル :なし
-- 作成テーブル :なし
-- 参照SP :
-- master.dbo.xp_cmdshell
-- sp_OACreate, sp_OAGetProperty, sp_OAMethod, sp_OASetProperty, sp_OAGetProperty, sp_OAGetErrorInfo, sp_OADestroy
--
-- 作成日:2006/10/18 asuka
-- コピーライト:BLUE-TRANSPARENCY〜限りなく透明に近いブルー〜
--/////////////////////////////////////////////////////////////////////////////////////////
CREATE PROCEDURE [dbo].[ExcelTest1]

AS
-- ////////////////////////////////////////////////////////////////////////////////////
-- 初期化
-- ////////////////////////////////////////////////////////////////////////////////////
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
SET ANSI_NULLS ON

-- ////////////////////////////////////////////////////////////////////////////////////
-- 変数とカーソルの宣言
-- ////////////////////////////////////////////////////////////////////////////////////
DECLARE @ChvFolderPath VARCHAR(100) -- エクセル格納先フォルダフルパス
DECLARE @ChvExcelFileName VARCHAR(100) -- エクセルファイルフルパス名

DECLARE @IntObject INTEGER -- オブジェクトの引数
DECLARE @IntHResult INTEGER -- オブジェクトの戻値
DECLARE @ChvProperty VARCHAR(255) --
DECLARE @return VARCHAR(255) --
DECLARE @return_int INTEGER --
DECLARE @objResults INTEGER -- sp_OAMethodのOUT PUT値

DECLARE @col VARCHAR(100) -- 未使用
DECLARE @row VARCHAR(100) -- 未使用
DECLARE @num_cols INTEGER -- セレクトしたテーブルのレコードセットの行(カラム)数
DECLARE @num_rows INTEGER -- セレクトしたテーブルのレコードセットの列数

DECLARE @resultados VARCHAR(255) --
DECLARE @pba VARCHAR(255) --
DECLARE @indRow INTEGER -- 取得したレコードセットの何行目から書込むか指定 1行目が0にあたる
DECLARE @indColumn INTEGER --

DECLARE @ObjExcel INTEGER -- エクセルオブジェクト
DECLARE @WorkBooks INTEGER -- エクセルワークブックスオブジェクト
DECLARE @WorkBook INTEGER -- エクセルワークブックオブジェクト

DECLARE @offrow INTEGER --
DECLARE @offcolumn INTEGER --
DECLARE @rango VARCHAR(100) --
DECLARE @Range INTEGER -- セルのプロパティオブジェクト
DECLARE @ChvQuerry VARCHAR(255) -- SQLクエリ文字列

-- ////////////////////////////////////////////////////////////////////////////////////
-- フォルダ作成 - エクセル格納先
-- ////////////////////////////////////////////////////////////////////////////////////
-- ファイル名作成
DECLARE @ChvYYYYMMDD VARCHAR(10)
DECLARE @ChvYYYY VARCHAR(8)
DECLARE @ChvMM VARCHAR(8)
DECLARE @ChvDD VARCHAR(8)
SET @ChvYYYY = DATENAME( year, GETDATE() )
SET @ChvMM = DATENAME( month, GETDATE() )
SET @ChvDD = DATENAME( day, GETDATE() )
SET @ChvDD = '0' + @ChvDD
SET @ChvDD = RIGHT( @ChvDD, 2 )
SET @ChvYYYYMMDD = @ChvYYYY + @ChvMM + @ChvDD

-- フォルダパス設定
SET @ChvFolderPath = 'C:\'+@ChvYYYYMMDD+''

-- DOSコマンド
DECLARE @ChvCmd VARCHAR(255)
DECLARE @IntRet INTEGER

-- ディレクトリ存在チェック
SET @ChvCmd = ' dir ' + @ChvFolderPath + ' /b'
EXEC @IntRet = master.dbo.xp_cmdshell @ChvCmd, no_output
IF @@ERROR != 0
BEGIN
 PRINT 'フォルダ存在チェックでエラーが発生しました。'
 RETURN @IntRet
END

-- ディレクトリが存在しない場合
IF @IntRet != 0
BEGIN
 -- ディレクトリ作成
 SET @ChvCmd = ' md ' + @ChvFolderPath + ''
 EXEC @IntRet = master.dbo.xp_cmdshell @ChvCmd, no_output
 IF @@ERROR != 0
 BEGIN
  PRINT 'ディレクトリの作成に失敗しました。@@ERROR != 0'
  RETURN @IntRet
 END

 IF @IntRet != 0
 BEGIN
  PRINT 'ディレクトリの作成に失敗しました。@IntRet != 0'
 END
END

-- エクセルファイル名作成(フルパス)
SET @ChvExcelFileName = ''+@ChvFolderPath+'\ExcelTest1.xls'

-- ////////////////////////////////////////////////////////////////////////////////////
-- オブジェクト生成
-- ////////////////////////////////////////////////////////////////////////////////////
EXEC @IntHResult = sp_OACreate 'SQLDMO.SQLServer', @IntObject OUT
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OACreateに失敗しました。'
 RETURN @IntHResult
END

-- ////////////////////////////////////////////////////////////////////////////////////
-- メソッドコール - sp_OAMethod
-- ////////////////////////////////////////////////////////////////////////////////////
-- データベースサーバ接続
EXEC @IntHResult = sp_OAMethod @IntObject, 'Connect', NULL,'FUJICHAWA', 'sa',''
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OAMethodに失敗しました。(データベースサーバ接続失敗)'
 RETURN @IntHResult
END

-- 接続確認
EXEC @IntHResult = sp_OAMethod @IntObject, 'VerifyConnection', @return OUT
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OAMethodに失敗しました。(データベースサーバ接続確認失敗)'
 RETURN @IntHResult
END

-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセル書込み - セル指定
-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルオブジェクト生成
EXEC @IntHResult = sp_OACreate 'Excel.Application', @ObjExcel OUT

-- エクセルにワークブックプロパティ取得
EXEC @IntHResult = sp_OAGetProperty @ObjExcel ,'WorkBooks', @WorkBooks OUT

--シート数設定
EXEC @IntHResult = sp_OASetProperty @WorkBooks,'Application.SheetsInNewWorkbook','5'

-- エクセルワークブックのアッドプロパティ取得
EXEC @IntHResult = sp_OAGetProperty @WorkBooks,'Add', @WorkBook OUT
EXEC @IntHResult = sp_OAMethod @WorkBooks,'ActiveSheet.Hyperlinks.Add',NULL,'Selection','"http://www.yahoo.co.jp"',NULL,NULL,'"Yahoo"'

-- 指定セル書込み
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A1")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', 'ExcelTest3 - 指定したセルに書き込み'
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A2")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', 'Copyright Fujisaw Yohei'
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A3")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', 'Copyright (C)2003 Video Research Netcom Ltd. All Rights Reserved.'

-- 太線
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A4")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', '太線'
EXEC @IntHResult = sp_OASetProperty @Range,'Font.Bold', 'TRUE'


-- 罫線
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("B7")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Borders(7).LineStyle', '1'
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("C7")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Borders(8).LineStyle', '1'
EXEC @IntHResult = sp_OASetProperty @Range,'Borders(9).LineStyle', '1'

-- 色変更
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("B10")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Interior.ColorIndex', '3'

-- 幅変更
-- 幅
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("B:B")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'ColumnWidth', '22'
-- 高
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("9:9")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'RowHeight', '22'

-- セルの結合 - TRUE:結合する FALSE:結合しない
-- MergeだとTRUEでもFALSEでも結合してしまう。
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("D4:E6")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'MergeCells', 'TRUE'

-- 画像 - sp_OASetPropertyにエクセルオブジェクト指定のこと
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("F3").Select',@Range OUT
EXEC @IntHResult = sp_OASetProperty @ObjExcel,'ActiveSheet.Pictures.Insert("C:\dora6.gif").Select', '1'

-- 寄せ
-- 左寄せ
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A14")', @Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', '左寄せ'
EXEC @IntHResult = sp_OASetProperty @Range,'HorizontalAlignment', '2'
-- 右寄せ
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A15")', @Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', '右寄せ'
EXEC @IntHResult = sp_OASetProperty @Range,'HorizontalAlignment', '4'
-- 中央寄せ
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("A16")', @Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', '中央'
EXEC @IntHResult = sp_OASetProperty @Range,'HorizontalAlignment', '3'
*/
-- ハイパーリンク
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("C22")',@Range OUT
EXEC @IntHResult = sp_OASetProperty @Range,'Value', '=HYPERLINK( "http://www.yahoo.co.jp" , "いけてる?" )'

-- ↓駄目なハイパーリンク
--EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Range("C22")',@Range OUT
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', '', '', '', ''
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', '', ''
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', ''
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp'
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp'
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, '"http://www.yahoo.co.jp","http://www.goo.ne.jp"'
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp,http://www.goo.ne.jp'
--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', 'http://www.goo.ne.jp', 'http://www.yahoo.co.jp'

--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', '', '', '', ''
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', '', ''
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp', ''
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'ActiveSheet.Hyperlinks.Add', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp'
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp'
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, '"http://www.yahoo.co.jp","http://www.goo.ne.jp"'
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp,http://www.goo.ne.jp'
--EXEC @IntHResult = sp_OAMethod @ObjExcel, 'Hyperlinks', 'http://www.goo.ne.jp', 'http://www.yahoo.co.jp'

--EXEC @IntHResult = sp_OAMethod @Range, 'Hyperlinks', @return OUT, 'http://www.yahoo.co.jp', 'http://www.yahoo.co.jp'
--EXEC @IntHResult = sp_OAMethod @IntObject, 'Connect', NULL,'FUJICHAWA', 'sa',''

-- デバック
/*
IF @IntHResult <> 0 -- Fehlerbehandlung
BEGIN
 DECLARE @verbindung INTEGER
 DECLARE @quelle VARCHAR(255)
 DECLARE @beschreibung VARCHAR(255)
 DECLARE @dbname VARCHAR(255)
 EXEC sp_OAGetErrorInfo @verbindung, @quelle OUTPUT, @beschreibung OUTPUT
 RAISERROR ( 'HyperLink %s: %s', 10 , -1, @quelle, @beschreibung)
END
*/
--objExcelSheet.Hyperlinks.Add objExcelSheet.Cells(5,2),"http://www.yahoo.co.jp","","http://www.yahoo.co.jpにリンク","Yahoo"



-- シート名変更
EXEC @IntHResult = sp_OAGetProperty @ObjExcel,'Sheets("Sheet1")'
EXEC @IntHResult = sp_OASetProperty @ObjExcel, 'Sheets("Sheet1").Name', 'ExcelTest3'



-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルファイル作成
-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルファイル破棄
SELECT @pba = "EXEC master..xp_cmdshell 'del " + @ChvExcelFileName + "', no_output"
EXEC(@pba)
-- エクセルファイル保存
SELECT @pba = 'SaveAs("' + @ChvExcelFileName + '")'
EXEC @IntHResult = sp_OAMethod @WorkBook, @pba

EXEC @IntHResult = sp_OAMethod @WorkBook, 'Close'

-- ////////////////////////////////////////////////////////////////////////////////////
-- オブジェクト破棄
-- ////////////////////////////////////////////////////////////////////////////////////
EXEC @IntHResult = sp_OADestroy @ObjExcel
EXEC @IntHResult = sp_OADestroy @IntObject

GO
その2.指定セルへの指定したテーブル名の結果を書込み
--/////////////////////////////////////////////////////////////////////////////////////////
-- ファイル名 :ExcelTest2
-- 概要 :
-- 指定セルへの指定セルへの指定したテーブル名の結果を書込み
--
-- 戻り値 :
-- :0:正常終了
-- :0以外:異常終了
--
-- SP存在サーバ名:BLUE-TRANSPARENCY
-- 参照テーブル :なし
-- 作成テーブル :なし
-- 参照SP :
-- master.dbo.xp_cmdshell
-- sp_OACreate, sp_OAGetProperty, sp_OAMethod, sp_OASetProperty, sp_OAGetProperty, sp_OAGetErrorInfo, sp_OADestroy
--
-- 作成日:2006/10/18 asuka
-- コピーライト:BLUE-TRANSPARENCY〜限りなく透明に近いブルー〜
--/////////////////////////////////////////////////////////////////////////////////////////
CREATE PROCEDURE [dbo].[ExcelTest2]

AS
-- ////////////////////////////////////////////////////////////////////////////////////
-- 初期化
-- ////////////////////////////////////////////////////////////////////////////////////
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
SET ANSI_NULLS ON

-- ////////////////////////////////////////////////////////////////////////////////////
-- 変数とカーソルの宣言
-- ////////////////////////////////////////////////////////////////////////////////////
DECLARE @ChvFolderPath VARCHAR(100) -- エクセル格納先フォルダフルパス
DECLARE @ChvExcelFileName VARCHAR(100) -- エクセルファイルフルパス名

DECLARE @IntObject INTEGER -- オブジェクトの引数
DECLARE @IntHResult INTEGER -- オブジェクトの戻値
DECLARE @ChvProperty VARCHAR(255) --
DECLARE @return VARCHAR(255) --
DECLARE @return_int INTEGER --
DECLARE @objResults INTEGER -- sp_OAMethodのOUT PUT値

DECLARE @col VARCHAR(100) -- 未使用
DECLARE @row VARCHAR(100) -- 未使用
DECLARE @num_cols INTEGER -- セレクトしたテーブルのレコードセットの行(カラム)数
DECLARE @num_rows INTEGER -- セレクトしたテーブルのレコードセットの列数

DECLARE @resultados VARCHAR(255) --
DECLARE @pba VARCHAR(255) --
DECLARE @indRow INTEGER -- 取得したレコードセットの何行目から書込むか指定 1行目が0にあたる
DECLARE @indColumn INTEGER --

DECLARE @ObjExcel INTEGER -- エクセルオブジェクト
DECLARE @WorkBooks INTEGER -- エクセルワークブックスオブジェクト
DECLARE @WorkBook INTEGER -- エクセルワークブックオブジェクト

DECLARE @offrow INTEGER --
DECLARE @offcolumn INTEGER --
DECLARE @rango VARCHAR(100) --
DECLARE @Range INTEGER -- セルのプロパティオブジェクト
DECLARE @ChvQuerry VARCHAR(255) -- SQLクエリ文字列

-- ////////////////////////////////////////////////////////////////////////////////////
-- フォルダ作成 - エクセル格納先
-- ////////////////////////////////////////////////////////////////////////////////////
-- ファイル名作成
DECLARE @ChvYYYYMMDD VARCHAR(10)
DECLARE @ChvYYYY VARCHAR(8)
DECLARE @ChvMM VARCHAR(8)
DECLARE @ChvDD VARCHAR(8)
SET @ChvYYYY = DATENAME( year, GETDATE() )
SET @ChvMM = DATENAME( month, GETDATE() )
SET @ChvDD = DATENAME( day, GETDATE() )
SET @ChvDD = '0' + @ChvDD
SET @ChvDD = RIGHT( @ChvDD, 2 )
SET @ChvYYYYMMDD = @ChvYYYY + @ChvMM + @ChvDD

-- フォルダパス設定
SET @ChvFolderPath = 'C:\'+@ChvYYYYMMDD+''

-- DOSコマンド
DECLARE @ChvCmd VARCHAR(255)
DECLARE @IntRet INTEGER

-- ディレクトリ存在チェック
SET @ChvCmd = ' dir ' + @ChvFolderPath + ' /b'
EXEC @IntRet = master.dbo.xp_cmdshell @ChvCmd, no_output
IF @@ERROR != 0
BEGIN
 PRINT 'フォルダ存在チェックでエラーが発生しました。'
 RETURN @IntRet
END

-- ディレクトリが存在しない場合
IF @IntRet != 0
BEGIN
 -- ディレクトリ作成
 SET @ChvCmd = ' md ' + @ChvFolderPath + ''
 EXEC @IntRet = master.dbo.xp_cmdshell @ChvCmd, no_output
 IF @@ERROR != 0
 BEGIN
  PRINT 'ディレクトリの作成に失敗しました。@@ERROR != 0'
  RETURN @IntRet
 END

 IF @IntRet != 0
 BEGIN
  PRINT 'ディレクトリの作成に失敗しました。@IntRet != 0'
 END
END

-- エクセルファイル名作成(フルパス)
SET @ChvExcelFileName = ''+@ChvFolderPath+'\ExcelTest2.xls'

-- ////////////////////////////////////////////////////////////////////////////////////
-- オブジェクト生成
-- ////////////////////////////////////////////////////////////////////////////////////
EXEC @IntHResult = sp_OACreate 'SQLDMO.SQLServer', @IntObject OUT
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OACreateに失敗しました。'
 RETURN @IntHResult
END

-- ////////////////////////////////////////////////////////////////////////////////////
-- メソッドコール - sp_OAMethod
-- ////////////////////////////////////////////////////////////////////////////////////
-- データベースサーバ接続
EXEC @IntHResult = sp_OAMethod @IntObject, 'Connect', NULL,'BLUE-TRANSPARENCY', 'sa',''
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OAMethodに失敗しました。(データベースサーバ接続失敗)'
 RETURN @IntHResult
END

-- 接続確認
EXEC @IntHResult = sp_OAMethod @IntObject, 'VerifyConnection', @return OUT
IF @IntHResult <> 0
BEGIN
 PRINT 'sp_OAMethodに失敗しました。(データベースサーバ接続確認失敗)'
 RETURN @IntHResult
END

-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセル書込み - テーブル内容
-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルオブジェクト生成
EXEC @IntHResult = sp_OACreate 'Excel.Application', @ObjExcel OUT

-- エクセルにワークブックプロパティ取得
EXEC @IntHResult = sp_OAGetProperty @ObjExcel ,'WorkBooks', @WorkBooks OUT

--シート数設定
EXEC @IntHResult = sp_OASetProperty @WorkBooks,'Application.SheetsInNewWorkbook','5'

-- エクセルワークブックのアッドプロパティ取得
EXEC @IntHResult = sp_OAGetProperty @WorkBooks,'Add', @WorkBook OUT

-- 結果セットのあるクエリーを実行 - ExecuteWithResults
SELECT @ChvQuerry='Select * from ProcedureDB..VariousTypeTable01 '
SELECT @resultados= 'ExecuteWithResults("' + @ChvQuerry +'")'
EXEC @IntHResult = sp_OAMethod @IntObject, @resultados, @objResults OUT

-- 取得した結果セットのテーブルの行(カラム)数取得
EXEC @IntHResult = sp_OAGetProperty @objResults, 'Columns' , @num_cols OUT

IF @IntHResult <> 0
BEGIN
EXEC sp_displayoaerrorinfo @IntObject, @IntHResult
RETURN
END




-- 取得した結果セットのテーブルの列数取得
EXEC @IntHResult = sp_OAGetProperty @objResults, 'Rows' , @num_rows OUT
PRINT ' Rows'
PRINT @num_rows
IF @IntHResult <> 0
BEGIN
 PRINT'結果セット取得失敗。'
 RETURN @IntHResult
END

-- デバッグ:行数、列数表示
SELECT @num_cols AS Num_Columns, @num_rows AS Num_Rows



-- 指定したテーブルの行、列の文字列を取得 - 'GetColumnString ', @col OUT, 列, 行
EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString',@resultados OUT, 1 , 1
--PRINT '1:1行1列目'
PRINT '1-1'
PRINT @resultados
EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString',@resultados OUT, 2 , 2
--PRINT '2:2行2列目'
PRINT '2-2'
PRINT @resultados
EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString',@resultados OUT, 3 , 3
--PRINT '3:3行3列目'
PRINT '3-3'
PRINT @resultados
EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString',@resultados OUT, 4 , 4
--PRINT '4:4行4列目'
PRINT '4-4'
PRINT @resultados
EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString',@resultados OUT, 5, 5
--PRINT '5:5行5列目'
PRINT '5-5'
PRINT @resultados

SELECT @offrow = 0
SELECT @offcolumn = 1
SELECT @indRow = 0 -- 取得したレコードセットの何行目から書込むか指定 1行目が0にあたる



-- 行
WHILE( @indRow <= @num_rows )
BEGIN
 -- テーブルの列(カラム数)指定
 SELECT @indColumn = 1 -- 1指定
 WHILE ( @indColumn <= @num_cols )
 BEGIN
  EXEC @IntHResult = sp_OAMethod @objResults, 'GetColumnString', @pba OUT, @indRow, @indColumn
  EXEC @IntHResult = sp_OASetProperty @Range,'Value', @pba
  SELECT @pba = RTRIM(@pba)
  EXEC @IntHResult = sp_OAGetProperty @Range,'Offset',@Range OUT ,@offrow , @offcolumn

  -- 列のインクリメント
  SELECT @indColumn = @indColumn + 1
 END -- 'Segundo While'

 SELECT @offrow=0
 SELECT @offcolumn=1
 SELECT @indRow=@indRow+1
 SELECT @rango = 'Range("A' + LTRIM(str(@indRow))+ '")'
 EXEC @IntHResult= sp_OAGetProperty @ObjExcel, @rango, @Range OUT
END

-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルファイル作成
-- ////////////////////////////////////////////////////////////////////////////////////
-- エクセルファイル破棄
SELECT @pba = "EXEC master..xp_cmdshell 'del " + @ChvExcelFileName + "', no_output"
EXEC(@pba)
-- エクセルファイル保存
SELECT @pba = 'SaveAs("' + @ChvExcelFileName + '")'
EXEC @IntHResult = sp_OAMethod @WorkBook, @pba
EXEC @IntHResult = sp_OAMethod @WorkBook, 'Close'

-- ////////////////////////////////////////////////////////////////////////////////////
-- オブジェクト破棄
-- ////////////////////////////////////////////////////////////////////////////////////
EXEC @IntHResult = sp_OADestroy @ObjExcel
EXEC @IntHResult = sp_OADestroy @IntObject

PRINT'End'
GO
考察
恐ろしいいくらいにCPUとメモリを使用します。
ASPとの連携の場合は、ASP側にExcel(エクセル)ファイルを作らせるのが吉のように思えます。
ループ内で行数を変えてExcel(エクセル)に書き込む場合は、最大行数(6万5536行)を監視する処理を追加する必要があります。
超える場合は別シート、あるいは別ファイルに記載するようにするべきでしょう。

バッチ処理にてSP(ストアドプロシージャ)で処理しなければならない仕事だったので作ってみたけれども、
多分SP(ストアドプロシージャ)はCOM操作に不向きなんだと思われます。

ちなみに、原因が不明で特定の行数から書き込めない現象なども発生しました。
メモリ増設で回避しましたが、他にも問題はありそうです。
毎回COMを解放してるのに何ででしょう・・・

■リンクサーバ
リンクサーバ
リンクサーバとは、(SP、クエリなどから)異なるサーバ間でテーブルの参照、挿入、削除などを行う際に相手側のサーバを登録しておく必要があります。

例:サーバSCOREからサーバSCORE-BACK-DBをリンクサーバに登録
1.EnterprISe ManagerのSCOREからセキュリティ→リンクサーバで右クリック→新規リンクサーバを選択




2.[全般]タブにてリンクサーバにSCORE-BACK-DBとサーバ名記載。サーバの種類をSQL Serverに設定。
[セキュリティ]タブにて次のセキュリティコンテキストを使用するを選択し、ログインIDとパスワードを設定します。
サーバまたぎ処理でリンクサーバANSINULLS
サーバ間をまたぐようなクエリを書くばあい、EnterPrISeManagerからお互いのサーバをリンクサーバとして登録しておく必要があります。
また、SPの先頭では以下のオプションを設定する必要があります。
 SET ANSI_NULLS ON
 SET ANSI_WARNINGS ON

例)
--TEST1サーバにあるテーブルを参照
SELECT TEST1.testDB.dbo.ATABLESELECT

■クロス集計
行と列を入れ替える
行と列を入れ替える集計
Accessではクロス集計用の命令があるが、SQLServerには無い。そのため、CASEとSUMを使います。
CASEを使うためには値を決めておく必要があるため、Accessのように全部の行列を入れ替えるということは出来ません。

SQLServerのクロスタブレポートの項から抜粋。

例)
以下のような Pivot というテーブルがあり、毎年のQuarterごとの集計を出したい場合。
Year      Quarter      Amount
----      -------      ------
1990      1           1.1
1990      2           1.2
1990      3           1.3
1990      4           1.4
1991      1           2.1
1991      2           2.2
1991      3           2.3
1991      4           2.4

このようなSELECT文を使います。
SELECT Year,
SUM(CASE Quarter WHEN 1 THEN Amount ELSE 0 END) AS Q1,
SUM(CASE Quarter WHEN 2 THEN Amount ELSE 0 END) AS Q2,
SUM(CASE Quarter WHEN 3 THEN Amount ELSE 0 END) AS Q3,
SUM(CASE Quarter WHEN 4 THEN Amount ELSE 0 END) AS Q4
FROM Northwind.dbo.Pivot GROUP BY Year

■上位レコード取得
TopとSET ROWCOUNT

上位10件を取得したい場合
例1)top を使う。topの場合、10のところに変数は使えない。

SELECT top 10 * FROM TABLEA ORDER BY ranking

例2) SET rowcount を使う。SET rowcountの場合、10のところに@IntNum等が使える。ただし、SET rowcount 0 で元に戻すのを忘れない事

DECLARE @IntNum
SET @IntNum = 10
SET rowcount @IntNum --取得行数を10件にセット
SELECT * FROM TABLEA ORDER BY ranking
SET rowcount 0 --0にすると全件対象になる。0にしておかないと次のクエリも10件しか取得されないので注意。

■ランキング
振り方
TABLE1(ID int,数値 int,Ranking int)というテーブルがあります。
このテーブルにID(ユニーク)と数値が入っていたときの数値で比べたときのランキングを求めて、updateするクエリを以下に示します。

update #tmpAnalyze00_01_MAIN
SET Ranking =
(SELECT count(T1.数値) FROM TABLE1 as T1 WHERE T1.数値 > TABLE1.数値) +1

ランキングを出すだけのSELECT文は以下のようになります。
SELECT ID,数値,
(SELECT count(T1.数値) FROM TABLE1 as T1 WHERE T1.数値 > TABLE1.数値) +1 as Rank
FROM TABLE1
ORDER BY Rank

■ソート
カンマ区切り文字列のソート
'3,6,2,5,12' という番号をカンマ区切りにした文字列を'2,3,5,6,12'に並べ替える方法

'3,6,2,5,12'をカンマ毎に区切って、一時テーブルに入れます。
一時テーブルを番号でORDER BY して SELECTします。そうすると
2,3,5,6,12の順で結果が取れます。そのSELECT分をカーソルでまわして
連結すれば、'3,6,2,5,12'という文字列が作れます。



例)

下の「カンマ区切り文字列からトークンを一つ取り出すSP」を作成してからこのクエリを実行してください。
DECLARE @IntPos int
SET @INtPos = 0
DECLARE @ChvToken1

CREATE TABLE #tmpSortTABLE (int n)

WHILE 1>0
BEGIN
 EXEC scoGetToken '3,6,2,5,12', @IntPos OUTPUT, @ChvToken1 OUTPUT

 IF @ChvToken1 IS NULL
  -- これ以上トークンがない
  BREAK
 
 INSERT INTO #tmpSortTABLE CONVERT(int,@ChvToken)
END

SELECT * FROM #tmpSortTABLE ORDER BY n
--このSELECT分をカーソルでまわして n を連結すればソートされた文字列になります。
--カーソルについては、Tipsの024か参考文献を参照してください。

カンマ区切り文字列からトークンを一つ取り出すSP
/******************************************************************************
** プロシジャ名:scoGetToken
** 説明    :引数で渡されたカンマ区切り文字列からトークンを1つ取り出します
**       :
**       :引数の@IntPosはトークンを切り出す位置です。たとえば0が指定さ
**       :れた場合には先頭のトークンが切り出されます。切り出しが完了する
**       :とこの変数は自動的に更新されます。(ファイルポインタみたいなも
**       :のですね)
******************************************************************************/
CREATE PROCEDURE dbo.scoGetToken
(
@ChvString varchar(256), -- (I) カンマ区切り文字列
@IntPos int OUTPUT, -- (I/O) トークンを切り出す位置
@ChvToken varchar(256) OUTPUT -- (O) 切り出したトークン
)
AS
-------------------------------------------------------------------------------
-- 初期化
-------------------------------------------------------------------------------
SET NOCOUNT ON

DECLARE @ChvTmp varchar(2)
DECLARE @IntStartPos int
DECLARE @IntLen int
SET @IntStartPos = @IntPos
SET @IntLen = LEN( @ChvString )
-------------------------------------------------------------------------------
-- 入力値チェック
-------------------------------------------------------------------------------
SET @ChvToken = NULL
IF @IntPos >= @IntLen
RETURN
-------------------------------------------------------------------------------
-- トークンの切り出し
-------------------------------------------------------------------------------
SET @ChvToken = ''

WHILE 1 > 0
BEGIN
SET @ChvTmp = LEFT( RIGHT( @ChvString, @IntLen - @IntPos ) , 1 )
IF ( @ChvTmp IS NULL ) or ( @ChvTmp = '' )
BEGIN
-- 文字列の最後に達した
RETURN
END
SET @IntPos = @IntPos + 1
IF @ChvTmp = ','
BEGIN
-- カンマに達した
RETURN
END
SET @ChvToken = @ChvToken + @ChvTmp
END
-- 無限ループなので、このRETURNは通らない。
RETURN
-- EOF --

■待機
一定時間待機(WAIT FOR)
WAITFOR DELAY '000:00:05' --5秒間待機します。

WAITFOR TIME '14:30:00' -- 14時30分まで待機します。

■サーバ名
[-]がある場合

クエリからBLUE-TRANSPARENCY-DBなど[-]のあるサーバに接続した場合。

SELECT * FROM MasterDB.dbo.MasterTABLE

通常上記のような文で参照可能ですが、あえてサーバ名を付加できます。
[-]がない場合はDB名の前に[サーバ名.]を付け加えて実行できます。
[-]がある場合は[BLUE-TRANSPARENCY-DB].のように[]でサーバ名を囲む必要があります。
SELECT * FROM [BLUE-TRANSPARENCY-DB].MasterDB.dbo.MasterTABLE

■拡張SP(ストアドプロシージャ)
拡張SP(ストアドプロシージャ)について
拡張ストアド プロシージャでは、C 言語などのプログラミング言語で独自の外部ルーチンを作成することができます。
拡張ストアド プロシージャは、ユーザーには通常のストアド プロシージャのように見え、同じ方法で実行されます。
データベース クエリは、拡張ストアド プロシージャにデータを渡し、その拡張ストアド プロシージャは結果とリターン ステータスを返します。

SQL Server に含まれている標準の拡張ストアド プロシージャには、たとえば以下のような電子メール機能を提供するものがあります。
・SQL メール クライアント セッションを開始する xp_startmail
・電子メールまたはページを送信する xp_sendmail
"文字のある文字列をバルクインサートする
■はじめに
Transact-SQLのSP内でファイルをテーブルにバルクインサートする際、文字列に "がある場合、bcpコマンドやBULK INSERTコマンドでは判断出来ません。
bcpコマンドやBULK INSERTコマンドで"文字をテーブルに挿入しようとするとエラーで止まります。

C(C++、VC++)言語では"文字の挿入が認められているため、C++でテーブルへの挿入用DLLを作成し、SPからDLL内の関数をコールさせて実現させます。



■サンプルの仕様
・概要
指定したファイルを指定したテーブルに挿入します。

・拡張ストアドプロシージャ名
xp_SPEX

・拡張ストアドプロシージャの引数
No. 変数名 IN/OUT 説明
01 @ChvServerName INPUT TCHAR[256] サーバ名
02 @ChvDBName INPUT TCHAR[256] DB名
03 @ChvOwnerName INPUT TCHAR[256] DB名を所有するオーナ名(dbo)
04 @ChvTableName INPUT TCHAR[256] テーブル名
05 @ChvID INPUT TCHAR[256] サーバへのログインID(文字列(sa))
06 @ChvPW INPUT TCHAR[256] サーバへのログインパスワード
07 @ChvInsertFileName INPUT TCHAR[256] 処理対象ファイルのフルパス
08 @DtmInsertDate INPUT INT 処理対象日(1900-01-01を0として1日ごとに1ずつ増える形式で値を取得する)
09 @IntSkipFirstRowFlg INPUT INT 処理対象ファイルの最初の行をスキップするかしないか(1:スキップする 0:スキップしない)
10 @ChSkipFirstLetter INPUT TCHAR[256] 処理対象ファイルの1行目1文字目にこの引数でもらう文字があれば処理しない(#行は処理しない。)
11 @ChvLogFilePath INPUT TCHAR[256] ログファイル出力先パス(\で終わること。)
12 @ChvErrorMessage OUTPUT TCHAR[256] エラーメッセージ
13 @IntDebug INPUT INT デバッグモード



■ポイント
SP(ストアドプロシージャ)内で宣言されている型がC++では何に対応するかがポイントになります。
正直DATETIME型の扱いは非常に厄介です。



■拡張SP(ストアドプロシージャ)をコールする側(SQ)のクエリのソースです。
--/////////////////////////////////////////////////////////////////////////////////////
-- 拡張ストアドプロシージャ
--/////////////////////////////////////////////////////////////////////////////////////
--/////////////////////////////////////////////////////////////////////////////////////
-- 登録
-- 登録は一度だけ実行します。
--sp_addextendedproc 'xp_SPEX', 'C:\Program Files\Microsoft SQL Server\MSSQL\Binn\xp_SPEX.dll'

--/////////////////////////////////////////////////////////////////////////////////////
-- 変数
DECLARE @IntErrorCode INTEGER -- 戻値
DECLARE @ChvServerName VARCHAR(1000) -- 1.CSVIn挿入先SQLサーバ名
DECLARE @ChvDBName VARCHAR(1000) -- 2.CSVIn挿入先SQLデータベース名
DECLARE @ChvOwnerName VARCHAR(1000) -- 3.CSVIn挿入先SQLオーナー名
DECLARE @ChvTableName VARCHAR(1000) -- 4.CSVIn挿入先SQLテーブル名
DECLARE @ChvID VARCHAR(1000) -- 5.CSVIn挿入先ログインID
DECLARE @ChvPW VARCHAR(1000) -- 6.CSVIn挿入先ログインPW
DECLARE @ChvInsertFileName VARCHAR(1000) -- 7.CSVInファイルのフルパス
DECLARE @DtmInsertDate DATETIME -- 8.CSVInのインサートデート ■注意:この値が空でない場合:ファイルから読込んだ1行にこの値を足してインサート
DECLARE @IntSkipFirstRowFlg INTEGER -- 9.CSVInファイルの初めの1行目をSkipするか 0:しない 1:する
DECLARE @ChSkipFirstLetter CHAR(1) -- 10.CSVInファイルの1行目の初めの文字が指定した文字の場合はスキップ
DECLARE @ChvLogFilePath VARCHAR(1000) -- 11.拡張ストアドプロシージャのログ出力先
DECLARE @ChvErrorMessage VARCHAR(1000) -- 12.エラーメッセージ(出力パラメータ)
DECLARE @IntDebug INTEGER -- 13.デバッグ 1:デバッグ 0しない

-- 変数初期値設定
SET @IntErrorCode = -1
SET @ChvServerName = 'BLUE-TRANSPARENCY'
SET @ChvDBName = 'TestDB'
SET @ChvOwnerName = 'dbo'
SET @ChvTableName = 'TestTable'
SET @ChvID = 'sa'
SET @ChvPW = ''
SET @ChvInsertFileName = 'C:\InsertFile.csv'
SET @DtmInsertDate = '2006-10-18'
SET @IntSkipFirstRowFlg = 1 --この値が空の場合:ファイルから読込んだ1行をそのままインサートします。
SET @ChSkipFirstLetter = '#'
SET @ChvLogFilePath = 'C:\LOG\'
SET @ChvErrorMessage = ''
SET @IntDebug = 1

--/////////////////////////////////////////////////////////////////////////////////////
-- 実行
EXEC @IntErrorCode = xp_SPEX
@ChvServerName, -- 1 - INPUT
@ChvDBName, -- 2 - INPUT
@ChvOwnerName, -- 3 - INPUT
@ChvTableName, -- 4 - INPUT
@ChvID, -- 5 - INPUT
@ChvPW, -- 6 - INPUT
@ChvInsertFileName, -- 7 - INPUT
@DtmInsertDate, -- 8 - INPUT
@IntSkipFirstRowFlg, -- 9 - INPUT
@ChSkipFirstLetter, -- 10 - INPUT
@ChvLogFilePath, -- 11 - INPUT
@ChvErrorMessage OUTPUT, -- 12 - OUTPUT
@IntDebug -- 13 - INPUT

-- 実行後の変数
PRINT '■■■ 実行後の変数 ■■■'
PRINT '@IntErrorCode='+CAST( @IntErrorCode AS CHAR )+' '
PRINT '@ChvServerName='+@ChvServerName+' '
PRINT '@ChvDBName='+@ChvDBName+' '
PRINT '@ChvOwnerName='+@ChvOwnerName+' '
PRINT '@ChvTableName='+@ChvTableName+' '
PRINT '@ChvID='+@ChvID+' '
PRINT '@ChvPW='+@ChvPW+' '
PRINT '@ChvInsertFileName='+@ChvInsertFileName+' '
PRINT '@IntSkipFirstRowFlg='+CAST( @IntSkipFirstRowFlg AS CHAR )+' '
PRINT '@ChSkipFirstLetter='+@ChSkipFirstLetter+' '
PRINT '@ChvLogFilePath='+@ChvLogFilePath+' '
PRINT '@ChvErrorMessage='+@ChvErrorMessage+' '
PRINT '@IntDebug='+CAST( @IntDebug AS CHAR )+' '

--/////////////////////////////////////////////////////////////////////////////////////
-- 削除
--sp_dropextendedproc 'xp_SPEX'

--/////////////////////////////////////////////////////////////////////////////////////
-- DLLアンロード
DBCC xp_SPEX (FREE)
--sp_helpextendedproc



■xp_SPEX.DLLのソースです
////////////////////////////////////////////////////////////////////////////////////////////
// DLL名:xp_SPEX
// 概要:Transact-SQLのSP内でファイルをテーブルにバルクインサートする際、文字列に
// "があることをbcpやBULK INSERTコマンドでは判断できないため、このDLLが拡張ス
// トアドプロシージャとして代替する。
// 処理対象ファイルの1行読込み、テーブルへインサート
//
// 戻値:正常時:XP_NOERROR 0
// 異常時:XP_ERROR 0
// ストアドプロシージャ側にもこの戻値が返る。Ex. EXEC @IntErrorCode = xp_SPEX ・・・
//
// 周囲:インサートするCSVファイルの形式はCHAR、VARCHAR、DATETIMEは"で囲むこと。INT系は囲まないこと。
//
// 参照テーブル:引数でもらうテーブル
//
// 拡張ストアドプロシージャ名:xp_SPEX
//
// 拡張ストアドプロシージャの引数:
// 01:@ChvServerName INPUT - 処理対象サーバ名
// 02:@ChvDBName INPUT - 処理対象DB名
// 03:@ChvOwnerName INPUT - 処理対象DB名を所有するオーナ名(dbo)
// 04:@ChvTableName INPUT - 処理対象テーブル名
// 05:@ChvID INPUT - 処理対象サーバのログインID(文字列(sa))
// 06:@ChvPW INPUT - 処理対象サーバのパスワード
// 07:@ChvInsertFileName INPUT - 処理対象ファイルのフルパス
// 08:@DtmInsertDate INPUT - 処理対象日
// 09:@IntSkipFirstRowFlg INPUT - 処理対象ファイルの最初の行をスキップするかしないか(1:スキップする 0:スキップしない)
// 10:@ChSkipFirstLetter INPUT - 処理対象ファイルの1行目1文字目にこの引数でもらう文字があれば処理しない(#行は処理しない。)
// 11:@ChvLogFilePath INPUT - このプロシージャが出すログファイル先パス(\で終わること。このプロシージャでは特にチェックしない。)
// 12:@ChvErrorMessage OUTPUT - このプロシージャが出すエラーメッセージ
// 13:@IntDebug INPUT - デバッグモード
//
// 作成日:2003/10/27
// 作成者:asuka
// 履歴:
////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////
// ヘッダ
////////////////////////////////////////////////////////////////////////////////////////////
#include <stdafx.h>
#include "Cprocfuncs.h"
#include "CReadFileLine.h"
#include <Shlwapi.h>
#pragma comment ( lib, "Shlwapi.lib" )
// ADO
#import "C:\Program Files\Common Files\System\ADO\msado15.dll" \
no_namespace rename("EOF", "EndOfFile")
#include <ole2.h>
#include <stdio.h>
#include <conio.h>
// Function declarations - マクロ
inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};

////////////////////////////////////////////////////////////////////////////////////////////
// 定義
////////////////////////////////////////////////////////////////////////////////////////////
#define XP_NOERROR 0 // 戻値:正常リターン値 - ストアドプロシージャ側と一致する
#define XP_ERROR -1 // 戻値:異常リターン値 - ストアドプロシージャ側と一致する
#define nNum 13 // ストアドプロシージャのパラメータの数
#define MODULENAME "xp_SPEX" // モジュール名
#define MODULEEXTD ".dll" // モジュールの拡張子

////////////////////////////////////////////////////////////////////////////////////////////
// 構造体
////////////////////////////////////////////////////////////////////////////////////////////
// インサート情報構造体
typedef struct structInsertInfo{
SRV_PROC *pSrvProc;
TCHAR szServerName[BUF_SIZE]; // 01:@ChvServerName INPUT - 処理対象サーバ名
TCHAR szDBName[BUF_SIZE]; // 02:@ChvDBName INPUT - 処理対象DB名
TCHAR szOwnerName[BUF_SIZE]; // 03:@ChvOwnerName INPUT - 処理対象DB名を所有するオーナ名(dbo)
TCHAR szTableName[BUF_SIZE]; // 04:@ChvTableName INPUT - 処理対象テーブル名
TCHAR szID[BUF_SIZE]; // 05:@ChvID INPUT - 処理対象サーバのログインID(文字列(sa))
TCHAR szPW[BUF_SIZE]; // 06:@ChvPW INPUT - 処理対象サーバのパスワード
TCHAR szInsertFileName[BUF_SIZE]; // 07:@ChvInsertFileName INPUT - 処理対象ファイルのフルパス
TCHAR szInsertDate[BUF_SIZE]; // 08:@DtmInsertDate INPUT - 処理対象日
INT nSkipFirstRowFlg; // 09:@IntSkipFirstRowFlg INPUT - 処理対象ファイルの最初の行をスキップするかしないか(1:スキップする 0:スキップしない)
TCHAR szSkipFirstLetter[BUF_SIZE]; // 10:@ChSkipFirstLetter INPUT - 処理対象ファイルの1行目1文字目にこの引数でもらう文字があれば処理しない(#行は処理しない。)
TCHAR szLogFilePath[BUF_SIZE]; // 11:@ChvLogFilePath INPUT - このプロシージャが出すログファイル先パス(\で終わること。このプロシージャでは特にチェックしない。)
TCHAR szErrorMessage[BUF_SIZE]; // 12:@ChvErrorMessage OUTPUT - このプロシージャが出すエラーメッセージ
INT nDebug; // 13:@IntDebug INPUT - デバッグモード
} STRUCT_INSERTINFO;
STRUCT_INSERTINFO InsertInfo;

////////////////////////////////////////////////////////////////////////////////////////////
// プロトタイプ
////////////////////////////////////////////////////////////////////////////////////////////
VOID PrintProviderError ( SRV_PROC *pSrvProc, CHAR* szErrorMsg ); // PrintProviderError - SQLサーバ側で発生したエラーを取得
VOID PrintUsageInfo( SRV_PROC *pSrvProc ); // PrintUsageInfo - ストアドプロシージャの使用方法が間違っているためクライアントに使用方法を伝える
VOID ADOPrintProviderError( _ConnectionPtr pConnection ); // ADOPrintProviderError - ADO使用によるSQLサーバ側で発生したエラーを取得
VOID ADOPrintComError( _com_error &e ); // ADOPrintComError - COMエラー情報取得
INT InsertThreadMain( STRUCT_INSERTINFO* lpInsertInfo ); // ファイルから1行ずつテーブルへインサート - スレッドが使用する関数
INT InsertThreadMain2( STRUCT_INSERTINFO* pInsertInfo ); // ファイルから1行ずつテーブルへインサート - 通常の関数

////////////////////////////////////////////////////////////////////////////////////////////
// クラス
////////////////////////////////////////////////////////////////////////////////////////////
// このプロシージャの処理を関数にしたクラス
Cprocfuncs cProcFuncs;

////////////////////////////////////////////////////////////////////////////////////////////
// グローバル変数
////////////////////////////////////////////////////////////////////////////////////////////
HANDLE _hThread; // スレッドハンドル
DWORD _dwThreadID; // スレッドID
HANDLE _hSemaphore; // セマフォアハンドル
LONG _lSemaCount; //

////////////////////////////////////////////////////////////////////////////////////////////
// プロシージャ
////////////////////////////////////////////////////////////////////////////////////////////
#ifdef __cplusplus
extern "C" {
#endif
RETCODE __declspec(dllexport) xp_SPEX( SRV_PROC *pSrvProc );
#ifdef __cplusplus
}
#endif
RETCODE __declspec(dllexport) xp_SPEX( SRV_PROC *pSrvProc )
{
////////////////////////////////////////////////////////////////////////////////////////////
// proc開始
cLog->WriteLogFile( "■■■xp_SPEX開始■■■" );

////////////////////////////////////////////////////////////////////////////////////////////
// 変数
// ↓SPのパラメータを受取る変数
TCHAR szServerName[BUF_SIZE]; // 01:@ChvServerName INPUT - 処理対象サーバ名
TCHAR szDBName[BUF_SIZE]; // 02:@ChvDBName INPUT - 処理対象DB名
TCHAR szOwnerName[BUF_SIZE]; // 03:@ChvOwnerName INPUT - 処理対象DB名を所有するオーナ名(dbo)
TCHAR szTableName[BUF_SIZE]; // 04:@ChvTableName INPUT - 処理対象テーブル名
TCHAR szID[BUF_SIZE]; // 05:@ChvID INPUT - 処理対象サーバのログインID(文字列(sa))
TCHAR szPW[BUF_SIZE]; // 06:@ChvPW INPUT - 処理対象サーバのパスワード
TCHAR szInsertFileName[BUF_SIZE]; // 07:@ChvInsertFileName INPUT - 処理対象ファイルのフルパス
TCHAR szInsertDate[BUF_SIZE]; // 08:@DtmInsertDate INPUT - 処理対象日
INT nInsertDate; // 1900-01-01を0として1日ごとに1ずつ増える形式で値を取得する
INT nSkipFirstRowFlg; // 09:@IntSkipFirstRowFlg INPUT - 処理対象ファイルの最初の行をスキップするかしないか(1:スキップする 0:スキップしない)
TCHAR szSkipFirstLetter[BUF_SIZE]; // 10:@ChSkipFirstLetter INPUT - 処理対象ファイルの1行目1文字目にこの引数でもらう文字があれば処理しない(#行は処理しない。)
TCHAR szLogFilePath[BUF_SIZE]; // 11:@ChvLogFilePath INPUT - このプロシージャが出すログファイル先パス(\で終わること。このプロシージャでは特にチェックしない。)
TCHAR szErrorMessage[BUF_SIZE]; // 12:@ChvErrorMessage OUTPUT - このプロシージャが出すエラーメッセージ
INT nDebug; // 13:@IntDebug INPUT - デバッグモード
// ↓SPのパラメータを受取る際に必要な変数
BYTE bType[nNum+1]; // ストアドプロシージャのパラメータの型
LONG cbMaxLen[nNum+1]; // ストアドプロシージャのパラメータの最大サイズ
LONG cbActualLen[nNum+1]; // ストアドプロシージャのパラメータの長さの実際値へのポインタです。
// 値が 0 (*pcbActualLen == 0 ) のときに *pfNull が FALSE に設定されている場合は、長さ 0 のデータを示します。
BOOL fNull[nNum]; // ストアドプロシージャのパラメータ データのバッファへのポインタです。
// pbData が NULL でない場合、Open Data Services は *pbData に *pcbActualLen バイトのデータを書き込みます。
// pbData が NULL の場合は、*pbData にデータは書き込まれません。
// *pbType、*pcbMaxLen、*pcbActualLen、および *pfNull が関数によって返されます。
// このバッファのメモリは、Open Data Services アプリケーションで管理する必要があります。

////////////////////////////////////////////////////////////////////////////////////////////
// 変数初期化 - なんかこれ呼ぶと一回目変数にゴミがはいっちゃう。。。
//cProcFuncs.InitParameters( szServerName, szDBName, szOwnerName, szTableName, szID, szPW, szInsertFileName, szInsertDate, &nSkipFirstRowFlg, szSkipFirstLetter, szLogFilePath, szErrorMessage, &nDebug );
ZeroMemory( szServerName, sizeof( szServerName ) );
ZeroMemory( szDBName, sizeof( szDBName ) );
ZeroMemory( szOwnerName, sizeof( szOwnerName ) );
ZeroMemory( szTableName, sizeof( szTableName ) );
ZeroMemory( szID, sizeof( szID ) );
ZeroMemory( szPW, sizeof( szPW ) );
ZeroMemory( szInsertFileName, sizeof( szInsertFileName ) );
ZeroMemory( szInsertDate, sizeof( szInsertDate ) );
ZeroMemory( szSkipFirstLetter, sizeof( szSkipFirstLetter ) );
ZeroMemory( szLogFilePath, sizeof( szLogFilePath ) );
ZeroMemory( szErrorMessage, sizeof( szErrorMessage ) );
nSkipFirstRowFlg = 0;
nDebug = 0;

////////////////////////////////////////////////////////////////////////////////////////////
// メッセージ - Start
srv_sendmsg( pSrvProc, SRV_MSG_INFO, 2001, SRV_INFO, 1, NULL, 0, 0, "*** xp_SPEX Start ***", SRV_NULLTERM );

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ数チェック
if( cProcFuncs.CheckSPNumOfParameters( pSrvProc, nNum ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ情報を取得 - データタイプ及び長さ情報取得。
// 実際はストアドプロシージャの引数はsrv_paraminfoを用いて取得するが、バッファーオーバーフローの脆弱性を考慮し、
// 後で取得する。
if( cProcFuncs.GetSPParametersInfo( pSrvProc, nNum, bType, cbMaxLen, cbActualLen, fNull ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのOUTPUT型変数の状態を取得
// srv_paramstatus - 特定のリモート ストアド プロシージャ呼び出しパラメータのステータスを返します。
// このパラメータのステータス フラグを持つ int を返します。
// 現在、フラグは 1 つだけあります。ビット 0 が 1 に設定されている場合、パラメータは戻りパラメータです。
// n 番目のパラメータがなかったり、リモート ストアド プロシージャがなかったりする場合は、-1 を返します。
if( cProcFuncs.GetSPParametersStatus( pSrvProc, 12 ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータの型確認
if( cProcFuncs.CheckSPDataType( pSrvProc, nNum, bType ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータのサイズ確認
if( cProcFuncs.CheckSPDataSize( pSrvProc, nNum, cbMaxLen, szServerName, szDBName, szOwnerName, szTableName, szID, szPW, szInsertFileName, szInsertDate, szSkipFirstLetter, szLogFilePath, szErrorMessage ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ情報を取得 - 値
// 上記までのチェックが済めばローカルバッファに引数コピー
if( cProcFuncs.GetSPParametersValue( pSrvProc, nNum, bType, cbMaxLen, cbActualLen, fNull, szServerName, szDBName, szOwnerName, szTableName, szID, szPW, szInsertFileName, szInsertDate, &nInsertDate, &nSkipFirstRowFlg, szSkipFirstLetter, szLogFilePath, szErrorMessage, &nDebug ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ出力先カラム設定
if( cProcFuncs.SetSPParametersOutputColumn( pSrvProc, nNum, bType, cbMaxLen, cbActualLen, fNull ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ出力先カラム長設定
// srv_setcollen - 可変長列または NULL 値を許容する列の、現在のデータ長をバイト数で示します。
if( cProcFuncs.SetSPParametersOutputColumnLength( pSrvProc, nNum ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// ストアドプロシージャのパラメータ出力先カラムへデータ設定
if( cProcFuncs.SetSPParametersOutputColumnData( pSrvProc, nNum, szServerName, szDBName, szOwnerName, szTableName, szID, szPW, szInsertFileName, szInsertDate, &nSkipFirstRowFlg, szSkipFirstLetter, szLogFilePath, szErrorMessage, &nDebug ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// クライアントに1行データ送信
if( cProcFuncs.SendRow( pSrvProc ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// クライアントに結果完了メッセージ送信
if( cProcFuncs.SendDone( pSrvProc ) == FALSE ){
return (XP_ERROR);
}

////////////////////////////////////////////////////////////////////////////////////////////
// メッセージ - 終了
srv_sendmsg( pSrvProc, SRV_MSG_INFO, 2001, SRV_INFO, 1, NULL, 0, 0, "*** xp_SPEX End ***", SRV_NULLTERM );

////////////////////////////////////////////////////////////////////////////////////////////
// ADOを用いてデータをテーブルに挿入
////////////////////////////////////////////////////////////////////////////////////////////
// 構造体設定
InsertInfo.pSrvProc = pSrvProc;
lstrcpyn( InsertInfo.szServerName, szServerName, BUF_SIZE );
lstrcpyn( InsertInfo.szDBName, szDBName, BUF_SIZE );
lstrcpyn( InsertInfo.szOwnerName, szOwnerName, BUF_SIZE );
lstrcpyn( InsertInfo.szTableName, szTableName, BUF_SIZE );
lstrcpyn( InsertInfo.szID, szID, BUF_SIZE );
lstrcpyn( InsertInfo.szPW, szPW, BUF_SIZE );
lstrcpyn( InsertInfo.szInsertFileName, szInsertFileName, BUF_SIZE );
lstrcpyn( InsertInfo.szInsertDate, szInsertDate, BUF_SIZE );
InsertInfo.nSkipFirstRowFlg = nSkipFirstRowFlg;
lstrcpyn( InsertInfo.szSkipFirstLetter, szSkipFirstLetter, BUF_SIZE );
lstrcpyn( InsertInfo.szLogFilePath, szLogFilePath, BUF_SIZE );
lstrcpyn( InsertInfo.szErrorMessage, szErrorMessage, BUF_SIZE );
InsertInfo.nDebug = nDebug;

////////////////////////////////////////////////////////////////////////////////////////////
// ファイルから1行ずつテーブルへインサート - 通常の関数
// スレッドを使うとSPはスレッドが終了するまで待たないのでSPからスレッド呼出し直後にテーブル
// のカウントを取られても0件になってしまう現状を回避するため通常の関数を使用。
INT nRet;
nRet = InsertThreadMain2( &InsertInfo );

////////////////////////////////////////////////////////////////////////////////////////////
// proc終了
cLog->WriteLogFile( "■■■xp_SPEX終了■■■" );
// XP_NOERRORを返すとストアドプロシージャの戻り値になる
if( nRet == XP_ERROR )
return XP_ERROR;
return XP_NOERROR ;
}

//---------------------------------------------------------------------------
// 概要:改行文字位置取得関数
// 機能:改行までの文字数を取得する
// 備考:
// 戻り値:
// 改行位置
// 作成日:2003/11/25
// 更新日:
//---------------------------------------------------------------------------
DWORD GL_SearchLineFeedPos(TCHAR *pszInput //入力文字列
)
{
DWORD dwLine = 0;//文字数
//入力チェック
if(pszInput==NULL){
return dwLine;
}
while(pszInput[dwLine]!='\0'){
if(pszInput[dwLine]=='\n'){
dwLine++;
return(dwLine);
}
dwLine++;
}
return(0);
}


////////////////////////////////////////////////////////////////////////////////////////////
// ローカル関数
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
// ファイルから1行ずつテーブルへインサート - 通常の関数
INT InsertThreadMain2( STRUCT_INSERTINFO* pInsertInfo ){

////////////////////////////////////////////////////////////////////////////////////////////
// newした構造体に実態をコピー
STRUCT_INSERTINFO* lpInsertInfo = new STRUCT_INSERTINFO;
lpInsertInfo->pSrvProc = pInsertInfo->pSrvProc;
lstrcpyn( lpInsertInfo->szServerName, pInsertInfo->szServerName, BUF_SIZE );
lstrcpyn( lpInsertInfo->szDBName, pInsertInfo->szDBName, BUF_SIZE );
lstrcpyn( lpInsertInfo->szOwnerName, pInsertInfo->szOwnerName, BUF_SIZE );
lstrcpyn( lpInsertInfo->szTableName, pInsertInfo->szTableName, BUF_SIZE );
lstrcpyn( lpInsertInfo->szID, pInsertInfo->szID, BUF_SIZE );
lstrcpyn( lpInsertInfo->szPW, pInsertInfo->szPW, BUF_SIZE );
lstrcpyn( lpInsertInfo->szInsertFileName, pInsertInfo->szInsertFileName, BUF_SIZE );
lstrcpyn( lpInsertInfo->szInsertDate, pInsertInfo->szInsertDate, BUF_SIZE );
lpInsertInfo->nSkipFirstRowFlg = pInsertInfo->nSkipFirstRowFlg;
lstrcpyn( lpInsertInfo->szSkipFirstLetter, pInsertInfo->szSkipFirstLetter, BUF_SIZE );
lstrcpyn( lpInsertInfo->szLogFilePath, pInsertInfo->szLogFilePath, BUF_SIZE );
lstrcpyn( lpInsertInfo->szErrorMessage, pInsertInfo->szErrorMessage, BUF_SIZE );
lpInsertInfo->nDebug = pInsertInfo->nDebug;

////////////////////////////////////////////////////////////////////////////////////////////
// クラス
CReadFileLine cReadFile; // ファイルから1行読込み

////////////////////////////////////////////////////////////////////////////////////////////
// 変数
// コネクションオブジェクト、レコードセットオブジェクト
_ConnectionPtr pConnection = NULL; // コネクションオブジェクト
HANDLE hFile; // ファイルハンドル
TCHAR szTmp[BUF_SIZE*2]; // 汎用バッファ
INT nError; nError = 0; // catch時に-1になる。


////////////////////////////////////////////////////////////////////////////////////////////
// COM初期化
if( cProcFuncs.CoInitialize( lpInsertInfo->pSrvProc ) == FALSE ){
lstrcpyn( szTmp, "CoInitialize failed...", BUF_SIZE );
cLog->WriteLogFile( szTmp );
return XP_ERROR;
}

try{
////////////////////////////////////////////////////////////////////////////////////////////
// SQLサーバ接続
if( cProcFuncs.ConnetSQLServer( lpInsertInfo->pSrvProc, pConnection, lpInsertInfo->szServerName, lpInsertInfo->szID, lpInsertInfo->szPW ) == FALSE ){
// ログ
lstrcpyn( szTmp, "ConnetSQLServer failed...", BUF_SIZE );
cLog->WriteLogFile( szTmp );
// COMのUnInitialize
cProcFuncs.CoUninitialize();
return XP_ERROR;
}

////////////////////////////////////////////////////////////////////////////////////////////
// CreateFile
hFile = CreateFile( lpInsertInfo->szInsertFileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
// エラー処理
if( hFile == INVALID_HANDLE_VALUE ) {
// ログ
wnsprintf( szTmp, BUF_SIZE, "CreateFile failed... return values was INVALID_HANDLE_VALUE, InsertFileName =%s", lpInsertInfo->szInsertFileName );
cLog->WriteLogFile( szTmp );
// COMのUnInitialize
cProcFuncs.CoUninitialize();
return XP_ERROR;
}
// エラー処理
if( GetFileSize( hFile, NULL ) == - 1 ) {
// ログ
lstrcpyn( szTmp, "GetFileSize failed... return value was -1", BUF_SIZE );
cLog->WriteLogFile( szTmp );
// CloseHandle
CloseHandle( hFile );
// COMのUnInitialize
cProcFuncs.CoUninitialize();
return XP_ERROR;
}

////////////////////////////////////////////////////////////////////////////////////////////
// インサート
DWORD dwCount = 0; // 行数カウント
DWORD dwCountIn = 0; // インサート回数
TCHAR szFileBuf[BUF_SIZE*2]; // ファイルから1行読込んだデータ
TCHAR szQuery[BUF_SIZE*2]; // クエリ文字列
//kawahara
DWORD dwGetSize = 0; //読み込みバイト数 k
DWORD dwFileSize = 0; //ファイルサイズ k
DWORD dwReadByte = 0; //読み込みサイズ k
DWORD dwDataSize = 0; //データ長 k
DWORD dwTempSize = 0; //一時領域文字数 k
DWORD dwLineSize = 0; //1行文字数 k
DWORD dwStrSize = 0; //処理済文字数 k
DWORD dwLineFeedPos = 0; //改行までの文字数 k
BOOL bSuccess = TRUE; //作業用 k
INT nLine = 0; //行番号 k
INT nkLen = 0;
static TCHAR m_pszBuf[1000001];
static TCHAR m_pszTempBuf[1000001];
static TCHAR m_pszLineBuf[1000001];
ZeroMemory(m_pszBuf, 1000001);
ZeroMemory(m_pszTempBuf, 1000001);
ZeroMemory(m_pszLineBuf, 1000001);
dwFileSize = dwReadByte = dwDataSize = dwTempSize = dwLineSize = dwStrSize = dwLineFeedPos = 0;
nLine = 0;

//ファイルサイズ取得
dwFileSize = GetFileSize(hFile , NULL);
//読み込みバッファサイズ決定
if(1000000 > dwFileSize){
dwGetSize = dwFileSize;
}else{
dwGetSize = 1000000;
}
dwDataSize = 0;
//読み込み処理
while(dwFileSize != dwDataSize){
if(!ReadFile(hFile, (LPVOID)m_pszBuf, dwGetSize, &dwReadByte, NULL)) {
bSuccess = FALSE;
}
m_pszBuf[dwReadByte] = '\0';
dwDataSize = dwDataSize + dwReadByte;
dwStrSize = 0; //処理中の文字サイズ初期化
dwLineSize = 0;
//loop 読み込んだバイトをすべて処理する
while(dwStrSize != dwReadByte){
//1行データを生成する
// 余りデータがあった場合は、データを1行データにコピーする
// 1行データの文字長を変更する
if(dwTempSize != 0){
CopyMemory(m_pszLineBuf + dwLineSize, m_pszTempBuf, dwTempSize);
dwLineSize = dwLineSize + dwTempSize;
m_pszLineBuf[dwLineSize] = '\0';
dwTempSize=0;//一時領域クリア
}
//改行位置を検索する
// 戻り値 改行あり・なし
// なしの場合、データが最後まで読まれていないか確認する。
// 最後まで読み込んでいる場合は、それまでの文字列を1行とする
// 読み込んでいない場合は、データを余りデータとして保持して次の行を読み込む
//
dwLineFeedPos = 0;
dwLineFeedPos = GL_SearchLineFeedPos(m_pszBuf + dwStrSize);
if(dwLineFeedPos == 0){
if(dwFileSize == dwDataSize){
//1行データにコピーする
CopyMemory(m_pszLineBuf + dwLineSize, m_pszBuf + dwStrSize, dwReadByte - dwStrSize);
dwLineSize = dwReadByte - dwStrSize + dwLineSize;
dwStrSize = dwReadByte;
m_pszLineBuf[dwLineSize] = '\0';
}else{
//一時領域にデータコピー
CopyMemory(m_pszTempBuf, m_pszBuf + dwStrSize, dwReadByte - dwStrSize);
dwTempSize = dwReadByte - dwStrSize;
m_pszTempBuf[dwTempSize] = '\0';
//次行読み込みバイト数の設定
if(dwGetSize > (dwFileSize - dwDataSize)){
dwGetSize = dwFileSize - dwDataSize;
}
//1行読み込み次検索
if(!ReadFile(hFile, (LPVOID)m_pszBuf, dwGetSize, &dwReadByte, NULL)) {
bSuccess = FALSE;
}
dwStrSize = 0;
dwDataSize = dwDataSize + dwReadByte;
m_pszBuf[dwReadByte] = '\0';
continue;
}
}else{
//1行データにコピーする
CopyMemory(m_pszLineBuf + dwLineSize, m_pszBuf + dwStrSize, dwLineFeedPos);
dwLineSize = dwLineFeedPos + dwLineSize;
dwStrSize = dwStrSize + dwLineFeedPos;
m_pszLineBuf[dwLineSize] = '\0';
}
//ルール生成処理
nLine++;
lstrcpy(szFileBuf,m_pszLineBuf);
nkLen =lstrlen(szFileBuf);
/*改行コードを削除する*/
if((nkLen > 2) && (szFileBuf[nkLen-2] == '\r') && (szFileBuf[nkLen-1] == '\n')){
szFileBuf[nkLen - 2] = '\0';
}else if(szFileBuf[nkLen-1]=='\n'){
szFileBuf[nkLen-1]='\0';
}
//1行開始位置初期化
dwLineSize=0;
//次行読み込みバイト数の設定
if(dwGetSize > (dwFileSize - dwDataSize)){
dwGetSize = dwFileSize - dwDataSize;
}
///////////////////////////////////////////////1行の処理を行う/////////////////////////////
// 行数カウント
dwCount++;
// 1行目スキップするか判定
if( lpInsertInfo->nSkipFirstRowFlg == 1 && dwCount == 1 ){
wnsprintf( szTmp, BUF_SIZE*2, "%04d行目:@IntSkipFirstRowFlg=1のためスキップしました。", dwCount );
cLog->WriteLogFile( szTmp );
continue;
}
// ファイルから取得した1行ラインの初めの文字が特定文字だった場合はスキップ
if( szFileBuf[0] == lpInsertInfo->szSkipFirstLetter[0] ){
wnsprintf( szTmp, BUF_SIZE*2, "%04d行目:@ChSkipFirstLetter=%sと一致したためスキップしました。", dwCount, lpInsertInfo->szSkipFirstLetter );
cLog->WriteLogFile( szTmp );
continue;
}
// SQL文作成 - 引数のDB名からユーザ定義のテーブル名取得
// 初めの1回だけSET ANSI_DEFAULTS OFF をつける。
// SET ANSI_DEFAULTS OFF : INSERT時に文字列が""で囲まれていてもインサートできる。
// 通常のINSERT文↓
// wnsprintf( szQuery, MAX_PATH, "INSERT INTO %s.%s.%s VALUES( 'D', '00000007', '0000000007', 'M', 1978, 7, 7, 7, 'V', '7', 'Reserve', '2000/06/27', '2000/06/27' )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName );
// 文字列が""で囲まれたINSERT文↓
// wnsprintf( szQuery, MAX_PATH, "SET ANSI_DEFAULTS OFF INSERT INTO %s.%s.%s VALUES( \"AD", \"00000008\", \"0000000008\", \"M\", 1978, 7, 7, 7, \"V\", \"7\", \"Reserve\", \"2000/06/27\", \"2000/06/27\" )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName );
if( dwCountIn == 0 ){
// @DtmInsertDateが空かNULLの場合は、ファイルから読取った1行をそのままインサート
if( lstrcmpi( lpInsertInfo->szInsertDate, "" ) == 0 || lpInsertInfo->szInsertDate == NULL ){
wnsprintf( szQuery, BUF_SIZE*2, "SET ANSI_DEFAULTS OFF INSERT INTO %s.%s.%s VALUES( %s )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName, szFileBuf );
}
// @DtmInsertDateが空かNULLの以外場合は、ファイルから読取った1行に@DtmInsertDateを足してインサート
else{
wnsprintf( szQuery, BUF_SIZE*2, "SET ANSI_DEFAULTS OFF INSERT INTO %s.%s.%s VALUES( %s,\"%s\" )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName, szFileBuf, lpInsertInfo->szInsertDate );
}
}
else{
if( lstrcmpi( lpInsertInfo->szInsertDate, "" ) == 0 || lpInsertInfo->szInsertDate == NULL ){
wnsprintf( szQuery, BUF_SIZE*2, "INSERT INTO %s.%s.%s VALUES( %s )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName, szFileBuf );
}
else{
wnsprintf( szQuery, BUF_SIZE*2, "INSERT INTO %s.%s.%s VALUES( %s,\"%s\" )", lpInsertInfo->szDBName, lpInsertInfo->szOwnerName, lpInsertInfo->szTableName, szFileBuf, lpInsertInfo->szInsertDate );
}
}
// ログ - インサートする文字列
wnsprintf( szTmp, BUF_SIZE, "%04d行目:%s", dwCount, szQuery );
cLog->WriteLogFile( szTmp );
// SQL文を_bstr_型変換
_bstr_t bstrQuery( szQuery );
// SQL文実行 - インサート
_variant_t vRecsAffected(0L);
pConnection->Execute( bstrQuery, &vRecsAffected, adOptionUnspecified );
// インサート回数カウント
dwCountIn++;

///////////////////////////////////////////////////////////////////////////////////////////////
}
}
}

catch( _com_error &e ){
// ログ
lstrcpyn( szTmp, "_com_error", BUF_SIZE );
cLog->WriteLogFile( szTmp );
// エラーの特定
ADOPrintProviderError(pConnection);
ADOPrintComError(e);
// フラグ設定
nError = XP_ERROR;
}

////////////////////////////////////////////////////////////////////////////////////////////
// CloseHandle
CloseHandle( hFile );

////////////////////////////////////////////////////////////////////////////////////////////
// コネクション破棄
if( pConnection ){
pConnection->Close();
pConnec