├── .prettierrc.json ├── .gitignore ├── .prettierignore ├── .eslintrc.json ├── src ├── utils │ └── fileUtils.ts └── index.ts ├── tsconfig.json ├── csv └── ExampleTable.csv ├── sql └── ExampleTable.sql ├── README.md ├── LICENSE ├── package.json ├── CONTRIBUTING.md ├── index.js └── CODE_OF_CONDUCT.md /.prettierrc.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .vscode 4 | dist/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .package-lock.json 2 | *.min.* 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "extends": ["standard-with-typescript", "airbnb", "prettier"], 4 | "plugins": ["prettier"], 5 | "rules": {} 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/fileUtils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "node:fs" 2 | export function createIfNot(dir:string) { 3 | if (!fs.existsSync(dir)) { 4 | fs.mkdirSync(dir, { recursive: true }) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "ES2020", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "noEmitOnError": true 9 | }, 10 | "include": ["src/**/*.ts"], 11 | "exclude": ["node_modules"] 12 | } 13 | -------------------------------------------------------------------------------- /csv/ExampleTable.csv: -------------------------------------------------------------------------------- 1 | id,description,entryDate,location,rate,isActive 2 | 1,American Farms,2023-11-17 00:00:00.000,New York,0.05,1 3 | 2,Beatles,2023-11-01 00:00:00.000,NULL,0.01,0 4 | 3,True Licensing,2023-12-02 00:00:00.000,NULL,0.25,1 5 | 4,1 100,2023-12-10 00:00:00.000,Chicago,0.5,1 6 | 6,"Smith, John",2023-10-03 00:00:00.000,Dallas,0.35,1 7 | 7,"Brown, Esq., John",2023-06-01 00:00:00.000,Finland,0.05,1 8 | 8,Merry Merry Christmas,2023-01-01 00:00:00.000,NULL,0,1 9 | 9,another entry,2023-03-24 00:00:00.000,NULL,1.05,0 10 | 10,42,2023-04-01 00:00:00.000,London,0.2,1 -------------------------------------------------------------------------------- /sql/ExampleTable.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO ExampleTable (id, description, entryDate, location, rate, isActive) 2 | VALUES 3 | (1, "American Farms", "2023-11-17 00:00:00.000", "New York", 0.05, 1), 4 | (2, "Beatles", "2023-11-01 00:00:00.000", NULL, 0.01, 0), 5 | (3, "True Licensing", "2023-12-02 00:00:00.000", NULL, 0.25, 1), 6 | (4, "1 100", "2023-12-10 00:00:00.000", "Chicago", 0.5, 1), 7 | (6, "Smith, John", "2023-10-03 00:00:00.000", "Dallas", 0.35, 1), 8 | (7, "Brown, Esq., John", "2023-06-01 00:00:00.000", "Finland", 0.05, 1), 9 | (8, "Merry Merry Christmas", "2023-01-01 00:00:00.000", NULL, 0, 1), 10 | (9, "another entry", "2023-03-24 00:00:00.000", NULL, 1.05, 0), 11 | (10, 42, "2023-04-01 00:00:00.000", "London", 0.2, 1); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # csv-to-sql-insert 2 | Provide table data as a CSV ([comma-separated values](https://en.wikipedia.org/wiki/Comma-separated_values)) file and output a SQL insert statement for a table with the same name as the file. 3 | 4 | ## Usage ⚙ 5 | 1. Confirm you have a directory named `csv` 6 | 2. Confirm you have a directory named `sql` 7 | 3. Save your input CSV file in the `csv` directory 8 | 4. In a terminal window, first run `npm install` to install dependencies and then run `npm start YourFileName` 9 | 5. Watch the terminal window for any error messages 10 | 6. Your SQL insert statement will be saved in `sql/YourFileName.sql` 11 | 12 | ## Support 👨‍💻 13 | - [Create an Issue](https://github.com/gitdagray/csv-to-sql/issues) 14 | - [X: @yesdavidgray](https://x.com/yesdavidgray) 15 | 16 | ## Contributing 🛠 17 | Please read [CONTRIBUTING.md](https://github.com/gitdagray/csv-to-sql/blob/main/CONTRIBUTING.md) prior to contributing. 18 | 19 | ## Code of Conduct 20 | Please see [CODE_OF_CONDUCT.md](https://github.com/gitdagray/csv-to-sql/blob/main/CODE_OF_CONDUCT.md). 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Dave Gray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csv-to-sql-insert", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "repository": "https://github.com/gitdagray/csv-to-sql", 6 | "description": "input a csv file, output a sql insert statement", 7 | "main": "index.js", 8 | "scripts": { 9 | "start": "npm run build && node dist/index.js", 10 | "build": "tsc --skipLibCheck" 11 | }, 12 | "author": { 13 | "name": "Dave Gray", 14 | "email": "dave@davegray.codes", 15 | "url": "https://www.davegray.codes/" 16 | }, 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@types/node": "^20.10.5", 20 | "@typescript-eslint/eslint-plugin": "^6.15.0", 21 | "eslint": "^8.56.0", 22 | "eslint-config-airbnb-base": "^15.0.0", 23 | "eslint-config-node": "^4.1.0", 24 | "eslint-config-prettier": "^9.1.0", 25 | "eslint-config-standard-with-typescript": "^43.0.0", 26 | "eslint-plugin-import": "^2.29.1", 27 | "eslint-plugin-n": "^16.5.0", 28 | "eslint-plugin-node": "^11.1.0", 29 | "eslint-plugin-prettier": "^5.1.1", 30 | "eslint-plugin-promise": "^6.1.1", 31 | "husky": "^8.0.3", 32 | "prettier": "^3.1.1", 33 | "pretty-quick": "^3.1.3", 34 | "typescript": "^5.3.3" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "pretty-quick --staged" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions of any kind are welcome! 3 | 4 | ## General Steps 5 | 1. Identify an Issue or feature to add 6 | 2. Check posted Issues to see if your issue has already been posted 7 | 3. If not, create a new Issue (see directions below) 8 | 4. Fork this repository to your GitHub 9 | 5. Clone the repository from your GitHub 10 | 6. Create a new branch with: `git checkout -b newBranchName` 11 | 7. Complete your work and push your new branch to your GitHub 12 | 8. Come back to this repo and create a Pull Request to merge your branch with the fix/feature (see directions below) 13 | 14 | ## Issues 15 | 16 | ### Creating an Issue 17 | If you find a bug or problem, or the documentation doesn't make sense, please create an Issue to document the concern. 18 | 19 | ### Description 20 | Please be descriptive in your Issue. The more information you provide, the more likely someone will be able to help. 21 | 22 | ### Code Examples 23 | If you're experiencing an issue with the code, the most helpful thing you can do is create an example reproducing the problem. This can be an GitHub repository or [gist](https://gist.github.com/), a private repository you share with the maintainers, or anything to reproduce the issue and show the code causing it. 24 | 25 | ## Pull Requests 26 | 27 | ### Creating a Pull Request 28 | If you fix an active Issue, please create a new Pull Request for the problem. There are no guarantees that the code will be merged _"as is"_, but, if you're willing to work with the maintainers, we will surely solve the Issue. 29 | 30 | ### Description 31 | Please be descriptive in your Pull Request. It's important to be able to understand the context of a change throughout the history of a project. 32 | 33 | ### Linking Fixed Issues 34 | If the Pull Request is addressing an active Issue, please link that Issue by specifying the `Fixes [Issue #]` syntax within the Pull Request. 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises" 2 | import { existsSync } from "node:fs" 3 | import { createIfNot } from "./utils/fileUtils.mjs" 4 | import path from "node:path" 5 | async function writeSQL(statement, saveFileAs = "") { 6 | try { 7 | const destinationFile = process.argv[2] || saveFileAs 8 | if (!destinationFile) { 9 | throw new Error("Missing saveFileAs parameter") 10 | } 11 | createIfNot(path.resolve(`./sql/${destinationFile}`)) 12 | await fs.writeFile(`sql/${process.argv[2]}.sql`, statement) 13 | } catch (err) { 14 | console.log(err) 15 | } 16 | } 17 | async function readCSV(csvFileName = "") { 18 | try { 19 | const fileAndTableName = process.argv[2] || csvFileName 20 | if (!fileAndTableName) { 21 | throw new Error("Missing csvFileName parameter") 22 | } 23 | if (!existsSync(path.resolve(`./csv/${fileAndTableName}.csv`))) { 24 | console.log("file not found") 25 | return 26 | } 27 | const data = await fs.readFile(`csv/${fileAndTableName}.csv`, { 28 | encoding: "utf8", 29 | }) 30 | const linesArray = data.split(/\r|\n/).filter(line => line) 31 | const columnNames = linesArray.shift().split(",") 32 | let beginSQLInsert = `INSERT INTO ${fileAndTableName} (` 33 | columnNames.forEach(name => (beginSQLInsert += `${name}, `)) 34 | beginSQLInsert = beginSQLInsert.slice(0, -2) + ")\nVALUES\n" 35 | let values = "" 36 | linesArray.forEach(line => { 37 | // Parses each line of CSV into field values array 38 | const arr = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/) 39 | if (arr.length > columnNames.length) { 40 | console.log(arr) 41 | throw new Error("Too Many Values in row") 42 | } else if (arr.length < columnNames.length) { 43 | console.log(arr) 44 | throw new Error("Too Few Values in row") 45 | } 46 | let valueLine = "\t(" 47 | arr.forEach(value => { 48 | // Matches NULL values, Numbers, 49 | // Strings accepted as numbers, and Booleans (0 or 1) 50 | if (value === "NULL" || !isNaN(+value)) { 51 | valueLine += `${value}, ` 52 | } else { 53 | // If a string is wrapped in quotes, it doesn't need more 54 | if (value.at(0) === '"') valueLine += `${value}, ` 55 | else { 56 | // This wraps strings in quotes 57 | // also wraps timestamps 58 | valueLine += `"${value}", ` 59 | } 60 | } 61 | }) 62 | valueLine = valueLine.slice(0, -2) + "),\n" 63 | values += valueLine 64 | }) 65 | values = values.slice(0, -2) + ";" 66 | const sqlStatement = beginSQLInsert + values 67 | // Write File 68 | writeSQL(sqlStatement) 69 | } catch (err) { 70 | console.log(err) 71 | } 72 | } 73 | readCSV() 74 | console.log("Finished!") 75 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs ,existsSync} from "fs"; 2 | import {createIfNot} from "./utils/fileUtils.js" 3 | import * as path from "node:path" 4 | async function writeSQL(statement: string, saveFileAs = "", isAppend: boolean = false) { 5 | try { 6 | const destinationFile = process.argv[2] || saveFileAs; 7 | if (!destinationFile) { 8 | throw new Error("Missing saveFileAs parameter"); 9 | } 10 | createIfNot(path.resolve(`./sql/${destinationFile}.sql`)) 11 | if(isAppend){ 12 | await fs.appendFile(`sql/${process.argv[2]}.sql`, statement); 13 | }else{ 14 | await fs.writeFile(`sql/${process.argv[2]}.sql`, statement); 15 | } 16 | } catch (err) { 17 | console.log(err); 18 | } 19 | } 20 | 21 | async function readCSV(csvFileName = "", batchSize: number = 0) { 22 | try { 23 | const fileAndTableName = process.argv[2] || csvFileName; 24 | 25 | batchSize = parseInt(process.argv[3]) || batchSize || 500; 26 | let isAppend: boolean = false; 27 | 28 | if (!fileAndTableName) { 29 | throw new Error("Missing csvFileName parameter"); 30 | } 31 | if(!existsSync(path.resolve(`./csv/${fileAndTableName}.csv`))){ 32 | console.log("file not found") 33 | return 34 | } 35 | const data = await fs.readFile(`csv/${fileAndTableName}.csv`, { 36 | encoding: "utf8", 37 | }); 38 | const linesArray = data.split(/\r|\n/).filter((line) => line); 39 | const columnNames = linesArray?.shift()?.split(",") || []; 40 | let beginSQLInsert = `INSERT INTO ${fileAndTableName} (`; 41 | columnNames.forEach((name) => (beginSQLInsert += `${name}, `)); 42 | beginSQLInsert = beginSQLInsert.slice(0, -2) + ")\nVALUES\n"; 43 | let values = ""; 44 | linesArray.forEach((line, index) => { 45 | // Parses each line of CSV into field values array 46 | const arr = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); 47 | if (arr.length > columnNames.length) { 48 | console.log(arr); 49 | throw new Error("Too Many Values in row"); 50 | } else if (arr.length < columnNames.length) { 51 | console.log(arr); 52 | throw new Error("Too Few Values in row"); 53 | } 54 | 55 | // Check batch size (rows per batch) 56 | if(index > 0 && index % batchSize == 0){ 57 | values = values.slice(0, -2) + ";\n\n"; 58 | 59 | const sqlStatement = beginSQLInsert + values; 60 | 61 | // Write File 62 | writeSQL(sqlStatement, fileAndTableName, isAppend); 63 | values = ""; 64 | isAppend = true; 65 | } 66 | 67 | let valueLine = "\t("; 68 | arr.forEach((value) => { 69 | // Matches NULL values, Numbers, 70 | // Strings accepted as numbers, and Booleans (0 or 1) 71 | if (value === "NULL" || !isNaN(+value)) { 72 | valueLine += `${value}, `; 73 | } else { 74 | // If a string is wrapped in quotes, it doesn't need more 75 | if (value.at(0) === '"') valueLine += `${value}, `; 76 | else { 77 | // This wraps strings in quotes 78 | // also wraps timestamps 79 | valueLine += `"${value}", `; 80 | } 81 | } 82 | }); 83 | valueLine = valueLine.slice(0, -2) + "),\n"; 84 | values += valueLine; 85 | }); 86 | values = values.slice(0, -2) + ";"; 87 | const sqlStatement = beginSQLInsert + values; 88 | // Write File 89 | writeSQL(sqlStatement, fileAndTableName, isAppend); 90 | } catch (err) { 91 | console.log(err); 92 | } 93 | } 94 | readCSV(); 95 | console.log("Finished!"); 96 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | dave@davegray.codes. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. --------------------------------------------------------------------------------