-
Quick Script: Convert a CSV file to a SQLite database file
Updated on October 4, 2025 to detect INTEGER/REAL/TEXT column types.
Here’s a quick bash script to convert a .csv file into a .sqlite file so you can query your CSV data in tools like TablePlus.
The file size is only slightly larger than the original CSV, and you can share it (it’s just a file!).
Just create the file csv-to-sqlite somewhere in your PATH and chmod +x csv-to-sqlite to make it executable.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211#!/bin/bash# csv-to-sqlite - Convert CSV file to SQLite database with type detection# Usage: csv-to-sqlite [options] <csvfile>set -e# Default valuestable_name="main"show_help=false# Function to show usageshow_usage() {cat << EOFUsage: $0 [OPTIONS] <csvfile>Convert a CSV file to an SQLite database with automatic type detection.Arguments:csvfile Path to the CSV file to importOptions:-t, --table-name NAME Name for the table (default: main)-h, --help Show this help messageExamples:$0 ~/mycsvfile.csv$0 --table-name employees ~/data/employees.csv$0 -t products ~/products.csvOutput:Creates a SQLite database with the same name as the CSV file but with.sqlite extension in the same directory as the input file.Column types are automatically detected:- INTEGER for whole numbers- REAL for decimal numbers- TEXT for everything elseEOF}# Parse command line argumentswhile [[ $# -gt 0 ]]; docase $1 in-t|--table-name)table_name="$2"shift 2;;-h|--help)show_usageexit 0;;-*)echo "Error: Unknown option $1" >&2show_usageexit 1;;*)# This should be the CSV fileif [[ -z "${csv_file:-}" ]]; thencsv_file="$1"elseecho "Error: Multiple CSV files specified" >&2show_usageexit 1fishift;;esacdone# Check if CSV file was providedif [[ -z "${csv_file:-}" ]]; thenecho "Error: CSV file not specified" >&2show_usageexit 1fi# Check if CSV file existsif [[ ! -f "$csv_file" ]]; thenecho "Error: File '$csv_file' not found" >&2exit 1fi# Expand tilde in pathcsv_file=$(eval echo "$csv_file")# Generate SQLite database filename# Remove extension and add .sqlitebase_db_file="${csv_file%.*}.sqlite"db_file="$base_db_file"# If file exists, add timestamp suffixif [[ -f "$db_file" ]]; thentimestamp=$(date +"%Y%m%d%H%M%S")# Extract directory, basename without extension, and add timestampdb_dir=$(dirname "$base_db_file")db_name=$(basename "$base_db_file" .sqlite)db_file="${db_dir}/${db_name}_${timestamp}.sqlite"echo "Warning: '$base_db_file' already exists, creating '$db_file' instead"fi# Validate table name (basic check for SQL injection prevention)if [[ ! "$table_name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; thenecho "Error: Invalid table name '$table_name'. Table names must start with a letter or underscore and contain only letters, numbers, and underscores." >&2exit 1fiecho "Converting '$csv_file' to SQLite database '$db_file'"echo "Table name: $table_name"# Detect column types using awk# This analyzes the first 1000 rows to determine INTEGER, REAL, or TEXT typescreate_table_sql=$(awk -F',' 'BEGIN {SAMPLE_SIZE = 1000}# Process header rowNR == 1 {for (i = 1; i <= NF; i++) {# Remove quotes and whitespace from headergsub(/^[ \t"]+|[ \t"]+$/, "", $i)# Use column number if header is emptyif ($i == "" || length($i) == 0) {headers[i] = "col" i} else {headers[i] = $i}# Initialize all columns as INTEGERtypes[i] = "INTEGER"}next}# Process data rows (sample only)NR <= SAMPLE_SIZE + 1 {for (i = 1; i <= NF; i++) {value = $i# Remove quotes and whitespacegsub(/^[ \t"]+|[ \t"]+$/, "", value)# Skip empty valuesif (value == "") continue# If already TEXT, skip further checksif (types[i] == "TEXT") continue# Check if value is an integerif (types[i] == "INTEGER") {if (value !~ /^-?[0-9]+$/) {# Not an integer, check if its a real numberif (value ~ /^-?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/) {types[i] = "REAL"} else {types[i] = "TEXT"}}}# Check if value is a real numberelse if (types[i] == "REAL") {if (value !~ /^-?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/) {types[i] = "TEXT"}}}}END {# Generate CREATE TABLE statementprint "CREATE TABLE IF NOT EXISTS temp_table ("for (i = 1; i <= length(headers); i++) {# Escape double quotes in column namessafe_name = headers[i]gsub(/"/, "\"\"", safe_name)printf " \"%s\" %s", safe_name, types[i]if (i < length(headers)) {print ","} else {print ""}}print ");"}' "$csv_file")# Import CSV to SQLite with proper schemasqlite3 "$db_file" << EOF-- Create temporary table with detected types$create_table_sql-- Import CSV data.mode csv.import "$csv_file" temp_table-- Create final table with correct nameCREATE TABLE "$table_name" AS SELECT * FROM temp_table;-- Drop temporary tableDROP TABLE temp_table;EOFecho "Successfully created SQLite database: $db_file"echo "Table '$table_name' contains $(sqlite3 "$db_file" "SELECT COUNT(*) FROM $table_name;") rows"echo ""# Show schemaecho "Table schema:"sqlite3 "$db_file" "PRAGMA table_info($table_name);" | awk -F'|' '{printf " %-20s %s\n", $2, $3}'echo ""echo "You can now open this SQLite database in applications like TablePlus for convenient data viewing and manipulation."As a side note, you can use something like dsq to query a csv file. I’ve stopped using that for this particular task.