我们一直在命令行使用grep、cut、sort、uniq、join来进行数据分析。 尽管存在缺点,但它们工作得很好。 例如,您必须为每个工具指定列号。 我们经常有宽文件(许多列)和给出列名称的列标题。 事实上,我们的文件看起来很像 SQL 表。 我确信有一个驱动程序(ODBC?)可以对分隔文本文件进行操作,并且有一些查询引擎将使用该驱动程序,因此我们可以在文本文件上使用 SQL 查询。 由于进行分析通常是临时的,因此必须进行最少的设置来查询新文件(只需使用我在此目录中指定的文件),而不是在某些配置中声明特定的表。
实际上来说,什么是最简单的? 也就是说,最容易设置和用于应用于文本文件的 SQL 引擎和驱动程序?
q - 直接在 CSV 或 TSV 文件上运行 SQL:
David Malcolm 编写了一个名为“squeal”(以前称为“show”)的小工具,它允许您使用类似 SQL 的命令行语法来解析各种格式的文本文件,包括 CSV。
squeal 主页上的示例:
$ squeal "count(*)", source from /var/log/messages* group by source order by "count(*)" desc
count(*)|source |
--------+--------------------+
1633 |kernel |
1324 |NetworkManager |
98 |ntpd |
70 |avahi-daemon |
63 |dhclient |
48 |setroubleshoot |
39 |dnsmasq |
29 |nm-system-settings |
27 |bluetoothd |
14 |/usr/sbin/gpm |
13 |acpid |
10 |init |
9 |pcscd |
9 |pulseaudio |
6 |gnome-keyring-ask |
6 |gnome-keyring-daemon|
6 |gnome-session |
6 |rsyslogd |
5 |rpc.statd |
4 |vpnc |
3 |gdm-session-worker |
2 |auditd |
2 |console-kit-daemon |
2 |libvirtd |
2 |rpcbind |
1 |nm-dispatcher.action|
1 |restorecond |
借鉴别人的建议,这里是 sqlite3 的 Python 脚本。 有点冗长,但它有效。
我不喜欢必须完全复制文件才能删除标题行,但我不知道如何说服 sqlite3 的 .import 跳过它。 我可以创建 INSERT 语句,但这看起来同样糟糕,甚至更糟。
调用示例:
$ sql.py --file foo --sql "从数据中选择 count(*)"
代码:
#!/usr/bin/env python
"""Run a SQL statement on a text file"""
import os
import sys
import getopt
import tempfile
import re
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def runCmd(cmd):
if os.system(cmd):
print "Error running " + cmd
sys.exit(1)
# TODO(dan): Return actual exit code
def usage():
print >>sys.stderr, "Usage: sql.py --file file --sql sql"
def main(argv=None):
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "h",
["help", "file=", "sql="])
except getopt.error, msg:
raise Usage(msg)
except Usage, err:
print >>sys.stderr, err.msg
print >>sys.stderr, "for help use --help"
return 2
filename = None
sql = None
for o, a in opts:
if o in ("-h", "--help"):
usage()
return 0
elif o in ("--file"):
filename = a
elif o in ("--sql"):
sql = a
else:
print "Found unexpected option " + o
if not filename:
print >>sys.stderr, "Must give --file"
sys.exit(1)
if not sql:
print >>sys.stderr, "Must give --sql"
sys.exit(1)
# Get the first line of the file to make a CREATE statement
#
# Copy the rest of the lines into a new file (datafile) so that
# sqlite3 can import data without header. If sqlite3 could skip
# the first line with .import, this copy would be unnecessary.
foo = open(filename)
datafile = tempfile.NamedTemporaryFile()
first = True
for line in foo.readlines():
if first:
headers = line.rstrip().split()
first = False
else:
print >>datafile, line,
datafile.flush()
#print datafile.name
#runCmd("cat %s" % datafile.name)
# Create columns with NUMERIC affinity so that if they are numbers,
# SQL queries will treat them as such.
create_statement = "CREATE TABLE data (" + ",".join(
map(lambda x: "`%s` NUMERIC" % x, headers)) + ");"
cmdfile = tempfile.NamedTemporaryFile()
#print cmdfile.name
print >>cmdfile,create_statement
print >>cmdfile,".separator ' '"
print >>cmdfile,".import '" + datafile.name + "' data"
print >>cmdfile, sql + ";"
cmdfile.flush()
#runCmd("cat %s" % cmdfile.name)
runCmd("cat %s | sqlite3" % cmdfile.name)
if __name__ == "__main__":
sys.exit(main())
也许编写一个脚本来创建 SQLite 实例(可能在内存中),从文件/stdin 导入数据(接受数据格式),运行查询,然后退出?
根据数据量,性能可以接受。
MySQL 有一个 CSV 存储引擎,如果您的文件是 CSV 文件,它可能会满足您的需要。
否则,您可以使用 mysqlimport 将文本文件导入 MySQL。您可以围绕 mysqlimport 创建一个包装器,它计算出列等并创建必要的表。
您也许还可以使用 DBD::AnyData,这是一个 Perl 模块,可让您像数据库一样访问文本文件。
也就是说,听起来你真的应该考虑使用数据库。在文本文件中保存面向表的数据真的更容易吗?
我已经使用 Microsoft LogParser 多次查询 csv 文件......它达到了目的。很惊讶地看到 M$ 提供了如此有用的工具,而且还是免费的!