sqlite-export-for-ynab 2.1.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {sqlite_export_for_ynab-2.1.0/sqlite_export_for_ynab.egg-info → sqlite_export_for_ynab-2.3.0}/PKG-INFO +151 -37
  2. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/README.md +150 -36
  3. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/setup.cfg +1 -1
  4. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/_main.py +92 -38
  5. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/ddl/create-relations.sql +1 -1
  6. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0/sqlite_export_for_ynab.egg-info}/PKG-INFO +151 -37
  7. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/testing/fixtures.py +6 -4
  8. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/tests/_main_test.py +89 -16
  9. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/LICENSE +0 -0
  10. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/pyproject.toml +0 -0
  11. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/setup.py +0 -0
  12. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/__init__.py +0 -0
  13. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/__main__.py +0 -0
  14. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/ddl/__init__.py +0 -0
  15. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/ddl/drop-relations.sql +0 -0
  16. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab/py.typed +0 -0
  17. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab.egg-info/SOURCES.txt +0 -0
  18. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab.egg-info/dependency_links.txt +0 -0
  19. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab.egg-info/entry_points.txt +0 -0
  20. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab.egg-info/requires.txt +0 -0
  21. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/sqlite_export_for_ynab.egg-info/top_level.txt +0 -0
  22. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/testing/__init__.py +0 -0
  23. {sqlite_export_for_ynab-2.1.0 → sqlite_export_for_ynab-2.3.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlite_export_for_ynab
3
- Version: 2.1.0
3
+ Version: 2.3.0
4
4
  Summary: SQLite Export for YNAB - Export YNAB Data to SQLite
5
5
  Home-page: https://github.com/mxr/sqlite-export-for-ynab
6
6
  Author: Max R
@@ -51,6 +51,7 @@ $ sqlite-export-for-ynab
51
51
  ```
52
52
 
53
53
  Running it again will pull only data that changed since the last pull (this is done with [Delta Requests](https://api.ynab.com/#deltas)). If you want to wipe the DB and pull all data again use the `--full-refresh` flag.
54
+ Pass `--quiet` to suppress all CLI output, including progress bars.
54
55
 
55
56
  <a id="db-path"></a>You can specify the DB path with the following options
56
57
  1. The `--db` flag.
@@ -81,7 +82,7 @@ The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/c
81
82
 
82
83
  1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions, loan account periodic values).
83
84
  1. Foreign keys are added as needed (ex: plan ID, transaction ID) so data across plans remains separate.
84
- 1. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions` respectively. They also filter out deleted transactions/subtransactions and project payee/category fields to make querying more ergonomic.
85
+ 1. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions` respectively. They also filter out deleted/unapproved transactions/subtransactions and project payee/category fields to make querying more ergonomic.
85
86
 
86
87
  ## Querying
87
88
 
@@ -135,6 +136,7 @@ WITH used_payees AS (
135
136
  FROM transactions
136
137
  WHERE
137
138
  TRUE
139
+ AND approved
138
140
  AND payee_id IS NOT NULL
139
141
  AND NOT deleted
140
142
  UNION
@@ -345,44 +347,80 @@ To compare assigned category values to a given account's balance:
345
347
  -- -cmd ".parameter set @account_name_like %Savings%" \
346
348
  -- -cmd ".parameter set @include_category_groups 'Home,Food'" \
347
349
  -- < query.sql
350
+ CREATE TEMP TABLE excess_query_results AS
348
351
  WITH params AS (
349
352
  SELECT
350
353
  TRIM(COALESCE(@account_name_like, '')) AS account_name_like
354
+ , TRIM(COALESCE(@plan_id, '')) AS plan_id
351
355
  , TRIM(COALESCE(@include_category_groups, ''))
352
356
  AS include_category_groups
353
357
  , TRIM(COALESCE(@exclude_category_groups, ''))
354
358
  AS exclude_category_groups
355
359
  )
356
360
 
357
- , validation AS (
358
- SELECT 'Set @account_name_like' AS error
359
- FROM params AS p
360
- WHERE p.account_name_like = ''
361
+ , scoped_plans AS (
362
+ SELECT
363
+ p.id
364
+ , p.name
365
+ FROM plans AS p
366
+ CROSS JOIN params AS prm
367
+ WHERE prm.plan_id = '' OR p.id = prm.plan_id
368
+ )
369
+
370
+ , split_include_category_groups (value, rest) AS (
371
+ SELECT
372
+ ''
373
+ , prm.include_category_groups || ','
374
+ FROM params AS prm
361
375
  UNION ALL
362
376
  SELECT
363
- 'Set only one of @include_category_groups'
364
- || ' or @exclude_category_groups' AS error
365
- FROM params AS p
366
- WHERE p.include_category_groups != '' AND p.exclude_category_groups != ''
377
+ TRIM(SUBSTR(rest, 1, INSTR(rest, ',') - 1))
378
+ , SUBSTR(rest, INSTR(rest, ',') + 1)
379
+ FROM split_include_category_groups
380
+ WHERE rest != ''
367
381
  )
368
382
 
369
- SELECT v.error AS error_message
370
- FROM validation AS v
371
- WHERE v.error IS NOT NULL
372
- ;
383
+ , include_category_groups AS (
384
+ SELECT value AS name
385
+ FROM split_include_category_groups
386
+ WHERE value != ''
387
+ )
373
388
 
374
- WITH params AS (
389
+ , split_exclude_category_groups (value, rest) AS (
375
390
  SELECT
376
- TRIM(COALESCE(@account_name_like, '')) AS account_name_like
377
- , TRIM(COALESCE(@include_category_groups, ''))
378
- AS include_category_groups
379
- , TRIM(COALESCE(@exclude_category_groups, ''))
380
- AS exclude_category_groups
391
+ ''
392
+ , prm.exclude_category_groups || ','
393
+ FROM params AS prm
394
+ UNION ALL
395
+ SELECT
396
+ TRIM(SUBSTR(rest, 1, INSTR(rest, ',') - 1))
397
+ , SUBSTR(rest, INSTR(rest, ',') + 1)
398
+ FROM split_exclude_category_groups
399
+ WHERE rest != ''
400
+ )
401
+
402
+ , exclude_category_groups AS (
403
+ SELECT value AS name
404
+ FROM split_exclude_category_groups
405
+ WHERE value != ''
406
+ )
407
+
408
+ , matching_accounts AS (
409
+ SELECT
410
+ sp.id AS plan_id
411
+ , sp.name AS plan_name
412
+ , COUNT(*) AS matches
413
+ FROM scoped_plans AS sp
414
+ INNER JOIN accounts AS a ON sp.id = a.plan_id
415
+ CROSS JOIN params AS prm
416
+ WHERE NOT a.deleted AND a.name LIKE prm.account_name_like
417
+ GROUP BY sp.id, sp.name
381
418
  )
382
419
 
383
420
  , validation AS (
384
421
  SELECT
385
422
  p.account_name_like
423
+ , p.plan_id
386
424
  , p.include_category_groups
387
425
  , p.exclude_category_groups
388
426
  FROM params AS p
@@ -398,11 +436,62 @@ WITH params AS (
398
436
  || ' or @exclude_category_groups' AS error
399
437
  FROM validation AS v
400
438
  WHERE v.include_category_groups != '' AND v.exclude_category_groups != ''
439
+ UNION ALL
440
+ SELECT 'No plan matched @plan_id' AS error
441
+ FROM validation AS v
442
+ WHERE
443
+ v.plan_id != '' AND NOT EXISTS (
444
+ SELECT 1
445
+ FROM scoped_plans
446
+ )
447
+ UNION ALL
448
+ SELECT 'No account names matched @account_name_like' AS error
449
+ FROM validation AS v
450
+ WHERE
451
+ v.account_name_like != '' AND NOT EXISTS (
452
+ SELECT 1
453
+ FROM matching_accounts
454
+ )
455
+ UNION ALL
456
+ SELECT
457
+ 'Matched more than 1 account in plan: '
458
+ || ma.plan_name AS error
459
+ FROM matching_accounts AS ma
460
+ WHERE ma.matches > 1
461
+ UNION ALL
462
+ SELECT
463
+ 'Unknown include category group in plan '
464
+ || sp.name
465
+ || ': '
466
+ || icg.name AS error
467
+ FROM scoped_plans AS sp
468
+ CROSS JOIN include_category_groups AS icg
469
+ LEFT JOIN category_groups AS cg
470
+ ON
471
+ sp.id = cg.plan_id
472
+ AND NOT COALESCE(cg.deleted, 0)
473
+ AND LOWER(cg.name) = LOWER(icg.name)
474
+ WHERE cg.id IS NULL
475
+ UNION ALL
476
+ SELECT
477
+ 'Unknown exclude category group in plan '
478
+ || sp.name
479
+ || ': '
480
+ || ecg.name AS error
481
+ FROM scoped_plans AS sp
482
+ CROSS JOIN exclude_category_groups AS ecg
483
+ LEFT JOIN category_groups AS cg
484
+ ON
485
+ sp.id = cg.plan_id
486
+ AND NOT COALESCE(cg.deleted, 0)
487
+ AND LOWER(cg.name) = LOWER(ecg.name)
488
+ WHERE cg.id IS NULL
401
489
  )
402
490
 
403
491
  , valid_params AS (
404
492
  SELECT
405
493
  v.account_name_like
494
+ , v.plan_id
406
495
  , v.include_category_groups
407
496
  , v.exclude_category_groups
408
497
  FROM validation AS v
@@ -425,7 +514,7 @@ WITH params AS (
425
514
  TRUE
426
515
  AND NOT a.deleted
427
516
  AND a.name LIKE v.account_name_like
428
- AND (COALESCE(@plan_id, '') = '' OR p.id = @plan_id)
517
+ AND (v.plan_id = '' OR p.id = v.plan_id)
429
518
  )
430
519
 
431
520
  , category_totals AS (
@@ -440,36 +529,61 @@ WITH params AS (
440
529
  AND c.category_group_name != 'Internal Master Category'
441
530
  AND (
442
531
  v.include_category_groups = ''
443
- OR INSTR(
444
- ','
445
- || LOWER(REPLACE(v.include_category_groups, ', ', ','))
446
- || ','
447
- , ',' || LOWER(c.category_group_name) || ','
532
+ OR EXISTS (
533
+ SELECT 1
534
+ FROM include_category_groups AS icg
535
+ WHERE LOWER(icg.name) = LOWER(c.category_group_name)
448
536
  )
449
- > 0
450
537
  )
451
538
  AND (
452
539
  v.exclude_category_groups = ''
453
- OR INSTR(
454
- ','
455
- || LOWER(REPLACE(v.exclude_category_groups, ', ', ','))
456
- || ','
457
- , ',' || LOWER(c.category_group_name) || ','
540
+ OR NOT EXISTS (
541
+ SELECT 1
542
+ FROM exclude_category_groups AS ecg
543
+ WHERE LOWER(ecg.name) = LOWER(c.category_group_name)
458
544
  )
459
- = 0
460
545
  )
461
- AND (COALESCE(@plan_id, '') = '' OR c.plan_id = @plan_id)
546
+ AND (v.plan_id = '' OR c.plan_id = v.plan_id)
462
547
  GROUP BY c.plan_id
463
548
  )
464
549
 
465
550
  SELECT
466
- ma.plan_name AS "plan"
551
+ ve.error AS error_message
552
+ , NULL AS "plan"
553
+ , NULL AS account
554
+ , NULL AS total
555
+ , NULL AS excess
556
+ FROM validation_errors AS ve
557
+
558
+ UNION ALL
559
+
560
+ SELECT
561
+ NULL AS error_message
562
+ , ma.plan_name AS "plan"
467
563
  , ma.account_name AS account
468
564
  , PRINTF('%.2f', COALESCE(ct.total, 0)) AS total
469
- , PRINTF('%.2f', COALESCE(ct.total, 0) - ma.account_amount) AS excess
565
+ , PRINTF('%.2f', ma.account_amount - COALESCE(ct.total, 0)) AS excess
470
566
  FROM matched_accounts AS ma
471
567
  LEFT JOIN category_totals AS ct ON ma.plan_id = ct.plan_id
472
- ORDER BY "plan", account
568
+ ;
569
+
570
+ SELECT error_message
571
+ FROM excess_query_results
572
+ WHERE error_message IS NOT NULL
573
+ ;
574
+
575
+ SELECT
576
+ eqr."plan"
577
+ , eqr.account
578
+ , eqr.total
579
+ , eqr.excess
580
+ FROM excess_query_results AS eqr
581
+ WHERE
582
+ NOT EXISTS (
583
+ SELECT 1
584
+ FROM excess_query_results AS eqr_errors
585
+ WHERE eqr_errors.error_message IS NOT NULL
586
+ )
473
587
  ;
474
588
  ```
475
589
 
@@ -31,6 +31,7 @@ $ sqlite-export-for-ynab
31
31
  ```
32
32
 
33
33
  Running it again will pull only data that changed since the last pull (this is done with [Delta Requests](https://api.ynab.com/#deltas)). If you want to wipe the DB and pull all data again use the `--full-refresh` flag.
34
+ Pass `--quiet` to suppress all CLI output, including progress bars.
34
35
 
35
36
  <a id="db-path"></a>You can specify the DB path with the following options
36
37
  1. The `--db` flag.
@@ -61,7 +62,7 @@ The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/c
61
62
 
62
63
  1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions, loan account periodic values).
63
64
  1. Foreign keys are added as needed (ex: plan ID, transaction ID) so data across plans remains separate.
64
- 1. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions` respectively. They also filter out deleted transactions/subtransactions and project payee/category fields to make querying more ergonomic.
65
+ 1. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions` respectively. They also filter out deleted/unapproved transactions/subtransactions and project payee/category fields to make querying more ergonomic.
65
66
 
66
67
  ## Querying
67
68
 
@@ -115,6 +116,7 @@ WITH used_payees AS (
115
116
  FROM transactions
116
117
  WHERE
117
118
  TRUE
119
+ AND approved
118
120
  AND payee_id IS NOT NULL
119
121
  AND NOT deleted
120
122
  UNION
@@ -325,44 +327,80 @@ To compare assigned category values to a given account's balance:
325
327
  -- -cmd ".parameter set @account_name_like %Savings%" \
326
328
  -- -cmd ".parameter set @include_category_groups 'Home,Food'" \
327
329
  -- < query.sql
330
+ CREATE TEMP TABLE excess_query_results AS
328
331
  WITH params AS (
329
332
  SELECT
330
333
  TRIM(COALESCE(@account_name_like, '')) AS account_name_like
334
+ , TRIM(COALESCE(@plan_id, '')) AS plan_id
331
335
  , TRIM(COALESCE(@include_category_groups, ''))
332
336
  AS include_category_groups
333
337
  , TRIM(COALESCE(@exclude_category_groups, ''))
334
338
  AS exclude_category_groups
335
339
  )
336
340
 
337
- , validation AS (
338
- SELECT 'Set @account_name_like' AS error
339
- FROM params AS p
340
- WHERE p.account_name_like = ''
341
+ , scoped_plans AS (
342
+ SELECT
343
+ p.id
344
+ , p.name
345
+ FROM plans AS p
346
+ CROSS JOIN params AS prm
347
+ WHERE prm.plan_id = '' OR p.id = prm.plan_id
348
+ )
349
+
350
+ , split_include_category_groups (value, rest) AS (
351
+ SELECT
352
+ ''
353
+ , prm.include_category_groups || ','
354
+ FROM params AS prm
341
355
  UNION ALL
342
356
  SELECT
343
- 'Set only one of @include_category_groups'
344
- || ' or @exclude_category_groups' AS error
345
- FROM params AS p
346
- WHERE p.include_category_groups != '' AND p.exclude_category_groups != ''
357
+ TRIM(SUBSTR(rest, 1, INSTR(rest, ',') - 1))
358
+ , SUBSTR(rest, INSTR(rest, ',') + 1)
359
+ FROM split_include_category_groups
360
+ WHERE rest != ''
347
361
  )
348
362
 
349
- SELECT v.error AS error_message
350
- FROM validation AS v
351
- WHERE v.error IS NOT NULL
352
- ;
363
+ , include_category_groups AS (
364
+ SELECT value AS name
365
+ FROM split_include_category_groups
366
+ WHERE value != ''
367
+ )
353
368
 
354
- WITH params AS (
369
+ , split_exclude_category_groups (value, rest) AS (
355
370
  SELECT
356
- TRIM(COALESCE(@account_name_like, '')) AS account_name_like
357
- , TRIM(COALESCE(@include_category_groups, ''))
358
- AS include_category_groups
359
- , TRIM(COALESCE(@exclude_category_groups, ''))
360
- AS exclude_category_groups
371
+ ''
372
+ , prm.exclude_category_groups || ','
373
+ FROM params AS prm
374
+ UNION ALL
375
+ SELECT
376
+ TRIM(SUBSTR(rest, 1, INSTR(rest, ',') - 1))
377
+ , SUBSTR(rest, INSTR(rest, ',') + 1)
378
+ FROM split_exclude_category_groups
379
+ WHERE rest != ''
380
+ )
381
+
382
+ , exclude_category_groups AS (
383
+ SELECT value AS name
384
+ FROM split_exclude_category_groups
385
+ WHERE value != ''
386
+ )
387
+
388
+ , matching_accounts AS (
389
+ SELECT
390
+ sp.id AS plan_id
391
+ , sp.name AS plan_name
392
+ , COUNT(*) AS matches
393
+ FROM scoped_plans AS sp
394
+ INNER JOIN accounts AS a ON sp.id = a.plan_id
395
+ CROSS JOIN params AS prm
396
+ WHERE NOT a.deleted AND a.name LIKE prm.account_name_like
397
+ GROUP BY sp.id, sp.name
361
398
  )
362
399
 
363
400
  , validation AS (
364
401
  SELECT
365
402
  p.account_name_like
403
+ , p.plan_id
366
404
  , p.include_category_groups
367
405
  , p.exclude_category_groups
368
406
  FROM params AS p
@@ -378,11 +416,62 @@ WITH params AS (
378
416
  || ' or @exclude_category_groups' AS error
379
417
  FROM validation AS v
380
418
  WHERE v.include_category_groups != '' AND v.exclude_category_groups != ''
419
+ UNION ALL
420
+ SELECT 'No plan matched @plan_id' AS error
421
+ FROM validation AS v
422
+ WHERE
423
+ v.plan_id != '' AND NOT EXISTS (
424
+ SELECT 1
425
+ FROM scoped_plans
426
+ )
427
+ UNION ALL
428
+ SELECT 'No account names matched @account_name_like' AS error
429
+ FROM validation AS v
430
+ WHERE
431
+ v.account_name_like != '' AND NOT EXISTS (
432
+ SELECT 1
433
+ FROM matching_accounts
434
+ )
435
+ UNION ALL
436
+ SELECT
437
+ 'Matched more than 1 account in plan: '
438
+ || ma.plan_name AS error
439
+ FROM matching_accounts AS ma
440
+ WHERE ma.matches > 1
441
+ UNION ALL
442
+ SELECT
443
+ 'Unknown include category group in plan '
444
+ || sp.name
445
+ || ': '
446
+ || icg.name AS error
447
+ FROM scoped_plans AS sp
448
+ CROSS JOIN include_category_groups AS icg
449
+ LEFT JOIN category_groups AS cg
450
+ ON
451
+ sp.id = cg.plan_id
452
+ AND NOT COALESCE(cg.deleted, 0)
453
+ AND LOWER(cg.name) = LOWER(icg.name)
454
+ WHERE cg.id IS NULL
455
+ UNION ALL
456
+ SELECT
457
+ 'Unknown exclude category group in plan '
458
+ || sp.name
459
+ || ': '
460
+ || ecg.name AS error
461
+ FROM scoped_plans AS sp
462
+ CROSS JOIN exclude_category_groups AS ecg
463
+ LEFT JOIN category_groups AS cg
464
+ ON
465
+ sp.id = cg.plan_id
466
+ AND NOT COALESCE(cg.deleted, 0)
467
+ AND LOWER(cg.name) = LOWER(ecg.name)
468
+ WHERE cg.id IS NULL
381
469
  )
382
470
 
383
471
  , valid_params AS (
384
472
  SELECT
385
473
  v.account_name_like
474
+ , v.plan_id
386
475
  , v.include_category_groups
387
476
  , v.exclude_category_groups
388
477
  FROM validation AS v
@@ -405,7 +494,7 @@ WITH params AS (
405
494
  TRUE
406
495
  AND NOT a.deleted
407
496
  AND a.name LIKE v.account_name_like
408
- AND (COALESCE(@plan_id, '') = '' OR p.id = @plan_id)
497
+ AND (v.plan_id = '' OR p.id = v.plan_id)
409
498
  )
410
499
 
411
500
  , category_totals AS (
@@ -420,36 +509,61 @@ WITH params AS (
420
509
  AND c.category_group_name != 'Internal Master Category'
421
510
  AND (
422
511
  v.include_category_groups = ''
423
- OR INSTR(
424
- ','
425
- || LOWER(REPLACE(v.include_category_groups, ', ', ','))
426
- || ','
427
- , ',' || LOWER(c.category_group_name) || ','
512
+ OR EXISTS (
513
+ SELECT 1
514
+ FROM include_category_groups AS icg
515
+ WHERE LOWER(icg.name) = LOWER(c.category_group_name)
428
516
  )
429
- > 0
430
517
  )
431
518
  AND (
432
519
  v.exclude_category_groups = ''
433
- OR INSTR(
434
- ','
435
- || LOWER(REPLACE(v.exclude_category_groups, ', ', ','))
436
- || ','
437
- , ',' || LOWER(c.category_group_name) || ','
520
+ OR NOT EXISTS (
521
+ SELECT 1
522
+ FROM exclude_category_groups AS ecg
523
+ WHERE LOWER(ecg.name) = LOWER(c.category_group_name)
438
524
  )
439
- = 0
440
525
  )
441
- AND (COALESCE(@plan_id, '') = '' OR c.plan_id = @plan_id)
526
+ AND (v.plan_id = '' OR c.plan_id = v.plan_id)
442
527
  GROUP BY c.plan_id
443
528
  )
444
529
 
445
530
  SELECT
446
- ma.plan_name AS "plan"
531
+ ve.error AS error_message
532
+ , NULL AS "plan"
533
+ , NULL AS account
534
+ , NULL AS total
535
+ , NULL AS excess
536
+ FROM validation_errors AS ve
537
+
538
+ UNION ALL
539
+
540
+ SELECT
541
+ NULL AS error_message
542
+ , ma.plan_name AS "plan"
447
543
  , ma.account_name AS account
448
544
  , PRINTF('%.2f', COALESCE(ct.total, 0)) AS total
449
- , PRINTF('%.2f', COALESCE(ct.total, 0) - ma.account_amount) AS excess
545
+ , PRINTF('%.2f', ma.account_amount - COALESCE(ct.total, 0)) AS excess
450
546
  FROM matched_accounts AS ma
451
547
  LEFT JOIN category_totals AS ct ON ma.plan_id = ct.plan_id
452
- ORDER BY "plan", account
548
+ ;
549
+
550
+ SELECT error_message
551
+ FROM excess_query_results
552
+ WHERE error_message IS NOT NULL
553
+ ;
554
+
555
+ SELECT
556
+ eqr."plan"
557
+ , eqr.account
558
+ , eqr.total
559
+ , eqr.excess
560
+ FROM excess_query_results AS eqr
561
+ WHERE
562
+ NOT EXISTS (
563
+ SELECT 1
564
+ FROM excess_query_results AS eqr_errors
565
+ WHERE eqr_errors.error_message IS NOT NULL
566
+ )
453
567
  ;
454
568
  ```
455
569
 
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = sqlite_export_for_ynab
3
- version = 2.1.0
3
+ version = 2.3.0
4
4
  description = SQLite Export for YNAB - Export YNAB Data to SQLite
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown