我有一些计算,想在查询中执行此操作。
存在具有一对多关系的parent和children表:
CREATE TABLE `parent` (
`id` int NOT NULL AUTO_INCREMENT,
`value` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `children` (
`id` int NOT NULL AUTO_INCREMENT,
`parent_id` int NOT NULL,
`multiple` decimal(10,2) DEFAULT NULL,
`sum` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)
);
为了找到父级的最终值,我应该迭代子级并计算以下公式:
newParentValue = childMultiple(parentValue + childSum)
代码中的实现如下:
function calculateFinalParentValue($parentValue, $children)
{
foreach ($children as $child) {
$parentValue = $child['multiple'] * ($parentValue + $child['sum']);
}
return $parentValue;
}
如何实现查询中的计算?
我尝试这种方式(使用临时变量):
set @value = 0;
SELECT
p.id,
@value := (c.multiple * (@value + c.sum)) AS value
FROM
parent p
JOIN
children c ON p.id = c.parent_id AND @value := p.value;
我在连接条件中设置变量
(@value := p.value)
以重置每个新父级的变量。
此查询返回每个父级的行以及子级的数量,我需要每个父级连接中的最后一行作为答案。
但是这种方式不可持续,有更好的方式吗?
示例:
mysql> select * from parent;
+----+-------+
| id | value |
+----+-------+
| 1 | 10.00 |
| 2 | 20.00 |
+----+-------+
mysql> select * from children;
+----+-----------+----------+------+
| id | parent_id | multiple | sum |
+----+-----------+----------+------+
| 1 | 1 | 1.00 | 1.00 |
| 2 | 1 | 1.00 | 1.00 |
| 3 | 1 | 1.00 | 1.00 |
| 4 | 2 | 2.00 | 2.00 |
| 5 | 2 | 2.00 | 2.00 |
+----+-----------+----------+------+
根据以上数据,我期望得到以下答案:
+----+--------+
| id | value |
+----+--------+
| 1 | 11.00 |
| 1 | 12.00 |
| 1 | 13.00 | <- final value for parant.id = 1
| 2 | 44.00 |
| 2 | 92.00 | <- final value for parant.id = 2
+----+--------+
对于parent.id=1,有三个孩子,parent.value为10,因此在计算第一个孩子的公式后,新值是
1 * (10 + 1) = 11
,第二个孩子的值是1 * (11 + 1) = 12
,正如预期的那样,第三个孩子的值是1 * (12 + 1) = 13
(在所有三个孩子中都是倍数,总和等于1)。
对于parent.id=2,有两个孩子,parent.value为20,因此在计算第一个孩子的公式后,新值是
2 * (20 + 2) = 44
,第二个孩子的值是2 * (44 + 2) = 92
(两个孩子都是倍数,总和等于2)。
最后我只想要每个父母的最终值,所以我的最终预期结果是:
+----+--------+
| id | value |
+----+--------+
| 1 | 13.00 |
| 2 | 92.00 |
+----+--------+
使用
ROW_NUMBER()
窗口函数对 children
的行进行排序,按 parent_id
分区并按 id
和 SUM()
窗口函数排序,以获得所需的总和。FIRST_VALUE()
窗口函数得到每个id的最后和:
WITH
cte_children AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY id) rn FROM children),
cte_sums AS (
SELECT p.id,
c.rn,
POW(c.multiple, c.rn) * p.value + SUM(POW(c.multiple, c.rn)) OVER (PARTITION BY p.id ORDER BY c.rn) * c.sum value
FROM parent p INNER JOIN cte_children c
ON c.parent_id = p.id
)
SELECT DISTINCT id,
FIRST_VALUE(value) OVER (PARTITION BY id ORDER BY rn DESC) value
FROM cte_sums;
查看演示。
有点棘手,因为当父母改变时,你必须重置你的值。
尝试以下查询:
SELECT
parentId,
ROUND(iteratingValue, 2) reqValue
FROM
(SELECT
parentId,
`childMultiple`,
childSum,
@running_parent,
(CASE WHEN @current_parent_value=0 THEN @current_parent_value:=parentValue ELSE @current_parent_value=@current_parent_value END) ,
(CASE WHEN @running_parent!=parentId THEN @current_parent_value:=parentValue ELSE @current_parent_value:=@current_parent_value END),
@current_parent_value:=(`childMultiple`*(@current_parent_value+childSum)) AS iteratingValue,
@running_parent:=parentId
FROM (SELECT
p.`id` parentId,
c.`multiple`childMultiple,
p.`value` parentValue,
c.`sum` AS childSum,
@current_parent_value:=0,
@running_parent:=0
FROM parent p
JOIN `children` c ON c.`parent_id`=p.`id`
) subTable ORDER BY parentId) finalTable;
您还可以将上面提到的
CASE
语句替换为IF
(更具可读性)
IF(@current_parent_value=0, @current_parent_value:=parentValue, @current_parent_value=@current_parent_value),
IF(@running_parent!=parentId, @current_parent_value:=parentValue, @current_parent_value:=@current_parent_value),
它应该给你你想要的输出。
我使用了两个变量
@current_parent_value
和 @running_parent
@running_parent
将帮助您确定上一行和当前行是否属于同一行parent
并且@current_parent_value
将帮助您存储当前的运行值。
为了获得预期的输出,我们需要首先识别每个父表的最后一个子表,然后将其与父表和子表连接:
SELECT p.id, c.multiple * ( p.value + c.sum )
FROM parent p
INNER JOIN (
select parent_id, max(id) as last_row
from children
group by parent_id
) as s on s.parent_id = p.id
inner join children c on c.id = s.last_row
在 SQL 中,这看起来像:
SELECT
`parent`.`id`,
`children`.`multiple` * ( `parent`.`value` + `children`.`sum` ) as CalculatedValue
FROM `parent`
INNER JOIN `children` ON `parent`.`id` = `children`.`parent_id`