February 11, 2009

Условное упорядочивание по произвольному набору полей

Вспомнил, вот, свою старенькую наработку и решил опубликовать:

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

Для решения этой проблемы я написал пару операторов:

@< - сортировка в прямом порядке
@> - сортировка в обратном порядке

(скрипт тут conditional_ordering.sql)

Приведу пример того, как с их помощью можно победить избыточность и придать коду красивый и хорошо читаемый вид. Сначала проблемный код:
if <condition1> then
for
select <fields>
from <tables>
where <restrictions>
order by
field1 desc,
field2
loop
<actions>
end loop;
elsif <condition2> then
for
select <fields>
from <tables>
where <restrictions>
order by
field3,
field1 desc,
field2 desc
loop
<actions>
end loop;
else
for
select <fields>
from <tables>
where <restrictions>
order by
field4
loop
<actions>
end loop;
end if;
Конечно это можно частично обойти за счёт использования курсоров или динамического SQL, но, сами понимаете, в первом случае избыточность в запросах никуда не денется, а во втором появятся проблемы со скоростью. Теперь та же логика, только с использованием новых операторов:
for
select <fields>
from <tables>
where <restrictions>
order by
case when <condition1> then
@>field1
@<field2
when <condition2> then
@<field3
@>field1
@>field2
else
@<field4
end
loop
<actions>
end loop;
Также, как можно заметить из следующего примера, применяя эти операторы можно получить эффект подобный оракловскому OVER PARTITION.
select * from (
values
(1.2, '2007-11-23 12:00'::timestamp, true),
(1.4, '2007-11-23 12:00'::timestamp, true),
(1.2, '2007-11-23 12:00'::timestamp, false),
(1.4, '2007-01-23 12:00'::timestamp, false),
(3.5, '2007-08-31 13:35'::timestamp, false)
) _
order by
@<column1 ||
case
when column1 = 1.2 then @<column3
when column1 = 1.4 then @>column3
else
@>column2
@<column3
end;

column1 | column2 | column3
---------+---------------------+---------
1.2 | 2007-11-23 12:00:00 | f
1.2 | 2007-11-23 12:00:00 | t
1.4 | 2007-11-23 12:00:00 | t
1.4 | 2007-01-23 12:00:00 | f
3.5 | 2007-08-31 13:35:00 | f
(5 rows)
Обратите внимание на то, что строки 1-2 и 3-4 имеют разный порядок в третьей колонке.

p.s. К сожалению операторы пока не работают с текстовыми полями, т.к. мне пока не удалось победить локализацию, да и цели такой пока не было.

Скрипт тут conditional_ordering.sql

1 comment:

gray-hemp said...

Кстати, в 8.4 будет OVER PARTITION. Скоро напишу об этом.

Post a Comment