sqlite> .mode csv
sqlite> .header on
sqlite> .import weight.csv weight
sqlite> .import person.csv person
sqlite> select * from person, weight where person.ID = weight.ID;
ID,sex,ID,weight
123,M,123,200
789,F,789,155
sqlite> sqlite> .mode csv
sqlite> .header on
sqlite> .import weight.csv weight
sqlite> .import person.csv person
sqlite> select * from person, weight where person.ID = weight.ID;
ID,sex,ID,weight
123,M,123,200
789,F,789,155
sqlite> CREATE EXTENSION file_fdw;
CREATE SERVER fdw FOREIGN DATA WRAPPER file_fdw;
Declare tables: CREATE FOREIGN TABLE person (id int, sex text)
SERVER fdw
OPTIONS (filename '/tmp/person.csv', format 'csv');
CREATE FOREIGN TABLE weight (id int, weight int)
SERVER fdw
OPTIONS (filename '/tmp/weight.csv', format 'csv');
Now: SELECT * FROM person JOIN weight ON weight.id = person.id; weight <- read.csv("weight.csv")
person <- read.csv("person.csv")
merge(weight, person, all=TRUE)
Of course, nowadays you would use data.table, but still the merging logic would be exactly the same.R in this case fits the bill and even allows for some relational algebra here (e.g. INNER JOIN would be merge(X, Y, all=FALSE), LEFT OUTER JOIN: merge(X, Y, all.x=TRUE), etc...)
But it's an important nuance for another reason, because you can operate on the original files in real time. You can update the underlying files and your queries will reflect the changes.
The downside is since the data is streamed at query time, it's less efficient and doesn't allow indexes to be created.
Plus in the last few years SQLite has gotten a lot more powerful, and has added lots of JSON support and has improved index usage. I'd avoid working with lots of data as flat ASCII text, because of all the I/O and wasted cycles to read the data and insert it into some data structure and then write it out as flat ASCII text, but it can be super debuggable.
The join utility is actually a part of POSIX[1], so every UNIX should have one. Here's one from OpenBSD[2], for example. The GNU version probably has more flags though.
[1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/j...
BTW, there's never a bad time to mention http://johnkerl.org/miller/doc/ ...
Later, I tried Miller and it took less than 4-6 hours.
It also takes some time to tune Postgres to make it faster for this particular task.
Perhaps something pathological like commiting (or flushing to disk) after every row?
And how much of that time was just getting the data into the tables? There are fast and slow ways to do that, too...
After import, it takes 3-6 hours additionally to create indexes for those tables.
I think the main problem was that I had text indices. I'm not an expert in RDBMS and used a simple join that was taking ages to start producing the actual data. There are definitely a lot of ways to tune such queries and PostgreSQL configs, but I wanted a simple and universal solution.
We have a CSV for timeseries data, where for some reason someone decided to force each line to represent one data point. However, for some cases, one data point may contain around 30 different statistics for 4 million different IPs, which get represented as 120 million columns in the CSV (think a CSV header like 'timestamp,10.0.0.1—Throughput,10.0.0.1-DroppedBytes,[...],10.215.188.251-Throughput,[...]'). With large numbers represented as text in a CSV, this can sometimes reach more than one 1GB per line.
In general the operation on my laptop can get up to 200 MB/s, it's basically IO limited to the SSD of your machine.
I feel you will be impressed with the performance.
Using the zipped CSV at [1] as a size estimate, I'd ballpark 300 million CSV rows as somewhere between 6.7-10.6 GB:
(300000000/500000) * 11.10 = 6.7 GB
(300000000/500000) * 17.68 = 10.6 GB
That could one minute to transfer. (Obviously if the rows contain a ton of data, these size estimates could be off, in an unbounded way.)1 = http://eforexcel.com/wp/downloads-18-sample-csv-files-data-s...
https://www.postgresql.org/message-id/CA+hUKGKzn3mjCAp=TDrji...
.once out.csv
select * from person, weight where person.ID = weight.ID;
.mode column