Overview
Multi-row test results are stored across four related tables that work together to represent the full result set. Unlike single-value tests (which store everything in one table), multi-row tests use a normalized "long format" structure that separates column definitions, row-level results, key values, and comparison values into separate tables.
This design supports tests with any number of columns and rows without requiring a fixed schema. The trade-off is that querying specific column values requires pivoting the data.
The Four Tables
TEST_EXECUTION_TABLE_COLUMNS — defines which columns exist for an execution
│
â–¼
TEST_EXECUTION_TABLE_RESULTS — one row per result row (pass/fail per row)
│
├──── TEST_EXECUTION_TABLE_RESULT_KEYS — key column values (long format)
│
└──── TEST_EXECUTION_TABLE_RESULT_VALUES — value column comparisons (long format)
TEST_EXECUTION_TABLE_COLUMNS
Defines the column structure for a test execution — what columns exist, their roles, and aggregate pass/fail counts.
| Column | Type | Description |
|---|---|---|
| TEST_EXECUTION_ID | NUMBER | Execution ID |
| TEST_ID | NUMBER | Test ID |
| TEST_VERSION_ID | NUMBER | Test version ID |
| COLUMN_SEQUENCE | NUMBER | Column position (used to join to KEYS and VALUES tables) |
| TEST_SET_COLUMN_NAME | VARCHAR | Column name from the test (source) dataset |
| TEST_SET_COLUMN_SEQUENCE | NUMBER | Original sequence in the test dataset |
| TEST_SET_COLUMN_TYPE_ID | NUMBER | Column type code |
| TEST_SET_COLUMN_TYPE_NAME | VARCHAR | "Key" or "Value" |
| TEST_SET_COLUMN_ROLE_ID | NUMBER | Column role code |
| TEST_SET_COLUMN_ROLE_NAME | VARCHAR | Column role (e.g., "Key", "Source Value", "Target Value") |
| CONTROL_SET_COLUMN_NAME | VARCHAR | Corresponding column name in the control dataset |
| CONTROL_SET_COLUMN_SEQUENCE | NUMBER | Sequence in the control dataset |
| CONTROL_SET_COLUMN_TYPE_ID | NUMBER | Control column type code |
| CONTROL_SET_COLUMN_TYPE_NAME | VARCHAR | Control column type name |
| CONTROL_SET_COLUMN_ROLE_ID | NUMBER | Control column role code |
| CONTROL_SET_COLUMN_ROLE_NAME | VARCHAR | Control column role name |
| PASSED_COUNT | NUMBER | Rows that passed for this column |
| FAILED_COUNT | NUMBER | Rows that failed for this column |
| ERROR_COUNT | NUMBER | Rows that errored for this column |
TEST_EXECUTION_TABLE_RESULTS
One row per result row in the test execution. Contains the overall evaluation for each row.
| Column | Type | Description |
|---|---|---|
| TEST_EXECUTION_ID | NUMBER | Execution ID |
| TEST_ID | NUMBER | Test ID |
| TEST_VERSION_ID | NUMBER | Test version ID |
| ROW_INDEX | NUMBER | Row position (1-based) |
| COVERAGE_FLAG | NUMBER | 3 = Matched, 1 = Unmatched Test Only, 2 = Unmatched Control Only |
| EVALUATION_RESULT_ID | NUMBER | 24 = Passed, 25 = Failed, 26 = Error |
| EVALUATION_RESULT_NAME | VARCHAR | "PASSED", "FAILED", or "ERROR" |
| PASSED_VALUE_COUNT | NUMBER | Number of value columns that passed for this row |
| FAILED_VALUE_COUNT | NUMBER | Number of value columns that failed |
| ERROR_VALUE_COUNT | NUMBER | Number of value columns that errored |
| KEY_HASH | VARCHAR | Hash of the combined key values for this row |
TEST_EXECUTION_TABLE_RESULT_KEYS
Stores the actual key column values for each result row in long format (one row per key column per result row).
| Column | Type | Description |
|---|---|---|
| TEST_EXECUTION_ID | NUMBER | Execution ID |
| ROW_INDEX | NUMBER | Row position — join to TABLE_RESULTS |
| COLUMN_SEQUENCE | NUMBER | Which column this value belongs to — join to TABLE_COLUMNS |
| RESULT_KEY | VARCHAR | The actual key value (stored as string regardless of original type) |
| OVERFLOW_FLAG | NUMBER | 0 = normal, 1 = value truncated |
TEST_EXECUTION_TABLE_RESULT_VALUES
Stores the comparison values for each value column per result row in long format.
| Column | Type | Description |
|---|---|---|
| TEST_EXECUTION_ID | NUMBER | Execution ID |
| ROW_INDEX | NUMBER | Row position — join to TABLE_RESULTS |
| COLUMN_SEQUENCE | NUMBER | Which column — join to TABLE_COLUMNS |
| NUMERIC_SOURCE_VALUE | DECIMAL(27,8) | Numeric value from source |
| NUMERIC_TARGET_VALUE | DECIMAL(27,8) | Numeric value from target |
| ACTUAL_DIFFERENCE | DECIMAL(27,8) | Source minus target |
| ABSOLUTE_DIFFERENCE | DECIMAL(27,8) | Absolute difference |
| PERCENT_DIFFERENCE | DECIMAL(18,9) | Percentage difference |
| EVALUATION_RESULT_ID | NUMBER | 24 = Passed, 25 = Failed, 26 = Error |
| EVALUATION_RESULT_NAME | VARCHAR | "PASSED", "FAILED", or "ERROR" |
| STRING_SOURCE_VALUE | VARCHAR | String value from source |
| STRING_TARGET_VALUE | VARCHAR | String value from target |
| DATE_SOURCE_VALUE | VARCHAR | Date value from source |
| DATE_TARGET_VALUE | VARCHAR | Date value from target |
| OVERFLOW_FLAG | NUMBER | 0 = normal, 1 = truncated |
Join Pattern
All four tables join on TEST_EXECUTION_ID. Within an execution:
TABLE_RESULTS+TABLE_RESULT_KEYSjoin onROW_INDEXTABLE_RESULTS+TABLE_RESULT_VALUESjoin onROW_INDEXTABLE_COLUMNSdefines whatCOLUMN_SEQUENCEmeans in KEYS and VALUES
-- Full join pattern
SELECT
cols.TEST_SET_COLUMN_NAME,
cols.TEST_SET_COLUMN_TYPE_NAME,
res.ROW_INDEX,
res.EVALUATION_RESULT_NAME AS ROW_RESULT,
keys.RESULT_KEY,
vals.NUMERIC_SOURCE_VALUE,
vals.NUMERIC_TARGET_VALUE,
vals.EVALUATION_RESULT_NAME AS VALUE_RESULT
FROM RESULTS.TEST_EXECUTION_TABLE_COLUMNS cols
LEFT JOIN RESULTS.TEST_EXECUTION_TABLE_RESULTS res
ON res.TEST_EXECUTION_ID = cols.TEST_EXECUTION_ID
LEFT JOIN RESULTS.TEST_EXECUTION_TABLE_RESULT_KEYS keys
ON keys.TEST_EXECUTION_ID = cols.TEST_EXECUTION_ID
AND keys.ROW_INDEX = res.ROW_INDEX
AND keys.COLUMN_SEQUENCE = cols.COLUMN_SEQUENCE
LEFT JOIN RESULTS.TEST_EXECUTION_TABLE_RESULT_VALUES vals
ON vals.TEST_EXECUTION_ID = cols.TEST_EXECUTION_ID
AND vals.ROW_INDEX = res.ROW_INDEX
AND vals.COLUMN_SEQUENCE = cols.COLUMN_SEQUENCE
WHERE cols.TEST_EXECUTION_ID = 3913134;
Pivoting Results into a Wide Format
Because key and value data is stored in long format (one row per column per result row), you typically need to pivot it into a human-readable wide format. The pivot pattern uses conditional aggregation:
Pivot Key Columns
-- First, identify the column sequences for your execution
SELECT COLUMN_SEQUENCE, TEST_SET_COLUMN_NAME, TEST_SET_COLUMN_TYPE_NAME
FROM RESULTS.TEST_EXECUTION_TABLE_COLUMNS
WHERE TEST_EXECUTION_ID = 3913134
ORDER BY COLUMN_SEQUENCE;
-- Then pivot the keys
SELECT
ROW_INDEX,
MAX(CASE WHEN COLUMN_SEQUENCE = 1 THEN RESULT_KEY END) AS KEY_COL_1,
MAX(CASE WHEN COLUMN_SEQUENCE = 2 THEN RESULT_KEY END) AS KEY_COL_2,
MAX(CASE WHEN COLUMN_SEQUENCE = 3 THEN RESULT_KEY END) AS KEY_COL_3
FROM RESULTS.TEST_EXECUTION_TABLE_RESULT_KEYS
WHERE TEST_EXECUTION_ID = 3913134
GROUP BY ROW_INDEX
ORDER BY ROW_INDEX;
Pivot Value Columns with Source/Target
SELECT
ROW_INDEX,
MAX(CASE WHEN COLUMN_SEQUENCE = 4 THEN NUMERIC_SOURCE_VALUE END) AS COL4_SOURCE,
MAX(CASE WHEN COLUMN_SEQUENCE = 4 THEN NUMERIC_TARGET_VALUE END) AS COL4_TARGET,
MAX(CASE WHEN COLUMN_SEQUENCE = 4 THEN EVALUATION_RESULT_NAME END) AS COL4_RESULT,
MAX(CASE WHEN COLUMN_SEQUENCE = 5 THEN NUMERIC_SOURCE_VALUE END) AS COL5_SOURCE,
MAX(CASE WHEN COLUMN_SEQUENCE = 5 THEN NUMERIC_TARGET_VALUE END) AS COL5_TARGET,
MAX(CASE WHEN COLUMN_SEQUENCE = 5 THEN EVALUATION_RESULT_NAME END) AS COL5_RESULT
FROM RESULTS.TEST_EXECUTION_TABLE_RESULT_VALUES
WHERE TEST_EXECUTION_ID = 3913134
GROUP BY ROW_INDEX
ORDER BY ROW_INDEX;
Complete Pivot with Keys and Values Joined
WITH pivoted_keys AS (
SELECT
ROW_INDEX,
MAX(CASE WHEN COLUMN_SEQUENCE = 1 THEN RESULT_KEY END) AS ORDERKEY,
MAX(CASE WHEN COLUMN_SEQUENCE = 2 THEN RESULT_KEY END) AS PARTKEY
FROM RESULTS.TEST_EXECUTION_TABLE_RESULT_KEYS
WHERE TEST_EXECUTION_ID = 3913134
GROUP BY ROW_INDEX
),
pivoted_values AS (
SELECT
ROW_INDEX,
MAX(CASE WHEN COLUMN_SEQUENCE = 3 THEN NUMERIC_SOURCE_VALUE END) AS AMOUNT_SOURCE,
MAX(CASE WHEN COLUMN_SEQUENCE = 3 THEN NUMERIC_TARGET_VALUE END) AS AMOUNT_TARGET,
MAX(CASE WHEN COLUMN_SEQUENCE = 3 THEN EVALUATION_RESULT_NAME END) AS AMOUNT_RESULT
FROM RESULTS.TEST_EXECUTION_TABLE_RESULT_VALUES
WHERE TEST_EXECUTION_ID = 3913134
GROUP BY ROW_INDEX
)
SELECT
k.ORDERKEY,
k.PARTKEY,
v.AMOUNT_SOURCE,
v.AMOUNT_TARGET,
v.AMOUNT_RESULT,
r.EVALUATION_RESULT_NAME AS ROW_RESULT
FROM pivoted_keys k
JOIN pivoted_values v ON v.ROW_INDEX = k.ROW_INDEX
JOIN RESULTS.TEST_EXECUTION_TABLE_RESULTS r
ON r.TEST_EXECUTION_ID = 3913134
AND r.ROW_INDEX = k.ROW_INDEX
ORDER BY k.ROW_INDEX;
Example Queries
Find All Failed Rows for an Execution
SELECT
r.ROW_INDEX,
r.EVALUATION_RESULT_NAME,
r.FAILED_VALUE_COUNT,
r.COVERAGE_FLAG
FROM RESULTS.TEST_EXECUTION_TABLE_RESULTS r
WHERE r.TEST_EXECUTION_ID = 3913134
AND r.EVALUATION_RESULT_NAME = 'FAILED'
ORDER BY r.ROW_INDEX;
Unmatched Rows (Rows in Source but Not Target)
SELECT
r.ROW_INDEX,
k.COLUMN_SEQUENCE,
c.TEST_SET_COLUMN_NAME,
k.RESULT_KEY
FROM RESULTS.TEST_EXECUTION_TABLE_RESULTS r
JOIN RESULTS.TEST_EXECUTION_TABLE_RESULT_KEYS k
ON k.TEST_EXECUTION_ID = r.TEST_EXECUTION_ID
AND k.ROW_INDEX = r.ROW_INDEX
JOIN RESULTS.TEST_EXECUTION_TABLE_COLUMNS c
ON c.TEST_EXECUTION_ID = r.TEST_EXECUTION_ID
AND c.COLUMN_SEQUENCE = k.COLUMN_SEQUENCE
WHERE r.TEST_EXECUTION_ID = 3913134
AND r.COVERAGE_FLAG = 1 -- Test-only (unmatched in source)
ORDER BY r.ROW_INDEX, k.COLUMN_SEQUENCE;
Row Count Summary by Execution
SELECT
te.TEST_NAME,
te.DATE_COMPLETED,
COUNT(*) AS TOTAL_ROWS,
SUM(CASE WHEN r.EVALUATION_RESULT_NAME = 'PASSED' THEN 1 ELSE 0 END) AS PASSED_ROWS,
SUM(CASE WHEN r.EVALUATION_RESULT_NAME = 'FAILED' THEN 1 ELSE 0 END) AS FAILED_ROWS,
SUM(CASE WHEN r.COVERAGE_FLAG = 1 THEN 1 ELSE 0 END) AS UNMATCHED_SOURCE,
SUM(CASE WHEN r.COVERAGE_FLAG = 2 THEN 1 ELSE 0 END) AS UNMATCHED_TARGET
FROM REPOSITORY.VW_DATASET_TEST_EXECUTIONS te
JOIN RESULTS.TEST_EXECUTION_TABLE_RESULTS r
ON r.TEST_EXECUTION_ID = te.TEST_EXECUTION_ID
WHERE te.LATEST_TEST_EXECUTION_INDEX = 1
AND te.TEST_NAME = 'Value Comparison - LINEITEM'
GROUP BY te.TEST_NAME, te.DATE_COMPLETED;
Coverage Flag Reference
| COVERAGE_FLAG | Meaning |
|---|---|
| 1 | Unmatched — row exists only in the test (source) dataset |
| 2 | Unmatched — row exists only in the control (target) dataset |
| 3 | Matched — row exists in both datasets and was compared |
Databricks Notes
- The pivot pattern using
MAX(CASE WHEN ... END)is identical in Databricks SQL - Databricks may have different internal metadata columns in place of
SF_DATE_CREATEDandSF_IMPORT_FILENAME - For very large result sets, consider filtering on
COVERAGE_FLAGor specificCOLUMN_SEQUENCEvalues before pivoting to improve performance