2009-02-18 4 views
0

У меня есть этот запрос:SQL (несколько вложенных запросов)

SELECT p.id, r.status, r.title 
FROM page AS p 
    INNER JOIN page_revision as r ON r.pageId = p.id AND (
     r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId AND r2.status = 'active') 
     OR r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId) 
    ) 

который возвращает каждую страницу и последнюю активную ревизию для каждого, если нет активного пересмотра отсутствует, в этом случае он просто возвращает последняя редакция.

Можно ли это оптимизировать для повышения производительности или просто общей читаемости? У меня сейчас нет проблем, но я беспокоюсь, что когда это попадает в производственную среду (где может быть много страниц), это будет плохо работать.

Кроме того, есть ли очевидные проблемы, о которых я должен знать? Использование подзапросов всегда меня пугает, но, насколько мне известно, это невозможно сделать без них.

Примечание:
Причины условие в JOIN, а не ИНЕК, что в других запросах (где используются та же логика) Я СЛЕВА, соединяющий из таблицы «сайта» в " page ", и если страниц нет, я все равно хочу вернуть сайт.

Джек

Edit: Я использую MySQL

+0

Какую платформу использовать используя? – Quassnoi

+0

Извините, забыли сказать, MySQL –

+0

какие индексы у вас есть на таблицах page_revision и page? –

ответ

2

Если «активный» является первым в алфавитном порядке migt быть в состоянии уменьшить подзапросов на:

SELECT p.id, r.status, r.title 
FROM page AS p 
    INNER JOIN page_revision as r ON r.pageId = p.id AND 
     r.id = (SELECT r2.id 
       FROM page_revision as r2 
       WHERE r2.pageId = r.pageId 
       ORDER BY r2.status, r2.id DESC 
       LIMIT 1) 

В противном случае вы можете заменить ORDER BY соответствии с

ORDER BY CASE r2.status WHEN 'active' THEN 0 ELSE 1 END, r2.id DESC 

Все они пришли из моего предположения на SQL Server, ваш пробег с MySQL может отличаться.

+0

Ага, я сделал что-то подобное, используя «FIELD (' r2'.'status', «active») », но это еще лучше, спасибо. :-) –

+0

Прекрасно работает в MySQL. –

+0

Вы все равно должны отметить, что добавляем дополнительный заказ. Если у вас есть только «активные» и «неактивные», это должно быть одинаковым, но если у вас есть больше, то вещь сначала будет сортироваться по статусу. Чтобы этого избежать, вы все равно можете использовать ORDER BY CASE. –

0

В MS SQL 2005+ и Oracle:

SELECT p.id, r.status, r.title 
FROM (
    SELECT p.*, r,*, 
     ROW_NUMBER() OVER (PARTITION BY p.pageId ORDER BY CASE WHEN p.status = 'active' THEN 0 ELSE 1 END, r.id DESC) AS rn 
    FROM page AS p, page_revision r 
    WHERE r.id = p.pageId 
) o 
WHERE rn = 1 

В MySQL, что может стать проблемой, так как подзапросы не может использовать INDEX RANGE SCAN в качестве выражение из внешнего запроса не считается постоянным.

Вам нужно создать два индекса и функцию, которая возвращает версию последней страницы, чтобы использовать эти индексы:

CREATE INDEX ix_revision_page_status_id ON page_revision (page_id, id, status); 

CREATE INDEX ix_revision_page_id (page_id, id); 

CREATE FUNCTION `fn_get_last_revision`(input_id INT) RETURNS int(11) 
BEGIN 
    DECLARE id INT; 
    SELECT r_id 
    INTO id 
    FROM (
    SELECT r.id 
    FROM page_revisions 
    FORCE INDEX (ix_revision_page_status_id) 
    WHERE page_id = input_id 
     AND status = 'active' 
    ORDER BY id DESC 
    LIMIT 1 
    UNION ALL 
    SELECT r.id 
    FROM page_revisions 
    FORCE INDEX (ix_revision_page_id) 
    WHERE page_id = input_id 
    ORDER BY id DESC 
    LIMIT 1 
) o 
    LIMIT 1; 
    RETURN id; 
END; 

SELECT po.id, r.status, r.title 
FROM (
    SELECT p.*, fn_get_last_revision(p.page_id) AS rev_id 
    FROM page p 
) po, page_revision r 
WHERE r.id = po.rev_id; 

Это будет эффективно использовать индекс, чтобы получить последнюю версию страницы.

P. S. Если вы используете коды для статусов и используете 0 для активных, вы можете избавиться от второго индекса и упростить запрос.

+0

Спасибо, но я использую MySQL, поэтому я не думаю, что это сработает. –

0

Ваша проблема - частный случай того, что описано в этом question.

Лучшее, что вы можете получить с помощью стандартного ANSI SQL, кажется:

SELECT p.id, r.status, r.title 
FROM page AS p 
INNER JOIN page_revision as r ON r.pageId = p.id 
AND r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId) 

Другие подходы доступны, но в зависимости от того, что базы данных вы используете. Я не уверен, что он может быть значительно улучшен для MySQL.

+0

Это не похоже на последнюю «активную» ревизию, как мой первоначальный запрос, или я что-то пропустил? –

2

Возможно, немного повторного факторинга на заказ?

Если вы добавили столбец latest_revision_id на pages, ваша проблема исчезла бы, надеюсь, только с несколькими строками, добавленными в ваш редактор страниц.

Я знаю, что это не нормализовано, но это упростит (и значительно ускорит) запрос, а иногда вам придется денормализовать производительность.