SQL Query Library

11 verified Oracle SQL queries for KNIME DB Query Reader nodes — all tested against the actual table schema.

Oracle SQL only. These queries use Oracle-specific syntax: SYSDATE, ADD_MONTHS(), LENGTH(), SUBSTR(), and || concatenation. T-SQL equivalents (GETDATE, LEN, DATEADD) will not work.

KNIME node: Use DB Query Reader. Paste SQL directly. Parameters use :variable_name syntax — map each to a flow variable from a Configuration node at workflow start.

Key rule: ZX_LINES joins via TRX_ID (not INVOICE_ID). Always add APPLICATION_ID = 200 for AP queries and APPLICATION_ID = 222 for AR. Column names are TAX_AMT / TAXABLE_AMT.

VAT Return Preparation

Input VAT Summary by Rate Code (Return Box Mapping)

Aggregates all input tax from ZX_LINES (AP side) by rate code for a specific entity and period. Uses TRX_ID — the correct join key from ZX_LINES to AP_INVOICES_ALL. Map results directly to VAT return boxes.

🔧 Bugs fixed vs original:

  • Fixed join: zx.TRX_ID = ai.INVOICE_ID (was wrongly zx.INVOICE_ID)
  • Added APPLICATION_ID = 200 filter to restrict to AP transactions
  • Fixed column names: TAXABLE_AMT and TAX_AMT (not TAXABLE_AMOUNT/TAX_AMOUNT)
  • Regime code uses underscore: NL_VAT not NL-VAT
Oracle SQL — DB Query Reader Node
-- Input VAT Summary for Return Preparation
-- Use in: DB Query Reader node
-- Parameters: set :org_id, :period_start, :period_end, :tax_regime as flow variables

SELECT
    zx.TAX_RATE_CODE,
    zx.TAX_STATUS_CODE,
    zx.SELF_ASSESSED_FLAG,
    ROUND(SUM(zx.TAXABLE_AMT), 2)          AS TOTAL_TAXABLE,
    ROUND(SUM(zx.TAX_AMT), 2)              AS TOTAL_VAT,
    COUNT(DISTINCT ai.INVOICE_ID)           AS INVOICE_COUNT
FROM ZX_LINES zx
INNER JOIN AP_INVOICES_ALL ai
    ON  zx.TRX_ID        = ai.INVOICE_ID   -- ZX_LINES links via TRX_ID, not INVOICE_ID
    AND zx.APPLICATION_ID = 200            -- 200 = AP; 222 = AR
WHERE ai.ORG_ID          = :org_id
    AND ai.INVOICE_DATE BETWEEN :period_start AND :period_end
    AND ai.CANCELLED_DATE  IS NULL
    AND zx.TAX_REGIME_CODE = :tax_regime   -- e.g. 'NL_VAT' (underscore, not hyphen)
GROUP BY
    zx.TAX_RATE_CODE,
    zx.TAX_STATUS_CODE,
    zx.SELF_ASSESSED_FLAG
ORDER BY zx.TAX_RATE_CODE;

KNIME Integration: Use DB Query Reader node. Map :org_id, :period_start, :period_end, :tax_regime to flow variables from a Configuration node at workflow start.

Output VAT Summary (AR Sales) — via ZX_LINES SSOT

Summarises output VAT from AR invoices using ZX_LINES as the Single Source of Truth. RA_CUSTOMER_TRX_LINES_ALL does not carry tax rate or VAT amount columns — always join to ZX_LINES for tax figures.

🔧 Bugs fixed vs original:

  • Complete rewrite: original used ctl.REVENUE_AMOUNT and ctl.TAX_RATE which do not exist on RA_CUSTOMER_TRX_LINES_ALL
  • Now correctly joins RA_CUSTOMER_TRX_ALL → ZX_LINES via TRX_ID with APPLICATION_ID = 222
  • VAT amounts read from zx.TAX_AMT and zx.TAXABLE_AMT (the correct ZX_LINES columns)
Oracle SQL — DB Query Reader Node
-- Output VAT from AR (Sales Invoices) — ZX_LINES is the tax SSOT
-- Use in: DB Query Reader node

SELECT
    zx.TAX_RATE_CODE,
    zx.TAX_STATUS_CODE,
    CASE
        WHEN zx.TAX_STATUS_CODE IN ('ZERO RATED','ZERO_RATED') THEN 'ZERO_RATED'
        WHEN zx.TAX_STATUS_CODE IN ('EXEMPT','EXEMPTED')       THEN 'EXEMPT'
        WHEN zx.SELF_ASSESSED_FLAG = 'Y'                       THEN 'REVERSE_CHARGE'
        ELSE 'STANDARD'
    END                                        AS VAT_CATEGORY,
    ROUND(SUM(zx.TAXABLE_AMT), 2)             AS TOTAL_NET_REVENUE,
    ROUND(SUM(zx.TAX_AMT), 2)                 AS TOTAL_OUTPUT_VAT,
    COUNT(DISTINCT ct.CUSTOMER_TRX_ID)         AS TRANSACTION_COUNT
FROM RA_CUSTOMER_TRX_ALL ct
INNER JOIN ZX_LINES zx
    ON  zx.TRX_ID        = ct.CUSTOMER_TRX_ID -- ZX_LINES links via TRX_ID
    AND zx.APPLICATION_ID = 222               -- 222 = AR
WHERE ct.ORG_ID          = :org_id
    AND ct.TRX_DATE BETWEEN :period_start AND :period_end
    AND zx.TAX_REGIME_CODE = :tax_regime
GROUP BY
    zx.TAX_RATE_CODE,
    zx.TAX_STATUS_CODE,
    CASE
        WHEN zx.TAX_STATUS_CODE IN ('ZERO RATED','ZERO_RATED') THEN 'ZERO_RATED'
        WHEN zx.TAX_STATUS_CODE IN ('EXEMPT','EXEMPTED')       THEN 'EXEMPT'
        WHEN zx.SELF_ASSESSED_FLAG = 'Y'                       THEN 'REVERSE_CHARGE'
        ELSE 'STANDARD'
    END
ORDER BY zx.TAX_RATE_CODE;

KNIME Integration: Combine output of this query with the Input VAT query using a DB Union node to build a complete VAT return dataset. Both queries use the same parameter variables.

Compliance & Error Detection

Invoices with Potential Rate Mismatches

Find AP invoices where the applied VAT rate does not match any known valid rate for the supplier country. Filters in WHERE — HAVING without GROUP BY (as in the original) is invalid SQL.

🔧 Bugs fixed vs original:

  • Fixed join to ZX_LINES: ON zx.TRX_ID = ai.INVOICE_ID AND zx.APPLICATION_ID = 200
  • Fixed column names: zx.TAX_AMT and zx.TAXABLE_AMT
  • Replaced invalid HAVING-without-GROUP-BY with WHERE OR conditions (HAVING is only valid after GROUP BY)
  • Removed duplicate CASE expression — condition now lives only in WHERE
Oracle SQL — DB Query Reader Node
-- Rate Mismatch Detection (Oracle SQL)
-- Flags AP invoices where the applied VAT rate is not a known valid rate
-- for that supplier's country. Filter logic lives in WHERE, not HAVING.

SELECT
    ai.INVOICE_NUM,
    ai.INVOICE_DATE,
    s.VENDOR_NAME,
    ss.COUNTRY                 AS SUPPLIER_COUNTRY,
    zx.TAX_RATE_CODE,
    zx.TAX_RATE                AS APPLIED_RATE,
    zx.TAX_AMT                 AS TAX_AMOUNT,
    zx.TAXABLE_AMT             AS TAXABLE_AMOUNT,
    hou.NAME                   AS CANON_ENTITY,
    'SUSPECT'                  AS RATE_CHECK
FROM AP_INVOICES_ALL ai
INNER JOIN ZX_LINES zx
    ON  zx.TRX_ID        = ai.INVOICE_ID
    AND zx.APPLICATION_ID = 200
INNER JOIN AP_SUPPLIERS s
    ON  ai.VENDOR_ID      = s.VENDOR_ID
INNER JOIN AP_SUPPLIER_SITES_ALL ss
    ON  ai.VENDOR_SITE_ID = ss.VENDOR_SITE_ID
INNER JOIN HR_OPERATING_UNITS hou
    ON  ai.ORG_ID         = hou.ORGANIZATION_ID
WHERE ai.INVOICE_DATE BETWEEN :period_start AND :period_end
    AND ai.CANCELLED_DATE IS NULL
    AND (
        (ss.COUNTRY = 'FR' AND zx.TAX_RATE NOT IN (0, 5.5, 10, 20))
     OR (ss.COUNTRY = 'DE' AND zx.TAX_RATE NOT IN (0, 7, 19))
     OR (ss.COUNTRY = 'NL' AND zx.TAX_RATE NOT IN (0, 9, 21))
     OR (ss.COUNTRY = 'BE' AND zx.TAX_RATE NOT IN (0, 6, 12, 21))
     OR (ss.COUNTRY = 'PL' AND zx.TAX_RATE NOT IN (0, 5, 8, 23))
    )
ORDER BY ai.INVOICE_DATE DESC;

KNIME Integration: Run monthly. In KNIME: pipe results to a Send Email node with an Excel attachment if row count > 0. Add more countries to the WHERE OR block as needed.

Missing Reverse Charge Detection

Find cross-border B2B purchase invoices from EU suppliers that do NOT have a self-assessed tax line (potential missing reverse charge). Uses a correlated NOT EXISTS subquery.

🔧 Bugs fixed vs original:

  • Fixed NOT EXISTS subquery: zx.TRX_ID = ai.INVOICE_ID (was zx.INVOICE_ID which does not exist on ZX_LINES)
  • Added APPLICATION_ID = 200 inside the subquery to avoid false matches against AR events
Oracle SQL — DB Query Reader Node
-- Missing Reverse Charge Detection (Oracle SQL)
-- Finds EU-supplier AP invoices with no self-assessed ZX_LINES row.
-- Cross-border B2B services from EU suppliers typically require reverse charge.

SELECT
    ai.INVOICE_ID,
    ai.INVOICE_NUM,
    ai.INVOICE_DATE,
    ai.INVOICE_AMOUNT,
    ai.INVOICE_CURRENCY_CODE,
    s.VENDOR_NAME,
    ss.COUNTRY                 AS SUPPLIER_COUNTRY,
    hou.NAME                   AS BUYER_ENTITY,
    'MISSING REVERSE CHARGE'   AS ISSUE
FROM AP_INVOICES_ALL ai
INNER JOIN AP_SUPPLIERS s
    ON  ai.VENDOR_ID       = s.VENDOR_ID
INNER JOIN AP_SUPPLIER_SITES_ALL ss
    ON  ai.VENDOR_SITE_ID  = ss.VENDOR_SITE_ID
INNER JOIN HR_OPERATING_UNITS hou
    ON  ai.ORG_ID          = hou.ORGANIZATION_ID
WHERE ai.INVOICE_DATE BETWEEN :period_start AND :period_end
    AND ai.CANCELLED_DATE IS NULL
    AND ss.COUNTRY <> :buyer_country  -- only cross-border
    AND ss.COUNTRY IN (               -- EU-27 supplier countries
        'AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR',
        'DE','GR','HU','IE','IT','LV','LT','LU','MT','NL',
        'PL','PT','RO','SK','SI','ES','SE'
    )
    AND NOT EXISTS (
        SELECT 1
        FROM ZX_LINES zx
        WHERE zx.TRX_ID        = ai.INVOICE_ID  -- correct join column
          AND zx.APPLICATION_ID = 200            -- AP events only
          AND zx.SELF_ASSESSED_FLAG = 'Y'
    )
ORDER BY ai.INVOICE_AMOUNT DESC;

KNIME Integration: NOT EXISTS subqueries must stay as SQL — there is no equivalent pure-KNIME node combination. Use DB Query Reader. Consider adding an invoice_type filter (e.g., INVOICE_TYPE_LOOKUP_CODE = 'STANDARD') to exclude prepayments.

E-Invoicing (IDF)

E-Invoice Submission Gap Analysis

Find AP invoices that fall under an active e-invoicing mandate but have no IDF submission record. Uses GO_LIVE_DATE from IDF_COUNTRY_CONFIG (not EFFECTIVE_DATE which does not exist).

🔧 Bugs fixed vs original:

  • Fixed: icc.EFFECTIVE_DATE → icc.GO_LIVE_DATE (actual column name from IDF_COUNTRY_CONFIG)
  • Fixed NOT EXISTS subquery: iel.SOURCE_TRX_ID = ai.INVOICE_ID (IDF_EINVOICE_LOG uses SOURCE_TRX_ID, not INVOICE_ID)
  • Added iel.SOURCE_SYSTEM = 'AP' to distinguish AP from AR e-invoices in the same log table
Oracle SQL — DB Query Reader Node
-- E-Invoice Submission Gap Analysis (Oracle SQL)
-- Finds AP invoices in mandate-active countries with no IDF_EINVOICE_LOG record.

SELECT
    ai.INVOICE_ID,
    ai.INVOICE_NUM,
    ai.INVOICE_DATE,
    ai.INVOICE_AMOUNT,
    ai.INVOICE_CURRENCY_CODE,
    s.VENDOR_NAME,
    ss.COUNTRY                 AS SUPPLIER_COUNTRY,
    hou.NAME                   AS ENTITY,
    icc.MANDATE_STATUS,
    icc.GO_LIVE_DATE,
    'NOT SUBMITTED'            AS IDF_STATUS
FROM AP_INVOICES_ALL ai
INNER JOIN AP_SUPPLIERS s
    ON  ai.VENDOR_ID      = s.VENDOR_ID
INNER JOIN AP_SUPPLIER_SITES_ALL ss
    ON  ai.VENDOR_SITE_ID = ss.VENDOR_SITE_ID
INNER JOIN HR_OPERATING_UNITS hou
    ON  ai.ORG_ID         = hou.ORGANIZATION_ID
INNER JOIN IDF_COUNTRY_CONFIG icc
    ON  ss.COUNTRY        = icc.COUNTRY_CODE
    AND icc.MANDATE_STATUS = 'MANDATORY'
WHERE ai.INVOICE_DATE   >= icc.GO_LIVE_DATE   -- GO_LIVE_DATE, not EFFECTIVE_DATE
    AND ai.CANCELLED_DATE IS NULL
    AND NOT EXISTS (
        SELECT 1
        FROM IDF_EINVOICE_LOG iel
        WHERE iel.SOURCE_TRX_ID  = ai.INVOICE_ID  -- SOURCE_TRX_ID, not INVOICE_ID
          AND iel.SOURCE_SYSTEM   = 'AP'           -- AP vs AR distinction
    )
ORDER BY ai.INVOICE_DATE;

KNIME Integration: Critical for daily compliance monitoring. Schedule this workflow on KNIME Hub to run at 07:00 every business day. Route results through a Row Filter node (count > 0) before the Send Email node to avoid empty alert emails.

E-Invoice Rejection Error Pattern Analysis

Analyses rejection patterns across countries to identify systemic data quality issues. Ranks errors by frequency so remediation effort targets the highest-impact problems first.

🔧 Bugs fixed vs original:

  • Fixed: iel.STATUS → iel.EINVOICE_STATUS (actual column name)
  • Fixed: ierr.ERROR_DESCRIPTION → ierr.ERROR_DETAIL (actual column name on IDF_ERROR_LOG)
  • Fixed: COUNT(DISTINCT iel.INVOICE_ID) → COUNT(DISTINCT iel.SOURCE_TRX_ID)
Oracle SQL — DB Query Reader Node
-- E-Invoice Rejection Error Pattern Analysis (Oracle SQL)
-- Ranks error codes by frequency to prioritise data quality fixes.

SELECT
    iel.COUNTRY_CODE,
    ierr.ERROR_CODE,
    ierr.ERROR_DETAIL,                              -- was ERROR_DESCRIPTION (does not exist)
    ierr.FIELD_NAME,
    COUNT(*)                                        AS ERROR_COUNT,
    COUNT(DISTINCT iel.SOURCE_TRX_ID)               AS AFFECTED_INVOICES,
    ROUND(COUNT(*) * 100.0 /
          SUM(COUNT(*)) OVER (PARTITION BY iel.COUNTRY_CODE), 1)
                                                    AS PCT_OF_COUNTRY_ERRORS,
    MIN(iel.SUBMISSION_DATE)                        AS FIRST_OCCURRENCE,
    MAX(iel.SUBMISSION_DATE)                        AS LAST_OCCURRENCE
FROM IDF_EINVOICE_LOG iel
INNER JOIN IDF_ERROR_LOG ierr
    ON  iel.EINVOICE_ID    = ierr.EINVOICE_ID
WHERE iel.EINVOICE_STATUS  = 'REJECTED'             -- EINVOICE_STATUS, not STATUS
    AND iel.SUBMISSION_DATE >= :analysis_start
GROUP BY
    iel.COUNTRY_CODE,
    ierr.ERROR_CODE,
    ierr.ERROR_DETAIL,
    ierr.FIELD_NAME
ORDER BY iel.COUNTRY_CODE, ERROR_COUNT DESC;

KNIME Integration: The window function (SUM OVER PARTITION) requires Oracle 11g+. In KNIME: after running this query, use a Pie/Bar Chart node on ERROR_CODE to visualise the distribution per country.

Corporate Income Tax (CIT)

Intercompany Transaction Summary for TP Documentation

Extracts intercompany transaction volumes by entity and IC partner (SEGMENT4) from the GL. Used for CbCR and local-file transfer pricing documentation.

🔧 Bugs fixed vs original:

  • In Oracle, empty string comparison (SEGMENT4 <> '') is always NULL — Oracle treats '' as NULL. Replaced with IS NOT NULL which was already present; removed the redundant empty-string check
  • Added HAVING to suppress entity/partner pairs with zero net movement
  • Period filter uses SUBSTR for safer year matching vs LIKE with concatenation
Oracle SQL — DB Query Reader Node
-- Intercompany Transaction Summary (Oracle SQL)
-- Extracts GL movements where SEGMENT4 (intercompany partner) is populated.
-- Adjust segment positions to match your specific Chart of Accounts.

SELECT
    gcc.SEGMENT1               AS ENTITY_CODE,
    gcc.SEGMENT4               AS IC_PARTNER_CODE,
    gcc.SEGMENT3               AS ACCOUNT_CODE,
    jh.PERIOD_NAME,
    jh.CURRENCY_CODE,
    ROUND(SUM(jl.ENTERED_DR), 2)                        AS TOTAL_DEBIT,
    ROUND(SUM(jl.ENTERED_CR), 2)                        AS TOTAL_CREDIT,
    ROUND(SUM(jl.ENTERED_DR) - SUM(jl.ENTERED_CR), 2)  AS NET_AMOUNT,
    ROUND(SUM(jl.ACCOUNTED_DR) - SUM(jl.ACCOUNTED_CR), 2) AS NET_FUNCTIONAL,
    COUNT(*)                                             AS LINE_COUNT
FROM GL_JE_LINES jl
INNER JOIN GL_JE_HEADERS jh
    ON  jl.JE_HEADER_ID       = jh.JE_HEADER_ID
INNER JOIN GL_CODE_COMBINATIONS gcc
    ON  jl.CODE_COMBINATION_ID = gcc.CODE_COMBINATION_ID
WHERE jh.STATUS       = 'P'                           -- posted journals only
    AND gcc.SEGMENT4  IS NOT NULL                     -- IC partner present
    AND gcc.SEGMENT4  <> '000'                        -- exclude clearing/dummy
    AND SUBSTR(jh.PERIOD_NAME, -2) IN                 -- fiscal year filter
        ('01','02','03','04','05','06',
         '07','08','09','10','11','12')
    AND jh.PERIOD_NAME LIKE :fiscal_year || '%'       -- e.g. flow var '2026'
GROUP BY
    gcc.SEGMENT1, gcc.SEGMENT4, gcc.SEGMENT3,
    jh.PERIOD_NAME, jh.CURRENCY_CODE
HAVING SUM(jl.ENTERED_DR) - SUM(jl.ENTERED_CR) <> 0  -- suppress zero-balance rows
ORDER BY gcc.SEGMENT1, gcc.SEGMENT4, jh.PERIOD_NAME;

KNIME Integration: Export to Excel with a Pivot node to create entity-pair matrices. For CbCR, further aggregate to annual level using a GroupBy node on ENTITY_CODE + IC_PARTNER_CODE.

Tax-Sensitive Account Balances (Provisions, R&D, Depreciation)

Extracts period balances for CIT-relevant account ranges. Adjust the account ranges to match your Chart of Accounts. Simplified from the original — the self-referencing subquery was redundant.

🔧 Bugs fixed vs original:

  • Removed unnecessary self-referencing subquery (SELECT SEGMENT3 FROM GL_CODE_COMBINATIONS WHERE … inside an IN clause on the same table being joined)
  • Replaced with direct BETWEEN filter on gcc.SEGMENT3 using OR
  • Added ACTUAL_FLAG = 'A' to the WHERE clause (was already correct); made comment clearer
Oracle SQL — DB Query Reader Node
-- CIT-Relevant Account Balances (Oracle SQL)
-- Adjust BETWEEN ranges to match your Chart of Accounts segment values.

SELECT
    gcc.SEGMENT1       AS ENTITY,
    gcc.SEGMENT3       AS ACCOUNT,
    CASE
        WHEN gcc.SEGMENT3 BETWEEN '5000' AND '5099' THEN 'Tax Provisions'
        WHEN gcc.SEGMENT3 BETWEEN '6100' AND '6199' THEN 'R&D Expense'
        WHEN gcc.SEGMENT3 BETWEEN '4500' AND '4599' THEN 'Depreciation'
        WHEN gcc.SEGMENT3 BETWEEN '7000' AND '7099' THEN 'Intercompany Revenue'
        WHEN gcc.SEGMENT3 BETWEEN '8000' AND '8099' THEN 'Intercompany Cost'
        ELSE 'Other Tax-Sensitive'
    END                                                         AS CIT_CATEGORY,
    gb.PERIOD_NAME,
    gb.CURRENCY_CODE,
    ROUND(gb.BEGIN_BALANCE_DR  - gb.BEGIN_BALANCE_CR, 2)       AS OPENING_BALANCE,
    ROUND(gb.PERIOD_NET_DR     - gb.PERIOD_NET_CR, 2)          AS PERIOD_MOVEMENT,
    ROUND((gb.BEGIN_BALANCE_DR + gb.PERIOD_NET_DR) -
          (gb.BEGIN_BALANCE_CR + gb.PERIOD_NET_CR), 2)         AS CLOSING_BALANCE
FROM GL_BALANCES gb
INNER JOIN GL_CODE_COMBINATIONS gcc
    ON  gb.CODE_COMBINATION_ID = gcc.CODE_COMBINATION_ID
WHERE gb.ACTUAL_FLAG  = 'A'       -- actuals only (not budget/encumbrance)
    AND gb.CURRENCY_CODE = 'STAT' -- remove this line to include all currencies
    AND gb.PERIOD_NAME   = :period -- e.g. 'JUN-26'
    AND (
           gcc.SEGMENT3 BETWEEN '5000' AND '5099'  -- tax provisions
        OR gcc.SEGMENT3 BETWEEN '6100' AND '6199'  -- R&D
        OR gcc.SEGMENT3 BETWEEN '4500' AND '4599'  -- depreciation
        OR gcc.SEGMENT3 BETWEEN '7000' AND '7099'  -- IC revenue
        OR gcc.SEGMENT3 BETWEEN '8000' AND '8099'  -- IC cost
    )
ORDER BY gcc.SEGMENT1, gcc.SEGMENT3;

KNIME Integration: Remove the CURRENCY_CODE = 'STAT' filter if you want functional currency balances. In KNIME: use a GroupBy node on ENTITY + CIT_CATEGORY to get a high-level summary for the tax provision schedule.

Data Quality & Monitoring

Supplier VAT Master Data Quality Check

Identifies active EU suppliers with missing or incorrectly formatted VAT registration numbers, ranked by invoice activity. Fixed Oracle-specific syntax throughout.

🔧 Bugs fixed vs original:

  • Fixed: LEN() → LENGTH() (LEN is T-SQL/SQL Server; Oracle uses LENGTH)
  • Fixed: GETDATE() → SYSDATE (Oracle date function)
  • Fixed: DATEADD(MONTH, -12, GETDATE()) → ADD_MONTHS(SYSDATE, -12) (Oracle syntax)
  • Fixed: HAVING without GROUP BY replaced by WHERE subquery — HAVING is only valid after GROUP BY
  • Moved all filter conditions into WHERE using a CASE expression in a subquery
Oracle SQL — DB Query Reader Node
-- Supplier VAT Master Data Quality Check (Oracle SQL)
-- Finds active EU suppliers with missing or malformed VAT numbers.
-- Ranked by invoice volume so the AP team tackles the most impactful records first.

SELECT
    dq.VENDOR_ID,
    dq.VENDOR_NAME,
    dq.VENDOR_SITE_ID,
    dq.COUNTRY,
    dq.VAT_REGISTRATION_NUM,
    dq.QUALITY_CHECK,
    dq.INVOICES_LAST_12M
FROM (
    SELECT
        s.VENDOR_ID,
        s.VENDOR_NAME,
        ss.VENDOR_SITE_ID,
        ss.COUNTRY,
        ss.VAT_REGISTRATION_NUM,
        CASE
            WHEN ss.VAT_REGISTRATION_NUM IS NULL              THEN 'MISSING'
            WHEN LENGTH(ss.VAT_REGISTRATION_NUM) < 8         THEN 'TOO_SHORT'  -- LENGTH not LEN
            WHEN ss.COUNTRY = 'NL'
                 AND ss.VAT_REGISTRATION_NUM NOT LIKE 'NL%'  THEN 'WRONG_PREFIX'
            WHEN ss.COUNTRY = 'DE'
                 AND ss.VAT_REGISTRATION_NUM NOT LIKE 'DE%'  THEN 'WRONG_PREFIX'
            WHEN ss.COUNTRY = 'FR'
                 AND ss.VAT_REGISTRATION_NUM NOT LIKE 'FR%'  THEN 'WRONG_PREFIX'
            WHEN ss.COUNTRY = 'BE'
                 AND ss.VAT_REGISTRATION_NUM NOT LIKE 'BE%'  THEN 'WRONG_PREFIX'
            ELSE 'OK'
        END AS QUALITY_CHECK,
        (
            SELECT COUNT(*)
            FROM AP_INVOICES_ALL ai
            WHERE ai.VENDOR_SITE_ID  = ss.VENDOR_SITE_ID
              AND ai.INVOICE_DATE   >= ADD_MONTHS(SYSDATE, -12)  -- Oracle: not DATEADD/GETDATE
        )   AS INVOICES_LAST_12M
    FROM AP_SUPPLIERS s
    INNER JOIN AP_SUPPLIER_SITES_ALL ss
        ON  s.VENDOR_ID  = ss.VENDOR_ID
    WHERE ss.COUNTRY IN (
        'AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR',
        'DE','GR','HU','IE','IT','LV','LT','LU','MT','NL',
        'PL','PT','RO','SK','SI','ES','SE'
    )
    AND s.ENABLED_FLAG = 'Y'
) dq
WHERE dq.QUALITY_CHECK <> 'OK'   -- filter on computed column via outer query
ORDER BY dq.INVOICES_LAST_12M DESC;

KNIME Integration: Schedule weekly. In KNIME: add a Row Filter node (QUALITY_CHECK != "OK") then route into an Excel Writer and Send Email node. Prioritise rows with INVOICES_LAST_12M > 5 for immediate correction.

ZX_LINES vs GL Reconciliation

Reconciles total VAT per ZX_LINES (the tax SSOT) against VAT postings in the GL via XLA. Exposes any gap between the subledger and the general ledger — a key control for VAT return sign-off.

Oracle SQL — DB Query Reader Node
-- ZX_LINES vs GL VAT Reconciliation (Oracle SQL)
-- Compares tax SSOT (ZX_LINES) against subledger postings (XLA_AE_LINES)
-- for the same period and entity.

WITH zx_summary AS (
    SELECT
        zx.INTERNAL_ORGANIZATION_ID    AS ORG_ID,
        zx.TAX_DETERMINE_DATE,
        TRUNC(zx.TAX_DETERMINE_DATE, 'MM') AS PERIOD_MONTH,
        zx.APPLICATION_ID,
        ROUND(SUM(zx.TAX_AMT), 2)     AS ZX_TAX_TOTAL
    FROM ZX_LINES zx
    WHERE zx.INTERNAL_ORGANIZATION_ID = :org_id
      AND zx.TAX_DETERMINE_DATE BETWEEN :period_start AND :period_end
    GROUP BY
        zx.INTERNAL_ORGANIZATION_ID,
        zx.TAX_DETERMINE_DATE,
        TRUNC(zx.TAX_DETERMINE_DATE, 'MM'),
        zx.APPLICATION_ID
),
gl_summary AS (
    SELECT
        xh.LEDGER_ID,
        TRUNC(xh.ACCOUNTING_DATE, 'MM')  AS PERIOD_MONTH,
        ROUND(SUM(xl.ACCOUNTED_DR) - SUM(xl.ACCOUNTED_CR), 2) AS GL_TAX_NET
    FROM XLA_AE_LINES xl
    INNER JOIN XLA_AE_HEADERS xh
        ON  xl.AE_HEADER_ID = xh.AE_HEADER_ID
    WHERE xl.ACCOUNTING_CLASS_CODE = 'TAX'
      AND xh.ACCOUNTING_DATE BETWEEN :period_start AND :period_end
    GROUP BY
        xh.LEDGER_ID,
        TRUNC(xh.ACCOUNTING_DATE, 'MM')
)
SELECT
    zx.PERIOD_MONTH,
    zx.APPLICATION_ID,
    zx.ZX_TAX_TOTAL,
    gl.GL_TAX_NET,
    ROUND(zx.ZX_TAX_TOTAL - gl.GL_TAX_NET, 2)  AS VARIANCE,
    CASE
        WHEN ABS(zx.ZX_TAX_TOTAL - gl.GL_TAX_NET) < 1  THEN 'RECONCILED'
        WHEN ABS(zx.ZX_TAX_TOTAL - gl.GL_TAX_NET) < 100 THEN 'MINOR VARIANCE'
        ELSE 'INVESTIGATE'
    END                                          AS RECON_STATUS
FROM zx_summary zx
LEFT JOIN gl_summary gl
    ON  TRUNC(zx.TAX_DETERMINE_DATE, 'MM') = gl.PERIOD_MONTH
ORDER BY zx.PERIOD_MONTH;

KNIME Integration: This uses CTEs (WITH clause) — supported in Oracle 11g+. In KNIME: route rows where RECON_STATUS = "INVESTIGATE" to a separate output port for immediate escalation. Run as part of the monthly close checklist.

Open AP Invoice Holds by Type and Age

Lists all currently active invoice holds, grouped by hold type and age bucket. Useful for AP teams to prioritise hold release and for identifying systemic hold causes.

Oracle SQL — DB Query Reader Node
-- Open AP Invoice Holds by Type and Age (Oracle SQL)
-- Shows holds that have not been released (RELEASE_LOOKUP_CODE IS NULL).

SELECT
    h.HOLD_LOOKUP_CODE                          AS HOLD_TYPE,
    hou.NAME                                    AS ENTITY,
    COUNT(DISTINCT h.INVOICE_ID)                AS HELD_INVOICES,
    ROUND(SUM(ai.INVOICE_AMOUNT), 2)            AS TOTAL_HELD_AMOUNT,
    ai.INVOICE_CURRENCY_CODE,
    SUM(CASE WHEN SYSDATE - ai.INVOICE_DATE <= 30  THEN 1 ELSE 0 END) AS AGED_0_30,
    SUM(CASE WHEN SYSDATE - ai.INVOICE_DATE BETWEEN 31 AND 60 THEN 1 ELSE 0 END) AS AGED_31_60,
    SUM(CASE WHEN SYSDATE - ai.INVOICE_DATE BETWEEN 61 AND 90 THEN 1 ELSE 0 END) AS AGED_61_90,
    SUM(CASE WHEN SYSDATE - ai.INVOICE_DATE > 90  THEN 1 ELSE 0 END) AS AGED_OVER_90
FROM AP_HOLDS_ALL h
INNER JOIN AP_INVOICES_ALL ai
    ON  h.INVOICE_ID = ai.INVOICE_ID
INNER JOIN HR_OPERATING_UNITS hou
    ON  ai.ORG_ID = hou.ORGANIZATION_ID
WHERE h.RELEASE_LOOKUP_CODE IS NULL      -- only active (unreleased) holds
    AND ai.CANCELLED_DATE IS NULL
    AND ai.ORG_ID = :org_id
GROUP BY
    h.HOLD_LOOKUP_CODE,
    hou.NAME,
    ai.INVOICE_CURRENCY_CODE
ORDER BY TOTAL_HELD_AMOUNT DESC;

KNIME Integration: Use a Bar Chart node in KNIME to visualise held amounts by hold type. AGED_OVER_90 > 0 rows should trigger an escalation email — add a Row Filter + Send Email branch for this.

Flow Variable Reference

These parameters appear across queries. Set them once at workflow start using a Configuration node, then pass as flow variables into each DB Query Reader.

VariableTypeExample ValueUsed For
:org_idInteger204Filter to specific Canon entity (ORG_ID from HR_OPERATING_UNITS)
:period_startDate2026-06-01Reporting period start (inclusive)
:period_endDate2026-06-30Reporting period end (inclusive)
:tax_regimeStringNL_VATZX_LINES.TAX_REGIME_CODE — uses underscore, not hyphen
:buyer_countryStringNLISO country code of the buying entity (for reverse charge logic)
:fiscal_yearString2026Fiscal year prefix for GL_JE_HEADERS.PERIOD_NAME LIKE filter
:periodStringJUN-26Exact GL period name for GL_BALANCES queries
:analysis_startDate2026-01-01IDF error analysis window start

Common Oracle vs T-SQL pitfalls in these queries:

LEN(x)LENGTH(x)GETDATE()SYSDATEDATEADD(MONTH,-12,GETDATE())ADD_MONTHS(SYSDATE,-12)HAVING without GROUP BY✅ Use WHERE or a subqueryzx.INVOICE_ID on ZX_LINESzx.TRX_ID + APPLICATION_IDzx.TAX_AMOUNTzx.TAX_AMTzx.TAXABLE_AMOUNTzx.TAXABLE_AMTiel.INVOICE_ID on IDF_EINVOICE_LOGiel.SOURCE_TRX_ID