Не посоветуете, можно ли сократить запрос?

Alibek B.
Дата: 15.09.2015 18:49:29
Есть у меня довольно зубодробительный запрос, который упростить пока не получается.
Одно из select-полей выглядит примерно так:
, case FT.FEE_PERIOD_UNIT
    when 'D' then trunc(nvl(ST.DATE_BEG, S.START_DATE))
    when 'W' then trunc(nvl(ST.DATE_BEG, S.START_DATE)) - decode(FT.FEE_PERIOD_ADJUST, 0, 0, FT.FEE_PERIOD_VALUE - 1 + (trunc(trunc(nvl(ST.DATE_BEG, S.START_DATE)), 'DD') - trunc(trunc(nvl(ST.DATE_BEG, S.START_DATE)), 'IW')))
    when 'M' then to_date(to_char(trunc(nvl(ST.DATE_BEG, S.START_DATE)),'YYYY')||to_char(trunc(nvl(ST.DATE_BEG, S.START_DATE)),'MM')||to_char(decode(FT.FEE_PERIOD_ADJUST, 0, extract(day from trunc(nvl(ST.DATE_BEG, S.START_DATE))), FT.FEE_PERIOD_ADJUST), 'FM00'), 'YYYYMMDD')
    when 'Y' then to_date(to_char(trunc(nvl(ST.DATE_BEG, S.START_DATE)),'YYYY')||to_char(decode(FT.FEE_PERIOD_ADJUST, 0, extract(month from trunc(nvl(ST.DATE_BEG, S.START_DATE))), FT.FEE_PERIOD_ADJUST), 'FM00')||to_char(decode(FT.FEE_PERIOD_ADJUST, 0, extract(day from trunc(nvl(ST.DATE_BEG, S.START_DATE))), 1), 'FM00'), 'YYYYMMDD')
    else null
  end as ST_PERIOD_START

Значение ST_PERIOD_START в свою очередь используется в не менее сложных выражениях, а вычисленные выражения используются в других выражениях попроще.

Пробный работающий запрос я для этого трижды оборачивал в подзапросы:
select S2.*
, FEE_PERIOD_NEXT - FEE_PERIOD_CURRENT as FEE_PERIOD_DAYS
from
(
select S1.*
, case FEE_PERIOD_UNIT
    when 'D' then trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/FEE_PERIOD_VALUE)
    when 'W' then trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/7/FEE_PERIOD_VALUE)
    when 'M' then trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/FEE_PERIOD_VALUE)
    when 'Y' then trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/12/FEE_PERIOD_VALUE)
    else null
  end as FEE_PERIOD_COUNT
, case FEE_PERIOD_UNIT
    when 'D' then FEE_PERIOD_START + FEE_PERIOD_VALUE*trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/FEE_PERIOD_VALUE)
    when 'W' then FEE_PERIOD_START + 7*FEE_PERIOD_VALUE*trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/7/FEE_PERIOD_VALUE)
    when 'M' then add_months(FEE_PERIOD_START, FEE_PERIOD_VALUE*trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/FEE_PERIOD_VALUE))
    when 'Y' then add_months(FEE_PERIOD_START, 12*FEE_PERIOD_VALUE*trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/12/FEE_PERIOD_VALUE))
    else null
  end as FEE_PERIOD_CURRENT
, case FEE_PERIOD_UNIT
    when 'D' then FEE_PERIOD_START + FEE_PERIOD_VALUE*(1+trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/FEE_PERIOD_VALUE))
    when 'W' then FEE_PERIOD_START + 7*FEE_PERIOD_VALUE*(1+trunc((trunc(sysdate) - FEE_PERIOD_START + 1)/7/FEE_PERIOD_VALUE))
    when 'M' then add_months(FEE_PERIOD_START, FEE_PERIOD_VALUE*(1+trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/FEE_PERIOD_VALUE)))
    when 'Y' then add_months(FEE_PERIOD_START, 12*FEE_PERIOD_VALUE*(1+trunc(months_between(trunc(sysdate), FEE_PERIOD_START)/12/FEE_PERIOD_VALUE)))
    else null
  end as FEE_PERIOD_NEXT
from
(
select S0.*
, case FEE_PERIOD_UNIT
    when 'D' then SERVICE_START
    when 'W' then SERVICE_START - decode(FEE_PERIOD_ADJUST, 0, 0, FEE_PERIOD_VALUE - 1 + (trunc(SERVICE_START, 'DD') - trunc(SERVICE_START, 'IW')))
    when 'M' then to_date(to_char(SERVICE_START,'YYYY')||to_char(SERVICE_START,'MM')||to_char(decode(FEE_PERIOD_ADJUST, 0, extract(day from SERVICE_START), FEE_PERIOD_ADJUST), 'FM00'), 'YYYYMMDD')
    when 'Y' then to_date(to_char(SERVICE_START,'YYYY')||to_char(decode(FEE_PERIOD_ADJUST, 0, extract(month from SERVICE_START), FEE_PERIOD_ADJUST), 'FM00')||to_char(decode(FEE_PERIOD_ADJUST, 0, extract(day from SERVICE_START), 1), 'FM00'), 'YYYYMMDD')
    else null
  end as FEE_PERIOD_START
from
(
select S.ACCOUNT_ID
...
) FEE on (FEE.TARIFF_ID = T.TARIFF_ID and FEE.TYPE_ID = T.TYPE_ID)
where S.STATUS != -20
) S0
) S1
) S2


Можно конечно все эти выражения развернуть, но тогда для некоторых полей будут выражения на полэкрана.
Для оптимизатора СУБД это скорее всего пустяки, но я сам в такой портянке не разберусь.
Не посоветуете, как можно сократить подобные выражения, может быть есть какой-нибудь «синтаксический сахар» для упрощения подобных конструкций?
Или обернуть в подзапрос будет оптимально?

________________________
Мы смотрим с оптимизмом...
...в оптический прицел.
Elic
Дата: 15.09.2015 18:59:39
Alibek B.
Или обернуть в подзапрос будет оптимально?
Жалобы есть?
"Можем сделать быстро, дёшево и качественно. Но одно из трёх - зачеркнуть." - А твой правильный вопрос - "Сопровождаемо?"
Alibek B.
Дата: 15.09.2015 19:13:38
Вообщем-то да, корректно было бы спросить именно так.
Можно ли данный запрос написать более сопровождаемым не используя подзапросов?

Лично мне кажется, что возможность подобного запроса:
select ...
, case FT.FEE_PERIOD_UNIT when ... then ... end as ST_PERIOD_START
, case FT.FEE_PERIOD_UNIT
    when 'D' then trunc((trunc(sysdate) - :self.ST_PERIOD_START + 1)/FEE_PERIOD_VALUE)
    when 'W' then trunc((trunc(sysdate) - :self.ST_PERIOD_START + 1)/7/FEE_PERIOD_VALUE)
    when 'M' then trunc(months_between(trunc(sysdate), :self.ST_PERIOD_START)/FEE_PERIOD_VALUE)
    when 'Y' then trunc(months_between(trunc(sysdate), :self.ST_PERIOD_START)/12/FEE_PERIOD_VALUE)
    else null
  end as ST_PERIOD_COUNT
...
from ...

реализовать программно не должно быть очень сложно.
Ведь столбец ST_PERIOD_START уже вычислен и даже поименован, поэтому ссылаться на его значение в той же строке, но в других столбцах не должно быть сложным, особенно если синтаксически определить, что ссылаться можно только если столбец был использован до использования.

Может быть программисты Oracle что-то подобное уже реализовали?
stax..
Дата: 15.09.2015 19:34:06
Alibek B.
Может быть программисты Oracle что-то подобное уже реализовали?

імхо, пока не реализовали (кроме order by)

ps
MySql реализовали

.....
stax
Elic
Дата: 15.09.2015 19:34:36
Alibek B.
Может быть программисты Oracle что-то подобное уже реализовали?
Да. Inline view.
stax..
Дата: 15.09.2015 19:46:40
Elic
Alibek B.
Может быть программисты Oracle что-то подобное уже реализовали?
Да. Inline view.

Inline view ето ж подзапрос?

ps
мож в 12-ке case... в lateral и дальше использовать в выражениях
аля
select ename,s,sal/s p1,comm/s p2 from emp e, lateral (select sal+nvl(comm,0) s from dual)
.....
stax
suPPLer
Дата: 15.09.2015 20:34:45
Alibek B.,

шо, опять?
Alibek B.
Дата: 15.09.2015 21:00:23
Да, в MySQL я такую штуку и видел, выглядит заманчиво.

suPLer
Alibek B.,
шо, опять?

Не совсем, теперь этот запрос нужно задействовать в более сложных выборках и нужно его еще более сократить.

Одним запросом у меня сократить никак не получается.
Решил сделать несколько вариантов запроса для каждого возможного FEE_PERIOD_UNIT и затем объединить их через union all.
Тогда выражения получаются вполне читаемые, и при этом не используются подзапросы.
suPPLer
Дата: 15.09.2015 21:10:29
Alibek B.,

как насчёт того, чтобы написать свою детерминированную функцию с тремя параметрами (p_unit, p_start, p_value) и использовать её вместо этих повторяющихся CASE? Это для начала.
Alibek B.
Дата: 16.09.2015 09:30:43
ST_PERIOD_START заменить будет можно, но остальные выражения зависят от текущей даты и не будут детерминированными.

В принципе четыре запроса, объединенных через union all, выглядят вполне приемлемым решением.
Выражения получаются вполне читаемыми и разобраться в них через год проблемой не будет.