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. また、説明や単位など、各図の追加情報も保存する必要があります (すべての図は 10 進数ですが、US$/EUR の場合もあれば、% の場合もあります)。何か変更があった場合、basic_figures テーブル、all_figures ビュー、および Figure 情報を含むテーブルがすべて正しく更新されていることを確認する必要があります。(これは、技術/実装の問題というよりも、データの正規化の問題です。)

~~

したがって、このテーブル設計を使用することを検討しました。

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

このエンティティ属性値のような設計は、これら 3 つの問題に対する最初の解決策になる可能性があります。ただし、計算が面倒になるという新たな欠点もあります。本当に面倒です。

上記のようなビューを作成するには、次のようなクエリを使用する必要があります。

(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");

それは美しさではありませんか?これは 1 つの Figure に対するクエリにすぎないことに注意してください。他のすべての計算された数値 (上で書いたように多数あります) も、このクエリで UNIONed する必要があります。

~~


私の問題についてのこの長い説明の後、私は実際の質問を締めくくります:

  1. どのデータベース設計を提案しますか? / 上記の 2 つのデザインのいずれかを使用しますか? (「はい」の場合、それはなぜですか?「いいえ」の場合、その理由は?)
  2. まったく別のアプローチの提案はありますか? (私は非常に感謝しています!)
  3. 結局、データベースは実際に計算を行うものである必要がありますか? 計算をアプリケーション ロジックに移動し、結果を単純に格納する方が理にかなっていますか?

ところで、私はすでに MySQL フォーラムで同様の質問をしました。ただし、回答が少しまばらであり、これは単なる MySQL の問題ではないため、質問を完全に書き直してここに投稿しました。(したがって、これはクロスポストではありません。) スレッドへのリンクは次のとおりです

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;
year_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ブロックを使用して、Figures列のキーごとに1つの列と、上記の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 の複合インデックスを使用しますが、パフォーマンスは似ているはずです。

質問の残りの部分については、列の数を制限するデータベースに問題があります。自動インクリメントされた主キーを使用して、基本データをテーブルに配置することをお勧めします。次に、同じ主キーでリンクされた集計テーブルを作成します。

多くの環境では、サマリー テーブルを 1 日に 1 回または 1 晩に 1 回再作成できます。リアルタイムの情報が必要な場合は、ストアド プロシージャ/トリガーを使用してデータを更新できます。つまり、データが更新または挿入されると、要約テーブルで変更できます。

また、SQL Server の計算列/計算列が、テーブル内の列の最大数 (1,024) に対してカウントされるかどうかを調べようとしました。決定的なものを見つけることができませんでした。これはテストするのに十分簡単ですが、私は現在データベースの近くにいません。

于 2012-07-29T19:46:04.280 に答える