Jira categorization rules
This article describes how to create categorization rules for Jira allocation reporting. A categorization rule is an ordered set SQL queries that define categories for grouping your Jira issues. DX executes categorization rules nightly. For testing purposes, you can execute categorization rules immediately while editing.
When defining a category, you can choose between two types:
- Fixed category – Your query must return a jira_issue_id column, which represents the specific set of issues which fall under your category.
- Dynamic category – Your query must return a jira_issue_id and value column. The value column will determine the category each issue is assigned to.
Below are additional best practices for creating and editing categories rules:
- Keep categories unpublished while you’re working on them to avoid confusion.
- Test changes by clicking Process issues and then Preview to see the output.
- Order rules from most specific to broadest.
- Prefer sub-queries over hardcoded IDs.
Core database tables
Below are the main database tables you will use when defining SQL categories:
| Table | Description |
|---|---|
jira_issues |
Table containing all Jira issues |
jira_custom_fields |
List of all custom fields |
jira_issue_custom_field_values |
Values of custom fields per issue |
jira_issue_types |
Story, Bug, Epic, etc. |
jira_issue_relationships |
Flattened representation of each issue’s hierarchy |
Tips for working with jira_issue_relationships
Jira issues are often structured hierarchically, e.g. Initiatives > Epics > Stories. The jira_issue_relationships table contains all ancestor-descendant relationships, not just direct parent-child links.
For example, given this hierarchy:
Initiative A
└── Epic B
└── Story C
The jira_issue_relationships table will include the following rules:
| Ancestor | Descendant |
|---|---|
| Initiative A | Epic B |
| Epic B | Story C |
| Initiative A | Story C |
You can use the jira_issue_relationships table to join data from any ancestor level in your categorization logic, avoid complex recursive CTEs or multiple joins, and cleanly apply fallback logic (e.g. check Story metadata, then Epic, then Initiative).
Examples
Categorize by issue type
To categorize issues based on issue type, you can define categories that group issues using the jira_issue_types table.
You can define a single Dynamic category like the one below:
SELECT DISTINCT
jira_issues.id AS jira_issue_id,
jira_issue_types.name AS value
FROM jira_issues
JOIN jira_issue_types
ON jira_issue_types.id = jira_issues.issue_type_id
Or, you can define multiple Fixed categories, for example:
-- This SQL is for our "Tech debt" category
SELECT DISTINCT jira_issues.id AS jira_issue_id
FROM jira_issues
JOIN jira_issue_types
ON jira_issue_types.id = jira_issues.issue_type_id
WHERE jira_issue_types.name IN ('Bug', 'Fix')
-- This SQL is for our "Innovation" category
SELECT DISTINCT jira_issues.id AS jira_issue_id
FROM jira_issues
JOIN jira_issue_types
ON jira_issue_types.id = jira_issues.issue_type_id
WHERE jira_issue_types.name IN ('Feature', 'Enhancement')
Categorize by custom field value
To categorize issues based on custom field values, define categories that group issues using the jira_custom_field_values tables. You may also want to use the jira_issue_relationships the table to fallback to Epic/Initiative custom field values if a field is not set for an issue.
You can define a single Dynamic category like the one below:
WITH custom_field_ids AS (
SELECT id
FROM jira_custom_fields
WHERE name = 'Example'
),
epic_issue_type_ids AS (
SELECT id
FROM jira_issue_types
WHERE name = 'Epic'
),
initiative_issue_type_ids AS (
SELECT id
FROM jira_issue_types
WHERE name = 'Initiative'
)
SELECT DISTINCT
jira_issues.id AS jira_issue_id,
COALESCE(
issue_values.value,
epic_values.value,
initiative_values.value
) AS value
FROM jira_issues
LEFT JOIN jira_issue_relationships relationships
ON jira_issues.id = relationships.descendant_id
LEFT JOIN jira_issues epics
ON relationships.ancestor_id = epics.id
AND epics.issue_type_id IN (
SELECT id
FROM epic_issue_type_ids
)
LEFT JOIN jira_issues initiatives
ON relationships.ancestor_id = initiatives.id
AND initiatives.issue_type_id IN (
SELECT id
FROM initiative_issue_type_ids
)
LEFT JOIN jira_issue_custom_field_values issue_values
ON jira_issues.id = issue_values.issue_id
AND issue_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
LEFT JOIN jira_issue_custom_field_values epic_values
ON epics.id = epic_values.issue_id
AND epic_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
LEFT JOIN jira_issue_custom_field_values initiative_values
ON initiatives.id = initiative_values.issue_id
AND initiative_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
WHERE COALESCE(
issue_values.value,
epic_values.value,
initiative_values.value) IS NOT NULL
Or, you can define multiple Fixed categories. Here’s one example:
-- This SQL is for our "Tech debt" category
WITH custom_field_ids AS (
SELECT id
FROM jira_custom_fields
WHERE name = 'Example'
),
epic_issue_type_ids AS (
SELECT id
FROM jira_issue_types
WHERE name = 'Epic'
),
initiative_issue_type_ids AS (
SELECT id
FROM jira_issue_types
WHERE name = 'Initiative'
)
SELECT DISTINCT jira_issues.id AS jira_issue_id
FROM jira_issues
LEFT JOIN jira_issue_relationships relationships
ON jira_issues.id = relationships.descendant_id
LEFT JOIN jira_issues epics
ON relationships.ancestor_id = epics.id
AND epics.issue_type_id IN (
SELECT id
FROM epic_issue_type_ids
)
LEFT JOIN jira_issues initiatives
ON relationships.ancestor_id = initiatives.id
AND initiatives.issue_type_id IN (
SELECT id
FROM initiative_issue_type_ids
)
LEFT JOIN jira_issue_custom_field_values issue_values
ON jira_issues.id = issue_values.issue_id
AND issue_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
LEFT JOIN jira_issue_custom_field_values epic_values
ON epics.id = epic_values.issue_id
AND epic_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
LEFT JOIN jira_issue_custom_field_values initiative_values
ON initiatives.id = initiative_values.issue_id
AND initiative_values.custom_field_id IN (
SELECT id
FROM custom_field_ids
)
WHERE COALESCE(
issue_values.value,
epic_values.value,
initiative_values.value) IN ('Bug', 'Fix', 'Maintenance')