Ansi SQL загадка - PullRequest
       20

Ansi SQL загадка

0 голосов
/ 22 мая 2019

У меня есть таблица (в Oracle 12, но я бы хотел использовать только ANSI sql), определенная и заполненная следующим образом:

CREATE TABLE MYTABLE (GROOM VARCHAR2(50), BRIDE VARCHAR2(50), STATE VARCHAR2(50));

INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('ALVIN','CARMEN','NJ');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('ALVIN','CARMEN','VA');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('ALVIN','ELEANOR','NJ');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('CARL','CARMEN','AL');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('CARL','ELEANOR','AL');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('DAVID','DIANA','NE');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('FRANK','DIANA','NV');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('MIKE',NULL,'RI');
INSERT INTO MYTABLE (GROOM,BRIDE,STATE) VALUES ('MIKE',NULL,'WI');

Я бы хотел получить результат по следующим критериям:

  • для каждого жениха (из алфавитного наименьшего имени) возьмите невесту «еще не замужем» с алфавитным наименьшим именем и с алфавитным наименьшим состоянием (если невеста такая же). Если невесты нет, установите для нее значение NULL.

Например: самый низкий жених - Элвин, который может жениться на Кармен (в Нью-Джерси и Вирджинии) или Элеоноре. Результат:

Alvin, Carmen, NJ

Теперь самым низким является Карл, который может быть женат на Кармен (но она уже замужем за Элвином) или Элеоноре. Итак, результат:

Carl, Eleanor, AL

Итак, в конце я бы хотел получить этот набор результатов:

Alvin, Carmen, NJ  
Carl, Eleanor, AL  
David, Diana, NE  
Frank, NULL, NV  
Mike, NULL, RI

Как я уже сказал, я хотел бы использовать только ANSI SQL (поэтому тот факт, что я использую Oracle не имеет значения), никаких временных таблиц, курсоров или самостоятельных объединений таблиц. Оконные функции в порядке.

Спасибо

Ответы [ 2 ]

1 голос
/ 23 мая 2019

Ну, во-первых, было бы хорошо, если бы вы могли объяснить природу ограничений.

Например, может быть разумно, когда кто-то хочет реализовать логику в чистом SQL, но какой в ​​этом смысл?запретить самостоятельные присоединения?Кроме того, вы рассматриваете коррелированные подзапросы из той же таблицы как самостоятельные объединения?А как насчет скалярных подзапросов?

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

Существует два типичных подхода для таких задач, как ваша (когда вы «перебираете» строки и сохраняете некоторое «состояние») в Oracle

  • рекурсивный факторинг подзапросов (также известный как рекурсивные CTE)
  • предложение модели

Позвольте мне начать с модели, даже если это очень специфическая функция Oracle

SQL> with t as
  2  (
  3    select *
  4    from mytable
  5    model
  6      dimension by (groom, bride, state)
  7      measures (0 reserved)
  8      (
  9        reserved[any,any,any] order by groom, bride, state
 10        = case
 11            -- current groom already has a bride
 12            when max(reserved)[cv(groom), lnnvl(bride > cv(bride)), any] = 1
 13            -- current bride already reserved for some groom
 14            or max(reserved)[groom < cv(groom), cv(bride), any] = 1
 15            then 0 else 1
 16          end
 17      )
 18  )
 19  select groom, bride, state
 20    from t
 21   where reserved = 1
 22   union all
 23  select groom, null, min(state)
 24    from mytable
 25   where groom not in (select groom from t where reserved = 1)
 26   group by groom
 27   order by 1;

GROOM      BRIDE      STATE
---------- ---------- ----------
ALVIN      CARMEN     NJ
CARL       ELEANOR    AL
DAVID      DIANA      NE
FRANK                 NV
MIKE                  RI

В этом решении столбец reserved используется для обозначения каждой строки, в которой «выделена» невеста.Подход работает только в Oracle, начиная с версии 10g Release 1, когда впервые было введено предложение модели.

Второе решение ниже

SQL> with rec(groom, bride, state, reserved)
  2       as (select min(groom),
  3                  min(bride) keep (dense_rank first order by groom),
  4                  min(state) keep (dense_rank first order by groom, bride),
  5                  min(bride) keep (dense_rank first order by groom)
  6             from mytable
  7           union all
  8           select t.groom,
  9                  t.bride,
 10                  t.state,
 11                  r.reserved || '#' || t.bride
 12             from rec r
 13             cross apply
 14              (select min(groom) groom,
 15                      min(bride) keep (dense_rank first order by groom) bride,
 16                      min(state) keep (dense_rank first order by groom, bride) state
 17                 from mytable
 18                where groom > r.groom and instr(r.reserved, bride) = 0) t
 19            where t.groom is not null)
 20             cycle groom set c to 1 default 0
 21  select groom, bride, state
 22    from rec
 23   union all
 24  select groom, null, min(state)
 25    from mytable
 26   where groom not in (select groom from rec)
 27   group by groom
 28   order by 1;

GROOM      BRIDE      STATE
---------- ---------- ----------
ALVIN      CARMEN     NJ
CARL       ELEANOR    AL
DAVID      DIANA      NE
FRANK                 NV
MIKE                  RI

В этом решении вы можете избавиться от определенной функции Oracle keep dense_rank и избегайте использования cross apply, который был введен только в 12c.Также вы можете отслеживать зарезервированные невесты, используя коллекцию вместо объединенной строки, но ... опять же, это решение для Oracle.

Однако, это (с небольшими изменениями) может быть принято, скажем, на SQL-сервере.

PS.

Говоря о производительности, рекурсивное решение сканирует весь mytable во время каждого выполнения рекурсивного члена, что делает его нежизнеспособным для любого относительно большого набора данных.

model может быть хорошо, скажем, на тысячах строк, но все же он вычисляет агрегаты (max(reserved)) для каждой строки, чего можно избежать в подходе, отличном от SQL.

0 голосов
/ 24 мая 2019

Я делаю несколько тестов со следующим запросом.Я не уверен в этом, поэтому, пожалуйста, не рассматривайте его как предлагаемое решение (если вы обнаружите какие-то концептуальные или фактические ошибки, это будет очень признательно).

Идея состоит в том, чтобы определить упорядоченноесписок жениха и невесты (с помощью оконных функций), а затем изменить имя невесты, если ее ранг выше, чем у жениха, с тем чтобы впоследствии исключить ее группировку по жениху и использование оператора min.

SELECT GROOM
/*oracle specific string functions, every system has its own equivalent*/
, REPLACE(SUBSTR(COMPOUND, 1, INSTR(COMPOUND, ';', 1, 1) -1), 'ZZZZZZZZZZ', NULL) AS BRIDE 
, SUBSTR(COMPOUND, INSTR(COMPOUND, ';', -1, 1) +1) AS STATE
FROM
(
    SELECT GROOM
    , MIN(CASE WHEN RANK_GROOM < RANK_BRIDE AND RANK_BRIDE <> 1 THEN 'ZZZZZZZZZZ' 
ELSE BRIDE END || ';' || STATE) AS COMPOUND
    FROM
    (
        SELECT
        GROOM, COALESCE(BRIDE, 'ZZZZZZZZZZ') AS BRIDE, STATE
        , DENSE_RANK() OVER (PARTITION BY GROOM ORDER BY BRIDE, STATE) AS RANK_GROOM
        , DENSE_RANK() OVER (PARTITION BY BRIDE ORDER BY GROOM, STATE) AS RANK_BRIDE
        FROM MYTABLE
    ) T1 
    GROUP BY GROOM
) T2

ps Использование имени 'ZZZZZZZZZZ' явно не «элегантно», но в моем реальном случае я работаю с числами, поэтому его можно рассматривать как замену максимального числового постоянного значения.

Редактировать: Я провел много тестов в эти дни, и кажется, что приведенный выше запрос соответствует всем моим потребностям.

...