小提琴: 请参阅此SQLFiddle:http://sqlfiddle.com/#!9/af05d8/1/0
场景:我需要根据列出收到的工作/服务的表格来更新客户帐户余额。通过向总账表中添加新行并添加新的客户帐户余额(previous_balance-价格),向每个客户帐户提供每个工作/服务的给定价格。
问题:当客户表中只有一项需要更新余额的工作时,提琴/查询工作正常。但是,当客户在作业表中有多于一行的内容时,不会为每个作业使用实际帐户余额-而是为所有作业使用原始帐户余额。
进行澄清:该问题已由问题1中的spencer5973确定。 SELECT正在表中运行,因为它位于语句的开头。通过查询添加到分类帐中的新行不会用于后续的SELECT,这意味着对于作业表中具有多行的客户,不会选择正确的运行总计。
详细信息:
tmptblLedger :所有客户的帐户余额(分类帐条目)的运行列表。每次更改客户帐户余额(付款,购买等)时,都会添加一个新行。每行记录期初余额(与上一个期末余额相同)和新的期末余额。
编辑以澄清:可以通过支付工作(如小提琴和这个问题所示)来减少客户帐户余额,或者通过向帐户付款来增加(未显示),或者否则由与Job表无关的事务更改。这会阻止spencer5973的以下答案起作用。分类帐是客户帐户余额中所有交易/调整的唯一权威来源。
tmptblJobs :一个“负责完成的工作”跟踪表。每次客户完成某项工作(一项工作)时,都会添加一行。该行指定完成了多少工作,以及每项工作的价格。
要求非常简单-必须更新每个客户的分类帐以反映完成的工作。用伪代码:
foreach(row in tmptblJobs)
{
get row.Customer ID;
get Customer's current account balance (MAX LedgerID in Ledger table)
new_balance = subtract (row.NumberOfJobs * row.PricePerJob) from current balance
write new row with new_balance to ledger table
}
当“客户”工作表中只有一行时,查询将正常工作。
问题似乎是,当客户在Jobs表中有多于1行时,Jobs表中的后续行将忽略写入Ledger表的新行(新余额)。而是使用查询之前的原始余额-就像缓存了结果一样。
我可以相信我缺少一些SQL或功能来完成这项工作,但是我不知道是什么...
表结构和示例数据:
DROP TABLE IF EXISTS tmptblLedger;
DROP TABLE IF EXISTS tmptblJobs;
CREATE TABLE `tmptblLedger` (
`LedgerID` int(10) UNSIGNED NOT NULL COMMENT 'Unique transaction ID',
`Timestamp` DATETIME,
`BalanceOpen` float NOT NULL COMMENT 'Last balance for customer before this record was created',
`BalanceClose` float NOT NULL COMMENT 'Balance now that record has been created = BalanceOpen + Adjustment Amount',
`Customer_CustID` int(10) UNSIGNED NOT NULL
);
ALTER TABLE `tmptblLedger`
MODIFY `LedgerID` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1,
ADD UNIQUE KEY (`LedgerID`);
/* some representative data, this example only uses Customer_CustID == 1). All ledgers start at zero, this example has a second ledger entry for CustID == 1 to put the balance to 100 */
INSERT INTO `tmptblLedger` (`Timestamp`, `BalanceOpen`, `BalanceClose`, `Customer_CustID`) VALUES (NOW(), 0, 0, 1), (NOW(), 0, 100, 1), (NOW(), 0, 5, 2), (NOW(), 0, 7, 3);
CREATE TABLE `tmptblJobs` (
`ScheduledTargetID` bigint(20) NOT NULL,
`Customer_CustID` int(10) UNSIGNED NOT NULL,
`NumberOfJobsCompleted` int UNSIGNED,
`PricePerJob` float UNSIGNED
);
ALTER TABLE `tmptblJobs`
MODIFY `ScheduledTargetID` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1,
ADD UNIQUE KEY(`ScheduledTargetID`);
/* insert 3 jobs for CustID 1, and an example 1 job for CustID 2 to represent real world data */
INSERT INTO `tmptblJobs` (`Customer_CustID`, `NumberOfJobsCompleted`, `PricePerJob`) VALUES (1, 2, 5), (1, 1, 3), (1, 1, 1), (2, 1, 1);
查询:
INSERT INTO `tmptblLedger` (`Timestamp`, `BalanceOpen`, `BalanceClose`, `Customer_CustID`)
SELECT
NOW(),
derivedLedger.LedgerCurrentBalance,
derivedLedger.LedgerCurrentBalance - ( @TotalPrice:=(tmptblJobs.NumberOfJobsCompleted * tmptblJobs.PricePerJob) ) AS NEWBALANCE,
tmptblJobs.Customer_CustID
FROM
(
/* Obtain current balance for each CustID, using newest LedgerID */
SELECT
derivedNewestLedgerRow.LedgerCustID,
tmptblLedger.BalanceClose AS LedgerCurrentBalance /* current balance == closing balance of newest ledger entry */
FROM
tmptblLedger
RIGHT JOIN
(
/* Obtain newest LedgerID for each CustID */
SELECT
tmptblLedger.Customer_CustID AS LedgerCustID,
MAX(LedgerID) AS NewestCustLedgerID
FROM
tmptblLedger
GROUP BY tmptblLedger.Customer_CustID
) as derivedNewestLedgerRow ON LedgerID = NewestCustLedgerID
) as derivedLedger
INNER JOIN tmptblJobs ON tmptblJobs.Customer_CustID = derivedLedger.LedgerCustID
WHERE 1;
/* LegerID rows 5, 6 and 7 will now all start from a BalanceOpen of 100, instead of BalanceOpen 100, then 90 (for ScheduledTargetID 1), then 87 (ScheduledTargetID 2), then 86 (ID 3)... */
SELECT * FROM tmptblLedger;
结果:
LedgerID Timestamp BalanceOpen BalanceClose Customer_CustID
1 2019-03-06T16:03:11Z 0 0 1
2 2019-03-06T16:03:11Z 0 100 1
3 2019-03-06T16:03:11Z 0 5 2
4 2019-03-06T16:03:11Z 0 7 3
5 2019-03-06T16:03:11Z 100 90 1
6 2019-03-06T16:03:11Z 100 97 1
7 2019-03-06T16:03:11Z 100 99 1
8 2019-03-06T16:03:11Z 5 4 2
第5、6和7行是错误的。 BalanceClose应该为:
Row 5 = 90
Row 6 = 87
Row 7 = 86
答案 0 :(得分:1)
注释1:“使用INSERT INTO tmptblLedger SELECT ... FROM tmptblLedger ...
语句,SELECT
将不会读取正在插入的行。{{1} }在该表的开头对表运行。我们可以只运行SELECT(不插入INSERT)。这就是要插入的行集。”
评论2:“解决此问题的一种方法是将工作SELECT
汇总到每个客户的单行中。因此,与其添加< em> custid = 1的三行(5,6,7),(每项工作一个),我们将添加一个单行,其balance_open = 100和balance_close = 86。( 100-((2 * 5)+(1 * 3)+(1 * 1)))
评论3:“如果我们需要为custid = 1添加三行(5,6,7),那么open_balance不仅是最新分类帐ID的余额。我们需要包括调整以前的工作。必须有一些方法可以唯一地对工作进行排序,...我在考虑一个相关子查询,该子查询获取当前行之前的工作的SUM(qty * unit_price)。从最新分类账行的余额中获取该余额,以获取当前行的balance_open。”
不可能获得SUM(qty*unit_price)
来读取语句开始执行时不存在的行,而不是在单个SQL语句的上下文中。 (我们可以在讨论事务隔离级别时深入了解杂草,并允许在事务的上下文中进行“脏读”操作,这有可能导致读取一些未提交的更改,但这是不可靠的,无法保证。甚至隔离级别允许脏读取,在执行SELECT
语句时仍然不会发生这种情况。
无法“修复” SQL语句在执行时无法读取正在插入的行。这不是需要解决的问题。这种限制实际上是关系数据库的设计特征。它实际上解决了可能发生的问题。
最重要的是,没有一种方法可以使SELECT读取正在插入的行。
我们需要做的是从要插入的其他行中获取值,然后,我们必须重新计算前几行中的值,并将这些值包括在当前行的结果中。
我们可以使用相关子查询来做到这一点。
当前,SELECT语句的格式为:
INSERT ... SELECT
我们可以引入一个相关子查询来为客户的先前工作获取SELECT NOW()
, d.ledgercurrentbalance
, d.ledgercurrentbalance - (j.numberofjobscompleted * j.priceperjob) AS newbalance
, j.customer_custid
FROM ( SELECT n.ledgercustid
, t.balanceclose AS ledgercurrentbalance /* current balance == closing balance of newest ledger entry */
FROM ( /* Obtain newest LedgerID for each CustID */
SELECT tmptblLedger.Customer_CustID AS LedgerCustID
, MAX(LedgerID) AS NewestCustLedgerID
FROM tmptblLedger l
GROUP BY l.customer_custid
) n
LEFT
JOIN tmptblLedger t
ON t.ledgerid = n.newestcustledgerid
) d
JOIN tmptblJobs j
ON j.customer_custid = d.ledgercustid
。鉴于SUM(qty*price)
在作业表中是唯一的,我们可以将其用于排序。
类似这样的东西:
scheduledtargetid
一些未解决的问题:
每个客户在目标SELECT NOW() AS `Timestamp`
, b.balanceclose - b.prevjobs_cost AS `BalanceOpen`
, b.balanceclose - b.prevjobs_cost - b.currjob_cost AS `BalanceClose`
, b.customer_custid AS `Customer_CustID`
FROM ( SELECT j.scheduledtargetid
, j.customer_custid
, d.balanceclose
, ( j.numberofjobscompleted * j.priceperjob ) AS currjob_cost
, IFNULL(
( /* correlated subquery to get total cost of previous jobs */
SELECT SUM(p.numberofjobscompleted * p.priceperjob)
FROM `tmptblJobs` p
WHERE p.customer_custid = j.customer_custid
AND p.scheduledtargetid < j.scheduledtargetid
)
,0) AS prevjobs_cost
FROM ( SELECT n.ledgercustid
, t.balanceclose
FROM ( /* latest ledgerid for each custid */
SELECT l.customer_custid AS ledgercustid
, MAX(l.ledgerid) AS newestcustledgerid
FROM `tmptblLedger` l
GROUP BY l.customer_custid
) n
LEFT
JOIN `tmptblLedger` t
ON t.ledgerid = n.newestcustledgerid
) d
JOIN `tmptblJobs` j
ON j.customer_custid = d.ledgercustid
) b
ORDER
BY b.customer_custid
, b.scheduledtargetid
表中至少需要有一行。如果不存在该查询,则该查询不会为该客户生成新行。