3P by GN⁺ | ★ favorite | 댓글 1개
  • ECB의 eurofxref-hist.zip은 단순한 환율 CSV 묶음이지만, curl, gunzip, sqlite3만으로 달러가 유로 대비 가장 강했던 날짜 2000-10-26을 바로 찾을 수 있음
  • 원본은 Date 뒤에 통화별 열이 붙는 wide format이라 분석에는 불편하고, Date,Currency,Rate 형태의 long format으로 바꾸는 정리가 필요함
  • 각 줄 끝의 trailing comma 때문에 CSV 파서가 빈 열을 읽어 들이며, Pandas에서 .iloc[:,:-1]로 마지막 열을 제거해야 melt 결과가 깨끗해짐
  • 정리한 CSV는 csvbase에 HTTP PUT으로 올린 뒤 gnuplot, DuckDB, sqlite3 같은 도구와 이어 붙여 그래프 작성, 이동 평균 계산, HTTP CSV 로딩에 활용할 수 있음
  • 접근 협상, 인증, 할당량, 복잡한 API 문서 없이 받을 수 있는 공개 데이터는 open API처럼 동작하며, 단순한 zip 파일도 금융 애플리케이션의 데이터 교환 기반이 될 수 있음

zip 파일 하나로 환율 질의하기

  • ECB는 유로와 다른 통화의 과거 환율 데이터를 공식 zip 파일로 공개함
  • 아래 파이프라인은 데이터를 내려받아 압축을 풀고, SQLite 메모리 DB로 CSV를 읽어 USD 값 기준으로 정렬한 뒤 첫 날짜를 구함
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| gunzip \
| sqlite3 ':memory:' '.import /dev/stdin stdin' \
  "select Date from stdin order by USD asc limit 1;"
  • 출력은 2000-10-26
  • curl -s는 표준 오류의 잡음을 줄이고, gunzip은 zip 파일을 압축 해제함
  • Mac OS나 BSD에서는 BSD 계열 gunzip이 zip 파일을 지원하지 않으므로 bsdtar -xOf -를 대신 써야 함
  • sqlite3 ':memory:'는 인메모리 DB를 쓰고, .import /dev/stdin stdin은 표준 입력을 stdin 테이블로 불러옴

CSV 형태 정리와 Pandas melt

  • 원본 CSV 헤더는 Date,USD,JPY,BGN,CYP,CZK,DKK,...처럼 날짜 열 뒤에 통화별 열이 이어지는 wide format
  • 필터와 집계를 하려면 Date,Currency,Rate 형태의 long format이 더 다루기 쉬움
  • wide format을 long format으로 바꾸는 작업은 흔히 melt라고 부름
  • 대부분의 SQL 데이터베이스에는 melt에 해당하는 연산이 없어 Pandas가 데이터 정리에 유용함
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).melt("Date").to_csv(sys.stdout, index=False)'
  • ECB 파일은 각 줄 끝에 trailing comma가 있어 CSV 파서가 마지막에 빈 열을 추가로 읽음
  • 이 빈 열은 melt 결과 끝에 쓸모없는 행을 만들기 때문에 제거가 필요함
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)'
  • .iloc[:, :-1]은 모든 행과 마지막 열을 제외한 모든 열을 선택함
  • ECB 외환 데이터는 포맷 정리가 필요하지만, 접근 협상, 결제, 영업 담당자와의 대화, 이메일·회사명·직함 제출, 할당량, 인증, API 문서 읽기 없이 바로 쓸 수 있음
  • 기본 포맷과 형태 문제만 처리하면 되기 때문에 공개 데이터 릴리스 중에서는 상대적으로 좋은 편임

csvbase에 정리한 데이터 올리기

  • 정리한 CSV는 csvbase table에 올려 반복 정리 작업을 피할 수 있음
  • 기존 파이프라인 끝에 curl을 하나 더 붙이면 CSV를 HTTP PUT으로 업로드할 수 있음
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)' | \
curl -n --upload-file - \
'https://csvbase.com/calpaterson/eurofxref-hist?public=yes'
  • --upload-file -는 표준 입력에서 받은 데이터를 지정 URL로 업로드함
  • csvbase에 테이블이 없으면 새로 만들고, 있으면 해당 테이블에 데이터를 넣음
  • -n~/.netrc인증 정보를 사용함

gnuplot으로 환율 그래프 그리기

  • 정리된 csvbase 테이블은 curl로 CSV를 받아 grep, cut, gnuplot과 연결할 수 있음
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
grep USD | \
cut -d, -f 2,4 | \
gnuplot -e "set datafile separator ','; set term dumb; \
plot '-' using 1:2 with lines title 'usd'"
  • 이 명령은 6,000개가 넘는 데이터 포인트를 80x25 문자 터미널에서 ASCII 아트로 어느 정도 읽을 수 있게 그림
  • gnuplot 설정은 CSV 입력을 받아 날짜와 환율을 선 그래프로 그리도록 맞춰져 있음
    • set datafile separator ',': 입력이 CSV임을 지정
    • set term dumb: ASCII 아트로 그림
    • plot -: 표준 입력에서 데이터를 받음
    • using 1:2 with lines: 1열과 2열, 즉 날짜와 환율로 선을 그림
    • title 'usd': 선 이름을 usd로 지정
  • SVG 이미지로도 출력할 수 있으며, 시계열 데이터처럼 보이게 하려면 x축이 시간임을 지정하고 시간 형식과 x축 눈금 회전을 설정해야 함
  • 반복 사용을 위해 plot_timeseries_to_svg Bash 함수로 묶을 수 있음

DuckDB로 이동 평균 계산하기

  • USD 환율의 추세선을 보려면 DuckDB로 이동 평균을 계산할 수 있음
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
duckdb -csv -c "select Date, avg(value) over \
(order by date rows between 100 preceding and current row) \
as rolling from read_csv_auto('/dev/stdin')
where variable = 'USD';" | \
plot_timeseries_to_svg rolling
  • duckdb가 없으면 같은 쿼리를 sqlite3용으로 바꾸는 것도 어렵지 않음
  • DuckDB는 SQLite와 비슷하지만 행 지향이 아니라 컬럼 지향
  • DuckDB는 HTTP에서 CSV를 바로 읽어 테이블 파일로 만들 수 있음
CREATE TABLE eurofxref_hist AS SELECT * FROM
read_csv_auto("https://csvbase.com/calpaterson/eurofxref-hist";);
  • DuckDB는 타입 추론을 꽤 잘하고, 터미널 크기를 감지해 큰 결과를 기본적으로 줄여 보여줌
  • 큰 쿼리에 진행 막대를 표시하고, Markdown 테이블 출력도 할 수 있음

공개 데이터가 open API처럼 동작하는 방식

  • zip 파일 안의 CSV와 brew install 또는 apt install로 쉽게 설치할 수 있는 도구만으로도 많은 작업이 가능함
  • eurofxref-hist.zip은 조직 간 데이터 교환 프로토콜로 매우 단순한 형태임
  • 이 zip 파일은 작아 보이지만 많은 금융 애플리케이션이 매일 사용함
  • ECB가 trailing comma를 그대로 두는 이유는 지금 제거하면 많은 코드가 깨질 수 있기 때문으로 볼 수 있음
  • 공개 데이터가 아주 쉽게 제공되면 open API 역할도 수행함
  • 많은 API가 원격 함수 호출보다 데이터 교환에 가깝다면, 쉽게 받을 수 있는 공개 데이터와 기능적으로 크게 다르지 않음

csvbase의 단순한 URL과 HTTP 동사

  • csvbase는 각 테이블에 하나의 URL을 둠
https://csvbase.com/<username>/<table_name>;
  • 예시는 다음과 같음
https://csvbase.com/calpaterson/eurofxref-hist
  • 각 URL에는 네 가지 주요 HTTP 동사가 있음
    • GET: CSV를 받음, 브라우저에서는 웹 페이지를 받을 수 있음
    • PUT: 새 CSV로 새 테이블을 만들거나 기존 테이블을 덮어씀
    • POST: 기존 테이블에 CSV 행을 대량 추가함
    • DELETE: 해당 테이블을 삭제함
  • 인증은 HTTP Basic Auth를 사용함

데이터 정리와 파이프라인에 대한 메모

  • SQL 데이터베이스 중 melt에 해당하는 기능을 제공하는 것으로 Snowflake의 UNPIVOT과 MS SQL Server의 PIVOT/UNPIVOT이 있음
  • R과 Pandas가 쓰이는 중요한 이유 중 하나는 데이터 정리 기능이 강하다는 점임
  • Bash 파이프라인은 멀티프로세스로 동작해 각 프로그램이 독립 프로세스에서 병렬로 실행됨
  • 2000년 10월 유로 대비 달러 환율은 0.8252였고, 이는 달러 1개로 유로 1.21개를 살 수 있었다는 뜻임
  • 유로는 1999년 1월 지폐와 동전 없이 출범했으며, 초기에는 은행 내부에만 존재했고 지폐와 동전은 나중에 나옴

댓글과 토론

Hacker News 의견들
  • ECB에서 15년쯤 전에 일할 때 이 파일을 기억함
    이 파일은 ECB 웹사이트에서 압도적으로 많이 내려받힌 파일이었고, 많은 사람과 금융기관이 매일 받아서 자기 시스템을 갱신하는 데 썼음
    매일 정해진 공개 시각 직후 몇 분 동안은 트래픽이 크게 튀었고, 압축을 풀면 단순한 CSV 파일이 되도록 한 건 의도적인 결정이었음
    덕분에 안정적이고 빠르게, 적은 자원으로 파일을 제공할 수 있었고 당시 ECB 공개 웹사이트를 맡던 작은 팀은 단일 정적 파일로 이 데이터를 제공한 기술적 결정에 매우 자부심을 가질 만했음

    • 이런 방식이 많은 데이터 교환을 떠받치지만, 오래된 시스템을 다뤄보지 않은 사람은 잘 모름
      화려하지도 않고 프레임워크도 없음
      15년쯤 전 누구나 제품을 사봤을 법한 오래된 대기업에서 제품 기록 시스템과 합병·인수로 남은 하위/병렬 시스템 사이의 데이터 교환을 다뤘는데, 대부분 고정 폭 파일이나 구분자 파일을 SFTP 서버로 주고받는 대량 가져오기/내보내기였음
      당시 제품은 이미 15년 된 상태였고, 그런 데이터 소스나 내보내기가 20~30개쯤 오갔지만 아주 잘 돌아갔음
      지금도 큰 변화 없이 쓰고 있을 가능성이 높고, 당시 프런트엔드는 Smalltalk로 된 예전 것을 새로 쓰는 중이었음
    • 이 파일을 나도 알고 있고, 그중 한 명이었음
      우리가 쓰던 데이터 소스 중에서 가장 다루기 좋았음
    • 내 회사에서 바뀌는 데이터를 정적 CSV/ZIP으로 올리려면 넘어야 할 관문을 생각하면 놀라움
      설계자는 ZIP이 이 목적의 사양에 맞는 형식이 아니라고 하고, 컴플라이언스는 개인정보 유출 검사가 필요하다고 하며, 리스크 쪽은 악의적 행위자가 파일을 내려받지 못하게 해야 한다고 할 것임
      웹 담당자는 사이트에 뭔가 추가하려면 승인된 변경 절차가 필요하다고 할 듯함
    • 왜 그냥 CSV를 올리고 HTTP 압축에 맡기지 않고 ZIP 파일로 제공하기로 했는지 궁금함
    • 이 파일이 어떤 식으로든 손상됐을 때, 예를 들어 열 제목이 틀렸을 때의 결과를 상상하면 조금 무서움
  • 단순한 파일 다운로드와 CSV 파일은 훌륭함
    더 많은 곳이 데이터를 이런 단순한 형식으로 공개했으면 좋겠고, 미국 정부 데이터 다운로드에서 “장바구니”를 채워야 할 때마다 조금씩 죽는 느낌임
    이 특정 파이프라인을 쉽게 해주는 래퍼 도구도 많고, 웹 뷰와 조금 더 고급 기능이 필요하면 Datasette 같은 것도 좋음

    • ZIP으로 묶인 CSV를 웹으로 제공할 때 좋은 점은 어디에도 저장하지 않고, 데이터베이스만 제외하면, 곧바로 스트리밍해서 매우 빠르게 넣을 수 있다는 것임
      ZIP 파일을 스트림으로 읽고 CSV를 줄 단위로 처리해 변환한 뒤, Postgres 기준으로 COPY FROM stdin을 써서 데이터베이스에 적재할 수 있음
    • SQLite가 CSV를 입력으로 받아 바로 질의할 수 있다는 게 놀라움
      너무 논리적이고 유용해 보이는데도 지금까지 접하지 못했음
      CSV로 된 보고가 많아서, 빨리 질의를 빠르게 돌리는 데 써보고 싶음
    • CSV는 “하나의 CSV”가 있고, 아무도 스프레드시트 프로그램으로 열거나 편집하거나 텍스트 편집기에서 손으로 고치지 않는다고 절대 보장할 수 있다면 훌륭함
      예를 들어 "Look, this contains \"quotes\"!",012345"Look, this contains ""quotes""!",012345처럼 따옴표 처리 방식이 갈리고, 더 망가진 예로 "Look, this contains "quotes"!",012345Look, this contains "quotes"!,012345도 나올 수 있음
      스프레드시트의 흔적으로 "Look, this contains ""quotes""!",12345처럼 앞의 0이 잘리기도 함
      이론적으로 JSON도 손으로 고쳐 반쯤 망가진 파일이 될 수 있지만, 실제로는 JSON 파일에 그렇게 하는 경우를 거의 못 봤고, 일련번호 같은 값도 JSON에서는 “친절한” 앱이 앞의 0을 잘라낼 정수가 아니라 문자열로 남는 편임
    • 정부 데이터의 장바구니 방식이 얼마나 터무니없는지 따져본 적이 없었음
      대체 왜 그런지, 정당한 이유가 있긴 한가?
    • 문서 형식 자체는 핵심 문제가 아닌 듯함
      CSV를 ZIP으로 묶은 JSON 문서로 바꿔도 장점은 같음
      진짜 문제는 정적으로 제공되는 파일 하나를 단순히 내려받는 데 너무 많은 장애물이 놓여 있다는 점임
  • 정부 기관용 API를 만든 적이 있는데, 데이터가 1년에 한 번 바뀌거나 아주 드물게 개정될 뿐이었음
    전체 데이터셋은 1MB 미만 ZIP 파일 하나로 묶을 수 있었지만, 솔루션 아키텍트가 요구사항을 잡으면서 일이 커졌음
    요청하는 바로 그 순간 데이터가 바뀌었을 수 있다는 이유로 캐시를 못 쓰게 해서 느린 API가 됐고, 데이터 변경을 구독자에게 알리는 과하게 복잡한 웹훅 시스템까지 생겼음
    ZIP 파일 하나는 너무 단순했을 수 있지만, 실제 필요했던 것과 크게 다르지도 않았음

    • 1MB 미만 데이터가 1년에 한두 번 바뀐다면, 올바른 API는 ETag/If-Modified-Since를 제대로 지원하는 정적 웹서버라고 봄
      더 멋지게 하고 싶다면 파일이 바뀔 때 트리거되는 웹훅을 추가해서, 클라이언트가 하루 한 번씩 폴링하지 않고 다시 내려받을 시점을 알게 하면 됨
      아니면 변경이 있을 때 미리 정한 이메일을 메일링 리스트로 보내는 스크립트만 만들어도 충분함
    • ETag를 지원하는 웹서버에 ZIP 파일을 두고, 접근이 필요할 때마다 폴링하면 됨
      이전과 달라진 게 없으면 빈 HTTP 304 응답을 받고, 바뀌었으면 새 ETag와 함께 1MB 미만 ZIP 파일을 다시 받으면 되는데, 여기서 빠진 게 뭔지 모르겠음
    • 데이터가 1년에 한 번 또는 드물게만 바뀐다면 그 데이터를 쓰는 사용자에게 API 사용 자체도 드문 일이므로 속도는 큰 문제가 아닐 수 있음
      캐시는 복잡성을 늘리고 수동으로 캐시를 재검증해야 할 위험도 생기니, 솔루션 아키텍트가 맞았을 가능성도 있음
  • 결과값 2000-10-26 하나를 얻으려고 565KB 파일을 내려받아야 한다면 끔찍한 API임
    다량의 데이터를 가져와 사용자에게 다시 제공하려는 경우라면 ZIP으로 묶은 CSV는 훌륭하고, 여러 언어 지원이 좋지 않은 대중교통 실시간 열차 시각용 protobuf보다 훨씬 선호함
    하지만 단일 값을 얻는 API처럼 취급하면 엄청난 낭비이고, 앱에 이런 식으로 넣는 사람은 없었으면 좋겠음
    글 자체는 멋지지만 제목이 너무 도발적인 주장처럼 느껴짐

    • 이건 과거 데이터
      하루에 한 번 이상 요청할 이유가 전혀 없고, 이런 데이터를 쓰는 사람들은 서로 매우 다른 필터나 집계를 원할 가능성이 큼
      현재 환율을 얻는 용도라면 나쁜 설계가 맞지만, 그 용도에는 다른 서비스가 있고 이 파일은 전형적인 사용 사례에 잘 맞음
    • 그런 걸 앱에 넣는 사람이 없길 바란다면, 나쁜 소식이 있음
      API와 직접 관련은 없지만, 예전에 토지 관리 애플리케이션을 지원할 때 새 버전이 나오기 전까지는 ISDN급 회선일 수도 있는 느린 위성 사무소에서도 잘 동작했는데, 새 버전은 전혀 안 됐음
      업체는 RDP 서버에서 돌리라고 했지만 말도 안 된다고 생각해 조사해보니, 어떤 호출 하나가 아무 이유 없이 SELECT * FROM sometable을 하고 있었고 같은 실행 안의 다른 호출들은 제대로 된 SQL select 절을 쓰고 있었음
      이걸 업체에 말하자 처음엔 우리가 그걸 어떻게 알아냈는지 매우 혼란스러워했고, 결국 느린 회선에서도 쓸 수 있게 수정한 새 버전을 내놨음
      자체 테스트에서 왜 그걸 못 잡고 고객에게 비싼 해결책을 밀었는지 이해하기 어려움
    • 20년 전 주장이 다시 전화해서 자기 주장을 돌려달라는 느낌임
      요즘 JavaScript를 조금이라도 봤다면, 565KB와 그중 큰 값을 찾는 로직은 어떤 합리적 기준으로도 아주 작음
    • 이 스레드에서 많은 사람이 “API”의 정의 차이를 대충 넘기는 것 같음
      어떤 사람은 “필터링 없이 전체 데이터를 받더라도 데이터를 얻는 방법”을 API로 보지만, 개인적으로는 전체 테이블 다운로드는 모델에 로직이 작동하지 않는 데이터 모델 다운로드이고, API는 내가 관심 있는 방식으로 모델 일부를 걸러 반환하는 로직이라고 봄
    • 565KB가 왜 중요하다고 가정하는지 모르겠음
      금융 소프트웨어를 백엔드와 프런트엔드 양쪽에서 많이 만들어봤는데, 프런트엔드에서는 실제 데이터에 도달하기 전부터 그 정도 양의 “데이터”를 전송하는 일이 안타깝게도 흔함
      백엔드에서는 설계 결정일 뿐이고, 밤마다 크론 작업이 환율을 파싱해 목적에 맞춘 todays-rates.json을 만들어 정적 파일로 모바일·웹·마이크로서비스 앱에 제공하는 것보다 빠른 건 없음
      모바일 앱이 반드시 이 ZIP-CSV-over-HTTP를 직접 소비해야 한다는 뜻은 어디에도 없음
  • 작은 데이터 하나가 필요할 때마다 큰 파일을 받아야 한다고 불평하는 사람을 위한 아주 단순한 최적화가 있음
    파일이 추가 전용이라고 보장하고 ZIP 파일 대신 HTTP gzip/brotli 같은 압축을 쓴다면, 범위 요청으로 마지막 갱신 이후의 새 데이터만 받을 수 있음
    여기에 안심용 체크섬 헤더를 하나 더하면 꽤 효율적이면서도 매우 단순한 증분 API가 됨
    물론 상태를 보관해야 하고 첫 다운로드와 상태 유지 비용을 치러야 하며, 2007-08-22의 EUR/JPY 환율 하나만 딱 한 번 필요할 때는 비효율적임

    • 맞음. ETag와 다른 기법을 써서 바로 그런 일을 하는 클라이언트 라이브러리를 구상 중임
      아직 매우 작업 중인 상태지만, 현재 “연구 품질” 코드는 여기 있음: https://pypi.org/project/csvbase-client/
    • 범위 요청 얘기를 더 하면, 서버가 ZIP 파일에 대해 범위 요청을 허용하고 ZIP 파일은 매우 크지만 그 안의 몇 파일만 필요할 때는, 전체 ZIP을 받지 않고 중앙 디렉터리와 필요한 파일의 압축 데이터만 받을 수 있음
      https://github.com/gtsystem/python-remotezip
    • 아니면 그냥 diff 파일 여러 개를 제공하면 됨
      하루치 패치 하나만 있어도 내 쪽에서 파일을 최신으로 유지하는 데 필요한 대역폭을 크게 줄일 수 있음
      하루에 몇백 KB 더 내려받는 게 의미 있을 때의 얘기고, 대개는 그렇지 않을 가능성이 큼
  • sqlite 예제에는 오타가 있음
    스크린샷에는 없지만, sqlite-csv 인자를 추가해야 함

    • 이상함. 원래 그 인자를 넣었다가 없어도 되는 것 같아서 뺐고, 내 환경에서는 동작했음
      다시 추가하고 캐시를 무효화하겠음. 아이들 재우고 나서 뭐가 잘못됐는지 확인해볼 예정임
      수정: 내 환경에서 동작한 이유는 ~/.sqliterc.separator ',' 설정이 있었기 때문임
      예전에 CSV 파일을 주로 넣는다는 걸 깨닫고 기본값으로 설정해둔 모양임
  • 잠깐 옆길로 새면, 유로가 처음에는 전자적으로만 존재했더라도 유로존 회원국의 기존 통화와는 고정 환율이 있었음
    특히 독일의 확립되고 신뢰받던 Deutsche Mark와 고정돼 있었음
    따라서 “초기 유로가 왜 약했나”를 설명하려면 당시 DEM이 왜 약했는지도 설명해야 하는데, 해당 문단의 설명은 그 검증을 통과하지 못하는 것 같음

  • 매번 전체 데이터베이스를 내려받아 읽기 전용으로 처리할 수 있는 작은 문제에서는 단순함의 가치를 과소평가하면 안 됨
    SQLite는 .json이나 .csv 파일처럼 이식성이 있으면서도 데이터베이스처럼 상호작용할 준비가 더 잘 되어 있어서 마음에 듦

    • clickhouse-local을 쓰면 오래된 CSV 파일도 데이터베이스처럼 다룰 수 있음
  • 핵심은 여기에 있음
    이 경우 하지 않아도 됐던 것들: 접근 권한 협상, 예를 들어 돈을 내거나 영업사원과 이야기하기, 이메일 주소·회사명·직책을 누군가의 잠재고객 데이터베이스에 넣기, 할당량 지키기, 인증하기, API 문서 읽기, 기본적인 형식과 구조보다 심각한 문제를 다루기

    • “할당량 지키기”가 없다는 부분은 회의적임
      대역폭은 공짜가 아님
  • SQLite는 ZIP 파일을 읽고 쓸 수 있음
    https://sqlite.org/zipfile.html
    gunzip 대신 sqlite3로 압축 해제가 가능한지 궁금함

    • 좋은 지점임
      파일을 디스크에 저장해도 된다면 이렇게 할 수 있음:

      sqlite3 -newline '' ':memory:' "SELECT data FROM zipfile('eurofxref-hist.zip')" \
      | sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
      "select ...;"

      임시 파일 없이 하는 건 까다로움. 예를 들어 readfile('/dev/stdin')은 SQLite가 seek()를 쓰려고 해서 동작하지 않음
      ZIP 파일을 xxd로 16진수로 바꿔 SQL 질의의 문자열 리터럴에 넣는 매우 못생긴 방법은 가능함:

      curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
      | { printf "SELECT data FROM zipfile(x'"; xxd -p | tr -d '\n'; printf "')"; } \
      | sqlite3 -newline '' \
      | sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
      "select ...;"