упорядочить по алфавиту - PullRequest
       10

упорядочить по алфавиту

0 голосов
/ 31 октября 2019

Вопрос не слишком конкретен, но не уверен, как правильно объяснить вопрос. У меня есть таблица в моей БД с именами в поле. Я хотел бы упорядочить имена таким образом, если имя начинается с определенного алфавита, сначала упорядочить, и так далее. Теперь у меня есть

SELECT
  (T.firstname||' '||T.lastname) as Full_Name
FROM 
  TABLE T
ORDER BY
  CASE
    WHEN LPAD(T.firstname, 1) = 'J' THEN T.firstname
    WHEN LPAD(T.firstname, 1) = 'B' THEN T.firstname
  END DESC,
Full_Name ASC

Теперь это возвращается, поскольку то, что я хотел бы видеть, имя, начинающееся с 'J', сначала упорядочено, затем 'B', затем остальные. Тем не менее, результат выглядит так:

What I get    What I want

Full_Name     Full_Name
----------    ----------
Junior MR     James A
John Doe      Joe Bob
Joe Bob       John Doe
James A       Junior MR
Brad T        B Test
Bob Joe       Bb Test
Bb Test       Bob Joe
B Test        Brad T
A Test        A Test
Aa Test       Aa Test
AFLKJASDFJ    AFLKJASDFJ
Ann Doe       Ann Doe

Но я хочу, чтобы J и B также сортировались в алфавитном порядке, сейчас он работает в обратном алфавитном порядке. Как я могу указать порядок внутри кейса? Я пытался использовать 2 отдельных оператора для разных случаев, начиная с 'J' и 'B', он просто показывает мне один и тот же результат

1 Ответ

2 голосов
/ 01 ноября 2019

Просто создайте один дополнительный столбец, материал с использованием 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)
...