How to Define Multiple Sort Keys in XSLT?

Sorting XML nodes by multiple criteria is a common requirement in XSLT transformations, allowing developers to create primary, secondary, and tertiary sorting orders. In XSLT, multiple sort keys are defined by placing consecutive xsl:sort elements within an xsl:for-each block or an xsl:apply-templates call. The XSLT processor evaluates these elements in the exact order they appear, meaning the first element defines the primary sort key, the second defines the secondary sort key, and each subsequent element handles further tie-breaking.

Understanding the xsl:sort Hierarchy

The xsl:sort instruction does not require a complex multi-column expression. Instead, the sequence of the xsl:sort elements dictates precedence:

  1. Primary Sort Key: The first xsl:sort child element determines the base ordering of the node-set.
  2. Secondary Sort Key: The second xsl:sort child element resolves any ties where nodes share identical values in the primary key.
  3. Tertiary and Beyond: Additional xsl:sort elements continue resolving subsequent ties in order.

Each xsl:sort element operates independently, allowing unique configurations for data types, sorting directions, case ordering, and language collations on a per-key basis.

Practical Example: Sorting Within xsl:for-each

Consider an XML document containing a list of employees with department, last name, and salary attributes:

<employees>
    <employee dept="Sales" lastName="Smith" salary="60000"/>
    <employee dept="Engineering" lastName="Doe" salary="85000"/>
    <employee dept="Sales" lastName="Adams" salary="75000"/>
    <employee dept="Engineering" lastName="Smith" salary="90000"/>
    <employee dept="Sales" lastName="Smith" salary="65000"/>
</employees>

To sort employees alphabetically by department (primary), then alphabetically by last name (secondary), and finally by salary in descending order (tertiary), place three xsl:sort elements inside the xsl:for-each loop:

<xsl:template match="/employees">
    <table border="1">
        <tr>
            <th>Department</th>
            <th>Last Name</th>
            <th>Salary</th>
        </tr>
        <xsl:for-each select="employee">
            <!-- Primary Key: Department ascending (text) -->
            <xsl:sort select="@dept" order="ascending" data-type="text"/>
            
            <!-- Secondary Key: Last Name ascending (text) -->
            <xsl:sort select="@lastName" order="ascending" data-type="text"/>
            
            <!-- Tertiary Key: Salary descending (numeric) -->
            <xsl:sort select="@salary" order="descending" data-type="number"/>
            
            <tr>
                <td><xsl:value-of select="@dept"/></td>
                <td><xsl:value-of select="@lastName"/></td>
                <td><xsl:value-of select="@salary"/></td>
            </tr>
        </xsl:for-each>
    </table>
</xsl:template>

Using Multiple Sort Keys with xsl:apply-templates

The exact same multi-key sorting mechanism applies when invoking template rules via xsl:apply-templates. The xsl:sort elements must appear as direct children of the xsl:apply-templates element, placed before any xsl:with-param elements:

<xsl:template match="/employees">
    <xsl:apply-templates select="employee">
        <xsl:sort select="@dept" order="ascending" data-type="text"/>
        <xsl:sort select="@lastName" order="ascending" data-type="text"/>
        <xsl:sort select="@salary" order="descending" data-type="number"/>
    </xsl:apply-templates>
</xsl:template>

<xsl:template match="employee">
    <div>
        <xsl:value-of select="concat(@dept, ' - ', @lastName, ': $', @salary)"/>
    </div>
</xsl:template>

Key Attributes for Fine-Tuning Sort Order

Customizing individual sort keys requires leveraging the standard attributes available on the xsl:sort element:

By combining multiple, distinct xsl:sort statements with these attribute configurations, XSLT provides robust control over complex, multi-tiered sorting scenarios.