3 điểm bởi GN⁺ 2023-09-16 | 1 bình luận | Chia sẻ qua WhatsApp
  • eurofxref-hist.zip của ECB chỉ là một gói CSV tỷ giá đơn giản, nhưng chỉ với curl, gunzip, sqlite3 là có thể tìm ngay ngày đồng đô la mạnh nhất so với euro: 2000-10-26
  • Dữ liệu gốc có wide format với các cột tiền tệ nối sau Date, nên không thuận tiện cho phân tích và cần được chuyển sang long format dạng Date,Currency,Rate
  • Do có trailing comma ở cuối mỗi dòng, trình phân tích CSV sẽ đọc thêm một cột rỗng; trong Pandas cần dùng .iloc[:,:-1] để bỏ cột cuối thì kết quả melt mới sạch
  • CSV sau khi chuẩn hóa có thể được tải lên csvbase bằng HTTP PUT rồi nối tiếp với các công cụ như gnuplot, DuckDB, sqlite3 để vẽ đồ thị, tính trung bình trượt và nạp CSV qua HTTP
  • Dữ liệu công khai có thể tải về mà không cần thương lượng quyền truy cập, xác thực, hạn mức hay tài liệu API phức tạp thực chất hoạt động giống như open API, và ngay cả một file zip đơn giản cũng có thể là nền tảng trao đổi dữ liệu cho các ứng dụng tài chính

Truy vấn tỷ giá chỉ với một file zip

  • ECB công bố dữ liệu tỷ giá lịch sử giữa euro và các đồng tiền khác dưới dạng file zip chính thức
  • Pipeline dưới đây tải dữ liệu về, giải nén, đọc CSV vào cơ sở dữ liệu SQLite trong bộ nhớ, sắp xếp theo giá trị USD rồi lấy ra ngày đầu tiên
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;"
  • Kết quả là 2000-10-26
  • curl -s giúp giảm bớt nhiễu trên standard error, còn gunzip dùng để giải nén file zip
  • Trên Mac OS hoặc BSD, gunzip dòng BSD không hỗ trợ file zip nên cần thay bằng bsdtar -xOf -
  • sqlite3 ':memory:' dùng cơ sở dữ liệu trong bộ nhớ, còn .import /dev/stdin stdin nạp standard input vào bảng stdin

Chuẩn hóa dạng CSV và Pandas melt

  • Header CSV gốc có dạng wide format như Date,USD,JPY,BGN,CYP,CZK,DKK,..., tức sau cột ngày là các cột theo từng đồng tiền
  • Để lọc và tổng hợp, long format dạng Date,Currency,Rate sẽ dễ thao tác hơn
  • Việc chuyển từ wide format sang long format thường được gọi là melt
  • Phần lớn cơ sở dữ liệu SQL không có phép toán tương đương melt, nên Pandas rất hữu ích cho khâu chuẩn hóa dữ liệu
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)'
  • File của ECB có trailing comma ở cuối mỗi dòng nên trình phân tích CSV sẽ đọc thêm một cột rỗng ở cuối
  • Cột rỗng này tạo ra các dòng vô nghĩa ở cuối kết quả melt, vì vậy cần loại bỏ
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] chọn tất cả các dòng và tất cả các cột trừ cột cuối cùng
  • Dữ liệu ngoại hối của ECB cần được chỉnh lại định dạng, nhưng có thể dùng ngay mà không cần thương lượng quyền truy cập, thanh toán, trao đổi với bộ phận kinh doanh, gửi email/tên công ty/chức danh, hạn mức, xác thực hay đọc tài liệu API
  • Vì chỉ cần xử lý định dạng và hình dạng dữ liệu cơ bản nên đây vẫn là một bản phát hành dữ liệu công khai tương đối tốt

Tải dữ liệu đã chuẩn hóa lên csvbase

  • CSV đã chuẩn hóa có thể được tải lên csvbase table để tránh phải lặp lại bước làm sạch nhiều lần
  • Chỉ cần thêm một curl vào cuối pipeline hiện có là có thể tải CSV lên bằng 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 - tải dữ liệu nhận từ standard input lên URL được chỉ định
  • Nếu chưa có bảng trên csvbase thì sẽ tạo mới, nếu đã có thì dữ liệu sẽ được đưa vào bảng đó
  • -n sử dụng thông tin xác thực trong ~/.netrc

Vẽ đồ thị tỷ giá bằng gnuplot

  • Bảng csvbase đã chuẩn hóa có thể lấy CSV bằng curl rồi nối với 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'"
  • Lệnh này vẽ hơn 6.000 điểm dữ liệu dưới dạng ASCII art sao cho vẫn có thể đọc được phần nào trong terminal ký tự 80x25
  • Cấu hình gnuplot được thiết lập để nhận đầu vào CSV và vẽ ngày cùng tỷ giá dưới dạng biểu đồ đường
    • set datafile separator ',': chỉ định đầu vào là CSV
    • set term dumb: vẽ dưới dạng ASCII art
    • plot -: nhận dữ liệu từ standard input
    • using 1:2 with lines: vẽ đường bằng cột 1 và cột 2, tức ngày và tỷ giá
    • title 'usd': đặt tên đường là usd
  • Cũng có thể xuất ra ảnh SVG; để trông giống dữ liệu chuỗi thời gian hơn thì cần chỉ định trục x là thời gian, đặt định dạng thời gian và xoay nhãn trục x
  • Có thể gói lại thành hàm Bash plot_timeseries_to_svg để dùng lặp lại

Tính trung bình trượt bằng DuckDB

  • Để xem đường xu hướng của tỷ giá USD, có thể dùng DuckDB để tính trung bình trượt
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
  • Nếu không có duckdb thì cũng không khó để chuyển truy vấn tương tự sang sqlite3
  • DuckDB giống SQLite nhưng là cơ sở dữ liệu column-oriented thay vì row-oriented
  • DuckDB có thể đọc trực tiếp CSV qua HTTP và tạo thành file bảng
CREATE TABLE eurofxref_hist AS SELECT * FROM
read_csv_auto("https://csvbase.com/calpaterson/eurofxref-hist";);
  • DuckDB suy luận kiểu dữ liệu khá tốt và có thể phát hiện kích thước terminal để mặc định rút gọn kết quả lớn khi hiển thị
  • Nó cũng có thể hiện thanh tiến trình cho truy vấn lớn và xuất bảng Markdown

Cách dữ liệu công khai hoạt động như open API

  • Chỉ với CSV trong file zip và các công cụ có thể cài dễ dàng bằng brew install hoặc apt install cũng đã làm được rất nhiều việc
  • eurofxref-hist.zip là một giao thức trao đổi dữ liệu giữa các tổ chức ở dạng cực kỳ đơn giản
  • File zip này trông nhỏ bé nhưng được rất nhiều ứng dụng tài chính sử dụng hằng ngày
  • Một cách nhìn là ECB giữ nguyên trailing comma vì nếu bỏ đi bây giờ có thể sẽ làm hỏng rất nhiều đoạn mã đang chạy
  • Khi dữ liệu công khai được cung cấp cực kỳ dễ dàng, nó cũng đảm nhiệm vai trò của một open API
  • Nếu nhiều API thực chất gần với trao đổi dữ liệu hơn là gọi hàm từ xa, thì về mặt chức năng chúng không khác nhiều so với dữ liệu công khai có thể tải về dễ dàng

URL đơn giản và các HTTP verb của csvbase

  • csvbase dùng một URL cho mỗi bảng
https://csvbase.com/<username>/<table_name>;
  • Ví dụ như sau
https://csvbase.com/calpaterson/eurofxref-hist
  • Mỗi URL có bốn HTTP verb chính
    • GET: nhận CSV, hoặc trên trình duyệt thì có thể nhận trang web
    • PUT: tạo bảng mới bằng CSV mới hoặc ghi đè bảng hiện có
    • POST: thêm hàng loạt dòng CSV vào bảng hiện có
    • DELETE: xóa bảng đó
  • Xác thực dùng HTTP Basic Auth

Ghi chú về chuẩn hóa dữ liệu và pipeline

  • Trong các cơ sở dữ liệu SQL, một số hệ có tính năng tương đương melt, chẳng hạn UNPIVOT của Snowflake và PIVOT/UNPIVOT của MS SQL Server
  • Một trong những lý do quan trọng khiến R và Pandas được dùng nhiều là khả năng chuẩn hóa dữ liệu rất mạnh
  • Bash pipeline chạy theo kiểu đa tiến trình, nên mỗi chương trình chạy song song trong một tiến trình độc lập
  • Vào tháng 10/2000, tỷ giá USD so với euro là 0.8252, nghĩa là 1 đô la có thể mua được 1,21 euro
  • Euro ra đời vào tháng 1/1999 mà chưa có tiền giấy và tiền xu; ban đầu nó chỉ tồn tại trong nội bộ ngân hàng và tiền giấy cùng tiền xu xuất hiện sau đó

1 bình luận

 
GN⁺ 2023-09-16
Ý kiến trên Hacker News
  • Tôi nhớ tệp này khi làm ở ECB khoảng 15 năm trước
    Đây là tệp được tải xuống nhiều áp đảo trên website của ECB, và rất nhiều người cùng tổ chức tài chính tải về mỗi ngày để cập nhật hệ thống của họ
    Trong vài phút ngay sau thời điểm công bố cố định hằng ngày, lưu lượng truy cập tăng vọt; việc khi giải nén ra chỉ là một tệp CSV đơn giản là một quyết định có chủ đích
    Nhờ vậy có thể cung cấp tệp một cách ổn định, nhanh chóng với ít tài nguyên, và nhóm nhỏ phụ trách website công khai của ECB khi đó hoàn toàn có thể tự hào về quyết định kỹ thuật là cung cấp dữ liệu này dưới dạng một tệp tĩnh duy nhất

    • Cách làm này chống đỡ rất nhiều hoạt động trao đổi dữ liệu, nhưng những ai chưa từng xử lý hệ thống cũ thường không biết
      Nó không hào nhoáng, cũng không có framework
      Khoảng 15 năm trước, tôi từng xử lý trao đổi dữ liệu giữa hệ thống ghi nhận sản phẩm của một tập đoàn lớn lâu đời mà hẳn ai cũng từng mua sản phẩm của họ, với các hệ thống con/song song còn lại sau sáp nhập và mua lại; phần lớn là nhập/xuất hàng loạt các tệp độ rộng cố định hoặc tệp có ký tự phân tách qua máy chủ SFTP
      Khi đó sản phẩm đã tồn tại 15 năm, và có khoảng 20–30 nguồn dữ liệu hoặc bản xuất dữ liệu như vậy qua lại, nhưng chúng chạy rất tốt
      Khả năng cao hiện nay chúng vẫn đang được dùng mà không thay đổi lớn; lúc đó frontend cũ viết bằng Smalltalk đang được viết lại
    • Tôi cũng biết tệp này, và từng là một trong những người đó
      Trong số các nguồn dữ liệu chúng tôi dùng, nó là thứ dễ xử lý nhất
    • Nghĩ đến những rào cản phải vượt qua nếu muốn đưa dữ liệu thay đổi lên dưới dạng CSV/ZIP tĩnh ở công ty tôi thì thật đáng kinh ngạc
      Kiến trúc sư sẽ nói ZIP không phải định dạng phù hợp với đặc tả cho mục đích này, bộ phận tuân thủ sẽ nói cần kiểm tra rò rỉ thông tin cá nhân, còn phía rủi ro sẽ nói phải ngăn tác nhân độc hại tải tệp xuống
      Người phụ trách web có lẽ sẽ nói muốn thêm thứ gì vào site thì cần quy trình thay đổi đã được phê duyệt
    • Tôi tò mò vì sao họ quyết định cung cấp dưới dạng tệp ZIP thay vì chỉ đăng CSV và giao cho nén HTTP xử lý
    • Nghĩ đến hậu quả nếu tệp này bị hỏng theo cách nào đó, chẳng hạn tiêu đề cột bị sai, thì hơi đáng sợ
  • Tải xuống tệp đơn giản và tệp CSV là điều tuyệt vời
    Tôi mong nhiều nơi công bố dữ liệu bằng những định dạng đơn giản như thế này hơn, và mỗi lần phải lấp đầy “giỏ hàng” để tải dữ liệu của chính phủ Mỹ là tôi lại cảm thấy như chết dần một chút
    Cũng có nhiều công cụ wrapper giúp pipeline cụ thể này dễ hơn; nếu cần giao diện web và thêm vài tính năng nâng cao hơn một chút thì những thứ như Datasette cũng rất tốt

    • Điểm hay khi cung cấp CSV được đóng ZIP qua web là có thể nạp rất nhanh bằng cách stream trực tiếp, không cần lưu ở đâu cả, ngoại trừ cơ sở dữ liệu
      Có thể đọc tệp ZIP dưới dạng stream, xử lý CSV theo từng dòng để chuyển đổi, rồi nạp vào cơ sở dữ liệu bằng COPY FROM stdin trong trường hợp Postgres
    • Thật ngạc nhiên là SQLite có thể nhận CSV làm đầu vào và truy vấn trực tiếp
      Nghe vừa hợp lý vừa hữu ích, vậy mà đến giờ tôi mới biết
      Tôi có nhiều báo cáo dạng CSV, nên rất muốn thử dùng nó để chạy truy vấn nhanh
    • CSV rất tuyệt nếu có thể bảo đảm tuyệt đối rằng chỉ có “một kiểu CSV”, và không ai mở, chỉnh sửa bằng chương trình bảng tính hay tự tay sửa trong trình soạn thảo văn bản
      Ví dụ cách xử lý dấu ngoặc kép có thể khác nhau như "Look, this contains \"quotes\"!",012345"Look, this contains ""quotes""!",012345; thậm chí có các ví dụ hỏng hơn như "Look, this contains "quotes"!",012345 hay Look, this contains "quotes"!,012345
      Dấu vết của bảng tính cũng có thể làm mất số 0 ở đầu, như "Look, this contains ""quotes""!",12345
      Về lý thuyết, JSON cũng có thể bị sửa tay thành một tệp hỏng một nửa, nhưng trên thực tế tôi hiếm khi thấy ai làm vậy với tệp JSON; các giá trị như số sê-ri trong JSON cũng thường được giữ dưới dạng chuỗi, chứ không bị một ứng dụng “thân thiện” cắt mất số 0 ở đầu như với số nguyên
    • Tôi chưa từng thật sự nghĩ xem cách dùng giỏ hàng cho dữ liệu chính phủ vô lý đến mức nào
      Rốt cuộc vì sao lại như vậy, có lý do chính đáng nào không?
    • Bản thân định dạng tài liệu dường như không phải vấn đề cốt lõi
      Đổi CSV được đóng ZIP thành tài liệu JSON được đóng ZIP thì lợi ích vẫn giống nhau
      Vấn đề thật sự là có quá nhiều rào cản chỉ để tải xuống một tệp duy nhất được phục vụ tĩnh
  • Tôi từng xây API cho một cơ quan chính phủ, trong đó dữ liệu chỉ thay đổi mỗi năm một lần hoặc rất hiếm khi được sửa đổi
    Toàn bộ dataset có thể gói trong một tệp ZIP dưới 1MB, nhưng mọi chuyện phình to khi solution architect xác định yêu cầu
    Họ không cho dùng cache vì dữ liệu có thể đã thay đổi ngay tại đúng khoảnh khắc yêu cầu được gửi, khiến API trở nên chậm; thậm chí còn sinh ra một hệ thống webhook quá phức tạp để thông báo thay đổi dữ liệu cho người đăng ký
    Một tệp ZIP duy nhất có thể là quá đơn giản, nhưng cũng không khác mấy so với thứ thực sự cần

    • Nếu dữ liệu dưới 1MB thay đổi một hoặc hai lần mỗi năm, tôi cho rằng API đúng đắn là một web server tĩnh hỗ trợ tốt ETag/If-Modified-Since
      Nếu muốn làm đẹp hơn, có thể thêm webhook được kích hoạt khi tệp thay đổi, để client biết khi nào cần tải lại thay vì polling mỗi ngày một lần
      Hoặc chỉ cần viết một script gửi email đã định sẵn đến mailing list khi có thay đổi là đủ
    • Chỉ cần đặt tệp ZIP trên web server hỗ trợ ETag, rồi polling mỗi khi cần truy cập
      Nếu không có gì khác trước thì nhận phản hồi HTTP 304 rỗng; nếu đã thay đổi thì tải lại tệp ZIP dưới 1MB kèm ETag mới. Tôi không rõ còn thiếu gì ở đây
    • Nếu dữ liệu chỉ thay đổi mỗi năm một lần hoặc hiếm hơn, thì người dùng dữ liệu đó có lẽ cũng hiếm khi dùng API, nên tốc độ có thể không phải vấn đề lớn
      Cache làm tăng độ phức tạp và tạo nguy cơ phải tự tay xác thực lại cache, nên có khả năng solution architect đã đúng
  • Nếu phải tải xuống một tệp 565KB chỉ để lấy một kết quả 2000-10-26 thì đó là một API tệ hại
    Nếu mục tiêu là lấy một lượng lớn dữ liệu rồi cung cấp lại cho người dùng, CSV được gói trong ZIP là rất tuyệt, và tôi thích nó hơn nhiều so với protobuf cho lịch tàu thời gian thực của giao thông công cộng vốn hỗ trợ nhiều ngôn ngữ không tốt
    Nhưng nếu coi nó như một API để lấy một giá trị đơn lẻ thì đó là sự lãng phí khủng khiếp, và tôi hy vọng không ai đưa nó vào app theo cách này
    Bản thân bài viết rất hay, nhưng tiêu đề có cảm giác quá giống một luận điểm khiêu khích

    • Đây là dữ liệu lịch sử
      Hoàn toàn không có lý do gì để yêu cầu quá một lần mỗi ngày, và những người dùng loại dữ liệu này có khả năng muốn các bộ lọc hoặc phép tổng hợp rất khác nhau
      Nếu dùng để lấy tỷ giá hiện tại thì đúng là thiết kế tệ, nhưng cho mục đích đó có các dịch vụ khác, còn tệp này phù hợp với trường hợp sử dụng điển hình
    • Nếu bạn hy vọng không ai đưa thứ như vậy vào app, thì có tin xấu đây
      Không liên quan trực tiếp đến API, nhưng trước đây khi tôi hỗ trợ một ứng dụng quản lý đất đai, trước khi phiên bản mới ra mắt, nó vẫn chạy tốt ngay cả ở các văn phòng vệ tinh có đường truyền chậm, có thể chỉ ngang ISDN; còn phiên bản mới thì hoàn toàn không chạy được
      Nhà cung cấp bảo là hãy chạy trên máy chủ RDP, nhưng tôi thấy quá vô lý nên điều tra, và phát hiện một lệnh gọi nào đó vô cớ thực hiện SELECT * FROM sometable, trong khi các lệnh gọi khác trong cùng lần chạy thì dùng mệnh đề SQL select đúng cách
      Khi nói chuyện này với nhà cung cấp, ban đầu họ rất bối rối không hiểu làm sao chúng tôi biết được, và cuối cùng họ phát hành phiên bản mới được sửa để có thể dùng cả trên đường truyền chậm
      Thật khó hiểu vì sao kiểm thử nội bộ của họ không bắt được lỗi đó mà lại đẩy một giải pháp đắt đỏ cho khách hàng
    • Cảm giác như một lập luận từ 20 năm trước gọi điện lại và đòi trả lập luận của mình
      Nếu dạo này bạn có nhìn qua JavaScript dù chỉ một chút, thì 565KB và logic tìm một giá trị lớn trong đó là rất nhỏ theo bất kỳ tiêu chuẩn hợp lý nào
    • Có vẻ nhiều người trong thread này đang bỏ qua sự khác biệt trong định nghĩa “API”
      Có người xem “cách lấy dữ liệu, dù là nhận toàn bộ dữ liệu không lọc” là API, nhưng cá nhân tôi thấy tải xuống toàn bộ bảng là tải xuống mô hình dữ liệu, nơi logic không hoạt động trên mô hình, còn API là logic lọc và trả về một phần của mô hình theo cách tôi quan tâm
    • Tôi không hiểu vì sao lại giả định 565KB là quan trọng
      Tôi đã làm khá nhiều phần mềm tài chính ở cả backend lẫn frontend, và ở frontend, đáng tiếc là việc truyền chừng đó “dữ liệu” còn trước khi chạm tới dữ liệu thật là chuyện phổ biến
      Ở backend thì đây chỉ là một quyết định thiết kế, và không gì nhanh hơn một cron job chạy hằng đêm để phân tích tỷ giá, tạo todays-rates.json phù hợp mục đích rồi phục vụ nó như tệp tĩnh cho các ứng dụng mobile, web và microservice
      Không có chỗ nào nói rằng ứng dụng mobile nhất thiết phải trực tiếp tiêu thụ ZIP-CSV-over-HTTP này
  • Có một tối ưu hóa rất đơn giản cho những ai phàn nàn rằng mỗi khi cần một mẩu dữ liệu nhỏ lại phải tải một tệp lớn
    Nếu bảo đảm tệp là chỉ được append và dùng nén kiểu HTTP gzip/brotli thay vì tệp ZIP, thì có thể dùng range request để chỉ lấy dữ liệu mới kể từ lần cập nhật cuối
    Thêm một header checksum để yên tâm nữa là có một API tăng dần khá hiệu quả mà vẫn rất đơn giản
    Tất nhiên bạn phải lưu trạng thái, chịu chi phí tải lần đầu và duy trì trạng thái, và nếu chỉ cần đúng một lần tỷ giá EUR/JPY ngày 2007-08-22 thì cách này không hiệu quả

    • Đúng vậy. Tôi đang phác thảo một thư viện client dùng ETag và các kỹ thuật khác để làm đúng việc đó
      Vẫn còn rất đang trong quá trình thực hiện, nhưng mã hiện ở mức “chất lượng nghiên cứu” có ở đây: https://pypi.org/project/csvbase-client/
    • Nói thêm về range request, nếu máy chủ cho phép range request với tệp ZIP, và tệp ZIP rất lớn nhưng bạn chỉ cần vài tệp bên trong, thì có thể không cần tải toàn bộ ZIP mà chỉ tải central directory và dữ liệu nén của các tệp cần thiết
      https://github.com/gtsystem/python-remotezip
    • Hoặc đơn giản là cung cấp nhiều tệp diff
      Chỉ cần một bản vá theo ngày cũng có thể giảm mạnh băng thông cần thiết để tôi giữ tệp phía mình luôn cập nhật
      Đó là trong trường hợp việc tải thêm vài trăm KB mỗi ngày có ý nghĩa, mà phần lớn có lẽ là không
  • Ví dụ sqlite có lỗi gõ nhầm
    Trong ảnh chụp màn hình không có, nhưng cần thêm đối số -csv cho sqlite

    • Lạ thật. Ban đầu tôi có đưa đối số đó vào, rồi thấy có vẻ không cần nên bỏ ra, và trên môi trường của tôi vẫn chạy
      Tôi sẽ thêm lại và vô hiệu hóa cache. Sau khi cho bọn trẻ đi ngủ tôi sẽ kiểm tra xem có gì sai
      Sửa: Lý do nó chạy trên môi trường của tôi là vì trong ~/.sqliterc có thiết lập .separator ','
      Có vẻ trước đây tôi nhận ra mình chủ yếu nạp tệp CSV nên đã đặt nó làm mặc định
  • Rẽ ngang một chút, dù ban đầu euro chỉ tồn tại dưới dạng điện tử, nó vẫn có tỷ giá cố định với các đồng tiền hiện có của các nước thành viên Eurozone
    Đặc biệt là nó được cố định với Deutsche Mark của Đức, một đồng tiền đã được thiết lập và đáng tin cậy
    Vì vậy, để giải thích “vì sao euro thời kỳ đầu yếu”, cũng cần giải thích vì sao DEM khi đó yếu, và phần giải thích trong đoạn đó có vẻ không vượt qua được phép kiểm tra này

  • Với các bài toán nhỏ nơi có thể tải xuống toàn bộ cơ sở dữ liệu mỗi lần và xử lý ở chế độ chỉ đọc, không nên đánh giá thấp giá trị của sự đơn giản
    Tôi thích SQLite vì nó có tính di động như tệp .json hay .csv, nhưng lại sẵn sàng hơn nhiều để tương tác như một cơ sở dữ liệu

    • Dùng clickhouse-local thì cũng có thể xử lý các tệp CSV cũ như cơ sở dữ liệu
  • Điểm mấu chốt nằm ở đây
    Những việc không phải làm trong trường hợp này: thương lượng quyền truy cập, chẳng hạn như trả tiền hoặc nói chuyện với nhân viên bán hàng; đưa địa chỉ email, tên công ty, chức danh của bạn vào cơ sở dữ liệu khách hàng tiềm năng của ai đó; tuân thủ quota; xác thực; đọc tài liệu API; xử lý các vấn đề nghiêm trọng hơn định dạng và cấu trúc cơ bản

    • Tôi hoài nghi về phần không có “tuân thủ quota”
      Băng thông không miễn phí
  • SQLite có thể đọc và ghi tệp ZIP
    https://sqlite.org/zipfile.html
    Tôi thắc mắc liệu có thể giải nén bằng sqlite3 thay vì gunzip không

    • Điểm hay đấy
      Nếu có thể lưu tệp xuống đĩa thì có thể làm như sau:

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

Làm mà không dùng tệp tạm thì khá rắc rối. Ví dụ `readfile('/dev/stdin')` không hoạt động vì SQLite cố dùng `seek()`  
Có thể dùng một cách rất xấu là đổi tệp ZIP sang dạng hex bằng `xxd` rồi đưa vào literal chuỗi của truy vấn 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 ...;"`