XPath First Items: Parentheses Change the Selected Nodes

I make XPath first items explicit because the placement of [1] changes which sequence it filters. One expression selects the first Item under each Group. Parentheses can instead select one first Item from the complete path result.

Gouache painting: three long raised garden rows run away from the viewer, two planted with round cabbages and the third bare soil
Two stacks of blank clothbound books flank an open blank book and pencils.

Use more than one parent

The first document has two populated Group elements and one empty Group. Each populated group contains two Items. A single populated group wouldn’t expose the difference between the two path expressions.

I keep the Item values distinct so each selection can be identified. A1 and A2 belong to the first group. B1 and B2 belong to the second. The empty third group has no first Item to contribute. These fixed inputs make every selected node traceable.

WITH Inputs AS
(
 SELECT CaseId, CAST(XmlText AS xml) AS Doc
 FROM (VALUES
  (1,N'<Root><Group><Item>A1</Item><Item>A2</Item></Group><Group><Item>B1</Item><Item>B2</Item></Group><Group/></Root>'),
  (2,N'<Root><Group/><Group/></Root>')
 ) v(CaseId,XmlText)
)
SELECT CaseId,
       CAST(Doc.query('/Root/Group/Item[1]') AS nvarchar(max)) AS FirstPerGroup,
       CAST(Doc.query('(/Root/Group/Item)[1]') AS nvarchar(max)) AS FirstOverall,
       Doc.value('count(/Root/Group/Item[1])','int') AS PerGroupCount,
       Doc.value('count((/Root/Group/Item)[1])','int') AS OverallCount
FROM Inputs
ORDER BY CaseId;
Native SSMS results contrasting the first Item in each Group with the first Item across the whole document.
Native SSMS results for both XML cases. The per-group expression returns A1 and B1, while the parenthesized expression returns only A1. The empty document returns empty XML and zero counts. Open the result at full size.

Read the step-local predicate

The path /Root/Group/Item[1] attaches the positional predicate to the Item step. It selects the first Item child for each selected Group. Its expected fragment contains A1 followed by B1.

PerGroupCount expects two, not one. A [1] in a path doesn’t automatically promise a single result for the entire document. I’d review the parent sequence before making that claim. The number of selected parents can determine how many first children survive this step-local filter.

Read the parenthesized path

The expression (/Root/Group/Item)[1] first forms the complete path result, then selects its first node. For this document, its expected fragment contains only A1. OverallCount therefore expects one.

The difference comes from where the predicate is applied, not from another source document. I’d keep the parentheses visible in a copyable query. Removing them while simplifying code can change the selection contract. A shorter-looking path can still return valid XML while selecting additional nodes the caller didn’t expect.

Brackets inside or outside

Keep the full XML fragment

Both output fragments retain Item elements, not just concatenated display text. The first column can contain multiple sibling elements. SQL Server’s XML value can represent that fragment without an invented wrapper element.

I’d compare the full selected XML and the counts together. A count alone doesn’t establish that the right Items were chosen. A single text extraction could also hide additional nodes. These small fragments show both selection scope and the particular source children that meet it.

Read an empty selection honestly

The second document contains only empty Group elements. Both paths return empty XML fragments, displayed here as empty strings. Their expected counts are zero. This differs from SQL NULL, which the example doesn’t supply.

I’d avoid replacing an empty selection with a fabricated first Item. The source simply contains no eligible child. A consumer requiring at least one item must enforce that requirement separately. The positional predicate doesn’t create a node when there is nothing available to select.

Choose the intended scope first

The query reads two inline documents and makes no writes. CaseId orders their output. Every result column belongs to the expected comparison, including the empty fragments and zero counts.

I’d decide whether the application needs one item per group or one item overall before editing the path. The correct syntax follows that requirement. Neither form is a universal fix for repeated data. Verify the source nesting and intended selection boundary before relying on a first-item shortcut.

Where the brackets sit decides what comes back.

The [1] is not a global first, it is a filter on the step it follows.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

SQL Datatype, SQL Function, SQL Scripts
Previous Post
SQL SERVER – Disable Windows Authentication – Remove Windows Authentication Login Account
Next Post
SQL SERVER – 2008 – Find Relationship of Foreign Key and Primary Key using T-SQL – Find Tables With Foreign Key Constraint in Database

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.