如何将固定 billing amount 199 改为数据库指定金额?
支付阿杰
结论
$billing_cost = "SELECT ..." 只是把 SQL 语句赋给了一个字符串变量,并没有执行查询。把这个字符串写入 DECIMAL 类型的 billing_cost 字段时,MySQL 在非严格模式下可能会将它转换为 0.00。
正确的处理方式是根据当前记录的 a_id 查询 agents.agent_billing_amount,拿到金额后再写入 sold_product。
正确写法
下面的示例使用预处理语句,并假设 agents.a_id 对应 $magazine_listing['a_id']:
$billing_date = time() + 604800;
$agent_id = (int) $magazine_listing['a_id'];
/*
* 查询当前 agent 的账单金额
*/
$billing_stmt = $mysqli->prepare(
"SELECT `agent_billing_amount`
FROM `agents`
WHERE `a_id` = ?
LIMIT 1"
);
if (!$billing_stmt) {
throw new RuntimeException($mysqli->error);
}
$billing_stmt->bind_param("i", $agent_id);
$billing_stmt->execute();
$billing_stmt->bind_result($billing_cost);
if (!$billing_stmt->fetch()) {
$billing_stmt->close();
throw new RuntimeException(
"No agent found for a_id: " . $agent_id
);
}
$billing_stmt->close();
if ($billing_cost === null || !is_numeric($billing_cost)) {
throw new RuntimeException(
"Invalid billing amount for a_id: " . $agent_id
);
}
/*
* 豁免 agent 不收费,否则正常收费
*/
$charge = in_array($agent_id, $exempt_agents, true) ? 0 : 1;
/*
* 插入 sold_product
*/
$insert_stmt = $mysqli->prepare(
"INSERT INTO `sold_product`
(
`m_id`,
`moveover_m_id`,
`listing_id`,
`a_id`,
`agent_mls_id`,
`billing_cost`,
`billing_date`,
`billed`,
`charge`
)
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)"
);
if (!$insert_stmt) {
throw new RuntimeException($mysqli->error);
}
$m_id = (int) $magazine_listing['m_id'];
$moveover_m_id = (int) $magazine_listing['moveover_m_id'];
$listing_id = (string) $data['L_ListingID'];
$agent_mls_id = (string) $magazine_listing['agent_mls_id'];
$insert_stmt->bind_param(
"iisissii",
$m_id,
$moveover_m_id,
$listing_id,
$agent_id,
$agent_mls_id,
$billing_cost,
$billing_date,
$charge
);
$insert_stmt->execute();
$insert_id = $mysqli->insert_id;
$insert_stmt->close();
邮件中的金额仍可按下面的方式格式化:
number_format((float) $billing_cost, 2)
为什么会得到 0.00
下面这行代码不会查询数据库:
$billing_cost = "SELECT `agent_billing_amount` FROM `agents`";
此时,$billing_cost 中保存的是以下文本:
SELECT `agent_billing_amount` FROM `agents`
将它插入数值字段时,MySQL 会尝试把这段文本转换成数字。由于它不是合法的数字字符串,在非严格 SQL 模式下通常会被转换为 0,所以最终得到 0.00。如果启用了严格 SQL 模式,这项操作通常会直接报错。
sold_product.billing_cost 的默认值也无法避免这个问题。只有在插入时省略该字段,或者显式使用 DEFAULT,默认值才会生效。当前代码已经向 billing_cost 写入了一个值,因此 MySQL 不会使用它的默认值。
也可以直接使用 INSERT ... SELECT
如果插入前不需要单独使用 $billing_cost,可以让数据库直接从 agents 表复制金额:
INSERT INTO `sold_product`
(
`m_id`,
`moveover_m_id`,
`listing_id`,
`a_id`,
`agent_mls_id`,
`billing_cost`,
`billing_date`,
`billed`,
`charge`
)
SELECT
?,
?,
?,
a.`a_id`,
?,
a.`agent_billing_amount`,
?,
0,
?
FROM `agents` AS a
WHERE a.`a_id` = ?
LIMIT 1
这种写法只需要一次数据库操作,并且能保证金额取自同一条 agent 记录。如果后续邮件还要显示 $billing_cost,则仍需查询金额,或者在插入后重新读取这条记录。
注意事项
- 查询中必须包含
WHERE a_id = ?,否则无法确定要使用哪位 agent 的金额。 - 如果
a_id不是整数,需要按照字段的实际类型调整bind_param()的类型字符。 - 金额字段适合使用
DECIMAL(10,2)等定点数类型,不要用FLOAT保存货币金额。 - 缺少 agent 记录时,不建议悄悄回退到
199,否则很难发现数据关联问题。更安全的做法是停止插入并记录错误。 - 如果
agent_billing_amount允许为NULL,需要明确对应的业务规则,例如拒绝生成账单或使用预设金额。 - 原代码还有多处通过字符串拼接生成 SQL。listing 数据可能来自外部系统,最好将这些语句也改成预处理语句,避免 SQL 注入和引号转义问题。
备注:内容仅供参考。
