Oracle:如何使用某些键的动态基数来旋转 EAV table?

Oracle: How can I pivot an EAV table with a dynamic cardinality for certain keys?

我在 Oracle 中有以下实体-属性-值 (EAV) table:

| ID |     Key     |    Value     |
|----|-------------|--------------|
|  1 | phone_num_1 | 111-111-1111 |
|  1 | phone_num_2 | 222-222-2222 |
|  1 | contact_1   | friend       |
|  1 | contact_2   | family       |
|  1 | first_name  | mike         |
|  1 | last_name   | smith        |
|  2 | phone_num_1 | 333-333-3333 |
|  2 | phone_num_2 | 444-444-4444 |
|  2 | contact_1   | family       |
|  2 | contact_2   | friend       |
|  2 | first_name  | john         |
|  2 | last_name   | adams        |
|  3 | phone_num_1 | 555-555-5555 |
|  3 | phone_num_2 | 666-666-6666 |
|  3 | phone_num_3 | 777-777-7777 |
|  3 | contact_1   | work         |
|  3 | contact_2   | family       |
|  3 | contact_3   | friend       |
|  3 | first_name  | mona         |
|  3 | last_name   | lisa         |

请注意,某些键已编入索引,因此与其他已编入索引的键有关联。例如,phone_num_1 将与 contact_1 相关联。

注意:索引的数量没有硬性限制。可以有10个,20个,甚至50个phone_num_*,但是保证每一个phone_num_N,都有一个对应的contact_N

这是我想要的结果:

| ID |  Phone_Num   | Contact | First_Name | Last_Name |
|----|--------------|---------|------------|-----------|
|  1 | 111-111-1111 | friend  | mike       | smith     |
|  1 | 222-222-2222 | family  | mike       | smith     |
|  2 | 333-333-3333 | family  | john       | adams     |
|  2 | 444-444-4444 | friend  | john       | adams     |
|  3 | 555-555-5555 | work    | mona       | lisa      |
|  3 | 666-666-6666 | family  | mona       | lisa      |
|  3 | 777-777-7777 | friend  | mona       | lisa      |

我 tried/looked 在:

我研究了Oracle的pivot函数;但是,我不认为这可以解决我的问题,因为我没有固定数量的属性可以作为重点。 我看过这些帖子:

Pivot rows to columns without aggregate

问题:

我想通过 SQL 完成所有可能的事情吗?如果可以,怎么办?如果不是,请说明原因。

非常感谢任何帮助,这里是 with table 来帮助您入门:

with
    table_1 ( id, key, value ) as (
        select 1,'phone_num_1','111-111-1111' from dual union all
        select 1,'phone_num_2','222-222-2222' from dual union all
        select 1,'contact_1','friend' from dual union all
        select 1,'contact_2','family' from dual union all
        select 1,'first_name','mike' from dual union all
        select 1,'last_name','smith' from dual union all
        select 2,'phone_num_1','333-333-3333' from dual union all
        select 2,'phone_num_2','444-444-4444' from dual union all
        select 2,'contact_1','family' from dual union all
        select 2,'contact_2','friend' from dual union all
        select 2,'first_name','john' from dual union all
        select 2,'last_name','adams' from dual union all
        select 3,'phone_num_1','555-555-5555' from dual union all
        select 3,'phone_num_2','666-666-6666' from dual union all
        select 3,'phone_num_3','777-777-7777' from dual union all
        select 3,'contact_1','work' from dual union all
        select 3,'contact_2','family' from dual union all
        select 3,'contact_3','friend' from dual union all
        select 3,'first_name','mona' from dual union all
        select 3,'last_name','lisa' from dual
     )
select * from table_1;

这很丑陋,但我认为可以满足您的需求

select t1.* , t2.value, t3.n, t3.f
from table_1 t1
inner join table_1 t2 on t1.id = t2.id and REPLACE(t1.key, 'phone_num_', '') = REPLACE(t2.key, 'contact_', '')
inner join (
    select ID, min(case when Key = 'first_name' then Value end) as n, min(case when Key = 'last_name' then Value end) as f
    from table_1
    group by ID
) t3 on t1.id = t3.id
where
t1.Key not in('first_name','last_name')

这不是动态枢轴,因为您有一组固定的键 - 您只需要先将键的枚举与键本身分开。

您需要:

  • phone_numcontact键前缀与枚举项分开;然后
  • 旋转没有枚举的公共键,使它们与每个枚举键相关联;最后,
  • 第二次旋转以获得连续的枚举键。

Oracle 设置:

CREATE TABLE table_1 ( id, key, value ) as
select 1,'phone_num_1','111-111-1111' from dual union all
select 1,'phone_num_2','222-222-2222' from dual union all
select 1,'contact_1','friend' from dual union all
select 1,'contact_2','family' from dual union all
select 1,'first_name','mike' from dual union all
select 1,'last_name','smith' from dual union all
select 2,'phone_num_1','333-333-3333' from dual union all
select 2,'phone_num_2','444-444-4444' from dual union all
select 2,'contact_1','family' from dual union all
select 2,'contact_2','friend' from dual union all
select 2,'first_name','john' from dual union all
select 2,'last_name','adams' from dual union all
select 3,'phone_num_1','555-555-5555' from dual union all
select 3,'phone_num_2','666-666-6666' from dual union all
select 3,'phone_num_3','777-777-7777' from dual union all
select 3,'contact_1','work' from dual union all
select 3,'contact_2','family' from dual union all
select 3,'contact_3','friend' from dual union all
select 3,'first_name','mona' from dual union all
select 3,'last_name','lisa' from dual

查询:

SELECT *
FROM   (
  SELECT id,
         CASE
         WHEN key LIKE 'phone_num_%' THEN 'phone_num'
         WHEN key LIKE 'contact_%'   THEN 'contact'
         ELSE key
         END AS key,
         CASE
         WHEN key LIKE 'phone_num_%'
         OR   key LIKE 'contact_%'
         THEN TO_NUMBER( SUBSTR( key, INSTR( key, '_', -1 ) + 1 ) )
         ELSE NULL
         END AS item,
         value,
         MAX( CASE key WHEN 'first_name' THEN value END )
           OVER ( PARTITION BY id ) AS first_name,
         MAX( CASE key WHEN 'last_name'  THEN value END )
           OVER ( PARTITION BY id ) AS last_name
  FROM   table_1
)
PIVOT( MAX( value ) FOR key IN ( 'contact' AS contact, 'phone_num' AS phone_num ) )
WHERE item IS NOT NULL
ORDER BY id, item

输出:

ID | ITEM | FIRST_NAME | LAST_NAME | CONTACT | PHONE_NUM   
-: | ---: | :--------- | :-------- | :------ | :-----------
 1 |    1 | mike       | smith     | friend  | 111-111-1111
 1 |    2 | mike       | smith     | family  | 222-222-2222
 2 |    1 | john       | adams     | family  | 333-333-3333
 2 |    2 | john       | adams     | friend  | 444-444-4444
 3 |    1 | mona       | lisa      | work    | 555-555-5555
 3 |    2 | mona       | lisa      | family  | 666-666-6666
 3 |    3 | mona       | lisa      | friend  | 777-777-7777

db<>fiddle here


如果您可以重构 table,那么一个简单的改进是添加一个额外的列来保存键的枚举,并在它是每个枚举的公共值时使用 NULL

CREATE TABLE table_1 ( id, key, line, value ) as
select 1, 'phone_num',  1,    '111-111-1111' from dual union all
select 1, 'phone_num',  2,    '222-222-2222' from dual union all
select 1, 'contact',    1,    'friend'       from dual union all
select 1, 'contact',    2,    'family'       from dual union all
select 1, 'first_name', NULL, 'mike'         from dual union all
select 1, 'last_name',  NULL, 'smith'        from dual

那么你的key集合永远是固定的,你不需要从key中提取枚举值。

SELECT id,
phone,
接触, first_value(last) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following) last_name,
first_value(FIRST) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following) first_name
从 (SELECT id,
值,
row_number() over ( partition BY id,SUBSTR(KEY,1 ,instr(KEY,'',1)-1) order by KEY) rn, SUBSTR(KEY,1 ,instr(KEY,'',1) -1) KEY 来自 table_1
) pivot ( MAX(value) FOR KEY IN ( 'phone' AS phone,'last' AS last,'first' AS FIRST,'contact' AS contact)) 按 ID 排序;