我编写了一个Ruby脚本来执行以下操作:
在我看来,这似乎是最简单,最合理的方式。这个过程需要可以配置并定期重复,因此脚本。我使用的是SQLite,因为数据总是以CSV格式存在(无法访问原始数据库),并且可以更轻松地将处理卸载到(容易更改的)SQL语句中。
问题在于步骤1和2需要很长时间。我已经找到了improve the performance of SQLite的方法。我已经实施了其中一些建议,但收效甚微。
PRAGMA synchronous = OFF
PRAGMA journal_mode = MEMORY
(不确定使用内存数据库时是否有帮助)完成所有这些后,我得到以下时间:
当然,我使用了与上述帖子不同的语言,并且存在编辑/解释等差异,但插入时间约为79,000对比12,000记录/秒 - 这个速度慢6倍。
我也试过索引一些(或所有)字段。这实际上具有相反的效果。索引花费的时间太长,以至于查询时间的任何改进都完全被索引时间所掩盖。此外,由于需要额外的空间,执行内存数据库最终会导致内存不足错误。
SQLite3不是这个数据量的正确数据库吗?我使用MySQL尝试过相同的功能,但其性能更差。
最后,这是一个严格的代码版本(删除了一些不相关的细节)。
require 'csv'
require 'sqlite3'
inputFile = ARGV[0]
outputFile = ARGV[1]
criteria1 = ARGV[2]
criteria2 = ARGV[3]
criteria3 = ARGV[4]
begin
memDb = SQLite3::Database.new ":memory:"
memDb.execute "PRAGMA synchronous = OFF"
memDb.execute "PRAGMA journal_mode = MEMORY"
memDb.execute "DROP TABLE IF EXISTS Area"
memDb.execute "CREATE TABLE IF NOT EXISTS Area (StreetName TEXT, StreetType TEXT, Locality TEXT, State TEXT, PostCode INTEGER, Criteria1 REAL, Criteria2 REAL, Criteria3 REAL)"
insertStmt = memDb.prepare "INSERT INTO Area VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"
# Read values from file
readCounter = 0
memDb.execute "BEGIN TRANSACTION"
blockReadTime = Time.now
CSV.foreach(inputFile) { |line|
readCounter += 1
break if readCounter > 100000
if readCounter % 10000 == 0
formattedReadCounter = readCounter.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
print "\rReading line #{formattedReadCounter} (#{Time.now - blockReadTime}s) "
STDOUT.flush
blockReadTime = Time.now
end
insertStmt.execute (line[6]||"").gsub("'", "''"), (line[7]||"").gsub("'", "''"), (line[9]||"").gsub("'", "''"), line[10], line[11], line[12], line[13], line[14]
}
memDb.execute "END TRANSACTION"
insertStmt.close
# Process values
sqlQuery = <<eos
SELECT DISTINCT
'*',
'*',
Locality,
State,
PostCode
FROM
Area
GROUP BY
Locality,
State,
PostCode
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
UNION
SELECT DISTINCT
StreetName,
StreetType,
Locality,
State,
PostCode
FROM
Area
WHERE
Locality NOT IN (
SELECT
Locality
FROM
Area
GROUP BY
Locality
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
)
GROUP BY
StreetName,
StreetType,
Locality,
State,
PostCode
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
eos
statement = memDb.prepare sqlQuery
# Output to CSV
csvFile = CSV.open(outputFile, "wb")
resultSet = statement.execute
resultSet.each { |row| csvFile << row}
csvFile.close
rescue SQLite3::Exception => ex
puts "Excepion occurred: #{ex}"
ensure
statement.close if statement
memDb.close if memDb
end
请随意嘲笑我天真的Ruby编码 - 不要杀我的东西会让我成为一个更强大的编码器。
答案 0 :(得分:1)
通常,如果可能,您应该尝试UNION ALL
而不是UNION
,这样就不必检查两个子查询是否有重复项。
但是,在这种情况下,SQLite必须在单独的步骤中执行DISTINCT
。这是否更快取决于您的数据。
根据我的EXPLAIN QUERY PLAN
实验,以下两个索引应该对此查询有所帮助:
CREATE INDEX i1 ON Area(Locality, State, PostCode);
CREATE INDEX i2 ON Area(StreetName, StreetType, Locality, State, PostCode);