├── _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 | [](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 | 
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