Doing a database join with CSV files
johndcook.com
johndcook.com
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 columnIf you're a Windows admin and haven't bumped into this utility then it's well worth a look.
edit: forgot to mention that it can also scripted from VBScript and Powershell for added fun.
https://www.microsoft.com/en-gb/download/details.aspx?id=246...
There's even a UI there : https://techcommunity.microsoft.com/t5/exchange-team-blog/in...
I think it's much more common than that implies, but have not looked beyond two machines I had immediate access to.
The interface is clumsier in some ways, but it's already there, which is often a win when writing scripts.
There is very little overlap between what xsv does and what standard Unix tools like `join` do. Chances are, if you're using xsv for something like this, then you probably can't correctly use `join` to do it because `join` does not understand the CSV format.
If your CSV data happen to fall into the subset of the CSV format that does not include escaped field separators (or record separators), then a tool like `join` could work. Notably, this might include TSV (tab separated files) or files that use the ASCII field/record separators (although I have literally never seen such a file in the wild). But if it's plain old comma separated values, then using a tool like `join` is perilous.
I didn't write xsv out of ignorance of standard line oriented tools. I wrote it specifically to target problems that cannot be solved by standard line oriented tools.
You might also argue that data should not be formatted in such a way, and philosophically, I don't necessarily disagree with you. But xsv is not a philosophical tool. It is a practical tool to deal with the actual data you have.
rg has all but replaced grep for me.
They are also the source of much highly practical domain-specific knowledge and advice on how to write Rust code that does e.g.; efficient text file reading, streaming, state machines, etc.
Many thanks for these contributions.
Is there anything for `json` files that you would recommend? `jq` is awesome but just wondering.
I don't believe I ever said that xsv shouldn't exist, or has no legitimate purpose, or was written out of ignorance - just that join is useful for similar tasks and worth knowing about, particularly because it's usually available without an install or compile.
You've obviously done a lot more CSV wrangling than I have, and the info here on what exactly xsv does that join cannot is helpful.
Thanks for sharing!
See p. 7 of this PDF (which contains SunOS 4.1.2 manual pages): http://www.bitsavers.org/pdf/sun/sunos/4.1.2/800-6641-10_Ref...
The short description it gives for the command is "relational database operator".
(The "Last change: 4 March 1988" at the bottom of the page refers to the last change of the intro(1) manual page, not the whole book.)
https://www.duckdb.org/docs/current/sql/copy.html
The reason why DuckDB is a better fit for this job is because DuckDB is a column store and has a block-oriented vectorized execution engine. This approach is orders of magnitude faster when you’re doing batch operations with millions of rows at a time.
In contrast, SQLite would be orders of magnitude faster that DuckDB when you’re operating on one row at a time.
It's still in an early stage currently, however, most of the functionality is there (full SQL support, permanent storage, ACID properties). Feel free to give it a try if you are interested. DuckDB has Python and R bindings, and a shell based off of the sqlite3 shell. You can find installation instructions here: https://www.duckdb.org/docs/current/tutorials/installation.h...
Otherwise it seems more flexible to just fire up a python interpreter and do it in like 3 lines of pandas (or with sqlite and .import like another commenter mentioned)
This tool is weird because if I’m going to download some tool that’s not in the distro, I’d rather use SQLite or try to figure it out with awk.
I definitely used to use `sqlite` for every CSV before and I've shifted to using `xsv`. If I want to do any heavy lifting I'm going to pop it into PostgreSQL anyway.
Crucial feature is that it's easy to pop some `xsv` pipeline in the middle of your `for i in ...` or `find . -print0 | xargs -0`.
Sometimes I also use xsv to just do a step of the analysis and dive deeper on some subset using pandas.
In my experience both SQLite and Pandas aren't as fast as fast for large files. So they are not really good options.
Pandas is especially bad because it uses a column oriented data structure internally so reading from or writing to CSV is incredibly slow in Pandas. If you can use parquet that's not a problem but unfortunately parquet is not nearly is ubiquitous as csv :(
Too big for excel is not big data, and my laptop can load this 10G in RAM (not that it necessarily need all of it) so why not if the data is here and the laptop on your lap ?
Often this resulted in one-off data loads or reporting metrics. It was almost always easier just to knock something out with command line tools than it was to write, test, and deploy code. The deployment process itself could take longer than actually processing the file.
https://support.microsoft.com/en-us/help/850320/creating-an-...
More usefully, MySQL has LOAD DATA INFILE facilities for bulk loading of flat files.
Abandon all hope, all ye who enter here.
For me it's the first choice when I find myself with a CSV file to work with. I've also encountered situations where just getting some raw data from a (slow) database and "querying" it with xsv ended up being the fastest option to get the results I wanted.
https://github.com/eBay/tsv-utils/blob/master/README.md
There are benchmark results comparing tsv-utils to a variety of similar tools including xsv.
It's interesting to note that around 1990 The Mark Williams Company wrote a fairly full-fledged database system using, basically, CSV files. It was called "/rdb". You wrote your queries as shell scripts, but using a set of /rdb utilities that handled CSV files with labeled columns, and they had a screen-based data-entry UI based on vi. I wouldn't want to use it instead of a database --- you had to write your query plan in the shell script, rather than a high-level SQL-like query, because the authors didn't really understand SQL --- but it's interesting that the approach is still useful in 2019.
It allows you to query and join data from multiple datasources simultaneously, which may include CSV files.
Currently available datasources are SQL databases, Redis, JSON and CSV (more coming...).
A few years ago, I was trying to turn open streetmaps dumps into json documents. OSM ships as a huge bzipped XML dump of what is essentially 3 tables that need to be joined to do anything productive. One of those tables containes a few billion nodes. Importing that into postgreql takes many hours and takes up diskspace too. Bzipped, this stuff was only around 35GB. But it unpacks to >1TB. So, I wrote a few simple tools that processed the XML using regular expressions into gzipped files, sorted those on id and then joined files on id by simply scanning through multiple of them. Probably far from optimal but it got me results quick enough. Without taking ages or consuming lots of disk.
There are better ways to do this that don't involve using this much memory, but this struck a good balance between memory usage and implementation complexity. I may improve on this some day.
So basically, as long as you have enough memory to store the entire join columns along with `8 * len(one_input)` bytes for the record index, then you should be good.
https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...
> These commands are instantaneous because they run in time and memory proportional to the size of the slice (which means they will scale to arbitrarily large CSV data).
The example given with LINQ reads the whole files into memory:
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
As a fellow LINQ (probably with LINQPad in this case for a quick and dirty script) user, I'd love to know LINQ can be used to read the files in "slices" like xsv. I'm sure this could be accomblished with enough code, but is there quick / easy way to do it?The examples indeed use `File.ReadAllLines` which returns a `string[]`, but the same thing can be done with `File.ReadLines` which returns an `IEnumerable<string>` - a lazy sequence of lines read on demand as the sequence is enumerated.
Despite pretty basic code, the performance is very good.
What I'm saying is that it's good to start with a database from the start, even if the problem seems too trivial for it. That way, if your problem grows into something that actually needs it, you already have it in a suitable database and the code to deal with that.
Sometimes functionality isn't the only thing that's important. Expression can be just as important.
It is a GUI tool for Windows and Mac. No syntax to remember. Just drag the two files on to EDT and click the 'Join' button then choose the columns to join.
Join is just one of 36 transforms available. There is a 7 day free trial.
The title mentions joins, I'm not expecting a GUI-tool.