У меня есть оператор SQL для данных из order_details, таблицы, в которой много столбцов, включая название продукта, код и т. д. Как я могу добавить столбец в оператор выбора, который всякий раз, когда в заказе есть определенный продукт (код продукта, который мне нужен, называется ' Pap') он пишет флаг 'pap', чтобы я мог визуально узнать, в каких заказах есть этот продукт?
Я попробовал код ниже:
select distinct order_id, customer_id,
(select distinct order_id from order_details
group by 1 having sum (case when product_code='pap'
then 1 else 0 end)=1
) as pap from orders
left join order_details
on order_details.order_id=orders.order_id
group by 1,2,3
Код, который я пытаюсь выполнить, выдает ошибку «[Firebird] несколько строк в одноэлементном выборе; HY000».
Ошибка нескольких строк в одноэлементном выборе означает, что ваш подзапрос создает более одной строки, что неудивительно, поскольку вы выбираете все идентификаторы заказов, соответствующие этому условию, вместо проверки существования идентификатора текущего заказа. Мне неясно, чего именно вы пытаетесь достичь, не могли бы вы предоставить образцы данных и ожидаемые результаты для этих образцов данных.
order_details - может ли в одном заказе быть несколько строк с одним и тем же товаром? Возможен ли заказ с 2+3 капсулами или только с одним итоговым 5 капсулами? Является ли (order_id,product_code) unique constraint или primary key над этой таблицей или нет?


SELECT
...
<foreign_table>.<your_desired_extra_column>
FROM
<current_table>
LEFT JOIN
<foreign_table> ON <foreign_table>.id = <current_table>.id
AND
<current_table>.<condition_field> = <condition_value>
Дополнительный столбец будет NULL, если условие не выполнено.
Полагаю, вы хотите показать «pap» для заказов, которые имеют один или несколько order_details с product_code «pap», в этом случае вы можете использовать:
select order_id, customer_id,
(select max(order_details.product_code)
from order_details
where order_details.order_id = orders.order_id
and order_details.product_code = 'pap') as pap
from orders
Или более общее решение (которое не зависит от product_code для отображения значения):
select order_id, customer_id,
case
when exists(
select 1
from order_details
where order_details.order_id = orders.order_id
and order_details.product_code = 'pap')
then 'pap'
end as pap
from orders
Отличный Марк! Общее решение работало отлично. Спасибо
Давайте попробуем построить ваш запрос шаг за шагом. От простого к сложному в устаревшем стиле снизу вверх :-)
Я предлагаю вам запускать каждый запрос, чтобы увидеть результаты и увидеть, как данные уточняются шаг за шагом, и заранее проверить, верно ли ваше предположение.
1-е неизвестное order_details - может ли в одном заказе быть несколько строк с одним и тем же товаром? Возможен ли заказ с 2+3 капсулами или только с одним итоговым 5 капсулами? Является ли (order_id,product_code) unique constraint или primary key над этим столом или нет?
Select Count(1), order_id, product_code
From order_details
Group by 2,3
Order by 1 DESC
Это может показать, существует ли такое повторение, но даже если нет — вы должны проверить метаданные (схему), чтобы увидеть, разрешено ли это таблицей constraints или indices.
Дело в том, что когда вы JOIN таблицы - их совпадающие строки умножаются (в терминах теории множеств). Так что если у вас могу несколько строк про пап в одном заказе - то мы должны сделать по этому поводу особую сделку. Что добавит дополнительную нагрузку на сервер, если мы не найдем способ сделать это бесплатно.
Мы можем легко проверить наличие этого продукта в одном конкретном заказе.
select 'pap' from order_details where order_id = :parameter_id and product_code='pap'
Затем мы можем подавить повторы — если они не запрещены ограничениями — стандартным способом (но требующим дополнительной сортировки) или специфичным для Firebird (но бесплатным) способом.
select DISTINCT 'pap' from order_details where order_id = :parameter_id and product_code='pap'
или
select FIRST(1) 'pap' from order_details where order_id = :parameter_id and product_code='pap'
Однако они могут соответствовать ответу Марка с коррелированным подзапросом:
select o.order_id, o.customer_id,
coalesce(
( select first(1) 'pap' /* the flag */ from order_details d
where o.order_id = d.order_id and d.product_code = 'pap' )
, '' /* just avoiding NULL */
) as pap
from orders o
Лайфхак: обратите внимание, как использование coalesce и first(1) здесь заменяет использование case и exists в оригинальном ответе Марка. Этот трюк можно использовать в Firebird везде, где вы используете единичный (и потенциально пустой) запрос с 1 столбцом в качестве выражения.
Чтобы избежать нескольких подзапросов и переключиться на внешнее соединение, нам нужно сделать один запрос, чтобы получить ВСЕ идентификаторы заказов с Paps, но только один раз.
select distinct order_id from order_details where product_code='pap'
Должен сделать трюк. Но, вероятно, за счет дополнительной сортировки для подавления возможного дублирования (опять же, возможно ли это?)
select order_id, count(order_id)
from order_details
where product_code='pap'
group by 1 order by 2 desc
Будет отображаться как повторы, если они уже есть. Просто чтобы объяснить, что я имею в виду. И посмотреть, сможете ли вы применить SQL constraints к уже существующим данным, если у вас их не было и вы решили укрепить структуру своей базы данных.
Таким образом, нам просто нужно выполнить внешнее соединение с ним и использовать CASE (или его сокращенную форму), чтобы выполнить типичный трюк фильтрации строк NULL внешнего соединения.
select o.order_id, o.customer_id,
iif ( d.order_id is null, '', 'pap') as pap
from orders o
left join (
select distinct order_id
from order_details
where product_code = 'pap'
and product_quantity > 0 ) d
on o.order_id=d.order_id
Как кто-то сказал, что это выглядит некрасиво, есть еще один «современный» способ написать именно такой запрос, может быть, он будет выглядеть лучше :-D
with d as (
select distinct order_id
from order_details
where product_code = 'pap'
and product_quantity > 0 )
select o.order_id, o.customer_id,
iif ( d.order_id is null, '', 'pap') as pap
from orders o left join d on o.order_id=d.order_id
Там, где повторения «pap» не могут (заметьте, не НЕ ДЕЛАТЬ, а НЕ МОЖЕТ) происходить в одном единственном order_id, тогда запрос станет еще проще и быстрее:
select o.order_id, o.customer_id,
iif ( d.order+id is null, '', 'pap') as pap
from orders o
left join order_details d
on o.order_id=d.order_id
and d.product_code='pap'
and d.product_quantity>0
Обратите внимание на важную деталь: d.product_code='pap' задается как внутреннее условие (до) соединения. Вы бы поместили его во внешнее предложение WHERE после соединения - это не сработает!
Теперь, чтобы сравнить эти два подхода, JOIN и коррелированные подзапросы, вы должны увидеть статистику запросов, сколько выборок и кэшированных выборок не будет генерироваться. Скорее всего - на таблицах среднего размера и с прогретыми кэшами дисков ОС и Firebird вы не увидите разницы во времени. Но не могли бы вы хотя бы выключить и перезапустить службу Firebird и улучшить весь компьютер - очистить указанные кеши - а затем получить эти запросы к последним строкам (выполнив «выбрать все» или «прокрутить до последней строки» в вашей базе данных IDE , или завернув мои запросы и запросы Марка в
select count(1) from ( /* measured query here */) вы также можете заметить изменение времени.
выберите order_id, customer_id, (выберите макс.(order_details.product_code) из order_details где order_details.order_id = orders.order_id и order_details.product_code = 'pap') как pap из заказов
постарайтесь выделить ключевые слова и четко указать формат, это поможет донести ваш ответ до других
MAX(null) = null - этот запрос, вероятно, потребует еще одного шага для замены NULL пустой строкой. Мы не знаем, как клиентское приложение отреагирует на строки NULL, поэтому более безопасным подходом было бы экранировать от него NULL.
Если бы вы правильно отформатировали это, я бы опубликовал свою душу с вашим кодом: D