如果不存在关联,则检索数据库关联并填充NULL值

时间:2016-11-21 11:28:04

标签: sql database postgresql

我有三个与另一个表关联的Postgresql表:

USER(id_user,first_name,last_name)

ADDRESS(id_address,id_user,address)

BANK_CARD(id_bank_card,id_user,number)

NETWORK(id_network,id_user,status)

用户可以拥有多个地址,银行卡和网络。

例如对于USER表:

id_user    first_name     last_name
-------------------------------------
 1          John           Doe
 2          David          Smith

对于ADDRESS表:

id_address    id_user    address
----------------------------------
 10            1          address1

对于BANK_CARD:

id_bank_card    id_user     number
-----------------------------------
 20              1           1234
 21              1           5678

对于NETWORK:

id_network    id_user    status
--------------------------------
 30            1          status1
 31            1          status2
 32            1          status3

我想检索用户和其他表之间的现有关联,如下所示:

id_user  id_address  address  id_bank_card  number  id_network  status
-----------------------------------------------------------------------
 1        10         address1      20       1234       30       status1
 1        NULL       NULL          21       5678       31       status2
 1        NULL       NULL          NULL     NULL       32       status3

我尝试使用JOIN或UNION进行不同的查询,但没有任何效果。谢谢你的帮助。

2 个答案:

答案 0 :(得分:1)

其中一种方法是使用union all和聚合:

select id_user, max(id_address) as id_address,
       max(id_bank_card) as id_bank_card,
       max(number) as number,
       max(id_network) as id_bank_card,
       max(status) as status)
from ((select id_user, id_address, NULL as id_bank_card, NULL as number,
              NULL as id_network, NULL as status
              row_number() over (partition by id_user order by id_address) as seqnum
       from address
      ) union all
      (select id_user, NULL as id_address, id_bank_card, number,
              NULL as id_network, NULL as  status
              row_number() over (partition by id_user order by id_bank_card) as seqnum
       from bank_card
      ) union all
      (select id_user, NULL as id_address, NULL as id_bank_card, NULL as number,
              id_network, status
              row_number() over (partition by id_user order by id_network) as seqnum
       from network
      )
     ) abcn
group by id_user, seqnum;

您可能会发现将结果放在数组中更实用:

select *
from (select id_user, array_agg(id_address) as id_addresses
      from address
      group by id_user
     ) a full outer join
     (select id_user, array_agg(id_bank_card) as id_bank_cards,
             array_agg(number) as numbers
      from bank_card
      group by id_user
     ) bc
     using (id_user) full outer join
     (select id_user, array_agg(id_network) as id_networds,
             array_agg(status) as statuses
      from network
      group by id_user
     ) n
     using (id_user);

答案 1 :(得分:0)

LEFT JOIN可以帮助你完成大部分工作。

最混乱的是你实际上想要没有明确关系但没有重复的连接。

下面的答案介绍了一个额外的计算字段,以便: - 第一张银行卡总是被分配到第一个地址
- 第一个网络总是被分配到第一个银行卡
- 第二张银行卡总是被分配到第二个地址
- 第二个网络总是被分配到第二个银行卡
- 等等

WITH
    sorted_address AS
(
    SELECT
        ROW_NUMBER() OVER (PARTITION BY id_user ORDER BY id_address) AS ordinal,
        *
    FROM
        address
),
    sorted_bank_card AS
(
    SELECT
        ROW_NUMBER() OVER (PARTITION BY id_user ORDER BY id_bank_card) AS ordinal,
        *
    FROM
        bank_card
),
    sorted_network AS
(
    SELECT
        ROW_NUMBER() OVER (PARTITION BY id_user ORDER BY id_network) AS ordinal,
        *
    FROM
        network
)
SELECT
    *
FROM
    user
LEFT JOIN
    sorted_address
        ON  sorted_address.id_user   = user.id_user
LEFT JOIN
    sorted_bank_card
        ON  sorted_bank_card.id_user = user.id_user
        AND sorted_bank_card.ordinal = sorted_address.ordinal
LEFT JOIN
    sorted_network
        ON  sorted_network.id_user   = user.id_user
        AND sorted_network.ordinal   = COALESCE(sorted_bank_card.ordinal, sorted_address.ordinal)

作为旁注:我建议不要将user等保留关键字用于表名。