คำนวณค่าแถวตามค่าแถวก่อนหน้าและค่าจริง


9

สวัสดีทุกคนและขอบคุณสำหรับความช่วยเหลือของคุณ
ฉันมีสถานการณ์ต่อไปนี้: ตารางที่เรียกว่า statement ที่มีฟิลด์id (int), stmnt_date (date), เดบิต (double), credit (double) และbalance (double) โครงสร้างของโต๊ะของฉัน

ฉันต้องการคำนวณยอดคงเหลือตามกฎเหล่านี้:

ยอดคงเหลือแถวแรก ( ตามลำดับ ) = เดบิต - เครดิตและส่วนที่เหลือของแถว

current row balance = สมดุลแถวก่อนหน้า + การเดบิตแถวปัจจุบัน - เครดิตแถวปัจจุบัน

อย่างที่คุณเห็นในภาพด้านบนแถวไม่ได้จัดเรียงตามวันที่และนั่นคือเหตุผลที่ฉันใช้คำตามลำดับเวลาสองครั้งเพื่อเน้นความสำคัญของค่า stmnt_date

ขอบคุณมากสำหรับความช่วยเหลือของคุณ.


คุณสามารถเข้าร่วมบัตรเดบิตและเครดิตในฟิลด์เดียวได้หรือไม่? ถ้าเป็นเช่นนั้นคุณสามารถใช้ค่าลบเป็นเดบิตและค่าบวกเป็นเครดิต
Mike

1
สำหรับคำถามในอนาคต (เนื่องจากได้รับคำตอบแล้ว) รหัสไปรษณีย์เป็นข้อความไม่ใช่หน้าจอพิมพ์ รวมถึงCREATE TABLEข้อความสั่งและข้อมูลตัวอย่าง (พร้อมINSERT)
ypercubeᵀᴹ

เป็นการแสดงความเคารพต่อคำตอบของคุณ @ypercube สำหรับทุกคนที่อ่านข้อความนี้ฉันได้เพิ่ม CREATE TABLE และ INSERT ตัวอย่างด้านล่างdba.stackexchange.com/a/183207/131900
Zack Morris

คำตอบ:


8

สมมติว่า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)

6

ฉันคิดว่าคุณสามารถลองต่อไปนี้:

set @balance := 0;

SELECT stmnt_date, debit, credit, (@balance := @balance + (debit - credit)) as Balance
FROM statements
ORDER BY stmnt_date;

2

คำตอบของ ypercubeนั้นยอดเยี่ยมมาก (ฉันไม่เคยเห็นการสร้างตัวแปรภายในเคียวรีเดียวผ่านตัวเลือกแบบจำลอง) ดังนั้นนี่คือคำสั่ง CREATE TABLE เพื่อความสะดวกของคุณ

สำหรับรูปภาพข้อมูลแบบตารางใน Google Image Search คุณสามารถใช้https://convertio.co/ocr/หรือhttps://ocr.space/เพื่อแปลงเป็นเอกสารข้อความ จากนั้นหาก OCR ตรวจไม่พบคอลัมน์อย่างถูกต้องและคุณมี Mac ให้ใช้TextWranglerโดยใช้ปุ่มตัวเลือกค้างไว้เพื่อทำการเลือกแบบสี่เหลี่ยมผืนผ้าและย้ายคอลัมน์ไปมา การรวมกันของตัวแก้ไข SQL เช่นSequel Pro , TextWrangler และสเปรดชีตเช่น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)
*/

1

ตารางการเข้าร่วมด้วยตนเองมันไม่เร็วมากบนโต๊ะขนาดใหญ่ ดังนั้นการจัดการกับงานนี้ใน PostgreSQL ฉันตัดสินใจที่จะใช้ฟังก์ชั่นทริกเกอร์สำหรับการคำนวณ "สมดุล" ฟิลด์ที่เก็บไว้ การคำนวณทั้งหมดจะเกิดขึ้นเพียงครั้งเดียวสำหรับแต่ละแถว

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;

-1

ในตัวอย่างเช่น MSSQL:

ใช้คำสั่ง with () เพื่อสร้าง CTE นี่คือชุดผลลัพธ์ชั่วคราวซึ่งจะแสดงค่าของแต่ละแถว คุณสามารถใช้คณิตศาสตร์ในคำสั่ง with เพื่อสร้างคอลัมน์ที่ส่วนท้ายโดยใช้คณิตศาสตร์เพื่อแสดงผลรวมของแถวคือ DEBIT-CREDIT ในคำสั่ง with ของคุณคุณจะต้องกำหนดหมายเลขแถวให้กับแต่ละแถวใช้ส่วนคำสั่ง OVER ของ WITH () เพื่อเรียงลำดับโดย stmnt_date

จากนั้นเข้าร่วมตารางซ้ำโดยใช้ a.ROWNUMBER = b.ROWNUMBER-1 หรือ +1 ซึ่งจะช่วยให้คุณอ้างอิง a.total + b.total = ผลรวมของแถวนี้และแถวก่อนหน้า

ฉันขอขอบคุณที่ฉันไม่ได้ให้รหัส แต่นี่เป็นวิธีการปฏิบัติเพื่อให้บรรลุนี้ ฉันสามารถจัดหารหัสได้หากมีการร้องขอ :)


1
คำถามเกี่ยวกับ MySQL ในขณะที่มันไม่ได้แย่ (ตรงกันข้าม) เพื่อให้รหัสของสิ่งนี้สามารถทำได้กับ CTEs หรือฟังก์ชั่นหน้าต่างใน DBMS ที่มีมัน (เช่น Postgres, SQL-Server, DB2, Oracle, ... รายการมีความยาว) คุณ อย่างน้อยควรให้รหัสเกี่ยวกับวิธีการทำเช่นนี้ใน MySQL
ypercubeᵀᴹ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.