Вложенный подзапрос слишком медленный - эквивалент внешнего соединения? - PullRequest
0 голосов
/ 12 февраля 2011

Я собираю некоторую базовую статистику по нашей кодовой базе и пытаюсь сгенерировать запрос, используя следующие данные схемы

  1. Таблица файлов, содержащая все файлы (синтетический идентификатор первичного ключа, уникальный путь и столбец региона, в котором указано, кому принадлежит файл.
  2. Таблица file_stats, содержащая данные для файлов на определенную дату (первичный ключ - это сочетание даты и идентификатора файла)

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

но производительность неприемлема из-за вложенного подзапроса.

Я пробовал ЛЕВЫЕ НАРУЖНЫЕ СОЕДИНЕНИЯ, но, кажется, никогда не смог заставить их работать. База данных SQLITE

Кто-нибудь может предложить лучший запрос?

Ответы [ 3 ]

0 голосов
/ 13 февраля 2011

Я подозреваю, что он пытается сканировать 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 голосов
/ 13 февраля 2011

Один (более медленный из-за снижения производительности второй половины) способ выполнения того, что вы хотите, - это СОЮЗ вещей, которые имеют счет с производимым списком вещей, которые имеют нулевой счет:

-- 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
0 голосов
/ 12 февраля 2011
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' не отображается, потому что в file_stats нет строки с идентификатором файла № 3. Одна строка имеет нулевую область, потому что ни одна строка в файлах не имеет идентификатора файла № 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|

означает, что в файлах есть строка с идентификатором 3, но в file_stats нет строки с идентификатором 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;
...