构建许多表的复杂查询

时间:2017-01-18 01:07:28

标签: sql postgresql

我有六张桌子:

Keys( id, name )
Values( id, name )
PropertyRoles( id, name )
Properties( id, name, key_id, value_id, role_id )
Components( id, name, type )
ComponentProperties( id, component_id, property_id, proprole_id )

作为输入,我有一个键值对列表。我想找到所有具有至少一个与键值对相关的属性的组件,其中输入列表的角色名称为X.最后,我想收集有关已创建组件的所有数据(id,name,properties)。

有人可以帮我构建合适的查询吗? 编辑:我完全不知道从哪里开始

SELECT c.id, c.name, p.name FROM components c 
INNER JOIN componentproperties cp ON cp.role = ( SELECT id FROM propertyroles WHERE name = 'primary' ) AND cp.component_id = c.id
INNER JOIN properties p ON p.key_id = ( SELECT id FROM keys WHERE name IN ( %1 ) ) AND  p.value_id = ( SELECT id FROM values WHERE name IN ( %2 ) )
UNION ALL
SELECT cp2.name FROM componentproperties cp2 WHERE cp2.id IN cp.id
UNION ALL
SELECT keys.name FROM keys WHERE keys.id IN %1
UNION ALL
SELECT values.name FROM values WHERE values.id IN %2
ORDER BY c.name;

PS。抱歉我的英文!

1 个答案:

答案 0 :(得分:1)

有几种方法可以执行此操作,具体取决于您拥有的Postgresql版本。我在这里用多个子查询来解决它。

注意:在您Properties - 表中指定了该属性的角色,但您在ComponentProperties中也有。我假设您不希望在多个位置拥有相同的数据,因此在此解决方案中我假设ComponentProperties的role_id不存在。

要解决的第一个问题是如何将key:value-pairs列表放入查询中,以便搜索它们。

一种方法是创建一个(临时)表:

CREATE TEMP TABLE key_value_role AS (
  key text,
  value text,
  role text
);

然后在该表中插入所有值。

INSERT INTO key_value_role VALUES
('Key 1', 'Value 1', 'Role 1'),
('Key 2', 'Value 2', 'Role 1'),
('Key 2', 'Value 3', 'Role 1');

即使有临时表,它也很快变得混乱。幸运的是,VALUES可以单独使用:

SELECT column1 as key, column2 as value, column3 as role
FROM (
  VALUES
    ('Key 1', 'Value 1', 'Role 1'),
    ('Key 2', 'Value 2', 'Role 1'),
    ('Key 2', 'Value 3', 'Role 1')
) AS kvr

如果您使用Postgresql> = 9.3,您可以将数据作为序列化JSON字符串发送,并使用json_each_text迭代键:

SELECT *, 'Role 1'::text as role
FROM json_each_text('{"Key 1":"Value 1", "Key 2":"Value 2"}')

在上面的示例中,我使用了一个文字字符串,但我假设您将使用参数化查询并在程序中编写JSON序列化字符串。

上述方法的限制是指定的密钥只能有一个值。也许这已经足够了,但我想要一个灵活的解决方案,以便能够搜索每个密钥的多个值。

Postgresql中有另一个函数> = 9.3可以使用json_populate_recordset,但它需要一个基类型。基类型可以是现有表,但您也可以创建要使用的类型:

CREATE TYPE key_value_role as(
  key text,
  value text,
  role text
);

指定类型后,可以使用json_populate_recordset

SELECT *
FROM json_populate_recordset(null::key_value_role,'[{"key":"Key 1", "value":"Value 1", "role":"Role 1"},{"key":"Key 2", "value":"Value 2", "role":"Role 1"}, {"key":"Key 2", "value":"Value 3", "role":"Role 1"}]')

示例输出:

   key   |   value   |  role
--------+------------+----------
 "Key 1" | "Value 1" | "Role 1"
 "Key 2" | "Value 2" | "Role 1"
 "Key 2" | "Value 3" | "Role 1"

现在,您可以为每个键填充多个值的对象,并在同一查询中搜索不同的角色。

下一个问题是将名称转换为ID。您没有指定ID的类型。我假设整数。

假设使用上述方法创建的列表名为list,那么从名称到ID的转换可以这样做:

SELECT
  Keys.id as key_id, Values.id as value_id, PropertyRoles.id as role_id,
  Keys.name as key_name, Values.name as value_name, PropertyRoles.name as role_name 
FROM list
  JOIN Keys ON ( list.key = Keys.name )
  JOIN Values ON ( list.value = Values.name )
  JOIN PropertyRoles ON (list.role = PropertyRoles.name )

示例输出:

 key_id | value_id | role_id | key_name | value_name | role_name
--------+----------+---------+----------+------------+-----------
      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"
      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"

通过上表,我们现在可以将它与Properties:

连接起来
SELECT Properties.id as property_id, Properties.name as property_name, kvr.*
FROM Properties
  JOIN (
    -- previous query here
  ) AS kvr ON ( Properties.key_id = kvr.key_id AND Properties.value_id = kvr.value_id AND Properties.role_id = kvr.role_id )

示例输出:

 property_id |    property_name     | key_id | value_id | role_id | key_name | value_name | role_name
-------------+--------------------+--------+----------+---------+----------+------------+-----------
           1 | "Property 01 k1v1r1" |      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
          13 | "Property 13 k2v2r1" |      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"
          16 | "Property 16 k2v3r1" |      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"

上面的表现在可以与ComponentProperties结合使用:

SELECT ComponentProperties.id as ComponentProperties_id, ComponentProperties.component_id, pkvr.*
FROM ComponentProperties
  JOIN (
    -- previous query here
  ) AS pkvr ON ( ComponentProperties.property_id = pkvr.property_id )

示例输出:

  componentproperties_id | component_id | property_id |    property_name     | key_id | value_id | role_id | key_name | value_name | role_name
-----------------------+--------------+-------------+--------------------+--------+----------+---------+----------+------------+-----------
                       1 |            1 |           1 | "Property 01 k1v1r1" |      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
                       2 |            1 |          13 | "Property 13 k2v2r1" |      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"
                       3 |            1 |          16 | "Property 16 k2v3r1" |      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"
                       4 |            2 |           1 | "Property 01 k1v1r1" |      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
                       7 |            3 |          16 | "Property 16 k2v3r1" |      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"
                       9 |            4 |          13 | "Property 13 k2v2r1" |      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"

最后,加入组件:

SELECT Components.name as component_name, Components.type as component_type, cpkvr.*
FROM Components
  JOIN (
    -- previous query here
  ) AS cpkvr ON ( Components.id = cpkvr.component_id )

示例输出:

 component_name |  component_type  | componentproperties_id | component_id | property_id |    property_name     | key_id | value_id | role_id | key_name | value_name | role_name
----------------+----------------+------------------------+--------------+-------------+--------------------+--------+----------+---------+----------+------------+-----------
 "Component 1"  | "Component type" |                      1 |            1 |           1 | "Property 01 k1v1r1" |      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
 "Component 1"  | "Component type" |                      2 |            1 |          13 | "Property 13 k2v2r1" |      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"
 "Component 1"  | "Component type" |                      3 |            1 |          16 | "Property 16 k2v3r1" |      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"
 "Component 2"  | "Component type" |                      4 |            2 |           1 | "Property 01 k1v1r1" |      1 |        1 |       1 | "Key 1"  | "Value 1"  | "Role 1"
 "Component 3"  | "Component type" |                      7 |            3 |          16 | "Property 16 k2v3r1" |      2 |        3 |       1 | "Key 2"  | "Value 3"  | "Role 1"
 "Component 4"  | "Component type" |                      9 |            4 |          13 | "Property 13 k2v2r1" |      2 |        2 |       1 | "Key 2"  | "Value 2"  | "Role 1"

这是整个查询,使用json_populate_recordset:

-- Get components from matching properties
SELECT Components.name as component_name, Components.type as component_type, cpkvr.*
FROM Components
  JOIN (
    -- Get component id from matching properties
    SELECT ComponentProperties.id as ComponentProperties_id, ComponentProperties.component_id, pkvr.*
    FROM ComponentProperties
      JOIN (
        -- Get propety id and name of matching values
        SELECT Properties.id as property_id, Properties.name as property_name, kvr.*
        FROM Properties
          JOIN (
            -- Convert key, value and role names to id
            SELECT
              Keys.id as key_id, Values.id as value_id, PropertyRoles.id as role_id,
              Keys.name as key_name, Values.name as value_name, PropertyRoles.name as role_name 
            FROM
              json_populate_recordset(null::key_value_role,'[{"key":"Key 1", "value":"Value 1", "role":"Role 1"},{"key":"Key 2", "value":"Value 2", "role":"Role 1"}]')
              AS list
              JOIN Keys ON ( list.key = Keys.name )
              JOIN Values ON ( list.value = Values.name )
              JOIN PropertyRoles ON (list.role = PropertyRoles.name )
          ) AS kvr ON ( Properties.key_id = kvr.key_id AND Properties.value_id = kvr.value_id AND Properties.role_id = kvr.role_id )
      ) AS pkvr ON ( ComponentProperties.property_id = pkvr.property_id )
  ) AS cpkvr ON ( Components.id = cpkvr.component_id )
ORDER BY component_name, property_name

以下是我使用的测试数据:

CREATE TYPE key_value_role as(
  key text,
  value text,
  role text
);

create table Keys(
  id integer unique primary key,
  name text unique
);

create table Values(
  id integer unique primary key,
  name text unique
);
create table PropertyRoles(
  id integer unique primary key,
  name text unique
);
create table Properties(
  id integer unique primary key,
  name text,
  key_id integer references Keys,
  value_id integer references Values,
  role_id integer references PropertyRoles
);
create table Components(
  id integer unique primary key,
  name text unique,
  type text
);
create table ComponentProperties(
  id integer unique primary key,
  component_id integer references Components,
  property_id integer references Properties,
  unique ( component_id, property_id )
);

INSERT INTO Keys values
(1, 'Key 1'),
(2, 'Key 2'),
(3, 'Key 3');

INSERT INTO Values values
(1, 'Value 1'),
(2, 'Value 2'),
(3, 'Value 3');

INSERT INTO PropertyRoles values
(1, 'Role 1'),
(2, 'Role 2'),
(3, 'Role 3');

INSERT INTO Properties values
( 1, 'Property 01 k1v1r1', 1, 1, 1),
( 2, 'Property 02 k1v1r2', 1, 1, 2),
( 3, 'Property 03 k1v1r3', 1, 1, 3),
( 4, 'Property 04 k1v2r1', 1, 2, 1),
( 5, 'Property 05 k1v2r2', 1, 2, 2),
( 6, 'Property 06 k1v2r3', 1, 2, 3),
( 7, 'Property 07 k1v3r1', 1, 3, 1),
( 8, 'Property 08 k1v3r2', 1, 3, 2),
( 9, 'Property 09 k1v3r3', 1, 3, 3),
(10, 'Property 10 k2v1r1', 2, 1, 1),
(11, 'Property 11 k2v1r2', 2, 1, 2),
(12, 'Property 12 k2v1r3', 2, 1, 3),
(13, 'Property 13 k2v2r1', 2, 2, 1),
(14, 'Property 14 k2v2r2', 2, 2, 2),
(15, 'Property 15 k2v2r3', 2, 2, 3),
(16, 'Property 16 k2v3r1', 2, 3, 1),
(17, 'Property 17 k2v3r2', 2, 3, 2),
(18, 'Property 18 k2v3r3', 2, 3, 3),
(19, 'Property 19 k3v1r1', 3, 1, 1),
(20, 'Property 20 k3v1r2', 3, 1, 2),
(21, 'Property 20 k3v1r3', 3, 1, 3),
(22, 'Property 20 k3v2r1', 3, 2, 1),
(23, 'Property 20 k3v2r2', 3, 2, 2),
(24, 'Property 20 k3v2r3', 3, 2, 3),
(25, 'Property 20 k3v3r1', 3, 3, 1),
(26, 'Property 20 k3v3r2', 3, 3, 2),
(27, 'Property 20 k3v3r3', 3, 3, 3);


INSERT INTO Components values
(1, 'Component 1', 'Component type'),
(2, 'Component 2', 'Component type'),
(3, 'Component 3', 'Component type'),
(4, 'Component 4', 'Component type');

INSERT INTO ComponentProperties values
(1, 1, 1),
(2, 1, 3),
(3, 1, 5),
(4, 2, 1),
(5, 2, 5),
(6, 2, 6),
(7, 3, 1),
(8, 4, 5),
(9, 4, 6);