कृपया नहीं करें एक DataTable
बनाएं बल्ककॉपी के माध्यम से लोड करने के लिए। यह डेटा के छोटे सेट के लिए एक अच्छा समाधान है, लेकिन डेटाबेस को कॉल करने से पहले सभी 10 मिलियन पंक्तियों को मेमोरी में लोड करने का कोई कारण नहीं है।
आपका सर्वश्रेष्ठ दांव (BCP
. के बाहर / BULK INSERT
/ OPENROWSET(BULK...)
) फ़ाइल से सामग्री को तालिका-मूल्यवान पैरामीटर (TVP) के माध्यम से डेटाबेस में स्ट्रीम करना है। एक टीवीपी का उपयोग करके आप फ़ाइल खोल सकते हैं, एक पंक्ति पढ़ सकते हैं और एक पंक्ति को तब तक भेज सकते हैं जब तक कि यह पूरा न हो जाए और फिर फ़ाइल को बंद कर दें। इस पद्धति में केवल एक पंक्ति का मेमोरी फ़ुटप्रिंट है। मैंने एक लेख लिखा था, एक अनुप्रयोग से SQL सर्वर 2008 में डेटा स्ट्रीमिंग, जिसमें इस परिदृश्य का एक उदाहरण है।
संरचना का एक सरल अवलोकन इस प्रकार है। मैं वही आयात तालिका और फ़ील्ड नाम मान रहा हूं जैसा कि ऊपर दिए गए प्रश्न में दिखाया गया है।
आवश्यक डेटाबेस ऑब्जेक्ट:
-- First: You need a User-Defined Table Type
CREATE TYPE ImportStructure AS TABLE (Field VARCHAR(MAX));
GO
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
@ImportTable dbo.ImportStructure READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE dbo.DATAs;
INSERT INTO dbo.DATAs (DatasField)
SELECT Field
FROM @ImportTable;
GO
उपरोक्त SQL ऑब्जेक्ट का उपयोग करने के लिए C# ऐप कोड नीचे है। ध्यान दें कि किसी ऑब्जेक्ट (जैसे डेटाटेबल) को भरने और फिर संग्रहीत प्रक्रिया को निष्पादित करने के बजाय, यह संग्रहीत प्रक्रिया का निष्पादन है जो फ़ाइल सामग्री को पढ़ने की शुरुआत करता है। संग्रहित प्रक्रिया का इनपुट पैरामीटर एक चर नहीं है; यह एक विधि का वापसी मूल्य है, GetFileContents
. उस विधि को तब कहा जाता है जब SqlCommand
कॉल ExecuteNonQuery
, जो फ़ाइल खोलता है, एक पंक्ति पढ़ता है और पंक्ति को SQL सर्वर को IEnumerable<SqlDataRecord>
के माध्यम से भेजता है और yield return
बनाता है, और फिर फ़ाइल को बंद कर देता है। संग्रहीत प्रक्रिया केवल एक तालिका चर, @ImportTable को देखती है, जिसे जैसे ही डेटा आना शुरू होता है, एक्सेस किया जा सकता है (नोट:डेटा थोड़े समय के लिए बना रहता है, भले ही पूर्ण सामग्री न हो, tempdb में) उन्हें> )।
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> GetFileContents()
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("Field", SqlDbType.VarChar, SqlMetaData.Max)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
try
{
_FileReader = new StreamReader("{filePath}");
// read a row, send a row
while (!_FileReader.EndOfStream)
{
// You shouldn't need to call "_DataRecord = new SqlDataRecord" as
// SQL Server already received the row when "yield return" was called.
// Unlike BCP and BULK INSERT, you have the option here to create a string
// call ReadLine() into the string, do manipulation(s) / validation(s) on
// the string, then pass that string into SetString() or discard if invalid.
_DataRecord.SetString(0, _FileReader.ReadLine());
yield return _DataRecord;
}
}
finally
{
_FileReader.Close();
}
}
GetFileContents
उपरोक्त विधि का उपयोग नीचे दिखाए गए अनुसार संग्रहीत प्रक्रिया के लिए इनपुट पैरामीटर मान के रूप में किया जाता है:
public static void test()
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
_Command.CommandType = CommandType.StoredProcedure;
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "@ImportTable";
_TVParam.TypeName = "dbo.ImportStructure";
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = GetFileContents(); // return value of the method is streamed data
_Command.Parameters.Add(_TVParam);
try
{
_Connection.Open();
_Command.ExecuteNonQuery();
}
finally
{
_Connection.Close();
}
return;
}
अतिरिक्त नोट:
- कुछ संशोधनों के साथ, उपरोक्त C# कोड को डेटा को बैच करने के लिए अनुकूलित किया जा सकता है।
- मामूली संशोधन के साथ, उपरोक्त C# कोड को कई क्षेत्रों में भेजने के लिए अनुकूलित किया जा सकता है (उदाहरण "स्टीमिंग डेटा ..." लेख में दिखाया गया है जो ऊपर लिंक 2 क्षेत्रों में गुजरता है)।
- आप
SELECT
. में प्रत्येक रिकॉर्ड के मान में हेर-फेर भी कर सकते हैं खरीद में बयान। - आप खरीद में WHERE शर्त का उपयोग करके पंक्तियों को फ़िल्टर भी कर सकते हैं।
- आप टीवीपी टेबल वैरिएबल को कई बार एक्सेस कर सकते हैं; यह केवल पढ़ने के लिए है लेकिन "केवल आगे" नहीं है।
SqlBulkCopy
से अधिक के लाभ :SqlBulkCopy
INSERT-only है जबकि TVP का उपयोग करने से डेटा को किसी भी रूप में उपयोग करने की अनुमति मिलती है:आपMERGE
पर कॉल कर सकते हैं; आपDELETE
कर सकते हैं किसी शर्त के आधार पर; आप डेटा को कई तालिकाओं में विभाजित कर सकते हैं; और इसी तरह।- एक TVP के केवल INSERT नहीं होने के कारण, आपको डेटा को डंप करने के लिए एक अलग स्टेजिंग टेबल की आवश्यकता नहीं है।
- आप
ExecuteReader
. पर कॉल करके डेटाबेस से डेटा वापस पा सकते हैंExecuteNonQuery
. के बजाय . उदाहरण के लिए, यदि कोईIDENTITY
थीDATAs
. पर फ़ील्ड आयात तालिका, आप एकOUTPUT
जोड़ सकते हैंINSERT
. का खंड वापस पास करने के लिएINSERTED.[ID]
(ID
मानकरIDENTITY
का नाम है खेत)। या आप एक पूरी तरह से अलग क्वेरी के परिणामों को वापस पास कर सकते हैं, या दोनों के बाद से कई परिणाम सेट भेजे और एक्सेस किए जा सकते हैंReader.NextResult()
.SqlBulkCopy
. का उपयोग करते समय डेटाबेस से जानकारी वापस प्राप्त करना संभव नहीं है फिर भी यहाँ S.O पर कई प्रश्न हैं। ठीक ऐसा करने के इच्छुक लोगों की संख्या (कम से कम नव निर्मितIDENTITY
. के संबंध में) मान)। - इस बारे में अधिक जानकारी के लिए कि यह कभी-कभी समग्र प्रक्रिया के लिए तेज़ क्यों होता है, भले ही डिस्क से SQL सर्वर में डेटा प्राप्त करने में थोड़ा धीमा हो, कृपया SQL सर्वर ग्राहक सलाहकार टीम का यह श्वेतपत्र देखें:TVP के साथ थ्रूपुट को अधिकतम करना