q - 直接在CSV或TSV文件上运行SQL

GitHub Stars GitHub forks

概述

q 是一个可以运行在 CSV / TSV 文件(或其他表格式的文本文件)上运行类SQL命令的命令行工具。

q 将普通文本(如上述)作为数据库表,且支持所有的SQL语法如:WHERE、GROUP BY、各种JOIN等。此外,还拥有自动识别列名和列类型及广泛支持多种编码的特性。

q "SELECT COUNT(*) FROM ./clicks_file.csv WHERE c3 > 32.3"
ps -ef | q -H "SELECT UID,COUNT(*) cnt FROM - GROUP BY UID ORDER BY cnt DESC LIMIT 3"

查看示例安装体验.

完全支持所有的字符编码 すべての文字エンコーディングを完全にサポート
모든 문자 인코딩이 완벽하게 지원됩니다 все кодировки символов полностью поддерживаются

非英语用户: q 完全支持所有类型的字符编码。 使用 -e data-encoding 设置输入编码; 使用 -Q query-encoding 设置查询编码; 使用 -E output-encoding 设置输出编码; 如上三个参数均设有合理的默认值。

如果遇到问题请与我联系,期待与你交流。

含有BOM的文件: python的csv模块并不能很好的支持含有Byte Order Mark 的文件。针对该种情况,使用 -e utf-8-sig 命令参数可读取包含BOM的UTF8编码文件。

我们计划将BOM相关处理与编码'解耦', 这样就可以支持所有编码的BOM文件了。

安装

格式 说明 备注
OSX 运行 brew install q 该方式暂不支持MAN手册, 可以使用 q --help 查看帮助
RPM Package 运行 rpm -ivh <package-filename> 如果安装过旧版则运行 rpm -U <package-filename> 该方式支持MAN手册,可运行man q查看
DEB Package 运行 sudo dpkg -i <package-filename> 该方式支持MAN手册,可运行man q查看
Windows Installer 运行安装可执行文件,一直点击下一步、下一步... q.exe 将被添加至PATH,以便于随处运行 PATH更新后并不会即时生效,重新打开cmd命令窗口便可
tar.gz 最新稳定版的所有源码文件。提示,q.py 文件不能直接使用,因为它需要python依赖
zip 最新稳定版的所有源码文件。提示,q.py 文件不能直接使用,因为它需要python依赖

旧版本可以在这儿下载 。按理说不会有人愿意用旧版本,要是你计划使用旧版,希望能与你交流。

须知

2.0.9版本开始,不需要任何外部依赖。Python(3.7)和其他所需的库包含在了安装文件中且与系统隔离。

使用

q <flags> "<query>"

  最简单的执行语句:q "SELECT * FROM myfile" 该语句会输出文件内容

q 支持在表格式的文本上执行类SQL命令。它的初衷是为Linux命令行附加SQL的表达力且实现对文本数据的轻松访问。

类SQL的查询将文件名(或标准输入流)看作表名。查询语句会作为命令输入的一个参数(使用引号包裹),如果将多个文件看作一张表,可以这样写 文件名1+文件名2....或者使用通配符(比如:my_files*.csv)。

使用 -H 表示输入内容中包含表头。该情况下列名会被自动识别,如果没有指定该参数,列名将会被以cX命名,X从1开始(比如: q "SELECT c3,c8 from ...") 。

使用 -d 声明输入的分隔符。

列类型可由工具自动识别,无需强制转换。 提示,使用--as-text 可以强制将所有列类型转换为文本类型。

依据sqlite规范,如果列名中含有空格,需要使用反引号 (即:`) 引起来。

完全支持查询/输入/输出的编码设置(q 力争提供一种开箱即用的方法), 可以分别使用-Q,-e-E来指定编码设置类型。

支持所有的sqlite3 SQL方法,包括文件之间的 JOIN(可以为文件设置别名)操作。在下面的限制小节可以看到一些少有使用的、欠支持的说明。

查询

q 的每一个参数都是由双引号包裹的一条完整的SQL语句。所有的查询语句会依次执行,最终结果以标准输出流形式输出。 提示,在同一命令行中执行多条查询语句时,仅在执行第一条查询语句时需要耗时载入数据,其他查询语句即时执行。

支持所有标准SQL语法,条件(WHERE 和 HAVING)、GROUP BY、ORDER BY等。

在WHERE条件查询中,支持JOIN操作和子查询,但在FROM子句中并不支持。JOIN操作时,可以为文件起别名。

SQL语法同sqlite的语法,详情见 http://www.sqlite.org/lang.html 或上网找一些示例。

注意

  • 支持所有类型的自动识别,无需强制转换或其他操作。

  • 如果重命名输出列,则需要为列指定别名并使用 -O 声明。如: q -O -H "select count(*) cnt,sum(*) as mysum from -" 便会将cntmysum作为列名输出。

指令

使用:
        q 支持在表格式的文本数据上执行类SQL查询。

        它的初衷是为Linux命令行附加SQL的表达力且实现对文本数据的轻松访问。

        基本操作是 q "SQL查询语句" 表名便是文件名(使用 - 从标注输入中读取数据)。若输入内容包含表头时,可以使用 -H 指定列名。若无表头,则列将会自动命名为 c1...cN。

        列类型可被自动识别。可以使用 -A 命令查看每列的名称及其类型。

        可以使用 -d (或 -t) 指定分隔符,使用 -D 指定输出分割符。

        支持所有的sqlite3 SQL方法。

        示例:

          例子1: ls -ltrd * | q "select c1,count(1) from - group by c1" 
          上例将会输出当前目录下,所有文件的权限表达式分组及每组数量。

          例子2: seq 1 1000 | q "select avg(c1),sum(c1) from -" 
          上例将会输出1到1000的平均数与和数。

          例子3: sudo find /tmp -ls | q "select c5,c6,sum(c7)/1024.0/1024 as total from - group by c5,c6 order by total desc" 
          上例将会输出在/tmp目录下,相同'用户+组'的文件所占用的MB磁盘空间。

          更多详情见 https://github.com/harelba/q/ 或查看帮助

选项:
  -h, --help            显示此帮助信息并退出 
  -v, --version         显示版本号
  -V, --verbose         出现问题时显示调试信息
  -S SAVE_DB_TO_DISK_FILENAME, --save-db-to-disk=SAVE_DB_TO_DISK_FILENAME
                        将数据库保存为一个 sqlite 数据库文件
  --save-db-to-disk-method=SAVE_DB_TO_DISK_METHOD
                        保存数据库到磁盘的方法
                        'standard' 不需要任何设置
                        'fast'需要手动在python的安装目录下执行`pip install sqlitebck`
                        打包的问题解决后,'fast'即被作为默认方式
  数据相关的选项:

    -H, --skip-header   忽略表头,在早期的版本中已修改为:仅支持用于标明列名的一行表头
    -d DELIMITER, --delimiter=DELIMITER
                        列分隔符,若无特别指定,默认为空格符
    -p, --pipe-delimited
                        作用同 -d '|',为了方便和可读性提供该参数
    -t, --tab-delimited
                        作用同 -d <tab>,这仅是一种简写,也可以在Linux命令行中使用$'\t'
    -e ENCODING, --encoding=ENCODING
                        输入文件的编码,默认是UTF-8
    -z, --gzipped       压缩数据,对于从输入流读取文件非常高效 .gz 是自动压缩后文件扩展名
    -A, --analyze-only  简单分析:各列的数据类型
    -m MODE, --mode=MODE
                        数据解析模式: 松散, 宽松和严格。在严格模式下必须指定 -c 
                        --column-count 参数。
    -c COLUMN_COUNT, --column-count=COLUMN_COUNT
                        当使用宽松或严格模式时,用于指定列的数量
    -k, --keep-leading-whitespace
                        保留每列前的空格。为了使其开箱即用,默认去除了列前的空格
                        如果有需要,可以指定该参数
    --disable-double-double-quoting
                        禁止一对双引号的转义。默认可以使用 "" 转义双引号
                        主要为了向后兼容
    --disable-escaped-double-quoting
                        禁止转义双引号
                        默认可以在双引号字段中使用 \" 进行转义
                        主要为了向后兼容 
    --as-text           不识别列类型(所有列被当作文本类型)
    -w INPUT_QUOTING_MODE, --input-quoting-mode=INPUT_QUOTING_MODE
                        输入内容的转义模式,可选值 all、minimal、none
                        该参数稍有误导性,-W 指定输出内容的转义模式 
    -M MAX_COLUMN_LENGTH_LIMIT, --max-column-length-limit=MAX_COLUMN_LENGTH_LIMIT
                        设置列的最大长度
    -U, --with-universal-newlines
                        设置通用换行符
                        -U 参数当前仅适用于常规文件,输入流或.gz类文件暂不支持

  输出相关的选项:
    -D OUTPUT_DELIMITER, --output-delimiter=OUTPUT_DELIMITER
                        输出列间的分隔符
                        若未指定,则与 -d 指定的分隔符相同;若均为指定,则默认为空格符
    -P, --pipe-delimited-output
                        同 -D '|' 为了方便和可读性提供该参数
    -T, --tab-delimited-output
                        同 -D <tab> 这仅是一种简写,也可以在Linux命令行中使用$'\t' 
    -O, --output-header
                        输出表头,输出的列名是由查询中指定的别名
                        如: 'select name FirstName, value1/value2 MyCalculation
                        from ...' 即使输入时未指定表头仍可使用该参数。
    -b, --beautify      美化输出结果,可能较慢...
    -f FORMATTING, --formatting=FORMATTING
                        格式化输出列
                        如格式X=fmt,Y=fmt等,上述中的X、Y是指第几列(如:1 表示 SELECT 
                        的第一列)
    -E OUTPUT_ENCODING, --output-encoding=OUTPUT_ENCODING
                        输出内容的编码,默认是 'none',跟随系统或终端的编码
    -W OUTPUT_QUOTING_MODE, --output-quoting-mode=OUTPUT_QUOTING_MODE
                        输出内容的转义模式,可选值 all、minimal、none
                        该参数稍有误导性,-w 指定输入内容的转义模式 
    -L, --list-user-functions
                        列出所有内置函数

  查询相关的参数:
    -q QUERY_FILENAME, --query-filename=QUERY_FILENAME
                        指定文件名,由文件中读取查询语句。
                        该操作常与查询编码(使用 -Q)一同使用
    -Q QUERY_ENCODING, --query-encoding=QUERY_ENCODING
                        查询编码(包含查询语句的文件编码)
                        实验性参数,对该参数的意见可反馈

示例

下述 -H 参数的例子,表示文件中含有表头时使用该参数。

-t 参数是指定文件以 tab 作为分隔符的缩写(可以使用 -d 参数指定任意分隔符)。

为了清楚起见,查询关键字均使用大写,实际上关键字(如 SELECT、WHERE等)对大小写并不敏感。

示例目录:

例1

对指定字段(点击数据中的uuid)执行 COUNT DISTINCT

q -H -t "SELECT COUNT(DISTINCT(uuid)) FROM ./clicks.csv"

输出:

229

例2

过滤数值数据、排序并限制输出数量

注意:q 将其看作数值类型并对其进行数值过滤(数值比较而不是字符串比较)

q -H -t "SELECT request_id,score FROM ./clicks.csv WHERE score > 0.7 ORDER BY score DESC LIMIT 5"

输出:

2cfab5ceca922a1a2179dc4687a3b26e    1.0
f6de737b5aa2c46a3db3208413a54d64    0.986665809568
766025d25479b95a224bd614141feee5    0.977105183282
2c09058a1b82c6dbcf9dc463e73eddd2    0.703255121794

例3

GROUP BY 简单示例

q -t -H "SELECT hashed_source_machine,count(*) FROM ./clicks.csv GROUP BY hashed_source_machine"

输出:

47d9087db433b9ba.domain.com 400000

例4

GROUP BY进阶示例 (以时间格式分组)

q -t -H "SELECT strftime('%H:%M',date_time) hour_and_minute,count(*) FROM ./clicks.csv GROUP BY hour_and_minute"

输出:

07:00   138148
07:01   140026
07:02   121826

例5

标准输入流作为输入

计算 /tmp 目录下各 user/group 的占用空间大小

sudo find /tmp -ls | q "SELECT c5,c6,sum(c7)/1024.0/1024 AS total FROM - GROUP BY c5,c6 ORDER BY total desc"

输出:

mapred hadoop   304.00390625
root   root     8.0431451797485
smith  smith    4.34389972687

例6

使用表头中列名

计算拥有进程数最多的前3位用户名及其数量

注意: 该查询中自动识别了列名

ps -ef | q -H "SELECT UID,COUNT(*) cnt FROM - GROUP BY UID ORDER BY cnt DESC LIMIT 3"

输出:

root 152
harel 119
avahi 2

例7

JOIN 两个文件

如下命令中JOIN一个ls命令输出内容文件(exampledatafile) 和一个包含group_name、email两列字段的文件(group-emails-example),每一邮件组均包含filename、email列, 为了输出简便,使用WHERE条件过滤出名为 ppp 的文件

q "SELECT myfiles.c8,emails.c2 FROM exampledatafile myfiles JOIN group-emails-example emails ON (myfiles.c4 = emails.c1) WHERE myfiles.c8 = 'ppp'"

输出:

ppp dip.1@otherdomain.com
ppp dip.2@otherdomain.com

可以看出 ppp 文件出现了两次,每次都匹配到了它所属的dip邮件组(如例中 dip.1@... / dip2@...),可以在 exampledatafilegroup-emails-example 文件中查看数据。

JOIN 的应用场景中也支持列名识别,在查询包含表头的文件时,只需指定 -H 参数即可。

声明

为了避免引用外部依赖,当前是使用由Python编写的内存数据库实现的。当前是支持 SELECT 语句及 各种JOIN ( 目前仅在 WHERE 语句中支持子查询)。 若想对数据进一步分析,可以使用 --save-db-to-disk 参数,以将结果输出为 sqlite 数据库文件,然后使用 sqlite3 语句来执行查询操作。

需要提示的是,当前并没有对数据量的大小进行检测和限制 - 也就是说,需要用户自己掌控文件大小。

请务必阅读限制小节。

开发

测试

源码中包含了测试用例,可以通过 test/test-all 来执行。若想要提交 PR的话,一定先确保其均执行成功。

限制

如下罗列了一些已知的限制,若你的使用场景中需要用到以下标明的限制,请联系我。

  • 不支持 FROM <subquery>
  • 不支持公用表表达式(CTE)
  • 不支持文件名中包含空格 (可以将文件以标准输入流的方式输入 q 或重命名文件)
  • 不支持较少用到的子查询

原理

你是否曾经盯着屏幕上的文本文件发呆,希望它要是数据库就好了,这样就可以找出自己想要的内容?我曾有过很多次,最终顿悟。我想要的不是数据库,而是 SQL。

SQL 是一种面向数据声明的语言,它允许自定义数据内容而无需关心其执行过程。这也正是SQL强大之处,因为它对于数据'所见即所得',而不是将数据看作字节码。

本工具的目的是:在文本文件和SQL之间搭建一座桥梁。

为什么其他Linux工具不能满足需求?

传统的Linux工具库也很酷,我也经常使用它们, 但Linux的整体理念是为任一部分搭配最好的工具。本工具为传统Linux工具集新添了 SQL 族类工具,其他工具并不会失去本来优势。 事实上,我也经常将 q 和其他Linux工具搭配使用,就如同使用管道将 awk/sed 和 grep 搭配使用一样。

另外需要注意的是,许多Linux工具就将文本看作文本,而不是数据。从这个意义上来讲,可以将 q 看作提供了 SQL 功能(如:表达式、排序、分组、聚合等)的元工具。

理念

本工具的设计遵从了 Linux/Unix 的传统设计原则。若你对这些设计原则感兴趣,可以阅读 这本书 ,尤其是书中 这部分 若你认为本工具工作方式与之背道而驰,愿洗耳恭听你的建议。

展望

  • 主要方向:将其作为python的模块公开。 在公开之前,需要对处理标准输入流做一些内部API的完善。
  • 支持分布式以提高算力。