Laravel查询未正确应用过滤参数问题求助
问题:费用管理Web应用过滤参数未生效,SQL查询未包含过滤条件
我正在开发一款费用管理Web应用,在分析页面实现过滤功能时遇到问题:前端已将过滤参数正确发送至服务器,但生成的原始SQL查询并未包含这些过滤参数。
相关代码
前端代码(Vue)
async getCategories() { this.loading = true; const { response } = await this.request('GET', '/get-other-categories', { params: { userId: this.activeUser.id, ...this.filters } }); this.categories = response.data; this.expenses = this.categories.reduce((sum, item) => sum + item.total_expenses, 0); this.loading = false; }, async getIncomeCategory() { this.loading = true; const { response } = await this.request('GET', '/get-income-category', { params: { userId: this.activeUser.id, ...this.filters } }); this.incomeCategories = response.data; this.income = this.incomeCategories.reduce((sum, item) => sum + item.total_income, 0); this.loading = false; }, filterRecordsByAccounts(accounts) { const selectedAccounts = [...new Set(accounts)]; if (!selectedAccounts.length) { this.filters.accounts = []; this.getCategories(); this.getIncomeCategory(); return; } this.filters.accounts = selectedAccounts; this.getCategories(); this.getIncomeCategory(); },
后端代码(Laravel)
public function getIncomeCategory(Request $request): JsonResponse { $userId = $request->query('userId'); $startDate = $request->query('startDate'); $endDate = $request->query('endDate'); $accountIds = $request->query('accounts', []); $categoryIds = $request->query('categories', []); $labels = $request->query('labels', []); $types = $request->query('types', []); $paymentTypes = $request->query('paymentTypes', []); $paymentStatuses = $request->query('paymentStatuses', []); Log::info('received-parameters', [ 'userId' => $userId, 'startDate' => $startDate, 'endDate' => $endDate, 'accountIds' => $accountIds, 'categoryIds' => $categoryIds, 'labels' => $labels, 'types' => $types, 'paymentTypes' => $paymentTypes, 'paymentStatuses' => $paymentStatuses, ]); $query = Category::with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('visible', true) ->with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) ->where('visible', true) ->with(['records' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) ->whereBetween('date', [$startDate, $endDate]) ->when($accountIds, function ($query, $accountIds) { $query->whereIn('account_id', $accountIds); }) ->when($categoryIds, function ($query, $categoryIds) { $query->whereIn('category_id', $categoryIds); }) ->whereHas('labels', function ($q) use ($labels) { $q->whereIn('label_id', $labels); }) ->when($types, function ($query, $types) { $query->whereIn('type', $types); }) ->when($paymentTypes, function ($query, $paymentTypes) { $query->whereIn('payment_type', $paymentTypes); }) ->when($paymentStatuses, function ($query, $paymentStatuses) { $query->whereIn('payment_status', $paymentStatuses); }); }]); }]); }]) ->whereNull('parent_id') ->where('visible', true) ->where(function ($query) { $query->whereRaw("json_extract(name, '$.en') = 'Income'") ->orWhereRaw("json_extract(name, '$.lv') = 'Ienākumi'"); }); Log::info('query', [$query->toSql()]); $categories = $query->get(); return response()->json(CategoryResource::collection($categories)); } public function getOtherCategories(Request $request): JsonResponse { $userId = $request->query('userId'); $startDate = $request->query('startDate'); $endDate = $request->query('endDate'); $accountIds = $request->query('accounts', []); $categoryIds = $request->query('categories', []); $labels = $request->query('labels', []); $types = $request->query('types', []); $paymentTypes = $request->query('paymentTypes', []); $paymentStatuses = $request->query('paymentStatuses', []); Log::info('received-parameters', [ 'userId' => $userId, 'startDate' => $startDate, 'endDate' => $endDate, 'accountIds' => $accountIds, 'categoryIds' => $categoryIds, 'labels' => $labels, 'types' => $types, 'paymentTypes' => $paymentTypes, 'paymentStatuses' => $paymentStatuses, ]); $query = Category::with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('visible', true) ->with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) ->where('visible', true) ->with(['records' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) ->whereBetween('date', [$startDate, $endDate]) ->when($accountIds, function ($query, $accountIds) { $query->whereIn('account_id', $accountIds); }) ->when($categoryIds, function ($query, $categoryIds) { $query->whereIn('category_id', $categoryIds); }) ->whereHas('labels', function ($q) use ($labels) { $q->whereIn('label_id', $labels); }) ->when($types, function ($query, $types) { $query->whereIn('type', $types); }) ->when($paymentTypes, function ($query, $paymentTypes) { $query->whereIn('payment_type', $paymentTypes); }) ->when($paymentStatuses, function ($query, $paymentStatuses) { $query->whereIn('payment_status', $paymentStatuses); }); }]); }]); }]) ->whereNull('parent_id') ->where('visible', true) ->where(function ($query) { $query->whereRaw("json_extract(name, '$.en') != 'Income'") ->orWhereRaw("json_extract(name, '$.lv') != 'Ienākumi'"); }); Log::info('query', [$query->toSql()]); $categories = $query->get(); return response()->json(CategoryResource::collection($categories)); }
日志信息
尝试按银行账户过滤时,日志输出如下:
[2024-07-05 02:44:21] local.INFO: received-parameters {"userId":"1","startDate":null,"endDate":null,"accountIds":["1"],"categoryIds":[],"labels":[],"types":[],"paymentTypes":[],"paymentStatuses":[]} [2024-07-05 02:44:21] local.INFO: query ["select * from `categories` where `parent_id` is null and `visible` = ? and (json_extract(name, '$.en') != 'Income' or json_extract(name, '$.lv') != 'Ienākumi') and `categories`.`deleted_at` is null"] [2024-07-05 02:44:24] local.INFO: received-parameters {"userId":"1","startDate":null,"endDate":null,"accountIds":["1"],"categoryIds":[],"labels":[],"types":[],"paymentTypes":[],"paymentStatuses":[]} [2024-07-05 02:44:24] local.INFO: query ["select * from `categories` where `parent_id` is null and `visible` = ? and (json_extract(name, '$.en') = 'Income' or json_extract(name, '$.lv') = 'Ienākumi') and `categories`.`deleted_at` is null"]
可以看到服务器已经收到accountIds参数,但生成的SQL查询里完全没有包含账户过滤的条件,请问该如何解决?
解决方案
问题原因
toSql()的局限性:你打印的$query->toSql()只输出主查询(categories表)的SQL,而过滤条件写在**关联预加载(with)**的闭包里,这部分SQL不会包含在主查询中,而是会在后续加载关联数据时单独执行。- 潜在逻辑漏洞:
whereBetween('date', [$startDate, $endDate])在startDate或endDate为null时会直接报错whereHas('labels', ...)当labels为空数组时,会过滤掉所有无标签的记录,不符合预期
修复后的代码
public function getIncomeCategory(Request $request): JsonResponse { $userId = $request->query('userId'); $startDate = $request->query('startDate'); $endDate = $request->query('endDate'); $accountIds = $request->query('accounts', []); $categoryIds = $request->query('categories', []); $labels = $request->query('labels', []); $types = $request->query('types', []); $paymentTypes = $request->query('paymentTypes', []); $paymentStatuses = $request->query('paymentStatuses', []); Log::info('received-parameters', [ 'userId' => $userId, 'startDate' => $startDate, 'endDate' => $endDate, 'accountIds' => $accountIds, 'categoryIds' => $categoryIds, 'labels' => $labels, 'types' => $types, 'paymentTypes' => $paymentTypes, 'paymentStatuses' => $paymentStatuses, ]); $query = Category::with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('visible', true) ->with(['children' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) ->where('visible', true) ->with(['records' => function ($query) use ($userId, $startDate, $endDate, $accountIds, $categoryIds, $labels, $types, $paymentTypes, $paymentStatuses) { $query->where('user_id', $userId) // 修复日期判断,避免null报错 ->when($startDate && $endDate, function ($query) use ($startDate, $endDate) { $query->whereBetween('date', [$startDate, $endDate]); }) ->when($accountIds, function ($query, $accountIds) { $query->whereIn('account_id', $accountIds); }) ->when($categoryIds, function ($query, $categoryIds) { $query->whereIn('category_id', $categoryIds); }) // 修复标签过滤逻辑,空数组时不应用过滤 ->when($labels, function ($query) use ($labels) { $query->whereHas('labels', function ($q) use ($labels) { $q->whereIn('label_id', $labels); }); }) ->when($types, function ($query, $types) { $query->whereIn('type', $types); }) ->when($paymentTypes, function ($query, $paymentTypes) { $query->whereIn('payment_type', $paymentTypes); }) ->when($paymentStatuses, function ($query, $paymentStatuses) { $query->whereIn('payment_status', $paymentStatuses); }); }]); }]); }]) ->whereNull('parent_id') ->where('visible', true) ->where(function ($query) { $query->whereRaw("json_extract(name, '$.en') = 'Income'") ->orWhereRaw("json_extract(name, '$.lv') = 'Ienākumi'"); }); // 查看所有执行的SQL,包括关联查询 DB::listen(function ($query) { Log::info('full-query', ['sql' => $query->sql, 'bindings' => $query->bindings]); }); $categories = $query->get(); return response()->json(CategoryResource::collection($categories)); } // getOtherCategories方法执行相同修复即可
验证方式
替换原有的Log::info('query', [$query->toSql()])为上述DB::listen代码,就能看到所有执行的SQL语句(包括关联预加载的查询),确认过滤条件是否生效。
内容的提问来源于stack exchange,提问作者screw4445
相关产品推荐
相关产品推荐

