web-dev-qa-db-ja.com

SQL Server 2012にXMLファイルをインポートする

毎週金曜日、2、3(時には300以上)のXMLファイルを2つのテーブルにインポートする必要があります。

テーブルの1つの構造R000000、次のようになります:

R00000010 | R00000020 | R00000030 | R00000040 | R00000050 | R00000060 
---------- ------------ ---------- ----------- ----------- ----------
R000000   |     I     |   0002    |     1     |     2     |    0026
R000000   |     I     |   0003    |     1     |     2     |    0025
R000000   |     I     |   0004    |     1     |     2     |    0021
R000000   |     I     |   0006    |     1     |     2     |    0023
R000000   |     I     |   0001    |     1     |     2     |    0022

^各行はXMLファイルに対応しています。

構造は変更されず、データのみが変更されます(この場合、ランダムなものを配置しました)。

XMLファイルは次のようになります。

<?xml version="1.0" encoding="ISO-8859-1"?>
<ns0:P4131 xmlns:ns0="http://switching/xi">
<R000000>
    <R00000010>R000000</R00000010>
    <R00000020>I</R00000020>
    <R00000030>0002</R00000030>
    <R00000040>1</R00000040>
    <R00000050>0026</R00000050>
    <R00000060>2</R00000060>
</R000000>
</ns0:P4131>

これを行う最良の方法は何ですか?私は現在、これをAccessで行っています。

5
Eudes

これは、SQLCLRを介して行うのが実際には非常に簡単です。ストアドプロシージャは、特定のディレクトリにあるxmlファイルを読み取るように(またはすべてのサブディレクトリを簡単にチェックするように)設定して、すべての内容を含む単一の結果セットを出力できます。これを行うと、次のクエリをテーブルに入力できます。

_INSERT INTO dbo.R000000 (R00000010, R00000020, R00000030, R00000040, R00000050, R00000060)
    EXEC dbo.GetXmlDataFromFiles(N'C:\Path\To\XML\Files');
_

そして、それはそれです。

次のコードは、_.xml_入力パラメーターで指定されたディレクトリ内の_@FilePath_ファイルを読み取り、オプションでサブディレクトリを走査して、各ファイルの内容の単一の結果セットを返します。

ご注意ください:

  • 質問は「各行はXMLファイルに対応している」と述べているため、コードはファイルごとに1つのノードのみを想定しています。例のデータはそのステートメントと一致しています。
  • ファイルのいずれかに複数の_<R000000>_ノードが含まれている可能性がある場合、このコードを変更してそれを処理するのは非常に簡単です。
  • 各ファイルの内容は、読み取られるとすぐに結果行として送り返されます。これは、一度にすべてのファイルを読み取って結果セット全体を送り返すのではなく、一度に1つのファイルのみがメモリにあることを意味します。したがって、これはかなり適切にスケーリングし、3000個のファイルをインポートする場合は問題になりません。
_using System;
using System.Data;
using System.Data.SqlTypes;
using System.IO;
using System.Xml;
using Microsoft.SqlServer.Server;

public class ImportXmlFiles
{
  [Microsoft.SqlServer.Server.SqlProcedure]
  public static void ReadXmlFiles([SqlFacet(MaxSize = 500)] SqlString FilePath,
     SqlBoolean Recursive)
  {
    XmlDocument _FileContents = new XmlDocument();

    SqlDataRecord _ResultRow = new SqlDataRecord(new SqlMetaData[]{
       new SqlMetaData("R00000010", SqlDbType.VarChar, 10),
       new SqlMetaData("R00000020", SqlDbType.VarChar, 10),
       new SqlMetaData("R00000030", SqlDbType.VarChar, 10),
       new SqlMetaData("R00000040", SqlDbType.Int),
       new SqlMetaData("R00000050", SqlDbType.VarChar, 10),
       new SqlMetaData("R00000060", SqlDbType.Int)
    });

    SqlContext.Pipe.SendResultsStart(_ResultRow);

    foreach (string _FileName in Directory.GetFiles(FilePath.Value, "*.xml",
       (Recursive.IsTrue) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
            )
    {
      _FileContents.Load(_FileName);
      XmlElement _Row = (XmlElement)_FileContents.SelectSingleNode("//R000000");

      _ResultRow.SetString(0, _Row.SelectSingleNode("./R00000010").InnerText);
      _ResultRow.SetString(1, _Row.SelectSingleNode("./R00000020").InnerText);
      _ResultRow.SetString(2, _Row.SelectSingleNode("./R00000030").InnerText);
      _ResultRow.SetInt32(3,
         Convert.ToInt32(_Row.SelectSingleNode("./R00000040").InnerText));
      _ResultRow.SetString(4, _Row.SelectSingleNode("./R00000050").InnerText);
      _ResultRow.SetInt32(5,
         Convert.ToInt32(_Row.SelectSingleNode("./R00000060").InnerText));

      SqlContext.Pipe.SendResultsRow(_ResultRow);
    }

    SqlContext.Pipe.SendResultsEnd();

    return;
  }
}
_

上記のSQLCLRストアドプロシージャの簡単にインストールできる実際の例は、Pastebinの次の場所にあります。

SQLCLR Stored Procは多数のXMLファイルの1つの結果セットを返します

!!アセンブリは_EXTERNAL_ACCESS_に設定されていますが、TRUSTWORTHYのデータベースプロパティはnotONに設定されていることに注意してください。これは、ほとんどのSQLCLRで行われますあなたがインターウェブでここで見つける例。アセンブリはコンパイル時に署名され(厳密な名前が付けられます)、インストールスクリプトは_[master]_に非対称キーを作成し、その非対称キーに基づくログインを作成して、そのログインに_EXTERNAL ACCESS Assembly_権限を付与します。これにより、_EXTERNAL_ACCESS_を必要とせずにアセンブリを_TRUSTWORTHY ON_に設定できるだけでなく、notを使用してアセンブリにUNSAFETRUSTWORTHYに設定されている場合に許可されるONに設定される!!


より一般的で、さまざまなXML構造をインポートできる別のアプローチは、ストアドプロシージャの代わりにテーブル値関数を使用することです。ここに示されているストアドプロシージャよりも、各ファイルの内容を単に読み取り、XMLデータ型の1つのフィールドである結果セットの各ファイルに対して1行を返す方が簡単です。次に、T-SQL .nodes()および.value()関数を使用して、必要に応じてさまざまな構造を解析できます。

2
Solomon Rutzky

以下のようなものを試してみてください...

環境に合わせて変数をプラグインし、データ型をチェックする必要があります(先行ゼロを維持するためにロジックを追加する必要があるかもしれません)、最終的な一時テーブルから通常のテーブルに変更するなど。

後でファイルを削除せずにXMLファイルから一時テーブルにインポートする場合は問題なく動作しますが、UNCパスからファイルを削除するロジックを追加することは、別のxp_cmdshellコマンドでそれほど難しくないはずです。

DECLARE @folder AS VARCHAR(1000) = '\\servername\sharename\folder\subfolder1\'
DECLARE @command VARCHAR(500) = 'DIR /B "' + @folder + '*.xml"'
DECLARE @file VARCHAR(100)
DECLARE @filesinafolder TABLE (filenameswithfolder VARCHAR(500))
DECLARE @sql NVARCHAR(4000)

-- create global temp table
IF OBJECT_ID('tempdb..##XMLImport') IS NOT NULL
    DROP TABLE ##XMLImport

CREATE TABLE ##XMLImport (
    R00000010 VARCHAR(7)
    ,R00000020 VARCHAR(1)
    ,R00000030 INT
    ,R00000040 INT
    ,R00000050 INT
    ,R00000060 INT
    )

INSERT INTO @filesinafolder
EXEC master..xp_cmdshell @command

-- create cursor
DECLARE filecurs CURSOR
FOR
SELECT REPLACE(filenameswithfolder, @folder, '') AS filenames
FROM @filesinafolder
WHERE filenameswithfolder IS NOT NULL

OPEN filecurs

FETCH NEXT
FROM filecurs
INTO @file

IF @file = 'FILE NOT FOUND'
    GOTO exitprocessing

WHILE @@fetch_status != - 1
BEGIN
    SET @sql = 'DECLARE @X XML

                    SELECT @X = P
                    FROM OPENROWSET(BULK ''' + @folder + '' + @file + ''', SINGLE_BLOB) AS Products(P)

                    DECLARE @iX INT

                    EXEC sp_xml_preparedocument @iX OUTPUT
                        ,@X

                    SELECT *
                    INTO #XMLResults
                    FROM OPENXML(@iX, ''/*/*'', 2) WITH (
                            R00000010 VARCHAR(7)
                            ,R00000020 VARCHAR(1)
                            ,R00000030 INT
                            ,R00000040 INT
                            ,R00000050 INT
                            ,R00000060 INT
                            )
                    EXEC sp_xml_removedocument @iX

                    INSERT INTO ##XMLImport
                    SELECT R00000010
                        ,R00000020
                        ,R00000030
                        ,R00000040
                        ,R00000050
                        ,R00000060
                    FROM #XMLResults'

    --PRINT @sql

    EXEC sp_executesql @sql

    -- process next file
    FETCH NEXT
    FROM filecurs
    INTO @file
END

exitprocessing:

-- clean up
CLOSE filecurs

DEALLOCATE filecurs

SELECT *
FROM ##XMLImport
2
Pimp Juice IT