@@ -209,7 +209,7 @@ To estimate taxable interest for a given year[^1]:
209209-- @interest_payee_name (optional, defaults to Interest)
210210--
211211-- Example with only required params:
212- -- sqlite3 -header -box path/to/db.sqlite3 \
212+ -- sqlite3 -header -box path/to/db.sqlite \
213213-- -cmd '.parameter init' \
214214-- -cmd ".parameter set @tax_rate 0.25" \
215215-- -cmd ".parameter set @year 2025" \
@@ -294,4 +294,145 @@ ORDER BY plan_name, plan_id
294294;
295295```
296296
297+ To compare assigned category values to a given account's balance:
298+
299+ ``` sql
300+ -- Parameters expected by this query:
301+ -- @account_name_like (required, the account name to match against)
302+ -- @plan_id (optional, defaults to output for all matching plans)
303+ -- @include_category_groups
304+ -- (optional, comma-separated category-group names to include;
305+ -- exclusive with @exclude_category_groups)
306+ -- @exclude_category_groups
307+ -- (optional, comma-separated category-group names to exclude;
308+ -- exclusive with @include_category_groups)
309+ --
310+ -- Example:
311+ -- sqlite -header -box path/to/db.sqlite \
312+ -- -cmd '.parameter init' \
313+ -- -cmd ".parameter set @account_name_like %Savings%" \
314+ -- -cmd ".parameter set @include_category_groups 'Home,Food'" \
315+ -- < query.sql
316+ WITH params AS (
317+ SELECT
318+ TRIM (COALESCE(@account_name_like, ' ' )) AS account_name_like
319+ , TRIM (COALESCE(@include_category_groups, ' ' ))
320+ AS include_category_groups
321+ , TRIM (COALESCE(@exclude_category_groups, ' ' ))
322+ AS exclude_category_groups
323+ )
324+
325+ , validation AS (
326+ SELECT ' Set @account_name_like' AS error FROM params AS p
327+ WHERE p .account_name_like = ' '
328+ UNION ALL
329+ SELECT
330+ ' Set only one of @include_category_groups'
331+ || ' or @exclude_category_groups' AS error
332+ FROM params AS p
333+ WHERE p .include_category_groups != ' ' AND p .exclude_category_groups != ' '
334+ )
335+
336+ SELECT v .error AS error_message FROM validation AS v
337+ WHERE v .error IS NOT NULL
338+ ;
339+
340+ WITH params AS (
341+ SELECT
342+ TRIM (COALESCE(@account_name_like, ' ' )) AS account_name_like
343+ , TRIM (COALESCE(@include_category_groups, ' ' ))
344+ AS include_category_groups
345+ , TRIM (COALESCE(@exclude_category_groups, ' ' ))
346+ AS exclude_category_groups
347+ )
348+
349+ , validation AS (
350+ SELECT
351+ p .account_name_like
352+ , p .include_category_groups
353+ , p .exclude_category_groups
354+ FROM params AS p
355+ )
356+
357+ , validation_errors AS (
358+ SELECT ' Set @account_name_like' AS error FROM validation AS v
359+ WHERE v .account_name_like = ' '
360+ UNION ALL
361+ SELECT
362+ ' Set only one of @include_category_groups'
363+ || ' or @exclude_category_groups' AS error
364+ FROM validation AS v
365+ WHERE v .include_category_groups != ' ' AND v .exclude_category_groups != ' '
366+ )
367+
368+ , valid_params AS (
369+ SELECT
370+ v .account_name_like
371+ , v .include_category_groups
372+ , v .exclude_category_groups
373+ FROM validation AS v
374+ WHERE NOT EXISTS (SELECT 1 FROM validation_errors)
375+ )
376+
377+ , matched_accounts AS (
378+ SELECT
379+ p .id AS plan_id
380+ , p .name AS plan_name
381+ , a .name AS account_name
382+ , a .cleared_balance / 1000 .0 AS account_amount
383+ FROM plans AS p
384+ INNER JOIN accounts AS a ON p .id = a .plan_id
385+ CROSS JOIN valid_params AS v
386+ WHERE
387+ TRUE
388+ AND NOT a .deleted
389+ AND a .name LIKE v .account_name_like
390+ AND (COALESCE(@plan_id, ' ' ) = ' ' OR p .id = @plan_id)
391+ )
392+
393+ , category_totals AS (
394+ SELECT
395+ c .plan_id
396+ , COALESCE(SUM (c .balance ), 0 ) / 1000 .0 AS total
397+ FROM categories AS c CROSS JOIN valid_params AS v
398+ WHERE
399+ TRUE
400+ AND NOT c .deleted
401+ AND c .category_group_name != ' Credit Card Payments'
402+ AND c .category_group_name != ' Internal Master Category'
403+ AND (
404+ v .include_category_groups = ' '
405+ OR INSTR(
406+ ' ,'
407+ || LOWER (REPLACE(v .include_category_groups , ' , ' , ' ,' ))
408+ || ' ,'
409+ , ' ,' || LOWER (c .category_group_name ) || ' ,'
410+ )
411+ > 0
412+ )
413+ AND (
414+ v .exclude_category_groups = ' '
415+ OR INSTR(
416+ ' ,'
417+ || LOWER (REPLACE(v .exclude_category_groups , ' , ' , ' ,' ))
418+ || ' ,'
419+ , ' ,' || LOWER (c .category_group_name ) || ' ,'
420+ )
421+ = 0
422+ )
423+ AND (COALESCE(@plan_id, ' ' ) = ' ' OR c .plan_id = @plan_id)
424+ GROUP BY c .plan_id
425+ )
426+
427+ SELECT
428+ ma .plan_name AS " plan"
429+ , ma .account_name AS account
430+ , ROUND(COALESCE(ct .total , 0 ), 2 ) AS total
431+ , ROUND(ma .account_amount - COALESCE(ct .total , 0 ), 2 ) AS excess
432+ FROM matched_accounts AS ma
433+ LEFT JOIN category_totals AS ct ON ma .plan_id = ct .plan_id
434+ ORDER BY " plan" , account
435+ ;
436+ ```
437+
297438[ ^ 1 ] : This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax advice, or a substitute for Forms 1099-INT, brokerage statements, bank records, or guidance from a qualified professional.
0 commit comments