0

我计划为商业智能系统设计一个数据库模型,该系统存储一组位置和一组年份的业务数据。

其中一些数字应根据同一年份和同一地点的其他数字计算得出。在下文中,我将把未计算的数字称为“基本数字”。为了存储基本数字,具有这些列的表格设计是有意义的:

| year | location_id | goods_costs | marketing_costs | warehouse_costs | administrative_costs |

使用此表,我可以创建一个计算所有其他必要数字的视图:

CREATE VIEW all_figures
SELECT *,
    goods_costs + marketing_costs + warehouse_costs + administrative_costs
    AS total_costs
FROM basic_figures

如果我没有遇到以下问题,那就太好了:

  1. 大多数数据库(包括我打算使用的 MySQL [编辑:但我不受约束])都有某种列数或行大小限制。由于我必须存储很多数字(并且必须计算更多),我会超过这个限制。
  2. 必须添加新数字的情况并不少见。(添加一个图形需要更改表格设计。由于这些更改通常表现不佳,它们会在很长一段时间内阻止对表格的任何访问。)
  3. 我还必须为每个数字存储附加信息,例如描述和单位(所有数字都是十进制数字,但有些可能是美元/欧元,而其他可能是百分比)。如果有任何变化,我必须确保 basic_figures 表、all_figures 视图和包含图形信息的表都正确更新。(这更像是一个数据规范化问题,而不是技术/实施问题。)

~~

因此我考虑使用这个表设计:

+---------+-------------+-------------+-------+
| year    | location_id | figure_id   | value |
+---------+-------------+-------------+-------+
|    2009 |           1 | goods_costs |   300 |
...

这种类似实体属性值的设计可能是这三个问题的第一个解决方案。然而,它也有一个新的缺点:计算变得混乱。真是乱七八糟。

要构建与上述类似的视图,我必须使用这样的查询:

(SELECT * FROM basic_figures_eav)
UNION ALL
(SELECT a.year_id, a.location_id, "total_costs", a.value + b.value + c.value + d.value
  FROM basic_figures_eav a
  INNER JOIN basic_figures_eav b ON a.year_id = b.year_id AND a.location_id = b.location_id AND b.figure_id = "marketing_costs"
  INNER JOIN basic_figures_eav c ON a.year_id = c.year_id AND a.location_id = c.location_id AND c.figure_id = "warehouse_costs"
  INNER JOIN basic_figures_eav d ON a.year_id = d.year_id AND a.location_id = d.location_id AND d.figure_id = "administrative_costs"
 WHERE a.figure_id = "goods_costs");

这不是美女吗?请注意,这只是对 ONE 数字的查询。所有其他计算的数字(其中有很多我上面写的)也必须与这个查询联合。

~~


在对我的问题进行了长时间的解释之后,我现在以我的实际问题结束:

  1. 您会建议哪种数据库设计?/ 你会使用上述两种设计中的一种吗?(如果是,是哪个,为什么?如果不是,为什么?)
  2. 您对完全不同的方法有什么建议吗?(我会非常非常感谢!)
  3. 毕竟数据库真的应该是进行计算的那个吗?将计算转移到应用程序逻辑并简单地存储结果是否更有意义?

顺便说一句:我已经在 MySQL 论坛上问过类似的问题。但是,由于答案有点稀疏,而且这毕竟不仅仅是 MySQL 问题,所以我完全重写了我的问题并将其发布在这里。(所以这不是一个交叉帖子。)这是那里的线程链接: http://forums.mysql.com/read.php?125,560752,560752#msg- 560752

4

3 回答 3

1

问题(至少在某种程度上)特定于 DBMS。

如果您可以考虑其他 DBMS,您可能想查看 PostgreSQL,它的hstore数据类型本质上是一个键/值对。

其缩小范围是,您会丢失数据类型检查,因为所有内容都作为字符串存储在地图中。

您针对的设计称为“实体属性值”。您可能还想找到其他替代方案。

编辑,这是一个如何使用它的例子:

表设置

CREATE TABLE basic_figures
(
  year_id         integer,
  location_id     integer,
  figures         hstore
);

insert into basic_figures (year_id, location_id, figures)
values
(1, 1, hstore ('marketing_costs => 200, goods_costs => 100, warehouse_costs => 400')),
(1, 2, hstore ('marketing_costs => 50, goods_costs => 75, warehouse_costs => 250')),
(1, 3, hstore ('adminstrative_costs => 100'));

基本选择

select year_id, 
       location_id,
       to_number(figures -> 'marketing_costs', 'FM999999') as marketing_costs,
       to_number(figures -> 'goods_costs', 'FM999999') as goods_costs,
       to_number(figures -> 'warehouse_costs', 'FM999999') as warehouse_costs,
       to_number(figures -> 'adminstrative_costs', 'FM999999') as adminstrative_costs
from basic_figures bf;

为它创建一个隐藏 hstore 值转换的视图可能更容易。这样做的缺点是,每次添加新的成本类型时都需要重新创建视图。

获取总数

要获取每个 year_id/location_id 的所有成本总和,您可以使用以下语句:

SELECT year_id, 
       location_id, 
       sum(to_number(value, '99999')) as total
FROM (
   SELECT year_id, 
          location_id, 
          (each(figures)).key,  
          (each(figures)).value
   FROM basic_figures
) AS data
GROUP BY year_id, location_id;
年号 | location_id | 全部的
---------+-------------+------
       1 | 3 | 100
       1 | 2 | 375
       1 | 1 | 700

这可以加入到上面的查询中,但是如果您创建一个计算单个hstore列中所有键的总数的函数,它可能会更快更容易使用:

汇总总数的函数

create or replace function sum_hstore(figures hstore)
  returns bigint
as
$body$
declare 
   result bigint;
   figure_values text[];
begin
  result := 0;
  figure_values := avals(figures);
  for i in 1..array_length(figure_values, 1) loop
     result := result + to_number(figure_values[i], '999999');
  end loop;
  return result;
end;
$body$
language plpgsql;

该功能可以很容易地在第一个选择中使用:

select bf.year_id, 
       bf.location_id,
       to_number(bf.figures -> 'marketing_costs', '99999999') as marketing_costs,
       to_number(bf.figures -> 'goods_costs', '99999999') as goods_costs,
       to_number(bf.figures -> 'warehouse_costs', '99999999') as warehouse_costs,
       to_number(bf.figures -> 'adminstrative_costs', '99999999') as adminstrative_costs,
       sum_hstore(bf.figures) as total
from basic_figures bf;

自动视图创建

以下 PL/pgSQL 块可用于(重新)创建一个视图,该视图包含一列对应于图形列中的每个键以及基于上述 sum_hstore 函数的总计:

do
$body$
  declare
     create_sql text;
     types record;
  begin
     create_sql := 'create or replace view extended_figures as select year_id, location_id ';

     for types in SELECT distinct (each(figures)).key as type_name FROM basic_figures loop
        create_sql := create_sql || ', to_number(figures -> '''||types.type_name||''', ''9999999'') as '||types.type_name;
     end loop;
     create_sql := create_sql ||', sum_hstore(figures) as total from basic_figures';
     execute create_sql;
  end;
$body$
language plpgsql;

运行该功能后,您可以简单地执行以下操作:

从extended_figures中选择*

并且您将获得与不同成本类型一样多的列。

请注意,如果 hstore 中的值实际上是数字,则根本没有错误检查。这可以通过触发器来完成。

于 2012-07-29T15:26:08.430 回答
0

这是一种无需旋转即可“非规范化”(旋转)EAV 表的方法。请注意左 JOIN 和合并,这会导致不存在的行显示为“零成本”。注意:我必须将字符串文字的引用替换为单引号。

CREATE TABLE basic_figures_eav
        ( year_id INTEGER
        , location_id INTEGER
        , figure_id varchar
        , value INTEGER
        );

INSERT INTO basic_figures_eav ( year_id , location_id , figure_id , value ) VALUES
        (1,1,'goods_costs', 100)
        , (1,1,'marketing_costs', 200)
        , (1,1,'warehouse_costs', 400)
        , (1,1,'administrative_costs', 800)
        , (1,2,'goods_costs', 100)
        , (1,2,'marketing_costs', 200)
        , (1,2,'warehouse_costs', 400)
        , (1,3,'administrative_costs', 800)
        ;

SELECT x.year_id, x.location_id
        , COALESCE (a.value,0) AS goods_costs
        , COALESCE (b.value,0) AS marketing_costs
        , COALESCE (c.value,0) AS warehouse_costs
        , COALESCE (d.value,0) AS administrative_costs
        --
        , COALESCE (a.value,0)
        + COALESCE (b.value,0)
        + COALESCE (c.value,0)
        + COALESCE (d.value,0)
                               AS total_costs
        -- need this to get all the {year_id,location_id} combinations
        -- that have at least one tuple in the EAV table
        FROM (
                SELECT DISTINCT year_id, location_id
                FROM basic_figures_eav
                -- WHERE <selection of wanted observations>
                ) AS x
LEFT JOIN basic_figures_eav a ON a.year_id = x.year_id AND a.location_id = x.location_id AND a.figure_id = 'goods_costs'
LEFT JOIN basic_figures_eav b ON b.year_id = x.year_id AND b.location_id = x.location_id AND b.figure_id = 'marketing_costs'
LEFT JOIN basic_figures_eav c ON c.year_id = x.year_id AND c.location_id = x.location_id AND c.figure_id = 'warehouse_costs'
LEFT JOIN basic_figures_eav d ON d.year_id = x.year_id AND d.location_id = x.location_id AND d.figure_id = 'administrative_costs'
        ;

结果:

CREATE TABLE
INSERT 0 8
 year_id | location_id | goods_costs | marketing_costs | warehouse_costs | administrative_costs | total_costs 
---------+-------------+-------------+-----------------+-----------------+----------------------+-------------
       1 |           3 |           0 |               0 |               0 |                  800 |         800
       1 |           2 |         100 |             200 |             400 |                    0 |         700
       1 |           1 |         100 |             200 |             400 |                  800 |        1500
(3 rows)
于 2012-07-29T15:53:44.957 回答
0

我只想指出,您查询的后半部分是不必要的复杂。你可以做:

(SELECT a.year_id, a.location_id, "total_costs", 
        sum(a.value)
 FROM basic_figures_eav a
 where a.figure_id in ('marketing_costs', 'warehouse_costs', 'administrative_costs',
                       'goods_costs')
)

尽管这使用聚合,在 year_id、location_id 和 figure_id 上使用复合索引,但性能应该相似。

至于您的其余问题,数据库限制列数存在问题。我建议您将基本数据放在具有自动递增主键的表中。然后,创建由相同主键链接的汇总表。

在许多环境中,您可以每天或每晚重新创建一次汇总表。如果您需要实时信息,您可以使用存储过程/触发器来更新数据。也就是说,当更新或插入数据时,可以在汇总表中对其进行修改。

此外,我试图找出 SQL Server 中的计算/计算列是否计入表中的最大列数 (1,024)。我找不到任何确定的东西。这很容易测试,但我现在不在数据库附近。

于 2012-07-29T19:46:04.280 回答