嵌套子查询太慢 - 外连接等效?

时间:2011-02-12 17:27:46

标签: sqlite join subquery outer-join

我正在收集有关代码库的一些基本统计信息,并尝试使用以下架构数据生成查询

  1. 包含所有文件的文件表(合成主键ID,唯一路径和区域列,用于保存文件所属的文件。
  2. 包含特定日期文件数据的file_stats表(主键是date和file_id的组合)

  3. 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
    

    不起作用,因为并非所有区域都在所有日期都被重新种植。 我试过了

    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

    有人可以建议更好的查询吗?

3 个答案:

答案 0 :(得分:0)

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

不起作用,因为并非所有地区都有   在所有日期都取消了。

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

  1. 日历表。
  2. 日历表上的左连接。
  3. 有了日历表之后,就像这样。 。

    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;
    

    如果您的数据是正确的,这将有效,但您的表格不保证数据是正确的。您没有外键引用,因此每个表中可能有文件ID号在另一个表中没有匹配的ID号。我们有一些样本数据。

    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号为3的file_stats中没有行。一行有一个空区域,因为文件中的行没有文件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;
    

答案 1 :(得分: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;

答案 2 :(得分: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