Помогите составить запрос

iAlexander
Дата: 01.11.2005 11:25:59
Всем привет.
Помогите составить следующий запрос:
Есть таблица заказов (order):

order_id | order_title | order_date
493 | В поход | 01.01.2005

Таблица товаров (products)

prod_id | product_title
4534 | Резиновые сапоги
8873 | котелок

Таблица товаров, вошедших в заказ (items):

order_id | prod_id | is_main
493 | 4534 | 1
493 | 8873 | 0
В этой последней таблице один из товаров является главным. Он отмечается 1 в колонке is_main. Главный товар не обязательно присутствует, то есть могут быть только неосновные товары.
Хочется составить SQL запрос, который будет выводить список заказов следующим образом:

order_id | order_title | order_date | prod_id | product_title
493 | В поход | 01.01.2005 | 4534 | Резиновые сапоги
причем если главный товар пристутсвует в заказе, то только он и выводится (то есть строка для каждого order_id единственная),
а если главного товара нет, то выводится та же структура, вот только перечисляются уже все товары, вошедшие в заказ, то есть если бы сапоги не были главным товаром, то результат:

order_id | order_title | order_date | prod_id | product_title
493 | В поход | 01.01.2005 | 4534 | Резиновые сапоги
493 | В поход | 01.01.2005 | 8873 | котелок
У меня есть два варианта решения, но оба они имеют недостатки:

1)

SELECT o.*, p.* FROM orders o
INNER JOIN items i ON o.order_id=i.order_id AND i.is_main=1
INNER JOIN products p ON i.prod_id=p.prod_id
- этот запрос не покажет записи, у которых нет главного товара. Замена INNER JOIN на LEFT JOIN затормаживает выборку на порядки (!), СУБД FireBird, да и мало что дает, ведь в этом случае в столбцах prod_id и product_title вообще будет пусто.

2)
SELECT o.*, p.* FROM orders o
INNER JOIN items i ON o.order_id=i.order_id AND
i.is_main=
(
SELECT MAX(is_main) FROM items WHERE items.order_id=o.order_id
)
INNER JOIN products p ON i.prod_id=p.prod_id
- этот запрос выведет только главный товар, если он есть, и все неглавные, если главный отсутствует (как и надо). Но работает он очень медленно, поскольку есть подзапрос.

Спасибо за терпение дочитавшим этот пост до конца. Можно ли придумать запрос, который будет выполнять это быстро?
Лентяй
Дата: 01.11.2005 11:56:04
Вот так попробуй:
SELECT o.*, p.* FROM orders o
INNER JOIN items i ON o.order_id=i.order_id AND 
  i.is_main=(SELECT first 1 is_main 
   FROM items 
   WHERE items.order_id=o.order_id
   order by is_main desc)
INNER JOIN products p ON i.prod_id=p.prod_id
Или так:
SELECT o.*, p.* FROM orders o
INNER JOIN items i ON o.order_id=i.order_id AND 
  i.is_main= 1 or 
  not exists (SELECT 1  FROM items 
  WHERE items.order_id=o.order_id and is_main = 1)
INNER JOIN products p ON i.prod_id=p.prod_id

sag494
Дата: 01.11.2005 12:26:14
iAlexander
Всем привет.
Привет,
iAlexander
В этой последней таблице один из товаров является главным. Он отмечается 1 в колонке is_main. Главный товар не обязательно присутствует, то есть могут быть только неосновные товары.
Есть возможность изменить схему?
iAlexander
Замена INNER JOIN на LEFT JOIN затормаживает выборку на порядки (!), СУБД FireBird,
выборку не ограничиваешь по каким-либо полям orders?
iAlexander
SELECT o.*, p.*
Звезды (т.е.все поля) действительно необходимы?
К возможным решениям, приведенным выше, добавлю еще парочку:
SELECT ...
FROM orders o
JOIN items i ON (o.order_id=i.order_id AND i.is_main=1)
union all
SELECT ...
FROM orders o
left JOIN items i1 ON (o.order_id=i1.order_id AND i1.is_main=1)
JOIN items i2 ON (o.order_id=i2.order_id AND i2.is_main=0)
where i1.is_main is null
order by
SELECT ...
FROM orders o
left JOIN items i1 ON (o.order_id=i1.order_id AND i1.is_main=1)
left JOIN items i2 ON (o.order_id=i2.order_id AND i2.is_main=0 AND i1.is_main is null )
where i1.order_id is not null or i2.order_id is not null
order by
iAlexander
Дата: 01.11.2005 13:01:49
Лентяй
Спасибо, я попробую. Есть надежда, что второй запрос благодаря lazy расчету будет работать значительно быстрее. Только там наверное скобки нужно было поставить:
SELECT o.*, p.* FROM orders o
INNER JOIN items i ON o.order_id=i.order_id AND 
(
  i.is_main= 1 or 
  not exists (SELECT 1  FROM items 
  WHERE items.order_id=o.order_id and is_main = 1)
)
INNER JOIN products p ON i.prod_id=p.prod_id

sag494
sag494
Есть возможность изменить схему?

Да, возможность есть. Но: делать главный товар характеристикой заказа нежелательно. Или есть другие варианты?

sag494
выборку не ограничиваешь по каким-либо полям orders?

Да, ограничиваю.
sag494
Звезды (т.е.все поля) действительно необходимы

Да, необходимы. Они все будут использованы.

sag494
К возможным решениям, приведенным выше, добавлю еще парочку:

Хитрые запросы. Order by в первом упорядочит совокупность обоих запросов
или каждый в отдельности? Для меня важно именно сквозное упорядочение
И LEFT'ы - можно что-то предпринять для их ускорения? (индексов в базе нет. пока.)
sag494
Дата: 01.11.2005 13:13:27
iAlexander
sag494
Есть возможность изменить схему?

Да, возможность есть. Но: делать главный товар характеристикой заказа нежелательно. Или есть другие варианты?
ммм....я не призываю все ломать, так, немножко подрихтовать... Например, признак "есть ли главный товар is_main в этом документе"; либо необяз.ссылка из шапки док-та на элемент с главным товаром.
iAlexander
Хитрые запросы. Order by в первом упорядочит совокупность обоих запросов или каждый в отдельности?
Обоих. С какими серверами ты работал раньше?
iAlexander
LEFT'ы - можно что-то предпринять для их ускорения?
не так страшен left... правильно приготовленный left погоды не испортит
iAlexander
(индексов в базе нет. пока.)
нет ни pk ни fk?
iAlexander
Дата: 01.11.2005 17:01:52
sag494
ммм....я не призываю все ломать, так, немножко подрихтовать... Например, признак "есть ли главный товар is_main в этом документе"; либо необяз.ссылка из шапки док-та на элемент с главным товаром.

Да собственно, раньше так и было. Id главного товара - как одно из полей заказа. Вот только изменяться список товаров в заказе может независимо от заказа многими путями, и приходилось каждый раз после изменения синхронизировать эту ссылку. Поэтому (а может и еще были причины) она была удалена и вместо нее введен признак is_main :)
sag494
Обоих. С какими серверами ты работал раньше?

MySQL, вот только UNION я никогда не пользовался.
sag494
нет ни pk ни fk?

Никаких. База в реконструкции после переезда с локальной СУБД. Поэтому индексов пока нет.
sag494
не так страшен left... правильно приготовленный left погоды не испортит

А какие LEFT'ы правильные?
kdv
Дата: 01.11.2005 17:38:36
Никаких. База в реконструкции после переезда с локальной СУБД. Поэтому индексов пока нет.

ужос. тогда это не база, а набор плоских таблиц.
sag494
Дата: 01.11.2005 20:59:11
iAlexander
sag494
ммм....я не призываю все ломать, так, немножко подрихтовать... Например, признак "есть ли главный товар is_main в этом документе"; либо необяз.ссылка из шапки док-та на элемент с главным товаром.
Да собственно, раньше так и было. Id главного товара - как одно из полей заказа.
Я не это имел в виду. В шапке документа можно хранить ссылку (fk) на элемент items с "главным" товаром. Или. Или в шапке документа хранить признак, есть ли в заказе главный товар ("glavniy_est", 0 - нету, 1 -есть такой), тогда твой запрос выглядел бы так
SELECT ...
FROM orders o
INNER JOIN items i 
ON o.order_id=i.order_id AND i.is_main=o.glavniy_est
INNER JOIN products p ON i.prod_id=p.prod_id
iAlexander
Вот только изменяться список товаров в заказе может независимо от заказа многими путями, и приходилось каждый раз после изменения синхронизировать эту ссылку.
А это уже другая история.
iAlexander
sag494
нет ни pk ни fk?
Никаких. База в реконструкции после переезда с локальной СУБД. Поэтому индексов пока нет.
Таблица без pk - это странное нечто, и fk не просто так придуманы.
iAlexander
sag494
не так страшен left... правильно приготовленный left погоды не испортит
А какие LEFT'ы правильные?
Правильные. Плохой из меня объясняльщик. Ну, попробую, на пальцах.
Что за боязнь такая left? Или наоборот, пихание его везде и всегда.
Рассмотрим вариант, который люди не боятся:
from A join B on (…) where
тут оптимизатор на основании условий on и where сам решает, какую таблицу обрабатывать первой, а какую доподсоединять следом, какие индексы при этом использовать.
Теперь "страшный" вариант:
from A left join B on (…) where
что поменялось? Мы хотим взять данные из A, а из B интересующие данные могут быть, а могут и не быть. То есть, мы принуждаем сервер _сначала_ обрабатывать первой именно таблицу A. Чуть меньше свободы оптимизатору.
Давай отвлечемся от join-ов. Запрос
select count(*) from A where
Как его убыстрить? Надо чтобы для условий в where наличествовали какие-то полезные индексы, которые могли бы быть использованы оптимизатором.
Вернемся к left. Раз A обрабатывается первой, хорошо бы наложить на нее ограничения, да такие, чтобы для их отработки использовались подходящие индексы по A, чтобы получилось побыстрее, чем тупой перебор plan A natural (речь о том случае, когда в A записей достаточно, то есть много). Таблица B будет подтянута на основании условий on, неплохо если найдется подходящий fk или (и) еще какой индекс.
Ну, в общем, все как обычно. Берем запрос, смотрим его план, думаем можно ли убыстрить и как. Пробуем.