├── .gitignore ├── LICENSE ├── README.md ├── analysis ├── invoices.sql └── running_total_by_account.sql ├── data ├── quickbooks_classifications.csv └── quickbooks_statement_order.txt ├── dbt_project.yml ├── models ├── base │ ├── quickbooks_accounts.sql │ ├── quickbooks_bill_lines.sql │ ├── quickbooks_bill_payments.sql │ ├── quickbooks_billpayment_lines.sql │ ├── quickbooks_bills.sql │ ├── quickbooks_classes.sql │ ├── quickbooks_customers.sql │ ├── quickbooks_deposit_lines.sql │ ├── quickbooks_deposits.sql │ ├── quickbooks_invoice_lines.sql │ ├── quickbooks_invoices.sql │ ├── quickbooks_items.sql │ ├── quickbooks_journal_entries.sql │ ├── quickbooks_journal_lines.sql │ ├── quickbooks_missing_account_classifications.sql │ ├── quickbooks_payments.sql │ ├── quickbooks_purchase_lines.sql │ ├── quickbooks_purchases.sql │ └── quickbooks_vendors.sql ├── tables │ ├── quickbooks_general_ledger.sql │ ├── quickbooks_parent_accounts_map.sql │ └── quickbooks_parent_class_map.sql └── transform │ ├── quickbooks_accounts_xf.sql │ ├── quickbooks_bill_transactions.sql │ ├── quickbooks_billpayment_transactions.sql │ ├── quickbooks_days.sql │ ├── quickbooks_deposit_transactions.sql │ ├── quickbooks_invoice_transactions.sql │ ├── quickbooks_invoices_xf.sql │ ├── quickbooks_journal_transactions.sql │ ├── quickbooks_ledger_by_month.sql │ ├── quickbooks_ledger_xf.sql │ ├── quickbooks_months.sql │ ├── quickbooks_payment_transactions.sql │ ├── quickbooks_purchase_transactions.sql │ ├── quickbooks_toplevel_account_metrics.sql │ └── quickbooks_toplevel_class_metrics.sql └── tests └── debits_minus_credits.sql /.gitignore: -------------------------------------------------------------------------------- 1 | /dbt_modules 2 | /target 3 | /logs 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Deprecated] quickbooks data models 2 | 3 | > ⚠️ **WARNING!** This package is no longer maintained. We do not recommend adding it to your dbt project. This code remains public to prevent breaking changes to any existing dbt projects that use this package. 4 | 5 | dbt data models for Quickbooks Online. 6 | 7 | Currently, these models support transformation of a large number of Quickbooks objects into the corresponding journal transactions so that account flows and balances can be trended over time. 8 | 9 | The data structures in this repo are built off of the [Stitch Quickbooks integration](https://www.stitchdata.com/integrations/quickbooks-online/). Other methods of denormalizing Quickbooks data could output slightly different data structures. 10 | 11 | The most important model in this repository is `general_ledger`. This model is the running GL for all accounts and should likely form the basis of most analysis. We recommend materializing this model into a table to improve query performance times, as it can take some time to generate with moderate dataset sizes. 12 | 13 | 14 | ### installation 15 | 16 | - modify your `profiles.yml` to include the following: 17 | ```YAML 18 | repositories: 19 | - "git@github.com:fishtown-analytics/quickbooks.git" 20 | ``` 21 | 22 | - copy the models within the `base-models` directory into your dbt project and modify them so that they select from the appropriate tables and fields within your environment. 23 | - run `dbt deps`. 24 | 25 | ### usage 26 | 27 | Once installation is completed, `dbt run` will build these models along with the other models in your project. 28 | 29 | ### contribution 30 | 31 | Additional contributions to this repo are very welcome! Please submit PRs to master. All PRs should only include functionality that is contained within all snowplow deployments; no implementation-specific details should be included. 32 | -------------------------------------------------------------------------------- /analysis/invoices.sql: -------------------------------------------------------------------------------- 1 | with lineitems as ( 2 | 3 | select * from analytics.quickbooks_invoice_lineitems 4 | 5 | ), items as ( 6 | 7 | select * from analytics.quickbooks_items 8 | 9 | ), accounts as ( 10 | 11 | select * from analytics.quickbooks_accounts 12 | 13 | ), invoices as ( 14 | 15 | select * from analytics.quickbooks_invoices 16 | 17 | ), customers as ( 18 | 19 | select * from analytics.quickbooks_customers 20 | 21 | ) 22 | 23 | select * 24 | from items 25 | inner join lineitems on items.id = lineitems.item_id 26 | inner join accounts on items.account_id = accounts.id 27 | inner join invoices on lineitems.invoice_id = invoices.id 28 | inner join customers on invoices.customer_id = customers.id 29 | -------------------------------------------------------------------------------- /analysis/running_total_by_account.sql: -------------------------------------------------------------------------------- 1 | with journal_entries as ( 2 | 3 | select * 4 | from analytics.quickbooks_adjusted_journal_entries 5 | 6 | ), accounts as ( 7 | 8 | select * 9 | from analytics.quickbooks_accounts_transformed 10 | 11 | ) 12 | 13 | select txn_date, account_id, adjusted_amount, description, account_name, 14 | sum(adjusted_amount) over (partition by account_id order by id rows unbounded preceding) 15 | from journal_entries 16 | order by account_id, id 17 | -------------------------------------------------------------------------------- /data/quickbooks_classifications.csv: -------------------------------------------------------------------------------- 1 | classification,statement,account_type 2 | Asset,bs,debit 3 | Liability,bs,credit 4 | Equity,bs,credit 5 | Expense,is,debit 6 | Revenue,is,credit 7 | -------------------------------------------------------------------------------- /data/quickbooks_statement_order.txt: -------------------------------------------------------------------------------- 1 | account_attribute,statement,order 2 | Income,is1,1 3 | Other Income,is1,2 4 | Cost of Goods Sold,is1,3 5 | Expense,is1,4 6 | Other Expense,is1,5 7 | -------------------------------------------------------------------------------- /dbt_project.yml: -------------------------------------------------------------------------------- 1 | name: 'quickbooks' 2 | version: '1.0' 3 | 4 | source-paths: ["models"] 5 | target-path: "target" 6 | clean-targets: ["target"] 7 | test-paths: ["test"] 8 | analysis-paths: ["analysis"] 9 | data-paths: ["data"] 10 | 11 | models: 12 | quickbooks: 13 | enabled: true 14 | materialized: view 15 | tables: 16 | materialized: table 17 | quickbooks_general_ledger: 18 | dist: id 19 | sort: txn_date 20 | base: 21 | materialized: ephemeral 22 | 23 | vars: 24 | "base.accounts" : "quickbooks.quickbooks_accounts" 25 | "base.bills_line" : "quickbooks.quickbooks_bills__line" 26 | "base.billpayments" : "quickbooks.quickbooks_billpayments" 27 | "base.billpayments_line" : "quickbooks.quickbooks_billpayments__line" 28 | "base.billpayments_line__linkedtxn" : "quickbooks.quickbooks_billpayments__line__linkedtxn" 29 | "base.bills" : "quickbooks.quickbooks_bills" 30 | "base.classes" : "quickbooks.quickbooks_classes" 31 | "base.customers" : "quickbooks.quickbooks_customers" 32 | "base.deposits_line" : "quickbooks.quickbooks_deposits__line" 33 | "base.deposits_line_linkedtxn" : "quickbooks.quickbooks_deposits__line__linkedtxn" 34 | "base.deposits" : "quickbooks.quickbooks_deposits" 35 | "base.invoices_lines" : "quickbooks.quickbooks_invoices__line" 36 | "base.invoices" : "quickbooks.quickbooks_invoices" 37 | "base.items" : "quickbooks.quickbooks_items" 38 | "base.journal_entries" : "quickbooks.quickbooks_journalentries" 39 | "base.journal_entries_line" : "quickbooks.quickbooks_journalentries__line" 40 | "base.payments" : "quickbooks.quickbooks_payments" 41 | "base.purchases_line" : "quickbooks.quickbooks_purchases__line" 42 | "base.purchases" : "quickbooks.quickbooks_purchases" 43 | "base.vendors" : "quickbooks.quickbooks_vendors" 44 | "level_0_id_field" : "_sdc_level_0_id" 45 | "source_key_id_field" : "_sdc_source_key_id" 46 | -------------------------------------------------------------------------------- /models/base/quickbooks_accounts.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int as id, 3 | name, 4 | fullyqualifiedname as fully_qualified_name, 5 | active, 6 | currentbalance as current_balance, 7 | nullif(parentref__value, '')::integer as parent_account_id, 8 | accounttype as type, 9 | accountsubtype as subtype, 10 | subaccount, 11 | classification, 12 | metadata__createtime as created_at, 13 | metadata__lastupdatedtime as updated_at 14 | from 15 | {{ var('base.accounts') }} 16 | -------------------------------------------------------------------------------- /models/base/quickbooks_bill_lines.sql: -------------------------------------------------------------------------------- 1 | select 2 | --only unique within a given bill_id 3 | id::int, 4 | amount, 5 | {{ var('source_key_id_field') }} as bill_id, 6 | nullif(accountbasedexpenselinedetail__classref__value::varchar, '')::bigint as class_id, 7 | accountbasedexpenselinedetail__accountref__value::int as account_id 8 | from 9 | {{ var('base.bills_line') }} 10 | -------------------------------------------------------------------------------- /models/base/quickbooks_bill_payments.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | totalamt as total, 4 | txndate::date as txn_date, 5 | vendorref__value::int as vendor_id, 6 | coalesce( 7 | nullif(creditcardpayment__ccaccountref__value, '')::int, 8 | nullif(checkpayment__bankaccountref__value, '')::int 9 | ) as payment_account_id, 10 | metadata__createtime as created_at, 11 | metadata__lastupdatedtime as updated_at 12 | from 13 | {{ var('base.billpayments') }} 14 | -------------------------------------------------------------------------------- /models/base/quickbooks_billpayment_lines.sql: -------------------------------------------------------------------------------- 1 | with lines as ( 2 | 3 | select *, 4 | {{ var('source_key_id_field') }}::int as bill_payment_id, 5 | {{ var('level_0_id_field') }}::int as _id 6 | from {{ var('base.billpayments_line') }} 7 | 8 | 9 | ), links as ( 10 | 11 | select *, 12 | {{ var('source_key_id_field') }}::int as bill_payment_id, 13 | {{ var('level_0_id_field') }}::int as _id 14 | from {{ var('base.billpayments_line__linkedtxn') }} 15 | 16 | ) 17 | 18 | select 19 | lines._id as id, 20 | lines.bill_payment_id, 21 | amount, 22 | txnid::int as bill_id 23 | from lines 24 | inner join links on links._id = lines._id and links.bill_payment_id = lines.bill_payment_id 25 | -------------------------------------------------------------------------------- /models/base/quickbooks_bills.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | txndate::date as txn_date, 4 | totalamt as total, 5 | duedate::date as due_date, 6 | balance, 7 | apaccountref__value::int as ap_account_id, 8 | metadata__createtime as created_at, 9 | metadata__lastupdatedtime as updated_at 10 | from 11 | {{ var('base.bills') }} 12 | -------------------------------------------------------------------------------- /models/base/quickbooks_classes.sql: -------------------------------------------------------------------------------- 1 | 2 | select 3 | id::bigint as id, 4 | name, 5 | fullyqualifiedname as fully_qualified_name, 6 | active, 7 | nullif(parentref__value, '')::bigint as parent_class_id, 8 | subclass, 9 | sparse, 10 | domain, 11 | metadata__createtime as created_at, 12 | metadata__lastupdatedtime as updated_at 13 | from 14 | {{ var('base.classes') }} 15 | -------------------------------------------------------------------------------- /models/base/quickbooks_customers.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | companyname as company_name, 4 | displayname as display_name, 5 | active, 6 | balance, 7 | metadata__createtime as created_at, 8 | metadata__lastupdatedtime as updated_at 9 | from 10 | {{ var('base.customers') }} 11 | -------------------------------------------------------------------------------- /models/base/quickbooks_deposit_lines.sql: -------------------------------------------------------------------------------- 1 | with lines as ( 2 | 3 | select *, 4 | {{ var('level_0_id_field') }}::int as _id, 5 | {{ var('source_key_id_field') }}::int as deposit_id 6 | from {{ var('base.deposits_line') }} 7 | 8 | ), links as ( 9 | 10 | select *, 11 | {{ var('level_0_id_field') }}::int as _id, 12 | {{ var('source_key_id_field') }}::int as deposit_id 13 | from {{ var('base.deposits_line_linkedtxn') }} 14 | 15 | ) 16 | 17 | select 18 | lines._id as id, 19 | lines.deposit_id, 20 | lines.amount, 21 | nullif(lines.depositlinedetail__classref__value::varchar, '')::bigint as class_id, 22 | nullif(lines.depositlinedetail__accountref__value::varchar, '')::int as account_id, 23 | links.txnid::int as payment_id 24 | from lines 25 | left outer join links on 26 | lines._id = links._id and 27 | lines.deposit_id = links.deposit_id 28 | -------------------------------------------------------------------------------- /models/base/quickbooks_deposits.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | totalamt as total, 4 | txndate::date as txn_date, 5 | deposittoaccountref__value::int as account_id, 6 | metadata__createtime as created_at, 7 | metadata__lastupdatedtime as updated_at 8 | from 9 | {{ var('base.deposits') }} 10 | -------------------------------------------------------------------------------- /models/base/quickbooks_invoice_lines.sql: -------------------------------------------------------------------------------- 1 | select 2 | --this id is only unique within a given invoice_id; may also want to create a globally unique id for this table. 3 | id::int, 4 | amount, 5 | description, 6 | {{ var('source_key_id_field') }}::int as invoice_id, 7 | nullif(salesitemlinedetail__classref__value::varchar, '')::bigint as class_id, 8 | salesitemlinedetail__itemref__value::int as item_id 9 | from 10 | {{ var('base.invoices_lines') }} 11 | where 12 | detailtype = 'SalesItemLineDetail' 13 | -------------------------------------------------------------------------------- /models/base/quickbooks_invoices.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | totalamt as total_amt, 4 | txndate::date as txn_date, 5 | duedate::date as due_date, 6 | balance, 7 | deliveryinfo__deliverytype as delivery_type, 8 | deliveryinfo__deliverytime::datetime as delivery_time, 9 | emailstatus as email_status, 10 | docnumber as doc_number, 11 | customerref__value::int as customer_id, 12 | metadata__createtime as created_at, 13 | metadata__lastupdatedtime as updated_at 14 | from 15 | {{ var('base.invoices') }} 16 | -------------------------------------------------------------------------------- /models/base/quickbooks_items.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | name, 4 | unitprice as unit_price, 5 | type, 6 | taxable, 7 | incomeaccountref__value::int as account_id, 8 | metadata__createtime as created_at, 9 | metadata__lastupdatedtime as updated_at 10 | from 11 | {{ var('base.items') }} 12 | -------------------------------------------------------------------------------- /models/base/quickbooks_journal_entries.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::integer, 3 | txndate::date as txn_date, 4 | adjustment, 5 | metadata__createtime as created_at, 6 | metadata__lastupdatedtime as updated_at 7 | from 8 | {{ var('base.journal_entries') }} 9 | -------------------------------------------------------------------------------- /models/base/quickbooks_journal_lines.sql: -------------------------------------------------------------------------------- 1 | select 2 | --this id is only unique within a given entry_id; may also want to create a globally unique id for this table. 3 | id::int, 4 | {{ var('source_key_id_field') }} as entry_id, 5 | coalesce(amount::numeric(38,6), 0::numeric(38,6)) as amount, 6 | description, 7 | nullif(journalentrylinedetail__accountref__value, '')::integer as account_id, 8 | nullif(journalentrylinedetail__classref__value::varchar, '')::bigint as class_id, 9 | journalentrylinedetail__postingtype as posting_type 10 | from 11 | {{ var('base.journal_entries_line') }} 12 | -------------------------------------------------------------------------------- /models/base/quickbooks_missing_account_classifications.sql: -------------------------------------------------------------------------------- 1 | select 64 as account_id, 'Revenue' as classification union all 2 | select 67, 'Liability' union all 3 | select 73, 'Equity' union all 4 | select 74, 'Equity' union all 5 | select 97, 'Expense' union all 6 | select 122, 'Expense' union all 7 | select 132, 'Expense' 8 | -------------------------------------------------------------------------------- /models/base/quickbooks_payments.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | totalamt as total, 4 | txndate::date as txn_date, 5 | --txnsource as txn_source, 6 | unappliedamt as unapplied_amt, 7 | --creditcardpayment__creditchargeinfo__processpayment as cc_pmt_processed, 8 | processpayment as payment_processed, 9 | nullif(deposittoaccountref__value::varchar, '')::int as account_id, 10 | customerref__value::int as customer_id, 11 | nullif(paymentmethodref__value::varchar, '')::int as payment_method_id, 12 | metadata__createtime as created_at, 13 | metadata__lastupdatedtime as updated_at 14 | from 15 | {{ var('base.payments') }} 16 | -------------------------------------------------------------------------------- /models/base/quickbooks_purchase_lines.sql: -------------------------------------------------------------------------------- 1 | select 2 | --this id is only unique within a given purchase_id; may also want to create a globally unique id for this table. 3 | id::int, 4 | amount, 5 | {{ var('source_key_id_field') }}::int as purchase_id, 6 | nullif(accountbasedexpenselinedetail__classref__value::varchar, '')::bigint as class_id, 7 | accountbasedexpenselinedetail__accountref__value::int as account_id 8 | from 9 | {{ var('base.purchases_line') }} 10 | -------------------------------------------------------------------------------- /models/base/quickbooks_purchases.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | paymenttype as payment_type, 4 | totalamt as total, 5 | txndate::date as txn_date, 6 | credit, 7 | accountref__value::int as account_id, 8 | --the following three fields contain information on who was paid for this record. 9 | --the payment can be made to a vendor, customer, or employee. this is found in entity_type. 10 | --based on entity_type, the id value should be mapped to the corresponding table to get details. 11 | entityref__value::int as vendor_id, 12 | entityref__name as entity_name, 13 | entityref__type as entity_type, 14 | metadata__createtime as created_at, 15 | metadata__lastupdatedtime as updated_at 16 | from 17 | {{ var('base.purchases') }} 18 | -------------------------------------------------------------------------------- /models/base/quickbooks_vendors.sql: -------------------------------------------------------------------------------- 1 | select 2 | id::int, 3 | displayname as name, 4 | balance, 5 | metadata__createtime as created_at, 6 | metadata__lastupdatedtime as updated_at 7 | from 8 | {{ var('base.vendors') }} 9 | -------------------------------------------------------------------------------- /models/tables/quickbooks_general_ledger.sql: -------------------------------------------------------------------------------- 1 | with unioned as ( 2 | 3 | select 4 | id, 5 | txn_date, 6 | amount, 7 | account_id, 8 | transaction_type::varchar(64), 9 | source::varchar(64), 10 | class_id 11 | from {{ref('quickbooks_bill_transactions')}} 12 | 13 | union all 14 | select * from {{ref('quickbooks_billpayment_transactions')}} 15 | union all 16 | select * from {{ref('quickbooks_invoice_transactions')}} 17 | union all 18 | select * from {{ref('quickbooks_purchase_transactions')}} 19 | union all 20 | select * from {{ref('quickbooks_journal_transactions')}} 21 | union all 22 | select * from {{ref('quickbooks_deposit_transactions')}} 23 | union all 24 | select * from {{ref('quickbooks_payment_transactions')}} 25 | 26 | ), accounts as ( 27 | 28 | select * from {{ref('quickbooks_accounts_xf')}} 29 | 30 | ) 31 | 32 | select 33 | unioned.*, 34 | case 35 | when accounts.account_type = unioned.transaction_type 36 | then amount 37 | else 38 | amount * -1 39 | end as adj_amount 40 | from unioned 41 | inner join accounts on unioned.account_id = accounts.id 42 | -------------------------------------------------------------------------------- /models/tables/quickbooks_parent_accounts_map.sql: -------------------------------------------------------------------------------- 1 | with accounts as ( 2 | 3 | select * from {{ref('quickbooks_accounts_xf')}} 4 | 5 | ), d1 as ( 6 | 7 | select 8 | a1.id as a1_id, 9 | a2.id as a2_id, 10 | a3.id as a3_id, 11 | a4.id as a4_id, 12 | a5.id as a5_id 13 | from accounts a1 14 | left outer join accounts a2 on a1.id = a2.parent_account_id 15 | left outer join accounts a3 on a2.id = a3.parent_account_id 16 | left outer join accounts a4 on a3.id = a4.parent_account_id 17 | left outer join accounts a5 on a4.id = a5.parent_account_id 18 | where a1.parent_account_id is null 19 | 20 | ), d2 as ( 21 | 22 | select 23 | a1_id as top_level_account_id, 24 | a1_id as account_id 25 | from d1 26 | 27 | union all 28 | 29 | select 30 | a1_id as top_level_account_id, 31 | a2_id as account_id 32 | from d1 33 | 34 | union all 35 | 36 | select 37 | a1_id as top_level_account_id, 38 | a3_id as account_id 39 | from d1 40 | 41 | union all 42 | 43 | select 44 | a1_id as top_level_account_id, 45 | a4_id as account_id 46 | from d1 47 | 48 | union all 49 | 50 | select 51 | a1_id as top_level_account_id, 52 | a5_id as account_id 53 | from d1 54 | 55 | ) 56 | 57 | select distinct account_id, top_level_account_id 58 | from d2 59 | where account_id is not null 60 | -------------------------------------------------------------------------------- /models/tables/quickbooks_parent_class_map.sql: -------------------------------------------------------------------------------- 1 | with classes as ( 2 | 3 | select * from {{ ref('quickbooks_classes') }} 4 | 5 | ), d1 as ( 6 | 7 | select 8 | c1.id as c1_id, 9 | c2.id as c2_id, 10 | c3.id as c3_id, 11 | c4.id as c4_id, 12 | c5.id as c5_id 13 | from classes c1 14 | left outer join classes c2 on c1.id = c2.parent_class_id 15 | left outer join classes c3 on c2.id = c3.parent_class_id 16 | left outer join classes c4 on c3.id = c4.parent_class_id 17 | left outer join classes c5 on c4.id = c5.parent_class_id 18 | where c1.parent_class_id is null 19 | 20 | ), d2 as ( 21 | 22 | select 23 | c1_id as top_level_class_id, 24 | c1_id as class_id 25 | from d1 26 | 27 | union all 28 | 29 | select 30 | c1_id as top_level_class_id, 31 | c2_id as class_id 32 | from d1 33 | 34 | union all 35 | 36 | select 37 | c1_id as top_level_class_id, 38 | c3_id as class_id 39 | from d1 40 | 41 | union all 42 | 43 | select 44 | c1_id as top_level_class_id, 45 | c4_id as class_id 46 | from d1 47 | 48 | union all 49 | 50 | select 51 | c1_id as top_level_class_id, 52 | c5_id as class_id 53 | from d1 54 | 55 | ), 56 | 57 | pivoted as ( 58 | 59 | select distinct class_id, top_level_class_id 60 | from d2 61 | where class_id is not null 62 | 63 | ) 64 | 65 | select class_id, top_level_class_id, c1.name as class_name, c2.name as top_level_class_name 66 | from pivoted 67 | join classes c1 on c1.id = class_id 68 | join classes c2 on c2.id = top_level_class_id 69 | -------------------------------------------------------------------------------- /models/transform/quickbooks_accounts_xf.sql: -------------------------------------------------------------------------------- 1 | with null_classification_accounts as ( 2 | 3 | select * from {{ref('quickbooks_accounts')}} 4 | 5 | ), classifications as ( 6 | 7 | select * from {{ref('quickbooks_classifications')}} 8 | 9 | ), missing_classifications as ( 10 | 11 | select * from {{ref('quickbooks_missing_account_classifications')}} 12 | 13 | ), accounts as ( 14 | 15 | select nca.id, nca.name, nca.fully_qualified_name, nca.active, nca.current_balance, nca.parent_account_id, 16 | nca.type, nca.subtype, nca.subaccount, nvl(nca.classification, mc.classification) as classification, 17 | nca.created_at, nca.updated_at 18 | from null_classification_accounts nca 19 | left outer join missing_classifications mc on nca.id = mc.account_id 20 | 21 | ) 22 | 23 | select accounts.*, classifications.statement, classifications.account_type 24 | from accounts 25 | inner join classifications on accounts.classification = classifications.classification 26 | -------------------------------------------------------------------------------- /models/transform/quickbooks_bill_transactions.sql: -------------------------------------------------------------------------------- 1 | --the registration of a bill debits expense and credits AP. 2 | --this query creates both of those transactions. 3 | 4 | with bills as ( 5 | 6 | select * from {{ref('quickbooks_bills')}} 7 | 8 | ), bill_lines as ( 9 | 10 | select * from {{ref('quickbooks_bill_lines')}} 11 | 12 | ), d1 as ( 13 | 14 | select bills.id, bills.txn_date, bill_lines.amount, 15 | bill_lines.account_id as payed_to_acct_id, 16 | ap_account_id, 17 | bill_lines.class_id 18 | from bills 19 | inner join bill_lines on bills.id = bill_lines.bill_id 20 | 21 | ) 22 | 23 | select id, 24 | txn_date, 25 | amount, 26 | payed_to_acct_id as account_id, 27 | 'debit'::varchar(16) as transaction_type, 28 | 'bill'::varchar(16) as source, 29 | class_id::bigint 30 | 31 | from d1 32 | 33 | union all 34 | 35 | select 36 | id, 37 | txn_date, 38 | amount, 39 | ap_account_id, 40 | 'credit' as transaction_type, 41 | 'bill', 42 | class_id::bigint 43 | from d1 44 | -------------------------------------------------------------------------------- /models/transform/quickbooks_billpayment_transactions.sql: -------------------------------------------------------------------------------- 1 | --paying a bill debits AP and credits cash. 2 | --this query creates both of those transactions. 3 | 4 | with bill_payments as ( 5 | 6 | select * from {{ref('quickbooks_bill_payments')}} 7 | 8 | ), billpayment_lines as ( 9 | 10 | select * from {{ref('quickbooks_billpayment_lines')}} 11 | 12 | ), d1 as ( 13 | 14 | select 15 | bp.id, 16 | bp.txn_date, 17 | bpl.amount, 18 | payment_account_id, 19 | ap.id as ap_id 20 | from bill_payments bp 21 | inner join billpayment_lines bpl on bp.id = bpl.bill_payment_id 22 | join (select id from {{ref('quickbooks_accounts')}} where type = 'Accounts Payable') ap 23 | on 1 = 1 24 | 25 | ) 26 | 27 | select 28 | id, 29 | txn_date, 30 | amount, 31 | payment_account_id as account_id, 32 | 'credit' as transaction_type, 33 | 'bill payment' as source, 34 | null::bigint as class_id 35 | from 36 | d1 37 | 38 | union all 39 | 40 | select 41 | id, 42 | txn_date, 43 | amount, 44 | ap_id, 45 | 'debit', 46 | 'bill payment', 47 | null::bigint 48 | from 49 | d1 50 | -------------------------------------------------------------------------------- /models/transform/quickbooks_days.sql: -------------------------------------------------------------------------------- 1 | select (min(txn_date) over () + row_number() over ())::date as date_day 2 | from {{ref('quickbooks_general_ledger')}} 3 | -------------------------------------------------------------------------------- /models/transform/quickbooks_deposit_transactions.sql: -------------------------------------------------------------------------------- 1 | --deposits debit cash and credit either undeposited funds or a specific other account indicated in the deposit line. 2 | --this query creates both of those transactions. 3 | 4 | with lines as ( 5 | 6 | select * from {{ref('quickbooks_deposit_lines')}} 7 | 8 | ), deposits as ( 9 | 10 | select * from {{ref('quickbooks_deposits')}} 11 | 12 | ), d1 as ( 13 | 14 | select 15 | deposits.id, 16 | deposits.txn_date, 17 | lines.amount, 18 | deposits.account_id as deposit_to_acct_id, 19 | coalesce(lines.account_id, udf.id) as deposit_from_acct_id, 20 | lines.class_id 21 | from deposits 22 | inner join lines on deposits.id = lines.deposit_id 23 | join (select id from {{ref('quickbooks_accounts')}} where subtype = 'UndepositedFunds') udf 24 | on 1 = 1 25 | 26 | ) 27 | 28 | select 29 | id, 30 | txn_date, 31 | amount, 32 | deposit_to_acct_id as account_id, 33 | 'debit' as transaction_type, 34 | 'deposit' as source, 35 | class_id 36 | from d1 37 | 38 | union all 39 | 40 | select 41 | id, 42 | txn_date, 43 | amount, 44 | deposit_from_acct_id, 45 | 'credit', 46 | 'deposit', 47 | class_id 48 | from d1 49 | -------------------------------------------------------------------------------- /models/transform/quickbooks_invoice_transactions.sql: -------------------------------------------------------------------------------- 1 | --the registration of an invoice debits AR and credits a specific revenue account indicated 2 | --on the invoice line. 3 | --this query creates both of those transactions. 4 | 5 | with invoice_lines as ( 6 | 7 | select * from {{ref('quickbooks_invoice_lines')}} 8 | 9 | ), invoices as ( 10 | 11 | select * from {{ref('quickbooks_invoices')}} 12 | 13 | ), items as ( 14 | 15 | select * from {{ref('quickbooks_items')}} 16 | 17 | ), d1 as ( 18 | 19 | select 20 | invoices.id, 21 | invoices.txn_date, 22 | invoice_lines.amount, 23 | items.account_id, 24 | invoice_lines.class_id 25 | from invoices 26 | inner join invoice_lines on invoices.id = invoice_lines.invoice_id 27 | inner join items on invoice_lines.item_id = items.id 28 | 29 | ) 30 | 31 | select 32 | id, 33 | txn_date, 34 | amount, 35 | account_id, 36 | 'credit' as transaction_type, 37 | 'invoice' as source, 38 | class_id 39 | from d1 40 | 41 | union all 42 | 43 | select 44 | d1.id, 45 | txn_date, 46 | amount, 47 | ar.id, 48 | 'debit' as transaction_type, 49 | 'invoice', 50 | class_id 51 | from d1 52 | join (select id from {{ref('quickbooks_accounts')}} where type = 'Accounts Receivable') ar 53 | on 1 = 1 54 | -------------------------------------------------------------------------------- /models/transform/quickbooks_invoices_xf.sql: -------------------------------------------------------------------------------- 1 | with invoices as ( 2 | 3 | select * from {{ref('quickbooks_invoices')}} 4 | 5 | ), 6 | 7 | payments as ( 8 | 9 | select 10 | *, 11 | total_amt - balance as amount_paid, 12 | case 13 | when balance < .05 * total_amt then 1 14 | else 0 15 | end as paid 16 | from invoices 17 | 18 | ), 19 | 20 | final as ( 21 | 22 | select 23 | *, 24 | case 25 | when paid = 0 and due_date < current_date then 1 26 | else 0 27 | end as overdue 28 | from payments 29 | 30 | ) 31 | 32 | select * from final 33 | -------------------------------------------------------------------------------- /models/transform/quickbooks_journal_transactions.sql: -------------------------------------------------------------------------------- 1 | with entries as ( 2 | 3 | select * from {{ref('quickbooks_journal_entries')}} 4 | 5 | ), lines as ( 6 | 7 | select * from {{ref('quickbooks_journal_lines')}} 8 | 9 | ) 10 | 11 | select 12 | entries.id, 13 | entries.txn_date, 14 | lines.amount, 15 | account_id, 16 | lower(posting_type) as transaction_type, 17 | 'journal' as source, 18 | lines.class_id 19 | from entries 20 | inner join lines on entries.id = lines.entry_id 21 | -------------------------------------------------------------------------------- /models/transform/quickbooks_ledger_by_month.sql: -------------------------------------------------------------------------------- 1 | with accounts as ( 2 | 3 | select * from {{ref('quickbooks_accounts_xf')}} 4 | 5 | ), ledger as ( 6 | 7 | select * from {{ref('quickbooks_ledger_xf')}} 8 | 9 | ), months as ( 10 | 11 | select * from {{ref('quickbooks_months')}} 12 | 13 | ), monthly_account_totals as ( 14 | 15 | select date_trunc('month', txn_date) date_month, account_id, 16 | sum(adj_amount) as total 17 | from ledger 18 | group by 1, 2 19 | 20 | ), account_months as ( 21 | 22 | select id as account_id, date_month 23 | from accounts 24 | join months on 1=1 25 | 26 | ) 27 | 28 | select am.date_month, am.account_id, coalesce(mat.total, 0.0) as total, 29 | sum(coalesce(mat.total, 0.0)) over (partition by am.account_id order by am.date_month rows unbounded preceding) as cumulative_total 30 | from account_months am 31 | left outer join monthly_account_totals mat on 32 | am.account_id = mat.account_id and 33 | am.date_month = mat.date_month 34 | order by 1, 2 35 | -------------------------------------------------------------------------------- /models/transform/quickbooks_ledger_xf.sql: -------------------------------------------------------------------------------- 1 | with accounts as ( 2 | 3 | select * from {{ref('quickbooks_accounts_xf')}} 4 | 5 | ), ledger as ( 6 | 7 | select * from {{ref('quickbooks_general_ledger')}} 8 | 9 | ), d1 as ( 10 | 11 | select ledger.*, 12 | case 13 | when transaction_type = account_type then 1 14 | else -1 15 | end as multiplier 16 | from ledger 17 | inner join accounts on ledger.account_id = accounts.id 18 | 19 | ) 20 | 21 | select 22 | id, 23 | txn_date, 24 | amount, 25 | account_id, 26 | class_id, 27 | transaction_type, 28 | source, 29 | amount * multiplier as adj_amount, 30 | sum(amount * multiplier) over (partition by account_id order by txn_date rows unbounded preceding) as current_account_balance 31 | from d1 32 | -------------------------------------------------------------------------------- /models/transform/quickbooks_months.sql: -------------------------------------------------------------------------------- 1 | select distinct date_trunc('month', date_day)::date as date_month 2 | from {{ref('quickbooks_days')}} 3 | where date_day <= current_date 4 | -------------------------------------------------------------------------------- /models/transform/quickbooks_payment_transactions.sql: -------------------------------------------------------------------------------- 1 | --payments debits either undeposited funds or cash and credits accounts receivable. 2 | --this query creates both of those transactions. 3 | 4 | 5 | with payments as ( 6 | 7 | select * from {{ref('quickbooks_payments')}} 8 | 9 | ) 10 | 11 | select 12 | payments.id, 13 | txn_date, 14 | total as amount, 15 | account_id, 16 | 'debit' as transaction_type, 17 | 'payment' as source, 18 | null::bigint as class_id 19 | from payments 20 | 21 | union all 22 | 23 | select 24 | payments.id, 25 | txn_date, 26 | total, 27 | ar.id, 28 | 'credit', 29 | 'payment', 30 | null::bigint 31 | from payments 32 | join (select id from {{ref('quickbooks_accounts')}} where type = 'Accounts Receivable') ar 33 | on 1 = 1 34 | -------------------------------------------------------------------------------- /models/transform/quickbooks_purchase_transactions.sql: -------------------------------------------------------------------------------- 1 | --purchases debit expense and credits the payment account (cash or credit). 2 | --this query creates both of those transactions. 3 | 4 | with purchases as ( 5 | 6 | select * from {{ref('quickbooks_purchases')}} 7 | 8 | ), purchase_lines as ( 9 | 10 | select * from {{ref('quickbooks_purchase_lines')}} 11 | 12 | ), accounts as ( 13 | 14 | select * from {{ref('quickbooks_accounts_xf')}} 15 | 16 | ), d1 as ( 17 | 18 | select 19 | purchases.id, 20 | purchase_lines.amount, 21 | purchases.txn_date, 22 | purchase_lines.account_id as payed_to_acct_id, 23 | purchases.account_id as payed_from_acct_id, 24 | case nvl(purchases.credit, false::bool) 25 | when true then 'debit' 26 | else 'credit' 27 | end as payed_from_transaction_type, 28 | case nvl(purchases.credit, false::bool) 29 | when true then 'credit' 30 | else 'debit' 31 | end as payed_to_transaction_type, 32 | purchase_lines.class_id 33 | from purchases 34 | inner join purchase_lines on purchases.id = purchase_lines.purchase_id 35 | 36 | ) 37 | 38 | select 39 | id, 40 | txn_date, 41 | amount, 42 | payed_from_acct_id as account_id, 43 | payed_from_transaction_type as transaction_type, 44 | 'purchase' as source, 45 | class_id 46 | from d1 47 | 48 | union all 49 | 50 | select 51 | id, 52 | txn_date, 53 | amount, 54 | payed_to_acct_id, 55 | payed_to_transaction_type, 56 | 'purchase', 57 | class_id 58 | from d1 59 | -------------------------------------------------------------------------------- /models/transform/quickbooks_toplevel_account_metrics.sql: -------------------------------------------------------------------------------- 1 | with ledger as ( 2 | 3 | select * from {{ref('quickbooks_general_ledger')}} 4 | 5 | ), accounts_map as ( 6 | 7 | select * from {{ref('quickbooks_parent_accounts_map')}} 8 | 9 | ) 10 | 11 | select 12 | txn_date::date as "date", 13 | top_level_account_id, 14 | sum(adj_amount) as amount 15 | from ledger 16 | inner join accounts_map on ledger.account_id = accounts_map.account_id 17 | group by 1, 2 18 | -------------------------------------------------------------------------------- /models/transform/quickbooks_toplevel_class_metrics.sql: -------------------------------------------------------------------------------- 1 | with ledger as ( 2 | 3 | select * from {{ref('quickbooks_general_ledger')}} 4 | 5 | ), class_map as ( 6 | 7 | select * from {{ref('quickbooks_parent_class_map')}} 8 | 9 | ) 10 | 11 | select 12 | txn_date::date as "date", 13 | top_level_class_id, 14 | sum(adj_amount) as amount 15 | from ledger 16 | inner join class_map on ledger.class_id = class_map.class_id 17 | group by 1, 2 18 | -------------------------------------------------------------------------------- /tests/debits_minus_credits.sql: -------------------------------------------------------------------------------- 1 | --this tests the entire general ledger process to ensure that debits = credits within each transaction id. 2 | --success is returning no records 3 | 4 | with ledger as ( 5 | 6 | select * 7 | from {{ref('quickbooks_general_ledger')}} 8 | 9 | ), debits as ( 10 | 11 | select id, sum(amount) as total 12 | from ledger 13 | where lower(transaction_type) = 'debit' 14 | group by 1 15 | 16 | ), credits as ( 17 | 18 | select id, sum(amount) as total 19 | from ledger 20 | where lower(transaction_type) = 'credit' 21 | group by 1 22 | 23 | ), transactions as ( 24 | 25 | select debits.id as transaction_id, debits.total as debits, credits.total as credits, 26 | debits.total - credits.total as difference 27 | from debits 28 | full outer join credits on debits.id = credits.id 29 | 30 | ) 31 | 32 | select * 33 | from transactions 34 | where difference > 0 35 | --------------------------------------------------------------------------------