How To Take A Csv Field And Write To Columns In Sql
Solution 1:
your problem is simple but I will take it one step further and let you know a better way to approach the issue.
when you have a problem to sold, always break it down into parts and apply each part in each own method. For example, in your case:
- 1 - read from the file
- 2 - create a sql query
- 3 - run the query
and you can even add validation to the file (imagine your file does not even have 7 fields in one or more lines...) and the example below it to be taken, only if your file never passes around 500 lines, as if it does normally you should consider to use a SQL statement that takes your file directly in to the database, it's called bulk insert
1 - read from file:
I would use a List<string>
to hold the line entries and I always use StreamReader
to read from text files.
using (StreamReader sr = File.OpenText(this.CsvPath))
{
while ((line = sr.ReadLine()) != null)
{
splittedLine = line.Split(new string[] { this.Separator }, StringSplitOptions.None);
if (iLine == 0 && this.HasHeader)
// header linethis.Header = splittedLine;
elsethis.Lines.Add(splittedLine);
iLine++;
}
}
2 - generate the sql
foreach (var line inthis.Lines)
{
string entries = string.Concat("'", string.Join("','", line))
.TrimEnd('\'').TrimEnd(','); // remove last ",'" this.Query.Add(string.Format(this.LineTemplate, entries));
}
3 - run the query
SqlCommand sql = new SqlCommand(string.Join("", query), mysqlconnectionstring);
sql.ExecuteNonQuery();
having some fun I end up doing the solution and you can download it here, the output is:
The code can be found here. It needs more tweaks but I will left that for others. Solution written in C#, VS 2013.
The ExtractCsvIntoSql
class is as follows:
publicclassExtractCsvIntoSql
{
privatestring CsvPath, Separator;
privatebool HasHeader;
private List<string[]> Lines;
private List<string> Query;
///<summary>/// Header content of the CSV File///</summary>publicstring[] Header { get; privateset; }
///<summary>/// Template to be used in each INSERT Query statement///</summary>publicstring LineTemplate { get; set; }
publicExtractCsvIntoSql(string csvPath, string separator, bool hasHeader = false)
{
this.CsvPath = csvPath;
this.Separator = separator;
this.HasHeader = hasHeader;
this.Lines = new List<string[]>();
// you can also set thisthis.LineTemplate = "INSERT INTO [table1] SELECT ({0});";
}
///<summary>/// Generates the SQL Query///</summary>///<returns></returns>public List<string> Generate()
{
if(this.CsvPath == null)
thrownew ArgumentException("CSV Path can't be empty");
// extract csv into object
Extract();
// generate sql query
GenerateQuery();
returnthis.Query;
}
privatevoidExtract()
{
string line;
string[] splittedLine;
int iLine = 0;
try
{
using (StreamReader sr = File.OpenText(this.CsvPath))
{
while ((line = sr.ReadLine()) != null)
{
splittedLine = line.Split(newstring[] { this.Separator }, StringSplitOptions.None);
if (iLine == 0 && this.HasHeader)
// header linethis.Header = splittedLine;
elsethis.Lines.Add(splittedLine);
iLine++;
}
}
}
catch (Exception ex)
{
if(ex.InnerException != null)
while (ex.InnerException != null)
ex = ex.InnerException;
throw ex;
}
// Lines will have all rows and each row, the column entry
}
privatevoidGenerateQuery()
{
foreach (var line inthis.Lines)
{
string entries = string.Concat("'", string.Join("','", line))
.TrimEnd('\'').TrimEnd(','); // remove last ",'" this.Query.Add(string.Format(this.LineTemplate, entries));
}
}
}
and you can run it as:
classProgram
{
staticvoidMain(string[] args)
{
string file = Ask("What is the CSV file path? (full path)");
string separator = Ask("What is the current separator? (; or ,)");
var extract = new ExtractCsvIntoSql(file, separator);
var sql = extract.Generate();
Output(sql);
}
privatestaticvoidOutput(IEnumerable<string> sql)
{
foreach(var query in sql)
Console.WriteLine(query);
Console.WriteLine("*******************************************");
Console.Write("END ");
Console.ReadLine();
}
privatestaticstringAsk(string question)
{
Console.WriteLine("*******************************************");
Console.WriteLine(question);
Console.Write("= ");
return Console.ReadLine();
}
}
Solution 2:
Usually i like to be a bit more generic so i'll try to explain a very basic flow i use from time to time:
I don't like the hard coded attitude so even if your code will work it will be dedicated specifically to one type. I prefer i simple reflection, first to understand what DTO is it and then to understand what repository should i use to manipulate it:
For example:
publicclassImportProvider
{
privatereadonlystring _path;
privatereadonly ObjectResolver _objectResolver;
publicImportProvider(string path)
{
_path = path;
_objectResolver = new ObjectResolver();
}
publicvoidImport()
{
var filePaths = Directory.GetFiles(_path, "*.csv");
foreach (var filePath in filePaths)
{
var fileName = Path.GetFileName(filePath);
var className = fileName.Remove(fileName.Length-4);
using (var reader = new CsvFileReader(filePath))
{
var row = new CsvRow();
var repository = (DaoBase)_objectResolver.Resolve("DAL.Repository", className + "Dao");
while (reader.ReadRow(row))
{
var dtoInstance = (DtoBase)_objectResolver.Resolve("DAL.DTO", className + "Dto");
dtoInstance.FillInstance(row.ToArray());
repository.Save(dtoInstance);
}
}
}
}
}
Above is a very basic class responsible importing the data. Nevertheless of how this piece of code parsing CSV files (CsvFileReader), the important part is thata "CsvRow" is a simple List.
Below is the implementation of the ObjectResolver:
publicclassObjectResolver
{
privatereadonly Assembly _myDal;
publicObjectResolver()
{
_myDal = Assembly.Load("DAL");
}
publicobjectResolve(string nameSpace, string name)
{
var myLoadClass = _myDal.GetType(nameSpace + "." + name);
return Activator.CreateInstance(myLoadClass);
}
}
The idea is to simple follow a naming convetion, in my case is using a "Dto" suffix for reflecting the instances, and "Dao" suffix for reflecting the responsible dao. The full name of the Dto or the Dao can be taken from the csv name or from the header (as you wish)
Next step is filling the Dto, each dto or implements the following simple abstract:
publicabstractclassDtoBase
{
publicabstractvoidFillInstance(paramsstring[] parameters);
}
Since each Dto "knows" his structure (just like you knew to create an appropriate table in the database), it can easily implement the FillInstanceMethod, here is a simple Dto example:
publicclassProductDto : DtoBase
{
publicint ProductId { get; set; }
publicdouble Weight { get; set; }
publicint FamilyId { get; set; }
publicoverridevoidFillInstance(paramsstring[] parameters)
{
ProductId = int.Parse(parameters[0]);
Weight = double.Parse(parameters[1]);
FamilyId = int.Parse(parameters[2]);
}
}
After you have your Dto filled with data you should find the appropriate Dao to handle it which is basically happens in reflection in this line of the Import() method:
var repository = (DaoBase)_objectResolver.Resolve("DAL.Repository", className + "Dao");
In my case the Dao implements an abstract base class - but it's not that relevant to your problem, your DaoBase can be a simple abstract with a single Save() method. This way you have a dedicated Dao to CRUD your Dto's - each Dao simply knows how to save for its relevant Dto. Below is the corresponding ProductDao to the ProductDto:
publicclassProductDao : DaoBase
{
privateconststring InsertProductQuery = @"SET foreign_key_checks = 0;
Insert into product (productID, weight, familyID)
VALUES (@productId, @weight, @familyId);
SET foreign_key_checks = 1;";
publicoverridevoidSave(DtoBase dto)
{
var productToSave = dto as ProductDto;
var saveproductCommand = GetDbCommand(InsertProductQuery);
if (productToSave != null)
{
saveproductCommand.Parameters.Add(CreateParameter("@productId", productToSave.ProductId));
saveproductCommand.Parameters.Add(CreateParameter("@weight", productToSave.Weight));
saveproductCommand.Parameters.Add(CreateParameter("@familyId", productToSave.FamilyId));
ExecuteNonQuery(ref saveproductCommand);
}
}
}
Please ignore the CreateParameter() method, since it's an abstraction from the base classs. you can just use a CreateSqlParameter or CreateDataParameter etc.
Just notice, it's a real naive implementation - you can easily remodel it better, depends on your needs.
Solution 3:
From the first impression of your questionc I guess you would be having hugely number of records (more than lacs). If yes I would consider the SQL bulk copies an option. If the record would be less go ahead single record. Insert. The reason for you insert not working is u not providing all the columns of the table and also there's some syntax error.
Post a Comment for "How To Take A Csv Field And Write To Columns In Sql"