2011-02-12 64 views
0

我收集关于我们的代码库的一些基本的统计数据,我试图使用下面的架构数据嵌套子查询太慢 - 外连接等效?

生成查询
  1. 一个文件表保存的所有文件(合成主键ID,唯一的路径和区域列持有该文件属于谁。
  2. 为在特定日期的文件file_stats表保存数据(主键是日期和file_id的组合)

CREATE TABLE files (
id INT PRIMARY KEY, 
path VARCHAR(255) NOT NULL UNIQUE, 
region VARCHAR(4) CHECK (region IN ('NYK', 'LDN', 'CORE', 'TKY')), 
) 

CREATE TABLE file_stats (
date DATE NOT NULL, 
file_id INT NOT NULL REFERENCES files, 
num_lines INT NOT NULL, 

CONSTRAINT file_stats__pk PRIMARY KEY(date, file_id) 
) 

我想创建一个查询,它将返回表中的日期和区域的所有组合以及该组合的文件数。

SELECT date, region, COUNT(*) FROM file_stats fs, files f WHERE fs.file_id = f.id 
GROUP BY date, region 

简单的方法是行不通的,因为不是所有的区域在所有日期represnted。 我试过

SELECT 
d.date, 
r.region, 
(SELECT COUNT(*) FROM file_stats fs, files f 
WHERE fs.file_id = file.id AND fs.date = d.date AND d.region = r.region 
) AS num_files 
FROM 
(SELECT DISTINCT date FROM file_stats) AS d, 
(SELECT DiSTINCT region FROM files) AS r 

但性能是因为嵌套子查询的是不可接受的。

我试过LEFT OUTER JOINS,但似乎没有能够让它们工作。 该数据库是SQLITE

任何人都可以提出更好的查询吗?

回答

0
SELECT date, region, COUNT(*) FROM file_stats fs, files f WHERE fs.file_id = f.id 
GROUP BY date, region 

不会因为工作并不是所有的地区都在 所有日期represnted。

假设你的意思是它能正常工作,但你需要所有日期来显示一个区域是否可以出现在那里,那么你需要两件事。

  1. 日历表。
  2. 日历表上的左连接。

有了日历表之后,就像这样。 。 。

SELECT c.cal_date, f.region, COUNT(*) 
FROM calendar c 
LEFT JOIN file_stats fs ON (fs.date = c.cal_date) 
INNER JOIN files f ON (fs.file_id = f.id) 
GROUP BY date, region 

我上面使用了cal_date。您使用的名称取决于您的日历表。这会让你开始。您可以使用电子表格来生成日期。

CREATE TABLE calendar (cal_date date primary key); 
INSERT INTO "calendar" VALUES('2011-01-01'); 
INSERT INTO "calendar" VALUES('2011-01-02'); 
INSERT INTO "calendar" VALUES('2011-01-03'); 
INSERT INTO "calendar" VALUES('2011-01-04'); 
INSERT INTO "calendar" VALUES('2011-01-05'); 
INSERT INTO "calendar" VALUES('2011-01-06'); 
INSERT INTO "calendar" VALUES('2011-01-07'); 
INSERT INTO "calendar" VALUES('2011-01-08'); 

如果您确定所有日期都在file_stats中,那么您可以不使用日历表。但是有一些警告。如果你的数据是正确的

select fs.date, f.region, count(*) 
from file_stats fs 
left join files f on (f.id = fs.file_id) 
group by fs.date, f.region; 

这是可行的,但你的表不保证该数据将是正确的。您没有外键引用,因此每个表中可能存在文件标识号,而其他表中没有匹配的标识号。我们来看一些示例数据。

insert into files values (1, 'a long path', 'NYK'); 
insert into files values (2, 'another long path', 'NYK'); 
insert into files values (3, 'a shorter long path', 'LDN'); -- not in file_stats 

insert into file_stats values ('2011-01-01', 1, 35); 
insert into file_stats values ('2011-01-02', 1, 37); 
insert into file_stats values ('2011-01-01', 2, 40); 
insert into file_stats values ('2011-01-01', 4, 35); -- not in files 

运行此查询(与上面相同,但添加ORDER BY)。 。 。

select fs.date, f.region, count(*) 
from file_stats fs 
left join files f on (f.id = fs.file_id) 
group by fs.date, f.region 
order by fs.date, f.region; 

。 。 。返回

2011-01-01||1 
2011-01-01|NYK|2 
2011-01-02|NYK|1 

“LDN”不显示,因为有与文件的ID号file_stats没有第3行中的一行有一个空的区域,因为文件中没有行有文件ID号为4

您可以使用左连接快速找到不匹配的行。

select f.id, fs.file_id 
from files f 
left join file_stats fs on (fs.file_id = f.id) 
where fs.file_id is null; 

回报

3| 

这意味着有一个在文件行中具有ID 3,但在file_stats没有行ID号为3翻转的周围表,以确定在file_stats不具有行匹配文件中的行。

select fs.file_id, f.id 
from file_stats fs 
left join files f on (fs.file_id = f.id) 
where f.id is null; 
+0

理想情况下,我想,它不需要额外的表的解决方案。由于所有日期都在file_stats表中,并且我感兴趣的所有区域都将出现在文件表中,所以它必须能够使用数据可见性生成所有组合。 – acann 2011-02-12 18:48:44

+0

file_stats(“REFERENCES files”)有一个外键约束,它确保每个file_stats记录都必须有一个相应的文件。但是,没有要求(或强制执行)每个文件都必须具有file_stats表中每个日期的条目。这是真实的世界情景,因为文件在项目的生命周期中出现并且失望 – acann 2011-02-13 10:26:03

+0

@acann:我读过这个。但你知道SQLite和外键引用。 。 。 – 2011-02-13 12:28:45

0

我怀疑它不得不为每一行输出扫描file_stats和文件。以下版本可能会快得多。而且它不需要创建新表格。

SELECT d.date 
    , r.region 
    , count(f.file_id) AS num_files 
FROM (SELECT DISTINCT date FROM file_states) AS d, 
    (SELECT DISTINCT region FROM files) AS r, 
    LEFT JOIN file_stats AS fs 
    ON fs.date = d.date 
    LEFT JOIN files f 
    ON f.file_id = fs.file_id 
     AND f.region = r.region 
GROUP BY d.date, r.region; 
0

一个(较慢由于下半年的性能损失),这样做的方式,你希望是什么东西,有东西制造列表中的计数具有零计数UNION:

-- Include the counts for date/region pairs that HAVE files 
SELECT date, region, COUNT(*) as COUNT1 
FROM file_stats fs, files f 
WHERE fs.file_id = f.id 
GROUP BY date, region 

UNION 

SELECT DISTINCT date, region, 0 as COUNT1 
FROM file_stats fs0, files f0 
WHERE NOT EXISTS (
    SELECT 1 
    FROM file_stats fs, files f 
    WHERE fs.file_id = f.id 
    AND fs.date=fs0.date 
    AND f.region=f0.region 
) 

我不完全确定你为什么反对使用临时表?例如。 (这是临时表人口的Sybasyish语法,但应该轻松移植 - 不要记得确切的SQLite之一)。表的大小应该是最小的(天的只是#*区域#)

CREATE TABLE COMBINATIONS TEMPORARY (region VARCHAR(4), date DATE) 

INSERT COMBINATIONS SELECT DISTINCT date, region FROM files, file_stats 

SELECT c.date, c.region, SUM(CASE WHEN file_stats.id IS NULL THEN 0 ELSE 1 END) 
FROM COMBINATIONS c 
LEFT JOIN files f ON f.region=c.region 
LEFT OUTER JOIN file_stats fs ON fs.date=c.date AND fs.file_id = f.id 
GROUP BY c.date, c.region