├── _config.yml ├── Tests ├── Sample.xlsx ├── QueryMakers │ ├── UpdateQueryMakerTests.cs │ ├── H2MergeQueryMakerTests.cs │ ├── OracleMergeQueryMakerTests.cs │ └── InsertQueryMakerTests.cs ├── Utils.cs ├── Tests.csproj ├── TableScriptGeneratorTests.cs └── ExcelReaderTests.cs ├── Readme └── Sample_Input.png ├── ExcelToSQLScripts ├── Constants.cs ├── QueryMakers │ ├── IQueryMaker.cs │ ├── UpdateQueryMaker.cs │ ├── InsertQueryMaker.cs │ ├── H2MergeQueryMaker.cs │ └── OracleMergeQueryMaker.cs ├── Models │ ├── DataType.cs │ ├── Column.cs │ ├── Value.cs │ ├── Script.cs │ ├── Table.cs │ └── Record.cs ├── ExcelToSQLScripts.csproj ├── TableScriptGenerator.cs ├── ValueRenderer.cs └── ExcelReader.cs ├── .gitignore ├── ExcelToSQLScripts.Console ├── Program.cs ├── QueryMakerFactory.cs ├── ExcelToSQLScripts.Console.csproj ├── Options.cs └── App.cs ├── .github └── workflows │ └── dotnet-core.yml ├── ExcelToSQLScripts.sln └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /Tests/Sample.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilal-fazlani/ExcelToSqlScripts/HEAD/Tests/Sample.xlsx -------------------------------------------------------------------------------- /Readme/Sample_Input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilal-fazlani/ExcelToSqlScripts/HEAD/Readme/Sample_Input.png -------------------------------------------------------------------------------- /ExcelToSQLScripts/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelToSQLScripts 2 | { 3 | public static class Constants 4 | { 5 | public const string NULL = "NULL"; 6 | } 7 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/QueryMakers/IQueryMaker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ExcelToSQLScripts.Models; 3 | 4 | namespace ExcelToSQLScripts.QueryMakers 5 | { 6 | public interface IQueryMaker 7 | { 8 | string GenerateQuery(Record record); 9 | } 10 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/DataType.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelToSQLScripts.Models 2 | { 3 | public enum DataType 4 | { 5 | //todo:add more types 6 | String = 0, 7 | Number = 1, 8 | DateTime = 2, 9 | Boolean = 3 10 | } 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | .vs 6 | .idea 7 | *.userprefs 8 | .vscode 9 | bin 10 | obj 11 | 12 | **launchSettings.json 13 | **xlsx 14 | ExcelToSQLScripts.Console/output 15 | -------------------------------------------------------------------------------- /ExcelToSQLScripts/ExcelToSQLScripts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 7.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/Column.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelToSQLScripts.Models 2 | { 3 | public class Column 4 | { 5 | public Column(string name, DataType dataType, int index) 6 | { 7 | Name = name; 8 | DataType = dataType; 9 | Index = index; 10 | } 11 | 12 | public string Name { get; } 13 | 14 | public DataType DataType { get; } 15 | 16 | public int Index { get; } 17 | } 18 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/Value.cs: -------------------------------------------------------------------------------- 1 | namespace ExcelToSQLScripts.Models 2 | { 3 | public class Value 4 | { 5 | public Value(Column column, string stringValue, object typedValue) 6 | { 7 | Column = column; 8 | StringValue = stringValue; 9 | TypedValue = typedValue; 10 | } 11 | 12 | internal readonly string StringValue; 13 | 14 | internal readonly Column Column; 15 | 16 | internal readonly object TypedValue; 17 | } 18 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/Script.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace ExcelToSQLScripts.Models 5 | { 6 | public class Script : IDisposable 7 | { 8 | public Script(string name, Stream content) 9 | { 10 | Name = name; 11 | Content = content; 12 | } 13 | 14 | public string Name { get; } 15 | 16 | public Stream Content { get; } 17 | 18 | public void Dispose() 19 | { 20 | Content?.Dispose(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandDotNet; 2 | using CommandDotNet.Models; 3 | 4 | namespace ExcelToSQLScripts.Console 5 | { 6 | class Program 7 | { 8 | static int Main(string[] args) 9 | { 10 | AppRunner appRunner = new AppRunner(new AppSettings 11 | { 12 | Case = Case.CamelCase, 13 | EnableVersionOption = true, 14 | HelpTextStyle = HelpTextStyle.Detailed 15 | }); 16 | 17 | return appRunner.Run(args); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | # branches: [ master ] 6 | pull_request: 7 | # branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 2.1 20 | - name: Install dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --configuration Release --no-restore 24 | - name: Test 25 | run: dotnet test --no-restore --verbosity normal Tests/Tests.csproj 26 | -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/Table.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace ExcelToSQLScripts.Models 5 | { 6 | public class Table 7 | { 8 | public Table(string name) 9 | { 10 | Name = name; 11 | } 12 | 13 | public string Name { get; } 14 | 15 | public List Columns { get; set; } = new List(); 16 | 17 | public List Records { get; set; } = new List(); 18 | 19 | public string PrimaryKeyName 20 | { 21 | get { return Columns.OrderBy(c => c.Index).FirstOrDefault().Name; } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Tests/QueryMakers/UpdateQueryMakerTests.cs: -------------------------------------------------------------------------------- 1 | using ExcelToSQLScripts; 2 | using ExcelToSQLScripts.QueryMakers; 3 | using FluentAssertions; 4 | using Xunit; 5 | using Record = ExcelToSQLScripts.Models.Record; 6 | 7 | namespace Tests.QueryMakers 8 | { 9 | public class UpdateQueryMakerTests 10 | { 11 | [Fact] 12 | public void CanMakeQuery() 13 | { 14 | UpdateQueryMaker queryMaker = new UpdateQueryMaker(new ValueRenderer(new string[] { })); 15 | Record record = Utils.GetTable().Records[0]; 16 | string query = queryMaker.GenerateQuery(record); 17 | query.Should().Be("UPDATE EMPLOYEES SET NAME = 'bilal' WHERE ID = 1;\n"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/QueryMakers/H2MergeQueryMakerTests.cs: -------------------------------------------------------------------------------- 1 | using ExcelToSQLScripts; 2 | using ExcelToSQLScripts.QueryMakers; 3 | using FluentAssertions; 4 | using Xunit; 5 | using Record = ExcelToSQLScripts.Models.Record; 6 | 7 | namespace Tests.QueryMakers 8 | { 9 | public class H2MergeQueryMakerTests 10 | { 11 | [Fact] 12 | public void CanMakeQuery() 13 | { 14 | H2MergeQueryMaker queryMaker = new H2MergeQueryMaker(new ValueRenderer(new string[] { })); 15 | Record record = Utils.GetTable().Records[0]; 16 | string query = queryMaker.GenerateQuery(record); 17 | query.Should().Be("MERGE INTO EMPLOYEES (ID, NAME) KEY (ID) VALUES (1, 'bilal');\n"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts.Console/QueryMakerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ExcelToSQLScripts.QueryMakers; 3 | 4 | namespace ExcelToSQLScripts.Console 5 | { 6 | public static class QueryMakerFactory 7 | { 8 | public static IQueryMaker Create(string mode, ValueRenderer valueRenderer) 9 | { 10 | switch (mode.ToLower()) 11 | { 12 | case "insert": 13 | return new InsertQueryMaker(valueRenderer); 14 | case "update": 15 | return new UpdateQueryMaker(valueRenderer); 16 | case "merge": 17 | return new OracleMergeQueryMaker(valueRenderer); 18 | default: 19 | throw new ArgumentException("invalid mode specified"); 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Tests/Utils.cs: -------------------------------------------------------------------------------- 1 | using ExcelToSQLScripts.Models; 2 | 3 | namespace Tests 4 | { 5 | public static class Utils 6 | { 7 | public static Table GetTable(int length = 1, string name = "bilal") 8 | { 9 | Table table = new Table("Employees"); 10 | 11 | Column idColumn = new Column("ID", DataType.Number, 1); 12 | Column nameColumn = new Column("Name", DataType.String, 2); 13 | 14 | table.Columns.Add(idColumn); 15 | table.Columns.Add(nameColumn); 16 | 17 | for (int i = 0; i < length; i++) 18 | { 19 | Record record = new Record(table) 20 | { 21 | [0] = new Value(idColumn, $"{i + 1}", 1), 22 | [1] = new Value(nameColumn, name, name) 23 | }; 24 | 25 | table.Records.Add(record); 26 | } 27 | 28 | return table; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Tests/QueryMakers/OracleMergeQueryMakerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ExcelToSQLScripts; 3 | using ExcelToSQLScripts.Models; 4 | using ExcelToSQLScripts.QueryMakers; 5 | using FluentAssertions; 6 | using Xunit; 7 | using Record = ExcelToSQLScripts.Models.Record; 8 | 9 | namespace Tests.QueryMakers 10 | { 11 | public class OracleMergeQueryMakerTests 12 | { 13 | [Fact] 14 | public void CanMakeQuery() 15 | { 16 | OracleMergeQueryMaker queryMaker = new OracleMergeQueryMaker(new ValueRenderer(new string[] { })); 17 | Record record = Utils.GetTable().Records[0]; 18 | string query = queryMaker.GenerateQuery(record); 19 | query.Should().Be(@"MERGE INTO EMPLOYEES T 20 | USING (SELECT 1 ID, 'bilal' NAME FROM DUAL) D 21 | ON (T.ID = D.ID) 22 | WHEN MATCHED THEN 23 | UPDATE SET T.NAME = D.NAME 24 | WHEN NOT MATCHED THEN 25 | INSERT (ID, NAME) VALUES (D.ID, D.NAME); 26 | "); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.1 4 | 7.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ExcelToSQLScripts/Models/Record.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace ExcelToSQLScripts.Models 5 | { 6 | public class Record 7 | { 8 | public Table Table { get; } 9 | 10 | public Record(Table table) 11 | { 12 | Table = table; 13 | } 14 | 15 | public List Values { get; set; } = new List(); 16 | 17 | public Value this[int columnIndex] 18 | { 19 | get => Values[columnIndex]; 20 | set => Values.Add(value); 21 | } 22 | 23 | public bool IsEmpty => Values.TrueForAll(x => string.IsNullOrEmpty(x.StringValue)); 24 | 25 | public (string Name, Value Value) PrimaryKey 26 | { 27 | get 28 | { 29 | string name = Table.Columns.OrderBy(c => c.Index).First().Name; 30 | Value value = Values.Single(x => x.Column.Name.ToUpperInvariant() == name.ToUpperInvariant()); 31 | return (name, value); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/QueryMakers/UpdateQueryMaker.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text; 3 | using ExcelToSQLScripts.Models; 4 | 5 | namespace ExcelToSQLScripts.QueryMakers 6 | { 7 | public class UpdateQueryMaker : IQueryMaker 8 | { 9 | private readonly ValueRenderer _valueRenderer; 10 | 11 | public UpdateQueryMaker(ValueRenderer valueRenderer) 12 | { 13 | _valueRenderer = valueRenderer; 14 | } 15 | 16 | public virtual string GenerateQuery(Record record) 17 | { 18 | var nameValuePair = string.Join(", ", 19 | record.Values.Skip(1).Select(v => $"{v.Column.Name.ToUpperInvariant()} = {_valueRenderer.Render(v)}")); 20 | 21 | StringBuilder stringBuilder = new StringBuilder( 22 | $"UPDATE {record.Table.Name.ToUpperInvariant()} SET {nameValuePair} " + 23 | $"WHERE {record.Table.PrimaryKeyName.ToUpperInvariant()} = " + 24 | $"{_valueRenderer.Render(record.PrimaryKey.Value)};\n"); 25 | 26 | return stringBuilder.ToString(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/TableScriptGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using ExcelToSQLScripts.Models; 4 | using ExcelToSQLScripts.QueryMakers; 5 | 6 | namespace ExcelToSQLScripts 7 | { 8 | public class TableScriptGenerator 9 | { 10 | readonly IQueryMaker _queryMaker; 11 | 12 | public TableScriptGenerator(IQueryMaker queryMaker) 13 | { 14 | _queryMaker = queryMaker; 15 | } 16 | 17 | public Script GenerateTableScript(Table table) 18 | { 19 | MemoryStream memoryStream = new MemoryStream(); 20 | 21 | using (StreamWriter streamWriter = new StreamWriter(memoryStream, Encoding.Unicode, 4096, true)) 22 | { 23 | streamWriter.AutoFlush = true; 24 | foreach (var tableRecord in table.Records) 25 | { 26 | streamWriter.Write(_queryMaker.GenerateQuery(tableRecord)); 27 | } 28 | } 29 | 30 | memoryStream.Position = 0; 31 | return new Script(table.Name, memoryStream); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts.Console/ExcelToSQLScripts.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp2.1 5 | excel2sql 6 | true 7 | 7.2 8 | Bilal Fazlani 9 | en-US 10 | Convert excel files into sql scripts 11 | dotnet core; console; sql; excel; convert 12 | https://github.com/bilal-fazlani/ExcelToSqlScripts 13 | false 14 | https://github.com/bilal-fazlani/ExcelToSqlScripts 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/TableScriptGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using ExcelToSQLScripts; 3 | using ExcelToSQLScripts.Models; 4 | using ExcelToSQLScripts.QueryMakers; 5 | using FluentAssertions; 6 | using NSubstitute; 7 | using NSubstitute.Core; 8 | using Xunit; 9 | 10 | namespace Tests 11 | { 12 | public class TableScriptGeneratorTests 13 | { 14 | [Fact] 15 | public void CanGenerateScripts() 16 | { 17 | IQueryMaker queryMakerSubstitute = Substitute.For(); 18 | 19 | queryMakerSubstitute.GenerateQuery(Arg.Any()).Returns("random string\n"); 20 | 21 | TableScriptGenerator tableScriptGenerator = new TableScriptGenerator(queryMakerSubstitute); 22 | 23 | Table table = Utils.GetTable(2); 24 | 25 | using (Script script = tableScriptGenerator.GenerateTableScript(table)) 26 | { 27 | script.Name.Should().Be("Employees"); 28 | script.Content.Should().NotBeNull(); 29 | 30 | StreamReader streamReader = new StreamReader(script.Content); 31 | string content = streamReader.ReadToEnd(); 32 | 33 | content.Should().Be("random string\nrandom string\n"); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/QueryMakers/InsertQueryMaker.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using ExcelToSQLScripts.Models; 3 | 4 | namespace ExcelToSQLScripts.QueryMakers 5 | { 6 | public class InsertQueryMaker : IQueryMaker 7 | { 8 | private readonly ValueRenderer _valueRenderer; 9 | 10 | public InsertQueryMaker(ValueRenderer valueRenderer) 11 | { 12 | _valueRenderer = valueRenderer; 13 | } 14 | 15 | public virtual string GenerateQuery(Record record) 16 | { 17 | StringBuilder stringBuilder = new StringBuilder("INSERT INTO "); 18 | 19 | stringBuilder.Append(record.Table.Name.ToUpperInvariant() + " ("); 20 | 21 | foreach (Column column in record.Table.Columns) 22 | { 23 | stringBuilder.Append(column.Name.ToUpperInvariant()); 24 | if (column.Index != record.Table.Columns.Count) 25 | { 26 | stringBuilder.Append(", "); 27 | } 28 | } 29 | stringBuilder.Append(") VALUES ("); 30 | 31 | int index = 0; 32 | 33 | foreach (Value value in record.Values) 34 | { 35 | stringBuilder.Append(_valueRenderer.Render(value)); 36 | 37 | if (index < record.Values.Count - 1) stringBuilder.Append(", "); 38 | 39 | index++; 40 | } 41 | 42 | stringBuilder.Append(");\n"); 43 | 44 | return stringBuilder.ToString(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/QueryMakers/H2MergeQueryMaker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using ExcelToSQLScripts.Models; 4 | 5 | namespace ExcelToSQLScripts.QueryMakers 6 | { 7 | public class H2MergeQueryMaker : IQueryMaker 8 | { 9 | readonly ValueRenderer _valueRenderer; 10 | 11 | public H2MergeQueryMaker(ValueRenderer valueRenderer) 12 | { 13 | _valueRenderer = valueRenderer; 14 | } 15 | 16 | public string GenerateQuery(Record record) 17 | { 18 | StringBuilder stringBuilder = new StringBuilder("MERGE INTO "); 19 | stringBuilder.Append(record.Table.Name.ToUpperInvariant() + " ("); 20 | 21 | 22 | foreach (Column column in record.Table.Columns) 23 | { 24 | stringBuilder.Append(column.Name.ToUpperInvariant()); 25 | if (column.Index != record.Table.Columns.Count) 26 | { 27 | stringBuilder.Append(", "); 28 | } 29 | } 30 | 31 | string keyName = record.Table.PrimaryKeyName.ToUpperInvariant(); 32 | 33 | stringBuilder.Append($") KEY ({keyName}) VALUES ("); 34 | 35 | int index = 0; 36 | 37 | foreach (Value value in record.Values) 38 | { 39 | stringBuilder.Append(_valueRenderer.Render(value)); 40 | 41 | if (index < record.Values.Count - 1) stringBuilder.Append(", "); 42 | 43 | index++; 44 | } 45 | 46 | stringBuilder.Append(");\n"); 47 | 48 | return stringBuilder.ToString(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/QueryMakers/InsertQueryMakerTests.cs: -------------------------------------------------------------------------------- 1 | using ExcelToSQLScripts; 2 | using ExcelToSQLScripts.Models; 3 | using ExcelToSQLScripts.QueryMakers; 4 | using FluentAssertions; 5 | using Xunit; 6 | using Record = ExcelToSQLScripts.Models.Record; 7 | 8 | namespace Tests.QueryMakers 9 | { 10 | public class QueryMakerTests 11 | { 12 | [Fact] 13 | public void CanMakeQuery() 14 | { 15 | InsertQueryMaker queryMaker = new InsertQueryMaker(new ValueRenderer(new string[] { })); 16 | 17 | Record record = Utils.GetTable().Records[0]; 18 | 19 | string query = queryMaker.GenerateQuery(record); 20 | 21 | query.Should().Be("INSERT INTO EMPLOYEES (ID, NAME) VALUES (1, 'bilal');\n"); 22 | } 23 | 24 | [Fact] 25 | public void CanReplaceSingleQuoteWithDoubleQuote() 26 | { 27 | InsertQueryMaker queryMaker = new InsertQueryMaker(new ValueRenderer(new string[] { })); 28 | 29 | Record record = Utils.GetTable(name: "sky's blue").Records[0]; 30 | 31 | string query = queryMaker.GenerateQuery(record); 32 | 33 | query.Should().Be("INSERT INTO EMPLOYEES (ID, NAME) VALUES (1, 'sky''s blue');\n"); 34 | } 35 | 36 | [Fact] 37 | public void CanReplaceNullReplacementsWithNulls() 38 | { 39 | InsertQueryMaker queryMaker = new InsertQueryMaker(new ValueRenderer(new[] {"n/a"})); 40 | 41 | Record record = Utils.GetTable(name: "N/A").Records[0]; 42 | 43 | string query = queryMaker.GenerateQuery(record); 44 | 45 | query.Should().Be("INSERT INTO EMPLOYEES (ID, NAME) VALUES (1, NULL);\n"); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Tests/ExcelReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using ExcelToSQLScripts; 4 | using ExcelToSQLScripts.Models; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace Tests 9 | { 10 | public class ExcelReaderTests 11 | { 12 | [Fact] 13 | public void CanReadExcelFile() 14 | { 15 | ExcelReader excelReader = new ExcelReader(false, null); 16 | 17 | List tables = excelReader.Read("Sample.xlsx").ToList(); 18 | 19 | tables.Should() 20 | .HaveCount(1) 21 | .And 22 | .Subject.First() 23 | .Name.Should() 24 | .Be("Employees"); 25 | 26 | tables.First().Columns.Should().HaveCount(3); 27 | tables.First().Records.Should().HaveCount(7); 28 | } 29 | 30 | [Fact] 31 | public void CanReadExcelFileWithEmptyRecords() 32 | { 33 | ExcelReader excelReader = new ExcelReader(true, null); 34 | 35 | List
tables = excelReader.Read("Sample.xlsx").ToList(); 36 | 37 | tables.Should() 38 | .HaveCount(1) 39 | .And 40 | .Subject.First() 41 | .Name.Should() 42 | .Be("Employees"); 43 | 44 | tables.First().Columns.Should().HaveCount(3); 45 | tables.First().Records.Should().HaveCount(10); 46 | } 47 | 48 | [Fact] 49 | public void CanReadExcelFileWithWorksheetFilter() 50 | { 51 | ExcelReader excelReader = new ExcelReader(true, new[] {3}); 52 | 53 | List
tables = excelReader.Read("Sample.xlsx").ToList(); 54 | 55 | tables.Should() 56 | .HaveCount(0); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/QueryMakers/OracleMergeQueryMaker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using ExcelToSQLScripts.Models; 5 | 6 | namespace ExcelToSQLScripts.QueryMakers 7 | { 8 | public class OracleMergeQueryMaker : IQueryMaker 9 | { 10 | readonly ValueRenderer _valueRenderer; 11 | 12 | public OracleMergeQueryMaker(ValueRenderer valueRenderer) 13 | { 14 | _valueRenderer = valueRenderer; 15 | } 16 | 17 | public string GenerateQuery(Record record) 18 | { 19 | //StringBuilder stringBuilder = new StringBuilder(); 20 | string tableName = record.Table.Name.ToUpperInvariant(); 21 | string primaryKeyColumnName = record.Table.PrimaryKeyName.ToUpperInvariant(); 22 | string columnNameValuePairs = string.Join(", ", 23 | record.Table.Columns 24 | .Select(c => 25 | $"{_valueRenderer.Render(record.Values.Single(v => v.Column.Name.ToUpperInvariant() == c.Name.ToUpperInvariant()))} {c.Name.ToUpperInvariant()}")); 26 | string columnNamesForUpdate = string.Join(", ", 27 | record.Table.Columns.Skip(1) 28 | .Select(c => $"T.{c.Name.ToUpperInvariant()} = D.{c.Name.ToUpperInvariant()}")); 29 | 30 | string allColumnNames = string.Join(", ", 31 | record.Table.Columns.Select(c => c.Name.ToUpperInvariant())); 32 | 33 | string dPrefixedColumnNames = string.Join(", ", 34 | record.Table.Columns.Select(c => $"D.{c.Name.ToUpperInvariant()}")); 35 | 36 | return $@"MERGE INTO {tableName} T 37 | USING (SELECT {columnNameValuePairs} FROM DUAL) D 38 | ON (T.{primaryKeyColumnName} = D.{primaryKeyColumnName}) 39 | WHEN MATCHED THEN 40 | UPDATE SET {columnNamesForUpdate} 41 | WHEN NOT MATCHED THEN 42 | INSERT ({allColumnNames}) VALUES ({dPrefixedColumnNames}); 43 | "; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/ValueRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using ExcelToSQLScripts.Models; 4 | 5 | namespace ExcelToSQLScripts 6 | { 7 | public class ValueRenderer 8 | { 9 | private readonly string[] _nullReplacements; 10 | 11 | public ValueRenderer(string[] nullReplacements) 12 | { 13 | _nullReplacements = nullReplacements ?? new string[0];; 14 | } 15 | 16 | public string Render(Value value) 17 | { 18 | if (string.IsNullOrEmpty(value.StringValue) 19 | || string.Equals(value.StringValue, Constants.NULL, StringComparison.OrdinalIgnoreCase) 20 | || _nullReplacements.Any(x => x.Equals(value.StringValue, StringComparison.OrdinalIgnoreCase))) 21 | return Constants.NULL; 22 | 23 | switch (value.Column.DataType) 24 | { 25 | case DataType.DateTime: 26 | try 27 | { 28 | DateTime dateValue = (DateTime) value.TypedValue; 29 | return $"TO_DATE('{dateValue.Year:0000}/{dateValue.Month:00}/{dateValue.Day:00} " + 30 | $"{dateValue.Hour:00}:{dateValue.Minute:00}:{dateValue.Second:00}', 'yyyy/mm/dd hh24:mi:ss')"; 31 | } 32 | catch (InvalidCastException e) 33 | { 34 | throw new Exception($"{value.StringValue} is not in a valid date formet", e); 35 | } 36 | case DataType.Boolean: 37 | return value.StringValue.ToLower(); 38 | case DataType.Number: 39 | return value.StringValue; 40 | case DataType.String: 41 | return $"'{value.StringValue.Replace("'", "''")}'"; 42 | 43 | default: 44 | throw new NotImplementedException(); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelToSQLScripts", "ExcelToSQLScripts\ExcelToSQLScripts.csproj", "{FEFCBE2A-871F-4D65-9601-5AC3047E64CC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{3A6E657C-1525-4EE0-8C8D-BF698FE9850D}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelToSQLScripts.Console", "ExcelToSQLScripts.Console\ExcelToSQLScripts.Console.csproj", "{71E3BE96-93A2-4917-969B-CE2BAF01B8B1}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {FEFCBE2A-871F-4D65-9601-5AC3047E64CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {FEFCBE2A-871F-4D65-9601-5AC3047E64CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {FEFCBE2A-871F-4D65-9601-5AC3047E64CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {FEFCBE2A-871F-4D65-9601-5AC3047E64CC}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {3A6E657C-1525-4EE0-8C8D-BF698FE9850D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {3A6E657C-1525-4EE0-8C8D-BF698FE9850D}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {3A6E657C-1525-4EE0-8C8D-BF698FE9850D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {3A6E657C-1525-4EE0-8C8D-BF698FE9850D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {71E3BE96-93A2-4917-969B-CE2BAF01B8B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {71E3BE96-93A2-4917-969B-CE2BAF01B8B1}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {71E3BE96-93A2-4917-969B-CE2BAF01B8B1}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {71E3BE96-93A2-4917-969B-CE2BAF01B8B1}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /ExcelToSQLScripts.Console/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using CommandDotNet; 4 | using CommandDotNet.Attributes; 5 | using FluentValidation; 6 | using FluentValidation.Attributes; 7 | 8 | namespace ExcelToSQLScripts.Console 9 | { 10 | [Validator(typeof(OptionsValidator))] 11 | public class Options : IArgumentModel 12 | { 13 | [Option(Inherited = true, ShortName = "i", LongName = "inputFile", 14 | Description = "Full path of excel file. File must have .xlsx extension.")] 15 | public string InputFile { get; set; } 16 | 17 | [Option(Inherited = true, ShortName = "o", LongName = "outputDirectory", 18 | Description = "Path the the directory where all sql files will be stored. " + 19 | "If one or more files exist with same name, they will be overriden. " + 20 | "If output directory doesn't exist, it will be created.")] 21 | public string OutputDirectory { get; set; } = "./sql-scripts/"; 22 | 23 | [Option(Inherited = true, ShortName = "w", LongName = "worksheet", Description = "Index of worksheets to be processed. Index beings from 1. This option can be used multiple times in one command.")] 24 | public List WorksheetsToRead { get; set; } 25 | 26 | [Option(Inherited = true, ShortName = "e", LongName = "insertEmptyRecords", Description = "Will insert NULLs in all fields for empty rows in excel sheet")] 27 | public bool ReadEmptyRecords { get; set; } 28 | 29 | [Option(Inherited = true, ShortName = "r", LongName = "replaceWithNULL", Description = "Replace the given text with null values in script. This option can be used multiple times in one command.")] 30 | public List NullReplacements { get; set; } 31 | } 32 | 33 | public class OptionsValidator : AbstractValidator 34 | { 35 | public OptionsValidator() 36 | { 37 | CascadeMode = CascadeMode.StopOnFirstFailure; 38 | 39 | RuleFor(x => x.InputFile).NotEmpty().WithMessage("Please provide a path to excel file") 40 | .Must(File.Exists).WithMessage(o => $"file not found: {o.InputFile}"); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Nuget](https://img.shields.io/nuget/v/ExcelToSqlScripts.Console?label=Latest%20Nuget%20Version&style=for-the-badge)](https://www.nuget.org/packages/ExcelToSQLScripts.Console) 2 | 3 | # Setup instructions 4 | 5 | This small command line tool to help you convert data of excel files into insert statements in SQL syntax. 6 | 7 | 1. Navigate to [dotnet core website](https://www.microsoft.com/net/core) and follow instructions to install dotnet core 8 | 2. `dotnet tool install -g ExcelToSQLScripts.Console` 9 | 3. `excel2sql --help` 10 | 11 | Note: Ensure that you have `~/.dotnet/tools` path added in your $PATH variable 12 | 13 | 14 | # Generating SQL scripts 15 | 16 | You can generate 3 types of scripts i.e. insert scipts, update scripts & merge scripts. 17 | 18 | ##### Sample Input 19 | 20 | ![Excel](https://raw.githubusercontent.com/bilal-fazlani/ExcelToSqlScripts/master/Readme/Sample_Input.png) 21 | 22 | ### Insert scripts 23 | 24 | ``` 25 | excel2sql insert -i -o 26 | ``` 27 | 28 | ##### Sample Output 29 | 30 | ```sql 31 | INSERT INTO EMPLOYEES (ID, NAME, LOCATION) VALUES (1, 'John', 'India'); 32 | INSERT INTO EMPLOYEES (ID, NAME, LOCATION) VALUES (2, 'Jason', 'US'); 33 | ``` 34 | 35 | ### Update scripts 36 | 37 | ``` 38 | excel2sql update -i -o 39 | ``` 40 | 41 | ##### Sample Output 42 | 43 | ```sql 44 | UPDATE EMPLOYEES SET NAME = 'John', LOCATION = 'India' WHERE ID = 1; 45 | UPDATE EMPLOYEES SET NAME = 'Jason', LOCATION = 'US' WHERE ID = 2; 46 | ``` 47 | 48 | ### Merge scripts 49 | 50 | ``` 51 | excel2sql merge -i -o 52 | ``` 53 | 54 | ##### Sample Output 55 | 56 | ```sql 57 | MERGE INTO EMPLOYEES T 58 | USING (SELECT 1 ID, 'John' NAME, 'India' LOCATION FROM DUAL) D 59 | ON (T.ID = D.ID) 60 | WHEN MATCHED THEN 61 | UPDATE SET T.NAME = D.NAME, T.LOCATION = D.LOCATION 62 | WHEN NOT MATCHED THEN 63 | INSERT (ID, NAME, LOCATION) VALUES (D.ID, D.NAME, D.LOCATION); 64 | 65 | MERGE INTO EMPLOYEES T 66 | USING (SELECT 2 ID, 'Jason' NAME, 'US' LOCATION FROM DUAL) D 67 | ON (T.ID = D.ID) 68 | WHEN MATCHED THEN 69 | UPDATE SET T.NAME = D.NAME, T.LOCATION = D.LOCATION 70 | WHEN NOT MATCHED THEN 71 | INSERT (ID, NAME, LOCATION) VALUES (D.ID, D.NAME, D.LOCATION); 72 | ``` 73 | -------------------------------------------------------------------------------- /ExcelToSQLScripts.Console/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using CommandDotNet.Attributes; 6 | using ExcelToSQLScripts.Models; 7 | using ExcelToSQLScripts.QueryMakers; 8 | 9 | using static System.Console; 10 | 11 | namespace ExcelToSQLScripts.Console 12 | { 13 | [ApplicationMetadata(Name = "excel2sql", Description = "Convert your excel files to sql scripts. For more information please visit \n" + 14 | "https://github.com/bilal-fazlani/ExcelToSqlScripts")] 15 | public class App 16 | { 17 | private readonly Options _options; 18 | 19 | public App(Options options) 20 | { 21 | _options = options; 22 | } 23 | 24 | [ApplicationMetadata(Description = "Generates insert scripts")] 25 | public int Insert() 26 | { 27 | return Process("insert"); 28 | } 29 | 30 | [ApplicationMetadata(Description = "Generates update scripts")] 31 | public int Update() 32 | { 33 | return Process("update"); 34 | } 35 | 36 | [ApplicationMetadata(Description = "Generates merge scripts")] 37 | public int Merge() 38 | { 39 | return Process("merge"); 40 | } 41 | 42 | private int Process(string mode) 43 | { 44 | try 45 | { 46 | Directory.CreateDirectory(_options.OutputDirectory); 47 | 48 | ExcelReader excelReader = new ExcelReader(_options.ReadEmptyRecords, _options.WorksheetsToRead?.ToArray()); 49 | 50 | ValueRenderer valueRenderer = new ValueRenderer(_options.NullReplacements?.ToArray()); 51 | 52 | IQueryMaker queryMaker = QueryMakerFactory.Create(mode, valueRenderer); 53 | 54 | TableScriptGenerator tableScriptGenerator = new TableScriptGenerator(queryMaker); 55 | 56 | IEnumerable
tables = excelReader.Read(_options.InputFile); 57 | 58 | foreach (Table table in tables) 59 | { 60 | string filePath = Path.Combine(_options.OutputDirectory, table.Name + ".sql"); 61 | Write($"writing {filePath} ..."); 62 | 63 | if (table.Records.Any()) 64 | { 65 | using (Script script = tableScriptGenerator.GenerateTableScript(table)) 66 | { 67 | using (FileStream fileStream = File.Create(filePath)) 68 | { 69 | script.Content.CopyTo(fileStream); 70 | WriteLine(" done"); 71 | } 72 | } 73 | } 74 | else 75 | { 76 | WriteLine(" empty (skipped)"); 77 | } 78 | } 79 | 80 | return 0; 81 | } 82 | catch (Exception ex) 83 | { 84 | Error.WriteLine($"Error: {ex.GetType().Name}"); 85 | Error.WriteLine($"Error: {ex.Message}"); 86 | #if DEBUG 87 | Error.WriteLine(ex.StackTrace); 88 | #endif 89 | return 1; 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /ExcelToSQLScripts/ExcelReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using ExcelToSQLScripts.Models; 6 | using OfficeOpenXml; 7 | 8 | namespace ExcelToSQLScripts 9 | { 10 | public class ExcelReader 11 | { 12 | private readonly bool _readEmptyRecords; 13 | private readonly int[] _worksheetsToRead; 14 | 15 | public ExcelReader(bool readEmptyRecords, int[] worksheetsToRead) 16 | { 17 | _readEmptyRecords = readEmptyRecords; 18 | _worksheetsToRead = worksheetsToRead; 19 | } 20 | 21 | public IEnumerable
Read(string filePath) 22 | { 23 | using (FileStream fileStream = new FileStream(filePath, FileMode.Open)) 24 | { 25 | ExcelPackage excel = new ExcelPackage(fileStream); 26 | 27 | foreach (var worksheet in excel.Workbook.Worksheets) 28 | { 29 | if (_worksheetsToRead == null || 30 | _worksheetsToRead?.Length == 0 || 31 | (_worksheetsToRead != null && _worksheetsToRead.Contains(worksheet.Index))) 32 | { 33 | Table table = new Table(worksheet.Name); 34 | 35 | FillColumns(worksheet, table); 36 | 37 | FillRecords(worksheet, table); 38 | 39 | yield return table; 40 | } 41 | } 42 | } 43 | } 44 | 45 | private void FillColumns(ExcelWorksheet worksheet, Table table) 46 | { 47 | for (int i = 1; i <= worksheet?.Dimension?.Columns; i++) 48 | { 49 | string columnName = worksheet.GetValue(1, i); 50 | 51 | if (!string.IsNullOrEmpty(columnName)) 52 | { 53 | DataType datType = GetDataType(worksheet, i); 54 | table.Columns.Add(new Column(columnName, datType, i)); 55 | } 56 | } 57 | } 58 | 59 | private DataType GetDataType(ExcelWorksheet worksheet, int columnIndex) 60 | { 61 | object value = worksheet.GetValue(2, columnIndex); 62 | if (value is DateTime) 63 | { 64 | return DataType.DateTime; 65 | } 66 | 67 | string firstDataValue = worksheet.GetValue(2, columnIndex); 68 | 69 | bool isnumber = IsNumber(firstDataValue); 70 | if (isnumber) return DataType.Number; 71 | 72 | bool isBool = IsBoolean(firstDataValue); 73 | if (isBool) return DataType.Boolean; 74 | 75 | return DataType.String; 76 | } 77 | 78 | private bool IsNumber(string value) 79 | { 80 | return double.TryParse(value, out double _); 81 | } 82 | 83 | private bool IsBoolean(string value) 84 | { 85 | return bool.TryParse(value, out bool _); 86 | } 87 | 88 | private void FillRecords(ExcelWorksheet worksheet, Table table) 89 | { 90 | for (int excelRowIndex = 2; excelRowIndex <= worksheet?.Dimension?.Rows; excelRowIndex++) 91 | { 92 | Record record = new Record(table); 93 | 94 | foreach (var column in table.Columns) 95 | { 96 | object value = worksheet.GetValue(excelRowIndex, column.Index); 97 | 98 | record[excelRowIndex - 2] = 99 | new Value(column, worksheet.GetValue(excelRowIndex, column.Index), value); 100 | } 101 | 102 | if (_readEmptyRecords || !record.IsEmpty) table.Records.Add(record); 103 | } 104 | } 105 | } 106 | } --------------------------------------------------------------------------------