Dirt, Spit, and Happy FLWOR Hands on with XQuery Dirt, Spit, and Happy FLWOR
Presentation Rating: ADVANCED INTERMEDIATE BEGINNER
Presentation Rating:
Purpose Introduce the basic syntax of FLWOR, the XQuery language. Interfaces with T-SQL Basic functions We won’t be covering conversion of SQL to XML (FOR XML…) No tricks or tips
Why Me? Data Architect working in the field of financial information security Prior experience as DBA and a reporting/database developer AtlantaMDF Chapter Leader Tutor, not a teacher
Assumptions About You Database professional (DBA, database developer, BI specialist) Some experience with XML
Let’s Review “Gentlemen, this is a football.” – Vince Lombardi
Basic XML Structure <root> <tag>This is an element</tag> <tag att=“attribute”> Another element</tag> </root> Elements and Attributes are both nodes.
Basic XML Structure XML can be Typed or Untyped Untyped = xml fragments; WYSIWYG Typed = validation of datatypes, relationships Typed XML requires a Schema SQL Server supports Schema SQL Server supports inline DTD Namespaces Used for providing uniquely named elements and attributes in an XML document
XML in SQL Server 2000+ Generation: Translation: FOR XML OPENXML RAW, AUTO, EXPLICIT Translation: OPENXML sp_xml_preparedocument sp_xml_removedocument
XML in SQL Server 2005+ Generation: Translation: FOR XML xml datatype PATH TYPE Translation: xml datatype XQuery
XML Translation xml datatype Well-formed fragments (no root required) 2 GB maximum Cannot be compared or sorted Supports conversion to (n)varchar(max) Required for XQuery
XML Translation XQuery Complete query language outside of SQL Server SQL Server 2005+ implements limited subset xml methods exist() value() query() nodes() modify() (beyond scope of presentation)
XML Translation SQL engine interfaces with XML engine
XML Translation XML method Syntax Returns .exist() .exist(XQuery) bit: 1 if True, 0 if False, NULL if xml is NULL .value() .value(XQuery, SQLtype) SQL data type: specified as a parameter in method .query() .query(XQuery) XML: 1 fragment per row .nodes() .nodes(XQuery) as Table(column) XML: each node becomes a new row Primary purpose of XQuery in SQL Server is to convert XML data to a tuple in a dataset. For robust handling of XML, use another tool.
FLWOR “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” – Martin Fowler
FLWOR For Let Where Order By Return Context switches ({}) Primary Language Other Components For Let Where Order By Return Context switches ({}) Functions (data(), position()) Positional References ([ ]) SQL Server’s implementation of XQuery is a limited subset; it improved between 2005 and 2008, but several functions are still not available.
Positional References [] DECLARE @x XML = '<Families> <Family name="Ainsworth"> <Parent name="Stuart"> <Child name="Isabel"/> <Child name="Grace"/> </Parent> </Family> <Family name="Smith"> <Parent name="Aaron"> <Child name="John"/> </Families>' SELECT @x.query('//Family').value('(Family/@name)[1]', 'varchar(100)'), @x.query('//Family').value('(Family/@name)[2]', 'varchar(100)')
Context switches {} DECLARE @x XML = '<Families> <Family name="Ainsworth"> <Parent name="Stuart"> <Child name="Isabel"/> <Child name="Grace"/> </Parent> </Family> <Family name="Smith"> <Parent name="Aaron"> <Child name="John"/> </Families>' SELECT @x.query('<New>{/Families/Family[1]}</New>')
Context switches {} <New> <Family name="Ainsworth"> <Parent name="Stuart"> <Child name="Isabel" /> <Child name="Grace" /> </Parent> </Family> </New>
(for)(let)… return FLWOR requires either for or let and a return statement return returns the actual dataset SELECT @x.query('<root> {for $f in /Families return "Hello World!"} </root> ') <root>Hello World!</root>
for for is an iterative operator Used to assign elements into a variable Step through that variable SELECT @x.query('<root> {for $f in /Family return "Hello World!"} </root> ') <root>Hello World! Hello World!</root>
let let assigns a value to a variable Introduced in SQL 2008 SELECT @x.query('<root>{for $f in //Family let $n:=data($f/@name) return $n} </root> ') <root>Ainsworth Smith</root>
Pop Quiz! <Families> <Family name="Ainsworth"> <Parent name="Stuart" /> </Family> <Family name="Smith"> <Parent name="Aaron" /> </Families> SELECT @x.query('<root>{for $f in //Family let $n:=data($f/@name) for $p in $f/Parent return <Person>{concat($p/@name, " ", $n)}</Person>} </root>')
Pop Quiz! <root> <Person>Stuart Ainsworth</Person> <Person>Aaron Smith</Person> </root>
where Similar to WHERE clause in SQL DECLARE @x XML = '<Families> <Family name="Ainsworth"> <Parent name="Stuart"> <Child name="Isabel"/> <Child name="Grace"/> </Parent> </Family> <Family name="Smith"> <Parent name="Aaron"> <Child name="John"/> </Families>'
where <root> <Person>Isabel Ainsworth</Person> SELECT @x.query('<root>{for $f in //Family let $n:=data($f/@name) for $c in $f/Parent/Child where $n="Ainsworth" return <Person>{concat($c/@name, " ", $n)}</Person>} </root>') <root> <Person>Isabel Ainsworth</Person> <Person>Grace Ainsworth</Person> </root>
order by SELECT @x.query('<root>{for $f in //Family let $n:=data($f/@name) for $c in $f/Parent/Child where $n="Ainsworth“ order by $c/@name return <Person>{concat($c/@name, " ", $n)}</Person>} </root>') <root> <Person>Grace Ainsworth</Person> <Person>Isabel Ainsworth</Person> </root>
More stuff to learn “A child of five would understand this. Send someone to fetch a child of five.” - Groucho Marx
Functions ceiling, floor, round concat, contains, substring lower-case Function (XQuery) string-length upper-case Function (XQuery) not number local-name Function (XQuery) namespace-uri Function (XQuery) last position empty distinct-values id Function (XQuery) count avg, min, max, sum string, data true Function (XQuery) false Function (XQuery) expanded-QName (XQuery) local-name-from-QName (XQuery) namespace-uri-from-QName (XQuery) sql:column() function (XQuery) sql:variable() function (XQuery)
sql:variable() DECLARE @x XML = '<Loc id="1">Atlanta</Loc> <Loc id="2">Washington</Loc>', @id INTEGER = 1 SELECT @x.query('<root>{for $l in /Loc where $l/@id="2" return data($l)}</root>') , @x.query('<root>{for $l in /Loc where $l/@id=sql:variable("@id") <root>Washington</root> <root>Atlanta</root>
sql:column() DECLARE @x XML = '<Loc id="1">Atlanta</Loc> <Loc id="2">Washington</Loc>' DECLARE @T TABLE (ID INTEGER) INSERT INTO @T (ID) VALUES(1), (2) SELECT @x.query('<root>{for $l in /Loc where $l/@id=sql:column("T.ID") return data($l)}</root>') FROM @T T _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ <root>Atlanta</root> <root>Washington</root>
Pop Quiz! DECLARE @x XML = '<row>A</row> <row>B</row> <row>C</row> <row>D</row> <row>E</row> ' DECLARE @T TABLE (ID INT) INSERT INTO @T (ID) SELECT ROW_NUMBER() OVER (ORDER BY c.column_id ) FROM sys.columns c SELECT @x.query('/row [position()=sql:column("T.ID")]') FROM @T t WHERE t.ID <= @x.query('<r> {count(/row)} </r>').value('.', 'int')
Pop Quiz! <row>A</row> <row>B</row> <row>C</row> <row>D</row> <row>E</row>
Questions? “It’s a miracle that curiosity survives formal education.” – Albert Einstein
Contact Information Stuart R Ainsworth stuart@codegumbo.com http://www.codegumbo.com http://www.twitter.com/codegumbo