Ansi SQL谜语

时间:2019-05-22 14:57:05

标签: sql oracle ansi

我有一个这样定义和填充的表(在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');

我希望获得符合以下条件的结果:

  • 对于每个新郎(从字母的最小名字开始),以字母最小的名字和字母最小的State(如果新娘相同)选择“尚未结婚”的新娘。如果没有新娘,则将其设置为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 个答案:

答案 0 :(得分:1)

好吧,首先,如果您可以解释这些限制的性质,那就太好了。

例如,当某人想要在纯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用于标记“分配”新娘的每一行。最初引入了模型子句后,该方法仅在10g 1版开始的Oracle中有效。

第二个解决方案是

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,并避免使用仅在12c中引入的cross apply。另外,您可以使用collection而不是串联字符串来跟踪保留的新娘,但是...这又是Oracle特定的解决方案。

但是,这个(稍作修改)可以用于SQL Server。

PS。

谈到性能,递归解决方案在每次执行递归成员时都会扫描整个mytable,这使得它在任何较大的数据集上都不可行。

model可能适用于数千行,但仍会为每行计算聚合(max(reserved)),这在非SQL方法中可以避免。

答案 1 :(得分:0)

我正在使用以下查询进行一些测试。 我不确定,所以请不要将其视为建议的解决方案(如果您发现一些概念性或事实上的错误,将不胜感激)。

此想法是(通过窗口功能)确定新郎和新娘的有序列表,然后在新娘的等级高于已婚新郎的情况下更改新娘的名称,以随后排除新郎和新娘的分组。使用最小运算符。

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

p.s。名称“ ZZZZZZZZZZZZ”的使用显然不是“优雅”的,但在我的实际情况下,我正在处理数字,因此可以将其视为最大数值常数的替代。

编辑:这几天我做了很多测试,上面的查询似乎可以满足我的所有需求。