みなさん、こんにちは。あなたの助けに感謝します。
次の状況があります:フィールドを含むステートメントと呼ばれるテーブルid(int)、stmnt_date(date)、debit (double)、credit(double)およびbalance(double)
次のルールに従って残高を計算したい:
最初の行の残高(年代順)=借方-貸方および残りの行
現在の行の残高=時系列的に前の行の残高+現在の行の借方-現在の行の貸方
上の図からわかるように、行は日付順に並べられていません。そのため、Wordを時系列で2回使用して、stmnt_date値の重要性を強調しました。
手伝ってくれてありがとうございます。
仮定して stmnt_date
にはUNIQUE
制約があります。これはウィンドウ/分析関数を使用するとかなり簡単です。
SELECT
s.stmnt_date, s.debit, s.credit,
SUM(s.debit - s.credit) OVER (ORDER BY s.stmnt_date
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
AS balance
FROM
statements AS s
ORDER BY
stmnt_date ;
残念ながら、MySQLは(まだ)分析関数を実装していません。厳密なSQLを使用するか、テーブルを自己結合するか(100%機能しますが、かなり非効率的です)、または特定のMySQL機能である変数(非常に効率的ですが、テストする必要があります)を使用して、問題を解決できます。 mysqlをアップグレードするときは、結果がまだ正しく、最適化の改善によって損なわれていないことを確認してください)。
SELECT
s.stmnt_date, s.debit, s.credit,
@b := @b + s.debit - s.credit AS balance
FROM
(SELECT @b := 0.0) AS dummy
CROSS JOIN
statements AS s
ORDER BY
stmnt_date ;
データを使用すると、次の結果になります。
+------------+-------+--------+---------+
| stmnt_date | debit | credit | balance |
+------------+-------+--------+---------+
| 2014-05-15 | 3000 | 0 | 3000 |
| 2014-06-17 | 20000 | 0 | 23000 |
| 2014-07-16 | 0 | 3000 | 20000 |
| 2014-08-14 | 0 | 3000 | 17000 |
| 2015-02-01 | 3000 | 0 | 20000 |
+------------+-------+--------+---------+
5 rows in set (0.00 sec)
私はあなたが以下を試すことができると思います:
set @balance := 0;
SELECT stmnt_date, debit, credit, (@balance := @balance + (debit - credit)) as Balance
FROM statements
ORDER BY stmnt_date;
ypercubeの答え は非常に壮観です(そのようなダミーの選択を介した単一のクエリ内での変数の作成を見たことがありません)。そのため、ここにCREATE TABLEステートメントを示します。
Google画像検索の表形式のデータ画像の場合、 https://convertio.co/ocr/ または https://ocr.space/ を使用して、テキスト文書。次に、OCRが列を適切に検出せず、Macを使用している場合は、オプションキーを押しながら TextWrangler を使用して 長方形の選択 を実行し、列を移動します。 Sequel Pro 、TextWranglerのようなSQLエディターと Google Docs のようなスプレッドシートの組み合わせにより、タブで区切られた表形式データの処理が非常に効率的になります。
これらすべてをコメントに含めることができるのであれば、この回答に反対票を投じないでください。
-- DROP TABLE statements;
CREATE TABLE IF NOT EXISTS statements (
id integer NOT NULL AUTO_INCREMENT,
stmnt_date date,
debit integer not null default 0,
credit integer not null default 0,
PRIMARY KEY (id)
);
INSERT INTO statements
(stmnt_date , debit, credit) VALUES
('2014-06-17', 20000, 0 ),
('2014-08-14', 0 , 3000 ),
('2014-07-16', 0 , 3000 ),
('2015-02-01', 3000 , 0 ),
('2014-05-15', 3000 , 0 );
-- this is slightly modified from ypercube's (@b := 0 vs @b := 0.0)
SELECT
s.stmnt_date, s.debit, s.credit,
@b := @b + s.debit - s.credit AS balance
FROM
(SELECT @b := 0) AS dummy
CROSS JOIN
statements AS s
ORDER BY
stmnt_date ASC;
/* result
+------------+-------+--------+---------+
| stmnt_date | debit | credit | balance |
+------------+-------+--------+---------+
| 2014-05-15 | 3000 | 0 | 3000 |
| 2014-06-17 | 20000 | 0 | 23000 |
| 2014-07-16 | 0 | 3000 | 20000 |
| 2014-08-14 | 0 | 3000 | 17000 |
| 2015-02-01 | 3000 | 0 | 20000 |
+------------+-------+--------+---------+
5 rows in set (0.00 sec)
*/
自己結合テーブルは、大きなテーブルではそれほど高速ではありません。そこで、PostgreSQLでこのタスクを処理するために、トリガーされた関数を使用して、格納されたフィールドの「バランス」を計算することにしました。すべての計算は、行ごとに1回だけ行われます。
DROP TABLE IF EXISTS statements;
CREATE TABLE IF NOT EXISTS statements (
id BIGSERIAL,
stmnt_date TIMESTAMP,
debit NUMERIC(18,2) not null default 0,
credit NUMERIC(18,2) not null default 0,
balance NUMERIC(18,2)
);
CREATE OR REPLACE FUNCTION public.tr_fn_statements_balance()
RETURNS trigger AS
$BODY$
BEGIN
UPDATE statements SET
balance=(SELECT SUM(a.debit)-SUM(a.credit) FROM statements a WHERE a.stmnt_date<=statements.stmnt_date)
WHERE stmnt_date>=NEW.stmnt_date;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
CREATE TRIGGER tr_statements_after_update
AFTER INSERT OR UPDATE OF debit, credit
ON public.statements
FOR EACH ROW
EXECUTE PROCEDURE public.tr_fn_statements_balance();
INSERT INTO statements
(stmnt_date , debit, credit) VALUES
('2014-06-17', 20000, 0 ),
('2014-08-14', 0 , 3000 ),
('2014-07-16', 0 , 3000 ),
('2015-02-01', 3000 , 0 ),
('2014-05-15', 3000 , 0 );
select * from statements order by stmnt_date;