【问题标题】:CASE Oracle SQL for State用于状态的 CASE Oracle SQL
【发布时间】:2013-06-06 07:41:55
【问题描述】:

我有一个可以在其中列出一个或多个状态的字段 (callcenter.stateimpact)。如果 callcenter.stateimpact 包含 "OK","TX","AK","TN","NC","SC","GA","FL","AL","MS" 或 "LA" I需要 SQL 的输出字段说“南”,如果不是,输出需要说“北”。如果 callcenter.stateimpact 有 South 和 North 两个州,它需要在输出中说“BOTH”。如何在 Select 语句中执行此操作?此表中的字段为 callcenter.callid、callcenter.stateimpact、callcenter.callstart 和 callcenter.callstop。非常感谢您的帮助。

【问题讨论】:

  • 创建查找表并执行连接。 case 在这种情况下是不好的做法 - 不可扩展并且可能需要维护。
  • 最好创建一个带状态+区域的小表并加入它。

标签: oracle select case


【解决方案1】:

达到预期结果的方法之一是使用multiset operators
但首先我们需要将由, 分隔的字符串分成几行。其中一种方法是使用connect by 技巧:

-- Trick with building resultset from tokenized string
with dtest_string as (
   select 'OK,TX,AK,TN,NC,SC,GA,FL,AL,MS' StateImpact from dual
)  
 select  
    level lvl,
    substr( -- Extract part of source string
      StateImpact,
      -- from N-th occurence of separator
      decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 ),
      -- with length of substring from N-th to (N+1)-th occurence of separator or to the end.  
      decode( instr(StateImpact,',',1,level), 0, length(StateImpact)+1, instr(StateImpact,',',1,level) )
        -                                         
        decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 )
    ) code
from test_string  
start with 
  StateImpact is not null -- no entries for empty string   
connect by 
  instr(StateImpact,',',1,level-1) > 0 -- continue if separator found on previous step   

只是为了好玩:SQLFiddle 上使用 ANSI 语法的相同技巧

接下来,我们需要声明可以用来存储集合的类型:

create or replace type TCodeList as table of varchar2(100);

之后就可以构建查询了:

with all_south_list as (
  -- prepare list of south states
  select 'OK' as code from dual union all
  select 'TX' as code from dual union all
  select 'AK' as code from dual union all
  select 'TN' as code from dual union all
  select 'NC' as code from dual union all
  select 'SC' as code from dual union all
  select 'GA' as code from dual union all
  select 'FL' as code from dual union all
  select 'AL' as code from dual union all
  select 'MS' as code from dual union all
  select 'LA' as code from dual
)
select 
  StateImpact,
  -- Make decision based on counts
  case 
    when total_count = 0 then 'None'
    when total_count = south_count then 'South'
    when south_count = 0 then 'North'
    else 'Both' 
  end RegionImpact,
  total_count,
  south_count,
  north_count
from (
  select 
    StateImpact, 

    -- count total number of states in StateImpact
    cardinality(code_list)                              total_count,

    -- count number of south states in StateImpact
    cardinality(code_list multiset intersect south_list) south_count,

    -- count number of non-south states in StateImpact
    cardinality(code_list multiset except south_list)    north_count
  from ( 
    select 
      StateImpact,

      (
        cast(multiset( -- Convert set of values into collection which acts like a nested table

          select  -- same trick as above       
              substr(
                StateImpact,
                decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 ),
                decode( instr(StateImpact,',',1,level), 0, length(StateImpact)+1, instr(StateImpact,',',1,level) )
                  -                                         
                  decode( level, 1, 1, instr(StateImpact,',',1,level-1)+1 )
              ) code
          from dual  
          start with StateImpact is not null
          connect by instr(StateImpact,',',1,level-1) > 0    

             ) as TCodeList
        ) 
      )  code_list,

      -- Build collection from south states list
      cast(multiset(select code from all_south_list) as TCodeList) south_list
    from
      CallCenter
  ) 
)

Link to SQLFiddle

【讨论】:

    【解决方案2】:

    这很难解释,所以有一个 SQL Fiddle here 列出了所涉及的值。

    我能想到的最佳方法(除了规范化 StateImpact 值)是使用 REGEXP_REPLACE 将所有“南”状态从字符串中吸出,然后查看剩下的长度。首先,这是REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)') 将对一些示例值执行的操作:

    StateImpact                   REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)')
    ----------------------------- -----------------------------------------------------------------
    OK,TX,AK,TN,NC,SC,GA,FL,AL,MS ,,,,,,,,,
    MI,MA                         MI,MA
    TX                            null
    TX,MI,MA                      ,MI,MA
    

    因此,如果您留下所有逗号或空值,则所有状态都是South。如果您留下原始字符串,则所有状态都是North。其他任何东西,它是Both。无论您如何编写它,这都会导致一个相当大且令人困惑的CASE 声明。我去比较前后的长度,像这样:

    • 替换后的长度 = 0(或 null):South
    • 替换后的长度 = (前的长度 + 1) * 3 - 1: South
    • 替换后的长度 = 替换前的长度:North
    • 其他:Both

    上面的第二个只是一些数学说明,如果(例如)StateImpact 中有五个状态并且它们都是South,那么您将剩下四个逗号。很难解释,但它有效:)

    这是查询:

    SELECT
      StateImpact,
      CASE NVL(LENGTH(REGEXP_REPLACE(StateImpact, '(OK|TX|AK|TN|NC|SC|GA|FL|AL|MS|LA)')), 0)
        WHEN LENGTH(StateImpact) THEN 'North'
        WHEN (LENGTH(StateImpact) + 1) / 3 - 1 THEN 'South'
        ELSE 'Both'
      END AS RegionImpact
    FROM CallCenter
    

    上面引用的 SQL Fiddle 还显示了 REGEXP_REPLACE 前后的长度,希望有助于解释计算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多