PHP month payment filter:按日期范围筛选付款记录
结论
可以。让用户通过两个 <input type="date"> 分别提交开始日期和结束日期,再用日期条件筛选 payments 表。
应使用预处理语句,避免把用户提交的日期直接拼接到 SQL 中。如果数据库字段 date 的类型是 DATE,查询条件可以写成:
WHERE `date` BETWEEN ? AND ?
如果 date 是 DATETIME 或 TIMESTAMP,建议使用左闭右开的日期范围:
WHERE `date` >= ? AND `date` < DATE_ADD(?, INTERVAL 1 DAY)
这种写法可以查到结束日期当天的所有记录。
完整示例
下面的代码保留了原有的记录列表和收支汇总,并加入日期范围筛选:
<?php
include 'db-connect.php';
$startDate = $_GET['start_date'] ?? '';
$endDate = $_GET['end_date'] ?? '';
$error = '';
$result = false;
function isValidDate(string $date): bool
{
$value = DateTime::createFromFormat('!Y-m-d', $date);
return $value !== false && $value->format('Y-m-d') === $date;
}
if ($startDate === '' && $endDate === '') {
// 未选择日期时显示全部记录
$result = mysqli_query(
$con,
"SELECT title, `date`, incoming, outgoing, notes
FROM payments
ORDER BY `date` ASC"
);
} elseif (
!isValidDate($startDate) ||
!isValidDate($endDate)
) {
$error = '请选择有效的开始日期和结束日期。';
} elseif ($startDate > $endDate) {
$error = '开始日期不能晚于结束日期。';
} else {
/*
* 适用于 DATE、DATETIME 或 TIMESTAMP 字段。
* 结束日期加一天,并使用 <,可包含结束日期当天的全部记录。
*/
$stmt = mysqli_prepare(
$con,
"SELECT title, `date`, incoming, outgoing, notes
FROM payments
WHERE `date` >= ?
AND `date` < DATE_ADD(?, INTERVAL 1 DAY)
ORDER BY `date` ASC"
);
if ($stmt) {
mysqli_stmt_bind_param($stmt, 'ss', $startDate, $endDate);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
} else {
$error = '无法创建数据库查询。';
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>付款记录</title>
</head>
<body>
<form method="get">
<label for="start_date">开始日期:</label>
<input
type="date"
id="start_date"
name="start_date"
value="<?= htmlspecialchars($startDate, ENT_QUOTES, 'UTF-8') ?>"
>
<label for="end_date">结束日期:</label>
<input
type="date"
id="end_date"
name="end_date"
value="<?= htmlspecialchars($endDate, ENT_QUOTES, 'UTF-8') ?>"
>
<button type="submit">筛选</button>
<a href="<?= htmlspecialchars($_SERVER['PHP_SELF'], ENT_QUOTES, 'UTF-8') ?>">
清除筛选
</a>
</form>
<?php if ($error !== ''): ?>
<p><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>
<?php elseif ($result === false): ?>
<p>查询失败:<?= htmlspecialchars(mysqli_error($con), ENT_QUOTES, 'UTF-8') ?></p>
<?php else: ?>
<?php
$totalIncoming = 0.0;
$totalOutgoing = 0.0;
?>
<table>
<thead>
<tr>
<th>Title</th>
<th>Date</th>
<th>Incoming</th>
<th>Outgoing</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php if (mysqli_num_rows($result) === 0): ?>
<tr>
<td colspan="5">所选日期范围内没有付款记录。</td>
</tr>
<?php else: ?>
<?php while ($row = mysqli_fetch_assoc($result)): ?>
<?php
$totalIncoming += (float) $row['incoming'];
$totalOutgoing += (float) $row['outgoing'];
?>
<tr>
<td><?= htmlspecialchars($row['title'], ENT_QUOTES, 'UTF-8') ?></td>
<td><?= htmlspecialchars($row['date'], ENT_QUOTES, 'UTF-8') ?></td>
<td>£<?= number_format((float) $row['incoming'], 2) ?></td>
<td>£<?= number_format((float) $row['outgoing'], 2) ?></td>
<td><?= htmlspecialchars($row['notes'], ENT_QUOTES, 'UTF-8') ?></td>
</tr>
<?php endwhile; ?>
<?php endif; ?>
</tbody>
</table>
<?php $profit = $totalIncoming - $totalOutgoing; ?>
<div class="payment-total">
<h1>Profit: £<?= number_format($profit, 2) ?></h1>
</div>
<?php endif; ?>
<?php
if (isset($stmt)) {
mysqli_stmt_close($stmt);
}
mysqli_close($con);
?>
</body>
</html>
工作方式
表单通过 GET 提交。选择日期后,浏览器会生成类似这样的地址:
payments.php?start_date=2026-08-01&end_date=2026-08-31
PHP 从 $_GET 读取这两个值,检查它们是否为有效的 Y-m-d 日期,然后通过预处理语句传给数据库。查询结果只包含指定日期范围内的记录,后续计算出的 incoming、outgoing 和 profit 也只统计这些记录。
如果只需要按月份筛选
如果用户只需选择月份,不需要设置任意日期范围,可以改用:
<form method="get">
<label for="month">月份:</label>
<input type="month" id="month" name="month" required>
<button type="submit">筛选</button>
</form>
收到 2026-08 这样的值后,计算该月的起始日期和下个月的起始日期:
$month = $_GET['month'] ?? '';
$monthObject = DateTime::createFromFormat('!Y-m', $month);
if ($monthObject && $monthObject->format('Y-m') === $month) {
$startDate = $monthObject->format('Y-m-d');
$endDate = (clone $monthObject)
->modify('first day of next month')
->format('Y-m-d');
$stmt = mysqli_prepare(
$con,
"SELECT title, `date`, incoming, outgoing, notes
FROM payments
WHERE `date` >= ? AND `date` < ?
ORDER BY `date` ASC"
);
mysqli_stmt_bind_param($stmt, 'ss', $startDate, $endDate);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
}
不建议写成 WHERE MONTH(date) = ?。对列使用 MONTH() 或 DATE_FORMAT(),通常会影响数据库使用普通日期索引,而且只比较月份时还可能查到其他年份的数据。
注意事项
确认日期字段类型
数据库中的 date 字段应使用 DATE、DATETIME 或 TIMESTAMP,不要用普通字符串保存日期。例如:
ALTER TABLE payments MODIFY `date` DATE NOT NULL;
转换字段前,应先确认现有数据都能转换为合法日期,并做好备份。
结束日期的边界
如果字段类型是 DATE,下面的写法通常可以正常工作:
WHERE `date` BETWEEN ? AND ?
如果字段还保存了时间,例如 2026-08-31 15:30:00,直接将结束值绑定为 2026-08-31,可能会漏掉当天零点之后的数据。因此,示例使用以下条件:
WHERE `date` >= ?
AND `date` < DATE_ADD(?, INTERVAL 1 DAY)
输出需要转义
数据库内容也可能包含 HTML 特殊字符。页面输出 title、notes 等字段时,应使用:
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
这样可以防止浏览器把数据当作 HTML 执行。
为日期字段建立索引
记录较多时,可以为日期字段添加索引:
CREATE INDEX idx_payments_date ON payments (`date`);
日期范围查询通常能够使用该索引,从而减少需要扫描的记录数量。
备注:内容仅供参考。
