Просто создайте один дополнительный столбец, материал с использованием triggers
или энергозависимый, используя выражение, выполняемое только при выполнении выбора, а затем используйте его для сортировки.
Для вторичной сортировки используйте оригинальные компоненты имен, а не выражениеобъединение обоих имен, таким образом уничтожая информацию, которая была какой.
Примеры: https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=fbf89b3903d3271ae6c55589fd9cfe23
create table T (
firstname varchar(10),
lastname varchar(10),
fullname computed by (
Coalesce(firstname, '-') || ' ' || Coalesce(T.lastname, '-')
),
sorting_helper computed by (
CASE WHEN firstname starting with 'J' then 100
WHEN firstname starting with 'B' then 50
ELSE 0
END
)
)
Обратите внимание важное различие: мое вспомогательное выражение«рейтинг» один. Он дает один из нескольких предварительно определенных рангов, таким образом, помещая «Джеймс» и «Джо» в один и тот же контейнер, имеющий точно такое же значение ранжирования. Ваше выражение по-прежнему дает имена сами, таким образом ошибочно сохраняет разницу между этими именами. Но вы НЕ хотите, чтобы это различие, вы сказали, что хотите, чтобы все J-начальные имена были перемещены вверх и затем отсортированы между собой по обычным правилам. Так что, просто делайте то, что говорите, создайте выражение, объединяющее все J-имена, БЕЗ различия между ними.
insert into T
select
'John', 'Doe'
from rdb$database union all select
'James', 'A'
from rdb$database union all select
'Aa ', 'Test'
from rdb$database union all select
'Ann', 'Doe'
from rdb$database union all select
'Bob', 'Joe'
from rdb$database union all select
'Brad', 'Test'
from rdb$database union all select
NULL, 'Smith'
from rdb$database union all select
'Ken', NULL
from rdb$database
8 rows affected
select * from T
FIRSTNAME | LASTNAME | FULLNAME | SORTING_HELPER
:-------- | :------- | :---------- | -------------:
John | Doe | John Doe | 100
James | A | James A | 100
Aa | Test | Aa Test | 0
Ann | Doe | Ann Doe | 0
Bob | Joe | Bob Joe | 50
Brad | Test | Brad Test | 50
<em>null</em> | Smith | - Smith | 0
Ken | <em>null</em> | Ken - | 0
Select FullName from T order by sorting_helper desc, firstname asc, lastname asc
| FULLNAME |
| :---------- |
| James A |
| John Doe |
| Bob Joe |
| Brad Test |
| - Smith |
| Aa Test |
| Ann Doe |
| Ken - |
или без computed-by
колонка
Select FullName from T order by (CASE WHEN firstname starting with 'J' then 0
WHEN firstname starting with 'B' then 1
ELSE 2
END) asc, firstname asc, lastname asc
| FULLNAME |
| :---------- |
| James A |
| John Doe |
| Bob Joe |
| Brad Test |
| - Smith |
| Aa Test |
| Ann Doe |
| Ken - |
Для дополнительной настройки позиционированиядля строк без имени или фамилии вы также можете использовать опцию NULLS FIRST
или NULLS LAST
, как описано в документации Firebird по адресу https://firebirdsql.org/file/documentation/reference_manuals/user_manuals/html/nullguide-sorts.html
Однако проблема с этим подходом на достаточно больших таблицах будетчто вы не сможете использовать индексы, построенные на именах и фамилиях, для сортировки, вместо этого вам придется прибегнуть к несортированному извлечению данных (например, NATURAL SORT
при чтении QUERY PLAN
), а затем к сортировке во временные файлы надиск. Который может оказаться очень медленным и требующим большого объема для достаточно больших данных.
Вы можете попытаться улучшить его, создав «индекс по выражению», используя там свое ранговое выражение. И надеюсь, что оптимизатор FB будет использовать его (это довольно сложно с подробными выражениями типа CASE
). Честно говоря, вы, вероятно, все равно остались бы без него (по крайней мере, мне не удалось заставить FB 2.1 использовать там индексное выражение для каждого случая).
Вы можете "материализовать" выражение ранжирования в обычное SmallInt Not Null
столбец вместо COMPUTED BY
один и использование TRIGGER
типа BEFORE UPDATE OR INSERT
, оставляя этот столбец заполненным правильными данными. Затем вы можете создать обычный индекс для этого обычного столбца. Хотя он добавляет два байта к каждой строке, это не так уж и много.
Но даже в этом случае индекс с очень небольшим количеством различных значений не добавляет большого значения, он будет иметь«низкая селективность». Кроме того, индекс по выражению не может быть compound
один (имеется в виду, включая другие столбцы после выражения).
Так что для больших данных вам будет практически лучше использовать ТРИ различных запроса, объединенных вместе. Добавьте строительные леса, если вы еще этого не сделали:
create index i58647579_names on T58647579 ( firstname, lastname )
Тогда вы можете сделать тройной выбор следующим образом:
WITH S1 as (
select FullName from T58647579
where firstname starting with 'J'
order by firstname asc, lastname asc
), S2 as (
select FullName from T58647579
where firstname starting with 'B'
order by firstname asc, lastname asc
), S3 as (
select FullName from T58647579
where (firstname is null)
or ( (firstname not starting with 'J')
and (firstname not starting with 'B')
)
order by firstname asc, lastname asc
)
SELECT * FROM S1
UNION ALL
SELECT * FROM S2
UNION ALL
SELECT * FROM S3
И хотя вы трижды пройдете по столу - вы сделаетеэто по предварительно отсортированному индексу:
PLAN (S1 T58647579 ORDER I58647579_NAMES INDEX (I58647579_NAMES))
PLAN (S2 T58647579 ORDER I58647579_NAMES INDEX (I58647579_NAMES))
PLAN (S3 T58647579 ORDER I58647579_NAMES)