CSVファイルから数千のレコードをテーブル_[Child]
_に挿入したい(そして、これを数十のファイルから同じテーブルに挿入する)。ただし、A。ルックアップ値をテーブル(_[Parent]
_の列値)とB。挿入が行われる前に、CSVに含まれるデータに対して他の計算を実行します。これはどのようにして最良に達成されますか?
ルックアップは基本的に、_Child.P_ID
_からIDENTITY
ベースの主キー値(_[Parent]
_として)を返すことです。ここで、_[Parent].NVarCharValue,[Parent].DateTimeValue
_はCSVデータの最初の2つのフィールドと一致します、しかし、_NVarCharValue
とDateTimeValue
自体を_[Child]
_に格納する手間を省きたいです。他の子レベルのテーブルとの将来の結合は_P_ID
_フィールドとテキスト情報は冗長であり、スペースを消費し、挿入時に追加のラグタイムを引き起こす可能性があります(ただし、ルックアップほどではないかもしれませんが、悲しいかな…)。
たとえば、計算によって得られたデータ(B。)の場合、エンコードされたGPS座標を[d] ddmm.mmmm、C(Degrees DecimalMinutes、Direction)形式に変換して、1つの10進度フィールド(またはgeography :: Pointの半分)に変換します。たとえば、私のCSVには_3748.9729,S
_などの素敵なエントリが含まれています。これは37°48.9729 ’Sとして解析され、それをLatitudeに保存される-37.816215に変換されます。
オプション?私は5つの可能性を考え出しましたが、アプローチのすべての落とし穴を考慮しない限り、それらのいずれにもコーディングしたくありません。
Insert Into
_ビュー+ _BULK INSERT
_子テーブルにのみ挿入するつもりですが、ルックアップを実行するために必要なマルチテーブルジョインは、行を複数の基本テーブルに挿入するときに MS制約 に違反している可能性があります。そのビューに挿入する場合、VIEW
のDDLで指定された計算または集計がない場合、確かに constraint に違反します。
そうでなければ、これは(本質的に)私がやりたいことです
_CREATE VIEW [dbo].[ivChild] as
SELECT
P.[ NVarCharValue],
P.[ DateTimeValue],
CAST(CAST((ABS(C.Latitude) as Integer) as VarChar(3))+STR(ABS(C.Latitude-CAST(C.Latitude as Integer))*60.0,9,6) as LatitudeMagnitude --not quite correct (single digit minutes), but close enough for the example
CASE
WHEN SIGN(C.Latitude)>0 THEN ‘N’
ELSE ‘S’
END as LatitudeDir,
CAST(CAST((ABS(C.Longitude) as Integer) as VarChar(3))+STR(ABS(C.Longitude-CAST(C.Longitude as Integer))*60.0,9,6) as LongitudeMagnitude
CASE
WHEN SIGN(C.Latitude)>0 THEN ‘E’
ELSE ‘W’
END as LongitudeDir
FROM Child as C join Parent as P on P.ID=C.P_id
_
しかし、おそらく最初の2つのサブクエリを使用して、Child
に対してのみ挿入を有効にすることができますか?例えば.
_SELECT
(SELECT P.NVarCharValue from Parent as P on P.ID=C.P_id) as NVarCharValue
(SELECT P.DateTimeValue from Parent as P on P.ID=C.P_id) as DateTimeValue
…
_
BULK INSERT
_をステージングテーブルに、ストアドプロシージャmerge
を実際のデータテーブルに?CSVのNVarCharValue
+ DateTimeValue
列をステージングテーブルに挿入し、次に_MERGE INTO
_または_INSERT INTO
_クエリを実行して、値をデータテーブルに移行します。可能な場合はインラインで計算を行い、検索を実行して文字列の入力値を解析し、最終的な格納値(たとえば、文字列からの10進度、次にdecimal(10,6)として格納)に変換します。
GPSMagnitude
のカスタムCLRを作成しますこれは、C#で着信文字列を解析し、内部的に10進数としてクラスに格納し、10進数データ型関数に自動変換します。何かのようなもの
_public static implicit operator Decimal(GPSMagnitude c) # GPS->decimal
{ return c._decimalMagnitude; }
Public static implicit operator GPSMagnitude(Decimal c) # decimal->GPS
{ return new GPSMagnitude(c); }
Public static implicit operator ToString(GPSMagnitude c)# GPS->string
{
d = (int)c;
m = (c-(int)c)*60.0;
return d.ToString()+m.ToString();
}
[SqlMethod(OnNullCall = false)]
public GPSMagnitude Parse(SqlString s) # string->GPS
{
Decimal f;
int Deg;
float Mn;
Int32.TryParse(s, out f);
Deg = ((int)f)/100
Mn = f – Deg*100.0
return new GPSMagnitude(Deg + Mn/60)
}
_
次に、CAST(LatitudeMagnitude as GPSMagnitude)
が指定されたビューを使用し、LatitudeMagnitude
がdecimal(10,6)
として指定された基になるテーブルを作成します。 2つの列をマージしてマグニチュードを署名させる(S/Wは負)ために、まだいくつかの面白いビジネスを行う必要があります。また、P_IDルックアップの問題を個別に処理する必要があります。
BULK INSERT
_と_FIRE_TRIGGERS
_文字列入力を受け取り、それを10進数としてテーブルに送信するカスタムトリガーで解析を行います。 BULK INSERTを聞くとかなり遅くなりますが、検索とGPSテキストの解析の両方ができるはずです。 TRIGGERコードがどうなるかについては、あまり考えていません。
たとえばPython GPS計算を実行し、マグニチュードと符号列を組み合わせます。ただし、P_IDルックアップの問題には対処する必要がありますが、
私が持っているものに混乱している場合は、これらのオプションをさらに具体化できます。P
ソロモンの提案に基づいて、私はC#ストリームパーサーを実装し始めています。
テーブルから既存の構造を取得するには、次のコードが機能する必要があります(いくつかの調整が必要です)。これは_else ifs
_ではなく_switch case
_を使用していることに注意してください。どちらがより効率的かわかりません。また、すべての不測の事態をカバーするためにすべての列を取得したわけではありませんが、最も基本的なタイプでは問題なく機能するはずです。繰り返しますが、これはSQLサーバーで既に設定したテーブルフィールドを概算するためのものです。
_ public SqlMetaData[] ServerLookupFields()
{
using (SqlConnection conn = new SqlConnection(ParserDict.ConnectionString))
{
SqlCommand command;
int siz;
Console.WriteLine("Activating SQL connection");
conn.Open();
Console.WriteLine("SQL connection open");
string num = "select count(*) as cnt from sys.columns as sc join sys.tables as st on sc.object_id = st.object_id where st.name=@table_name;";
command = new SqlCommand(num, conn);
command.Parameters.AddWithValue("@table_name", this.tableDestination);
string result = command.ExecuteScalar().ToString();
Int32.TryParse(result, out siz);
command.Dispose();
SqlMetaData[] smd = new SqlMetaData[siz];
string cmd = "exec sp_columns @table_name=@table_name, @table_owner=dbo";
command = new SqlCommand(cmd, conn);
command.Parameters.AddWithValue("@table_name", this.tableDestination);
SqlDataReader rd = command.ExecuteReader();
int i = 0;
while (rd.Read())
{
smd[i] = ReadSingleMetaRow((IDataRecord)rd);
Console.WriteLine(smd[i].Name.ToString()+","+smd[i].DbType.ToString());
i++;
}
rd.Close();
command.Dispose();
return smd;
}
}
private SqlMetaData ReadSingleMetaRow(IDataRecord meta)
{
bool nullable;
byte precision;
byte length;
byte scale;
SqlMetaData smd;
string columnName = meta[3].ToString(); //Column_name
string dataType = meta[5].ToString(); //Type_name
string dt_fw_lower = dataType.ToLower().Split(new Char[] { ' ' }, 2)[0]; //dataType_firstWord_ToLower
byte.TryParse(meta[6].ToString(), out precision); //Precision
byte.TryParse(meta[7].ToString(), out length); //Length
byte.TryParse(meta[8].ToString(), out scale); //Scale
Boolean.TryParse(meta[10].ToString(), out nullable); //Nullable
string datetimesub = meta[14].ToString(); //DateTimeSub(type)
if (dt_fw_lower == "datetime2") { smd = new SqlMetaData(columnName, SqlDbType.DateTime2); }
else if (dt_fw_lower == "nvarchar") { smd = new SqlMetaData(columnName, SqlDbType.NVarChar, length); }
else if (dt_fw_lower == "varchar") { smd = new SqlMetaData(columnName, SqlDbType.VarChar, length); }
else if (dt_fw_lower == "bigint") { smd = new SqlMetaData(columnName, SqlDbType.BigInt); }
else if (dt_fw_lower == "float") { smd = new SqlMetaData(columnName, SqlDbType.Float, precision, length); }
else if (dt_fw_lower == "decimal") { smd = new SqlMetaData(columnName, SqlDbType.Decimal, precision, scale); }
else if (dt_fw_lower == "binary") { smd = new SqlMetaData(columnName, SqlDbType.Binary, length); }
else if (dt_fw_lower == "varbinary") { smd = new SqlMetaData(columnName, SqlDbType.VarBinary, length); }
else if (dt_fw_lower == "bit") { smd = new SqlMetaData(columnName, SqlDbType.Bit); }
else if (dt_fw_lower == "char") { smd = new SqlMetaData(columnName, SqlDbType.Char, length); }
else if (dt_fw_lower == "nchar") { smd = new SqlMetaData(columnName, SqlDbType.NChar, length); }
else if (dt_fw_lower == "datetimeoffset") { smd = new SqlMetaData(columnName, SqlDbType.DateTimeOffset); }
else if (dt_fw_lower == "datetime") { smd = new SqlMetaData(columnName, SqlDbType.DateTime); }
else if (dt_fw_lower == "date") { smd = new SqlMetaData(columnName, SqlDbType.Date, length); }
else if (dt_fw_lower == "image") { smd = new SqlMetaData(columnName, SqlDbType.Image, length); }
else if (dt_fw_lower == "int") { smd = new SqlMetaData(columnName, SqlDbType.Int); }
else if (dt_fw_lower == "money") { smd = new SqlMetaData(columnName, SqlDbType.Money, length); }
else if (dt_fw_lower == "ntext") { smd = new SqlMetaData(columnName, SqlDbType.NText, length); }
else if (dt_fw_lower == "real") { smd = new SqlMetaData(columnName, SqlDbType.Real, precision, scale); }
else if (dt_fw_lower == "smalldatetime") { smd = new SqlMetaData(columnName, SqlDbType.SmallDateTime); }
else if (dt_fw_lower == "smallint") { smd = new SqlMetaData(columnName, SqlDbType.SmallInt); }
else if (dt_fw_lower == "smallmoney") { smd = new SqlMetaData(columnName, SqlDbType.SmallMoney); }
else if (dt_fw_lower == "structured") { smd = new SqlMetaData(columnName, SqlDbType.Structured, length); }
else if (dt_fw_lower == "text") { smd = new SqlMetaData(columnName, SqlDbType.Text, -1); }
else if (dt_fw_lower == "timestamp") { smd = new SqlMetaData(columnName, SqlDbType.Timestamp); }
else if (dt_fw_lower == "time") { smd = new SqlMetaData(columnName, SqlDbType.Time); }
else if (dt_fw_lower == "tinyint") { smd = new SqlMetaData(columnName, SqlDbType.TinyInt); }
else if (dt_fw_lower == "udt") { smd = new SqlMetaData(columnName, SqlDbType.Udt, length); }
else if (dt_fw_lower == "uniqueidentifier") { smd = new SqlMetaData(columnName, SqlDbType.UniqueIdentifier, length); }
else if (dt_fw_lower == "variant") { smd = new SqlMetaData(columnName, SqlDbType.Variant, length); }
else if (dt_fw_lower == "xml") { smd = new SqlMetaData(columnName, SqlDbType.Xml, length); }
else { smd = new SqlMetaData(columnName, 0); }
return smd;
}
_
これをバラバラに行う必要はありません。このすべてを、セットベースのアプローチで、バッチで実行できます(そのため、入力ファイルのサイズは関係ありません)。ファイルから行を取得して、最初から最後まで処理する方法について考えてください。次に、各ステップが単一の行ではなく行のセットで動作していることを確認します。
これはSQLCLRを必要としませんが、XMLまたはテーブル値パラメーター(TVP)を使用して、行のバッチをSQL Serverに転送しますafterローカルで処理されました。
基本的なワークフローは次のとおりです(これについては別の回答で説明していますが、現時点では見つかりません)。ストアドプロシージャに各行を転送するには、ユーザー定義のテーブルタイプを作成する必要があります(これはTVPであり、SQL Server 2008以降で使用できます)。
IEnumerable<SqlDataRecord>
を実装するメソッドを使用して、nota DataTable
!)をストアドプロシージャに送信します。 ____。]IDENTITY
値を保持します。[Parent]
に挿入します。ここで、[Parent].NVarCharValue
および[Parent].DateTimeValue
は存在しません。 IDENTITY
値を[Parent].NVarCharValue
および[Parent].DateTimeValue
とともに別の一時テーブルにキャプチャして、メインの一時テーブルを更新し、新しいIDENTITY値を取得できるようにするか、またはそれをスキップして、最後のINSERT
を実行するときに[Parent]
テーブルにJOINするだけです。[Parent]
の新しいIDENTITY値を2番目の一時テーブルにキャプチャした場合は、NVarCharValue
とDateTimeValue
に一致するそれらの値でメインの一時テーブルを更新します。UPDATE
[Child]
テーブル、行を一意に識別する1つ以上の列(つまり、代替キー)に結合します。これは実際にはWHERE EXISTS
です。INSERT INTO [Child]
、行を一意に識別する列でWHERE NOT EXISTS
を使用私はこのアプローチを数回使用し、大きな成功を収めました。フォールトトレラントであるため、中断されたときに中断したところから簡単に再開できます。また、ローカル一時テーブルがその役割を果たして自動的にクリーンアップされるため、ステージングテーブル(クリーンアップしてインポートをマルチスレッド化する場合は乱雑にする必要がある)は必要ありませんandは本質的にスレッドセーフです:-)。これにより、アプリレイヤーでの処理に最も適した部分を処理し、残りをデータレイヤーで処理することもできます。また、メモリに関する限り、メモリ内にインポートファイルが一度に1バッチ分しか存在しないため、インポートファイルが10 kBでも10 GBでもかまいません。
また、必要に応じて、DB側のプロセスをできます。通常は、挿入、更新、またはスキップを判別するための状況列があります。次に、メインの一時テーブルにデータを入力した後、既存の宛先テーブルと比較します。行はあるが1つ以上の列が異なる場合、その行は更新としてマークされます。行はあるがすべての列が同じ場合、スキップとマークされます(変更されていない行を更新する必要はありません)。そうでない場合は、挿入としてマークされます。次に、最後にI UPDATE
where Status = Update、次にI INSERT
where Status = Insertとします。もちろん、私はそれぞれが独自の「ファンキーな」ルールを持っているいくつかのテーブルにインポートしていました;-)。
Stack Overflowの私の別の回答でいくつかのサンプルコードを見ることができます: 1000万レコードを可能な限り最短時間で挿入するにはどうすればよいですか? そのコードの構造はバッチ処理されません。ストアドプロシージャを1回実行し、ファイルを開き、行の読み取りと送信を行い、最後に閉じるプロセスを開始します。バッチ処理するには、次のように変更する必要があります。
break;
rowCount++;