我有一个 ~280GB SQLite DB,其中包含公司财务信息,我正在对其运行各种查询。是的,它就是 SQLite,因为它运行良好并且使用起来更简单。 我也有这个数据库的 MySQL 版本,但如果可以的话我宁愿避免使用它。
有一个名为
ItemValues
的表,有大约十亿行。下面两个表中显示的每一列都有一个在数据库构建作业时构建的索引(过夜)
公司ID | 项目类型ID | 数值 | 日期纪元 |
---|---|---|---|
AB1234 | 1 | 100 | 1569884400 |
AB1234 | 2 | 200 | 1569884400 |
G17895 | 7 | 50 | 1632956400 |
ItemTypeId
是一个外键,它引用的表称为ItemTypes
,看起来像这样
身份证 | 项目简短描述 |
---|---|
1 | 成本 |
2 | 收入 |
3 | 其他一些财务指标1 |
4 | 其他一些财务指标2 |
我可以非常轻松地查找具有特定
Cost
或 Revenue
的公司,并且搜索只需几毫秒即可返回结果。
当我必须查找满足特定
Cost
AND Revenue
标准的公司时,问题就出现了。虽然从技术上讲,我可以查找具有特定 Cost
的所有公司,然后查找具有特定 Revenue
的所有公司,然后将两者结合起来,仅选择满足我的标准的公司子集,这种方法会返回大量数据,如下所示有数百万行与 Cost
匹配(相对而言,在 Revenue
的许多行中找不到 ItemValues
)。
我可以采取哪些选择来解决这一挑战?目前我只搜索成本和收入,我想我可以将这两个值添加到每个
ItemValues
,但我需要搜索的字段可能会增加。我想知道我的表设计是否需要重新考虑,或者也许我应该研究面向列的数据库,但我相信我应该首先尝试针对现有数据库结构优化查询。
SQL Fiddle 内容:
CREATE TABLE ItemTypes (
Id INTEGER PRIMARY KEY,
ItemShortDescription TEXT
);
CREATE INDEX idx_ItemTypes_ItemShortDescription ON ItemTypes (ItemShortDescription);
INSERT INTO ItemTypes (Id, ItemShortDescription) VALUES (1, 'Cost');
INSERT INTO ItemTypes (Id, ItemShortDescription) VALUES (2, 'Revenue');
INSERT INTO ItemTypes (Id, ItemShortDescription) VALUES (3, 'SomeOtherFinancialMetric1');
INSERT INTO ItemTypes (Id, ItemShortDescription) VALUES (4, 'SomeOtherFinancialMetric2');
CREATE TABLE ItemValues (
CompanyId TEXT,
ItemTypeId INTEGER,
NumericValue INTEGER,
DateEpoch INTEGER
);
CREATE INDEX idx_ItemValues_CompanyId ON ItemValues (CompanyId);
CREATE INDEX idx_ItemValues_ItemTypeId ON ItemValues (ItemTypeId);
CREATE INDEX idx_ItemValues_NumericValue ON ItemValues (NumericValue);
CREATE INDEX idx_ItemValues_DateEpoch ON ItemValues (DateEpoch);
INSERT INTO ItemValues (CompanyId, ItemTypeId, NumericValue, DateEpoch) VALUES ('AB1234', 1, 100, 1569884400);
INSERT INTO ItemValues (CompanyId, ItemTypeId, NumericValue, DateEpoch) VALUES ('AB1234', 2, 200, 1569884400);
INSERT INTO ItemValues (CompanyId, ItemTypeId, NumericValue, DateEpoch) VALUES ('G17895', 7, 50, 1632956400);
WITH salesIdsCTE AS (
SELECT Id
FROM ItemTypes
WHERE ItemShortDescription = 'Cost' Or ItemShortDescription = 'Revenue'
),
filteredReportItems AS (
SELECT *
FROM ItemValues
WHERE ItemTypeId IN (SELECT Id FROM salesIdsCTE)
AND NumericValue > 5
)
SELECT *
FROM filteredReportItems
LIMIT 5;
当然,在表中查找特定行(例如,所有匹配
ItemTypeId = 1 and NumericValue > 5
的行)比选择两个这样的(可能很大)数据集然后连接它们要快得多。
您说您想要匹配成本和收入条件的行,但您的查询正在查找匹配成本或收入条件的行。
一个正确的查询是:
SELECT companyid FROM itemvalues WHERE itemtypeid = 1 AND numericvalue > 5
INTERSECT
SELECT companyid FROM itemvalues WHERE itemtypeid = 2 AND numericvalue > 5;
最合适的索引是
CREATE INDEX idx ON itemvalues(numericvalue, itemtypeid, companyid);
如果请求的值非常高并且仅适用于少量的表行,则此索引特别有用。因此,DBMS 可以立即排除大部分行,只运行索引的一小部分。该表本身不必被读取,因为所有信息都在索引中可用。 (这称为覆盖索引)。
编写查询的另一种方法是:
SELECT companyid
FROM itemvalues
WHERE itemtypeid IN (1,2)
GROUP BY companyid
HAVING MIN(numericvalue) > 5;
您可能需要调整
HAVING
子句。如果公司可能缺少类型 1 或 2,则必须添加 AND COUNT(*) = 2
或类似内容以确保这两个值均存在且大于 5。如果您正在寻找不同的值,则可能需要条件聚合,例如HAVING MIN(numericvalue) FILTER (WHERE itemtypeid = 1) > 5 AND MIN(numericvalue) FILTER (WHERE itemtypeid = 2) > 10
。
对于此查询,索引应以 ItemTypeId 开头,因为这是我们首先过滤行的依据。不过,由于存在的类型很少,我们可以假设大部分行都受到
itemtypeid IN (1,2)
的影响。 (甚至可能是表的一半。)因此,该索引仅在覆盖索引时才有意义,因此不得读取该表,如前所述:
CREATE INDEX idx ON itemvalues(itemtypeid, numericvalue, companyid);
最后:是的,另一种表设计会更适合这种查询。对于不同类型的单独列,您将再次只寻找匹配条件的单行:
CREATE TABLE company (
companyid INTEGER PRIMARY KEY,
companyname TEXT,
cost INTEGER,
cost_dateepoch INTEGER
revenue INTEGER,
revenue_dateepoch INTEGER
);
SELECT * FROM company WHERE cost > 5 AND revenue > 5;
不需要索引,因为顺序读取整个表是最快的方法。但您缺乏灵活性,无法随时添加新指标,甚至无法让用户出于自己的目的添加新指标。