通常よりも詳細なレポート– Microsoft Access
通常、レポートを作成するときは、通常、より詳細にレポートを作成します。たとえば、クライアントは通常、売上の月次レポートを求めています。データベースは個々の売上を単一のレコードとして保存するため、各月の数値を合計しても問題ありません。年と同じように、またはサブカテゴリからカテゴリに移動することもできます。
しかし、彼らが下に行く必要があるとしましょう ?より可能性が高いのは、「データベース設計は良くない」ということです。スクラップしてやり直してください!」結局のところ、データに適切な粒度を持たせることは、堅固なデータベースにとって不可欠です。しかし、これは正規化が行われなかった場合ではありませんでした。在庫と収益を考慮し、FIFO方式でそれらを処理する必要性を考えてみましょう。私はCBAではないことを指摘するためにすぐに立ち去ります。私が行う会計上の主張は、最大限の疑いを持って扱われるべきです。不明な点がある場合は、会計士に連絡してください。
免責事項がわからなくなったところで、現在データをどのように保存しているかを見てみましょう。この例では、製品の購入を記録してから、購入したばかりの購入の売上を記録する必要があります。
1つの製品に対して3つの購入があるとします。Date | Qty | Per-Cost
9/03 | 3 | $45
9/08 | 6 | $40
9/09 | 8 | $50
その後、これらの製品をさまざまな機会にさまざまな価格で販売します。Date | Qty | Per-Price
9/05 | 2 | $60
9/07 | 1 | $55
9/10 | 4 | $50
9/12 | 3 | $60
9/15 | 3 | $65
9/19 | 4 | $55
粒度はトランザクションレベルであることに注意してください。購入ごと、注文ごとに1つのレコードを作成します。これは非常に一般的で論理的に理にかなっています。特定のトランザクションに対して指定された価格で販売した製品の数量を入力するだけで済みます。
OK、あなたが否認した会計関連のものはどこですか?
レポートの場合、製品の各ユニットで得た収益を計算する必要があります。彼らは、FIFO方式で製品を処理する必要があると言っています。つまり、購入した最初の製品ユニットは、注文する最初の製品ユニットである必要があります。次に、その製品ユニットで作成したマージンを計算するには、その特定の製品ユニットのコストを調べて、注文した価格から差し引く必要があります。
粗利益=製品の収益–製品のコスト
地球を壊すものは何もありませんが、待って、購入と注文を見てください! 3つの異なるコストポイントで3つの購入しかありませんでしたが、3つの異なる価格ポイントで6つの注文がありました。では、どのコストポイントがどの価格ポイントに移行するのでしょうか?
FIFO方式で粗利益を計算するこの単純な式では、製品の個々の単位の粒度に移動する必要があります。データベースのどこにもありません。ユーザーが製品のユニットごとに1つのレコードを入力することを提案した場合、かなり大きな抗議があり、おそらくいくつかの名前が呼ばれることになると思います。では、どうすればよいですか?
解散
会計上の目的で、購入日を使用して製品の個々のユニットを並べ替えるとします。これがどのように出てくるかです:
Line # | Purch Date | Order Date | Per-Cost | Per-Price
1 | 9/03 | 9/05 | $45 | $60
2 | 9/03 | 9/05 | $45 | $60
3 | 9/03 | 9/07 | $45 | $55
4 | 9/08 | 9/10 | $40 | $50
5 | 9/08 | 9/10 | $40 | $50
6 | 9/08 | 9/10 | $40 | $50
7 | 9/08 | 9/10 | $40 | $50
8 | 9/08 | 9/12 | $40 | $60
9 | 9/08 | 9/12 | $40 | $60
10 | 9/09 | 9/12 | $50 | $60
11 | 9/09 | 9/15 | $50 | $65
12 | 9/09 | 9/15 | $50 | $65
13 | 9/09 | 9/15 | $50 | $65
14 | 9/09 | 9/19 | $50 | $55
15 | 9/09 | 9/19 | $50 | $55
16 | 9/09 | 9/19 | $50 | $55
17 | 9/09 | 9/19 | $50 | $55
内訳を調べると、1回の購入で一部の製品を消費するために注文が発生し、別の購入で履行される注文がある場合は重複していることがわかります。
前述のように、実際にはデータベースのどこにもその17行はありません。購入は3行、注文は6行しかありません。どちらのテーブルから17行を取得するにはどうすればよいですか?
泥を追加する
しかし、まだ終わっていません。たまたま17ユニットの購入の完璧なバランスがあり、同じ製品の17ユニットの注文に対抗するという理想的な例を示しました。実生活では、それほどきれいではありません。時々私達は余分な製品を残されます。ビジネスモデルによっては、在庫よりも多くの注文を保持できる場合もあります。株式市場でプレーしている人は、売り切れなどを認識しています。
不均衡の可能性は、すべてのコストと価格を単純に合計し、次に差し引いてマージンを得るという近道をとることができない理由でもあります。 Xユニットが残っている場合、在庫を計算するためのコストポイントを知る必要があります。同様に、1回の購入で1つのコストポイントで未履行の注文が適切に履行されるとは限りません。したがって、私たちが行う計算は、理想的な例だけでなく、過剰な在庫や未履行の注文がある場合にも機能する必要があります。
まず、検討する必要のある製品の初期化の数を把握する問題に取り組みましょう。注文したユニットの数量または購入したユニットの数量の単純なSUM()では不十分であることは明らかです。いいえ、むしろ、購入した製品の数量と注文した製品の数量の両方をSUM()する必要があります。次に、SUM()を比較し、上位のものを選択します。次のクエリから始めることができます:
WITH ProductPurchaseCount AS (
SELECT
p.ProductID,
SUM(p.QtyBought) AS TotalPurchases
FROM dbo.tblProductPurchase AS p
GROUP BY p.ProductID
), ProductOrderCount AS (
SELECT
o.ProductID,
SUM(o.QtySold) AS TotalOrders
FROM dbo.tblProductOrder AS o
GROUP BY o.ProductID
)
SELECT
p.ProductID,
IIF(ISNULL(pc.TotalPurchases, 0) > ISNULL(oc.TotalOrders, 0), pc.TotalPurchases, oc.TotalOrders) AS ProductTransactionCount
FROM dbo.tblProduct AS p
LEFT JOIN ProductPurchaseCount AS pc
ON p.ProductID = pc.ProductID
LEFT JOIN ProductOrderCount AS oc
ON p.ProductID = oc.ProductID
WHERE NOT (pc.TotalPurchases IS NULL AND oc.TotalOrders IS NULL);
ここで行っているのは、3つの論理的なステップに分割することです。
a)製品によって購入された数量のSUM()を取得します
b)製品によって注文された数量のSUM()を取得します
購入はあるが注文がない商品や、注文はあるが購入していない商品があるかどうかわからないため、2つのテーブルを結合したままにすることはできません。そのため、知りたいすべてのProductIDの信頼できるソースとして製品テーブルを使用します。これにより、3番目のステップに進みます。
c)合計を製品と照合し、製品にトランザクション(たとえば、これまでに行われた購入または注文のいずれか)があるかどうかを判断し、ある場合は、ペアの数が多い方を選択します。これは、製品が行ったトランザクションの総数です。
しかし、なぜトランザクションがカウントされるのですか?
ここでの目的は、購入または注文のいずれかに参加した製品の個々のユニットを適切に表すために、製品ごとに生成する必要のある行数を把握することです。最初の理想的な例では、3回の購入と6回の注文があり、どちらも購入してから注文した合計17ユニットの製品にバランスが取れていたことを思い出してください。その特定の製品については、上の図にあるデータを生成するために17行を作成できる必要があります。
では、1行の17の単一の値を17行に変換するにはどうすればよいでしょうか。そこで、タリーテーブルの魔法が入ります。
タリーテーブルについて聞いたことがない場合は、今すぐ行う必要があります。集計表の件名を他の人に記入させます。ここ、ここ、ここ。言うまでもなく、SQLツールキットに含まれているのは手ごわいツールです。
上記のクエリを修正して、最後の部分がProductTransactionCountという名前のCTEになると仮定すると、次のようにクエリを記述できます。<the 3 CTEs from previous exampe>
INSERT INTO tblProductTransactionStaging (
ProductID,
TransactionNumber
)
SELECT
c.ProductID,
t.Num AS TransactionNumber
FROM ProductTransactionCount AS c
INNER JOIN dbo.tblTally AS t
ON c.TransactionCount >= t.Num;
そしてペスト!これで、アカウンティングを実行する必要のある製品ごとに、必要な数の行が正確に作成されました。 ON句の式に注意してください-三角形の結合を行っています-薄い空気から17行を生成したいので、通常の等式演算子を使用していません。 CROSSJOINとWHERE句でも同じことができることに注意してください。両方を試して、どちらが効果的かを見つけてください。
トランザクション数を増やす
したがって、一時テーブルに適切な行数を設定します。次に、購入と注文に関するデータをテーブルに入力する必要があります。図からわかるように、購入日と注文日までにそれぞれ注文できる必要があります。そこで、ROW_NUMBER()と集計テーブルが役に立ちます。
SELECT
p.ProductID,
ROW_NUMBER() OVER (PARTITION BY p.ProductID ORDER BY p.PurchaseDate, p.PurchaseID) AS TransactionNumber,
p.PurchaseDate,
p.CostPer
FROM dbo.tblProductPurchase AS p
INNER JOIN dbo.tblTally AS t
ON p.QtyBought >= t.Num;
タリーのNum列を使用できるのに、なぜROW_NUMBER()が必要なのか不思議に思うかもしれません。答えは、複数の購入がある場合、Numはその購入数量と同じだけ高くなるが、17まで高くする必要があるということです。つまり、3、6、および8ユニットの3つの別々の購入の合計です。したがって、ProductIDでパーティション化するのに対し、tallyのNumはPurchaseIDでパーティション化されていると言えますが、これは私たちが望んでいることではありません。
SQLを実行すると、購入した製品のユニットごとに、購入日順に並べられた行が返される、すばらしいブレークアウトが得られます。また、PurchaseIDで並べ替えて、同じ日に同じ商品を複数購入した場合に対処するため、コストあたりの数値が一貫して計算されるように、なんらかの方法でタイを解除する必要があることに注意してください。次に、購入で一時テーブルを更新できます。
WITH PurchaseData AS (
<previous query>
)
MERGE INTO dbo.tblProductTransactionStaging AS t
USING PurchaseData AS p
ON t.ProductID = p.ProductID
AND t.TransactionNumber = p.TransactionNumber
WHEN MATCHED THEN UPDATE SET
t.PurchaseID = p.PurchaseID,
t.PurchaseDate = p.PurchaseDate,
t.CostPer = p.CostPer;
注文の部分は基本的に同じです。「購入」を「注文」に置き換えるだけで、投稿の最初の元の図と同じようにテーブルがいっぱいになります。
そして、この時点で、製品をトランザクションのレベルから、商品のコストを収益に正確にマッピングする必要のある単位のレベルに分割したので、他のすべての種類の会計の良さを実行する準備が整いました。会計士の要求に応じてFIFOまたはLIFOを使用する特定の製品ユニット。計算は基本的なものになりました。
OLTPの世界における粒度
粒度の概念は、OLTPアプリケーションよりもデータウェアハウスで一般的な概念ですが、説明したシナリオは、OLTPのスキーマの現在の粒度を明確に特定する必要性を浮き彫りにしていると思います。ご覧のとおり、最初は粒度が間違っていたため、レポートを作成するために必要な粒度を取得できるように、やり直す必要がありました。この場合、すべてのコンポーネントデータがすでに存在しているため、粒度を正確に下げることができ、データを変換するだけで済みました。これは常に当てはまるわけではなく、スキーマが十分に細かくない場合は、スキーマの再設計が必要になる可能性が高くなります。それでも、要件を満たすために必要な粒度を特定することは、その目標に到達するために実行する必要のある論理的な手順を明確に定義するのに役立ちます。
ポイントを示す完全なSQLスクリプトは、DemoLowGranularity.sqlを取得できます。