Wednesday, February 14, 2007

SQL Server 2005 XML Methods, Part Three, The XPath Expression

We've discussed the basics of XML in SQL, we've seen a few examples of XQueries and their performance; I guess we're ready to go a bit deeper. In this article we'll look more closely at XPath expressions and see how they behave in SQL Server 2005 XML Queries.


More XPaths than one lead to <Rome/>

First of all there are several ways of formulating a valid XPath expression – for a comprehensive overview of the subject please consult the W3C Recommendations on XPath (mentioned in previous posts) and other sources (e.g. the XPath Tutorial at W3Schools Online Web Tutorials).

Generally, we have two options (with examples later in this article):

  • The exact path – where either the full path (starting at the root node) to a particular node (be it an element or an attribute) is specified (a.k.a. the absolute path) or the partial path to a particular node is specified depending on the current context (a.k.a. the relative path); or
  • The approximate path – where the path to the node is specified using XPath wildcards (e.g. "//" – anywhere, "element::*" or "*" – any element, "attribute::*" or "@*" – any attribute;1 of course more on the subject can be found in the W3C Recommendations). Although wildcards can also be used as part of the absolute or the relative path, such XPath expressions cannot be considered exact.

Specifying the exact path is generally the fastest, but it requires quite an intimate knowledge of the XML in question, and may require existing XPath expressions (e.g. used in T-SQL modules) to be corrected if the XML structure is subsequently modified for whatever reason. Approximate paths, on the other hand, are less structure-dependent, but they suffer from a higher performance cost, especially in complex structures.

Of course neither of the two options may lead to the desired node if more nodes exist in a given XML that correspond to a particular XPath expression. It is therefore necessary to extend the expression in such cases with an appropriate XPath predicate.

If we were to compare XPath/XQuery expressions with T-SQL queries we could say that the XPath expression corresponds to the T-SQL SELECT and FROM clauses and the XPath predicate functions as the T-SQL WHERE clause. An appropriate XPath predicate is practically indispensable in situations where the XPath expression should point to a specific node – e.g. if a singleton is needed (as is the case with the value() XML method; we've seen such a case before).

For instance, using the following XML:

declare @example xml

set @example = N'<document>
 <title>Hamsters prefer pumpkin seeds</title>
 <paragraph>George B. Quick is a golden hamster.</paragraph>
 <paragraph>He''s quick when curious about his surroundings and even quicker
  when hungry.</paragraph>
 <paragraph>Although he''s presented with a wide variety of tasty and
  nutritious goodies each day, George prefers shiny pumpkin seeds.
  </paragraph>
</document>'

...all the following queries return the value of the title element using three different XPath expressions:

-- Exact (absolute) path
select @example.query(
   '/document/title'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Title

-- Approximate path using the "anywhere" wildcard
select @example.query(
   '//title'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Title

-- Approximate path using the "any element" wildcard
select @example.query(
   '/*/title'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Title

No predicates are necessary in this particular example as there is only a single title element in our XML. However, if we wanted to extract the value of a particular paragraph element we might end up with unexpected results:

select @example.query(
   '/document/paragraph'
    ).value(
     '.'
     ,'varchar(max)'
     ) as Paragraph

select @example.query(
   '//paragraph'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

select @example.query(
   '/*/paragraph'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

All three queries return a concatenation of values from all paragraph elements. While this may be valid for some it may not be valid for all queries. What can we do? Well, with an appropriate predicate we can restrict the XML Query result. For instance, we can qualify the XPath expression using the paragraph element's position:

-- Extract the value of the second paragraph...

-- ...using the full syntax for the predicate:
select @example.query(
   '//paragraph[position() = 2]'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

-- ...using shorthand:
select @example.query(
   '//paragraph[2]'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

Although this method is frequently used in printed and online references on the subject of querying XML data (including Books Online!) I strongly advice against it, as it serves as a doorway to a whole world of possible problems. According to the XML specification the original order of elements need not be preserved (if several elements of the same name are allowed in a given XML) – meaning that it is perfectly legal to change element positions, to insert additional elements anywhere inside the existing element island(s), etc. Even XML Schema won't prevent this.

Being presented with an unfortunate XML as the one in our example there is little we can do in XML queries to prevent unexpected or incorrect results due to changes in the XML. If, on the other hand, the XML structure includes a means of uniquely identifying individual nodes, we can use more resilient XPath expressions:

declare @example xml

set @example = N'<document>
 <title>Hamsters prefer pumpkin seeds</title>
 <paragraph id="1">George B. Quick is a golden hamster.</paragraph>
 <paragraph id="2">He''s quick when curious about his surroundings and even
  quicker when hungry.</paragraph>
 <paragraph id="3">Although he''s presented with a wide variety of tasty and
  nutritious goodies each day, George prefers shiny pumpkin seeds.
  </paragraph>
</document>'

-- Exact (absolute) path using the appropriate predicate
select @example.query(
   '/document/paragraph[@id = 2]'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

-- Approximate path using the "anywhere" wildcard and the appropriate predicate
select @example.query(
   '//paragraph[@id = 2]'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

-- Approximate path using the "any element" wildcard and the appropriate predicate
select @example.query(
   '//*[@id = 2]'
   ).value(
    '.'
    ,'varchar(max)'
    ) as Paragraph

In the example above we've modified the XML by adding attributes that uniquely identify each repeated element – this way we can use XPath expressions that make the query no longer depend on element positions, but rather on more reliable properties. Of course this is not the only method that can be used nor may it be the most appropriate in all situations. We can quite clearly see that data integrity does start in the XML, and typing the XML (e.g. with XML Schema) is only one of the many steps we have to take to maintain it.

Undoubtedly, data integrity is important, but we should not neglect the performance of data retrieval.


Which XPath is the quickest?

In our performance test2 we will focus on the use of XML retrieval methods in query conditions – i.e. a comparison of various XPath expressions in the WHERE clause and their effect on performance. The queries used in this test are listed in footnotes at the bottom of this article. Please, refer to Part Two of the series for DDL and sample data. Note that both tables are XML-indexed and appropriate XML schema collections are used to type XML columns.

Performance
(All XML indexes)
Query EAV Schema Query Typed Schema
CPU Reads CPU Reads
#1 29260 1180945 #7 11837 403710
#2 29929 1180896 #8 13175 456104
#3 28148 884298 #9 18164 764994

As we've already seen in previous articles the performance of XML queries in comparison to T-SQL queries is quite poor. However, it should be apparent now that we can actually improve the performance of XML retrieval simply by using more appropriate XPath expressions.

For the EAV XML type approximate XPath expressions seem to be less resource-intensive, while for the strongly typed XML using an exact path will yield better performance. Of course, once again it seems that SQL Server prefers typed XML.

Anyway, we're not quite done yet. Believe it or not, there still remains some room for improvement. And it won't require any changes to the XML or the XML Schema – the XPath expressions can be further improved. How about halving CPU time and decimating reads?

Performance
(All XML indexes)
Query EAV Schema Query Typed Schema
CPU Reads CPU Reads
#4 8995 148077 #10 5670 87871
#5 9035 148077 #11 5740 87871
#6 12332 228022 #12 10497 242122

By using even more exact XPath expressions we can reduce CPU time by 2 to 3 times and reduce the number of reads by 3 to 8 times! This *is* without a doubt a significant amount.

But where's the catch?

Well, where does the value of a simple (or mixed) XML element actually reside? In its text node, that's where. It is therefore logical to look for it right there and nowhere else, and apparently this is exactly how XML data retrieval works in SQL: by specifying an XPath expression that is as exact as possible we allow the database engine to navigate past other possible locations (e.g. child nodes) and go directly to the intended target (the bull's-eye).

Even though our target element holds no descendants (and – according to XML schema collections being used – isn't allowed to hold any) the "/attributeCollection/firstName" XPath expression actually points to the entire element (i.e. a node-set, not just a node!), while the "/attributeCollection/firstName/text()" XPath expression points to a single final node. Well, this is what I've observed – after all, I haven't designed the darn thing. ;) It makes perfect sense, though.


Mental aggregation

What have we learned?

  1. First of all, serious XML use starts with serious XML modeling: most of the considerations related to an efficient SQL data model are necessary for efficient XML models as well. Use the XML Schema to provide domain integrity (XDR, although supported by SQL Server 2005, is fast becoming obsolete), use appropriate relational modeling (yes, relational!) inside your XML Schema, and consider all relevant aspects of XML use *before* you introduce your XML model into your SQL model;
  2. Second, know your XML: XML Queries should reflect the expected structure of XML data – consider the XML Schema as your guide through the XML. Can you afford to use incorrect data?
  3. Third, really know your XML: the difference between exact and approximate XPath expressions may be insignificant on small quantities of data, but may grow dramatically on large quantities. SQL Server prefers the exact XPath and so should you;
  4. And last but not least, if your particular business case requires an XML model that can also be modeled in SQL, use SQL instead; especially so if a SQL pattern or practice that matches your requirements already exists. After all, the superiority of SQL over XML regarding data modeling, storage and retrieval remains undisputed.

Next time we'll take a look at something rather unexpected and quite useful (in some respects).


ML


  • 1 Quotation marks are not part of the XPath expression.
  • 2 You'll need this for the rest of the queries to work (in addition to DDL and sample data available here):
    declare @firstName varchar(8)
    set @firstName = 'Karen'
  • Query #1 (EAV Type, absolute path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '/attributeCollection/attribute[name = "firstName"]/value'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #2 (EAV Type, approximate path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '//attribute[name = "firstName"]/value'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #3 (EAV Type, an "even more approximate" path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '//value[../name = "firstName"]'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #4 (EAV Type, the "bull's-eye" path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '/attributeCollection/attribute[name/text() = "firstName"]/value/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #5 (EAV Type, the "near bull's-eye" path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '//attribute[name/text() = "firstName"]/value/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #6 (EAV Type, the "in the neighbourhood of the bull's-eye" path):
    select ANV.AttributeId as AttributeId
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '//value[../name/text() = "firstName"]/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #7 (Strong Type, absolute path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '/attributeCollection/firstName'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #8 (Strong Type, approximate path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '//firstName'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #9 (Strong Path, the "ridiculously complicated" path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '//*[local-name() = "firstName"]'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #10 (Strong Type, the "bulls's-eye" path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '/attributeCollection/firstName/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #11 (Strong Type, the "near bull's-eye" path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '//firstName/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)
  • Query #12 (Strong Type, the "grasping at straws" path):
    select AT.AttributeId as AttributeId
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        '//*[local-name() = "firstName"]/text()'
        ).value(
         '.'
         ,'varchar(max)'
         ) = @firstName)

Wednesday, January 24, 2007

XQuery 1.0 becomes a W3C Recommendation

On January 23rd 2007 the World Wide Web Consortium announced that XQuery 1.0, XPath 2.0 and XSLT 2.0 are now official W3C Recommendations, meaning that the R&D activities regarding the current versions have come to a conclusion and that all three standards are now ready for implementation. On the other hand this also means that the work on the next versions has started – I believe the XML Query Working Group is now counting on feedback from implementors in order to improve the standards.

I've posted on this subject before, when the Proposed Recommendations were announced.

The specifications are available at the following locations:

Additional documents are available at the following locations:

As far as the XQuery/XPath functions are concerned the collection remains unchanged.

The R&D activities concerning XQuery 1.0 and XPath 2.0 Full-Text language extensions are also well under way.

I guess we should now expect some changes to the SQL Server implementation of the standards, perhaps not in a service release but certainly in the next full version of the platform.


ML

Tuesday, January 09, 2007

SQL Server 2005 XML Methods, Part Two, Retrieval Comparison

After a brief introduction to XML retrieval methods in SQL Server 2005 we can examine the behaviour of a few practical examples.

The implementation of XML as a native data type could not have been considered complete without XML indexes. Especially when considering data retrieval – indexing may just as well be the deciding element of the entire implementation. There are four types of XML indexes available: a primary XML index and three secondary XML indexes each of the latter with a specific intended purpose. Please, follow the link above (to Books Online) for all the details.

Originally the subject of this post was supposed to be how XML indexes improve the performance of XML retrieval methods. I say *originally* because after a lot of testing and playing around with different possibilities and different approaches I've come to the conclusion that if XML indexes should have had a positive impact on performance then there must have been something seriously wrong with my sample data, or perhaps I have yet to find the magic combination. Either way, I don't believe finding a solution to a problem as trivial as this really should be this difficult. Well, I'm not ready to give up. Yet.


Roll up the sleeves

First of all, execute this script in your favourite testing database. The script creates two tables and fills them up with XML data, assuming that the AdventureWorks database is accessible from your favourite testing database. Then, to spice things up, add these four functions. Spicy and XPathy.

I assume you always read through each script before executing it. ;)


What's in a table?

When executed the script above will build two tables (dbo.AttributesNameValue and dbo.AttributesTyped) and insert two types of XML into them from the Person.Contact table of the AdventureWorks database (i.e. customer attributes such as FirstName, MiddleName and LastName are encapsulated into a single XML per CustomerID).

In the script you'll notice the @step variable; its purpose is to limit the number of rows retrieved from the source table. I've set it to 100,000 which exceeds the actual number of rows, and you can reduce it appropriately – to play around with the size of the test table. I would be very interested in knowing whether anyone comes up with different results.

The difference between the tables is that the XML in the dbo.AttributesNameValue is based on an untyped (or at least very loosely typed) EAV model and the one in the dbo.AttributesTyped is typed. Here are the XML Schemas used:

  • One for the EAV XML stored in the dbo.AttributesNameValue.Attribute table; and
  • Another for the Typed XML stored in the dbo.AttributesTyped.Attribute table.

I'll start by being mean and ruthless, simply to illustrate what we're up against. The following table shows the performance of the original query against the Person.Contact table:

Query Performance
Primary Key Only Covering Index
CPU Reads CPU Reads
#1 20 561 0 3

The performance of query #1 is pretty much what should be expected – no special burden on the engine even without the covering index. Mind you, all 19972 rows reside in 559 pages.

The situation is quite different once we query XML data. The first thing to consider in this case is the increased number of pages: 5136 pages for dbo.AttributesTyped and 9087 pages for dbo.AttributesNameValue (the number of pages has grown 9 and 16 times respectively). The other consideration is the fact that now we also need an appropriate XQuery to access the data encapsulated in the XML. I've constructed 10 queries (5 per each table) using 5 different approaches to querying XML (you can find each query in the footnotes of this article). These are the results of each query's performance:

Performance
(Primary Key Only)
Query EAV Schema Query Typed Schema
CPU Reads CPU Reads
#2 35193 1825 #7 26819 966
#3 21723 1824 #8 4454 966
#4 21565 1818 #9 4502 960
#5 74417 1820 #10 44344 962
#6 25449 1820 #11 4676 962

The loss of performance is blindingly obvious – a lot more reads are necessary to access the data, of course the problem remains the same: "return the ID and the LastName of the person with the FirstName 'Karen'".


What's in a query?

Allow me to explain what the functions actually do. There are four of them, two per each table (per XML Type).

  • One pair of functions uses the nodes XML retrieval method (used in queries #2, #3, #7 and #8);
  • While the other pair uses the query XML retrieval method (used in queries #5, #6, #10 and #11).

Each function accepts two parameters: the XML and the name of the element, and returns the value of the given element.

Queries #4 and #9 are 'function-free', using only the query XML retrieval method with appropriate XQueries to access the nodes.


What!? No XML indexes!?

Yes, in the first series of tests the only existing indexes were the tables' primary keys. Could that be the reason behind poor performance? Well, let's add those immediately.

Performance
(All XML indexes)
Query EAV Schema Query Typed Schema
CPU Reads CPU Reads
#2 34876 1823 #7 27355 966
#3 26330 977379 #8 7693 142280
#4 22532 785248 #9 7765 142537
#5 74347 1819 #10 45836 962
#6 23530 386552 #11 7919 142276

Dreaming of performance improvements? Keep on dreaming. With XML indexes even more reads are necessary to access data, yet – in the case of the EAV type – the CPU overhead is not all that significant compared to the case without XML indexes. The increase in the number of reads is due to the way XML indexing is implemented in SQL Server 2005: additional system tables are created to store the XML index data.

In other words: 419412 additional rows in 2786 pages for dbo.AttributesNameValue and 179748 additional rows in 1105 pages for dbo.AttributesTyped.

The number of reads in queries against the dbo.AttributesNameValue table where XML indexes are actually used (i.e. queries #3, #4 and #6) is 536, 432 and 212 times higher compared to the number of reads when only the table's primary key is used. The good thing is that the impact on the processor remains pretty much unchanged – the queries seem to take just as much time with or without the XML indexes.

There is a significant difference, however, with the queries against the dbo.AttributesTyped table. Here, the impact on the processor is approximately 1.7 times higher, while the number of reads (for queries #8, #9 and #11) is approximately 148 times higher compared to the number of reads when only the table's primary key is used.

Books Online suggest using the VALUE and the PATH XML indexes for these kinds of queries, but as I have found out (using this particular test data) the only XML index ever used was the PRIMARY XML index (at least none of the other indexes ever showed up on the execution plans); although the performance was best with all XML indexes. In fact, I have experienced the most positive impact on performance (the lowest number of reads) after adding the PROPERTY index, which may have something to do with the nillability of the MiddleName element in both XML types, but I'm not certain. Anyway, according to the execution plans, no secondary XML index was ever used by the optimizer.

Definitely a matter worth exploring further...


Weak or strong?

XML Schema Collections bring proper domain integrity to the XML data type, but is that all they bring? Let's add the XML Schemas for the two XML types, and after the XML Schema Collections have been created we need to drop all XML indexes prior to altering the two XML columns – the rule that indexed columns cannot be altered applies to XML columns as well. Before executing the queries re-create all XML indexes. Now we're ready to see whether this affects the performance of the queries in any way.

Performance
(All XML indexes)
Query EAV Schema Query Typed Schema
CPU Reads CPU Reads
#2 43112 9115 #7 30424 5156
#3 14614 511056 #8 7220 145909
#4 13794 512012 #9 7096 146163
#5 77025 9111 #10 45956 5152
#6 16732 309900 #11 7241 145905

It seems that typing the XML enables the SQL Server engine to organize the XML indexes more efficiently, as a consequence the number of logical reads is reduced – that is, for queries where XML indexes are used (i.e. queries #3, #4, #6, #8, #9 and #11). While the number of logical reads is increased for the queries where XML indexes aren't used (i.e. queries #2, #5, #7 and #10).

Actually, both the number of logical reads as well as the stress on the CPU have dropped dramatically for the case of the EAV XML type, while the number of logical reads has increased slightly for the strongly typed XML, but at the same time the stress on the CPU has decreased. Apparently, typing does improve performance of retrieval methods. Again, this is a matter worthy of more detailed investigation.


Mental aggregation

We've compared the performance aspects of a few typical use-cases for the built-in XML retrieval methods. So, what is the pattern that we've experienced? Regarding XML in SQL Server several points have become apparent:

  1. Queries against the XML data type are by far more resource-intensive than queries against data properly normalised in SQL;
  2. XML indexes are very much different and behave quite differently from regular indexes: many more reads are required to access data;
  3. SQL Server 2005 seems to "prefer" typed XML; nonetheless, the performance of queries against the XML data type will diminish as the complexity of the XML data increases;
  4. We've seen proof once again of why functions on data columns in query restrictions should be avoided – with user-defined functions in the WHERE clause the performance was really very poor. However, the overall performance of queries *could* benefit from functions being used in the SELECT clause (as is the case with queries #3 and #6 vs. query #4 or queries #8 and #11 vs. #9), but this should never be taken as a general rule – it merely represents an alternative to consider.

I would have expected XML Queries to perform less efficiently than SQL queries, but judging from my test data the drop in performance can be quite dramatic. To be honest, I also did not expect XML Schema Collections to have a positive impact on retrieval performance; quite the opposite!

In one of the following posts I'll be focusing some more on XML index use; most importantly – I'll be discussing the execution plans which we haven't looked at yet. After all, this was supposed to be only a slightly more detailed introduction into the world of XML retrieval methods introduced with SQL Server 2005. Next up is a look at XPath expressions.


ML


  • Query #1:
    select Person.Contact.ContactID as ContactID
     ,Person.Contact.LastName as LastName
     from Person.Contact
     where (Person.Contact.FirstName = 'Karen')
  • The covering index for the Person.Contact table:
    create nonclustered index x_Person_Contact_FullName
     on Person.Contact
      (
      FirstName
      ,MiddleName
      ,LastName
      )
  • Query #2:
    select dbo.AttributesNameValue.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromNameValue(
      dbo.AttributesNameValue.Attribute, 'lastName'
      ) as LastName
     from dbo.AttributesNameValue
     where (dbo.fnGet_Value_byAttributeName_fromNameValue(
        dbo.AttributesNameValue.Attribute
        ,'firstName'
        ) = 'Karen')
  • Query #3:
    select ANV.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromNameValue(
      ANV.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        'attributeCollection/attribute[name = "firstName"]/value'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')
  • Query #4:
    select ANV.AttributeId as AttributeId
     ,ANV.Attribute.query(
      'attributeCollection/attribute[name = "lastName"]/value'
       ).value(
        '.'
        ,'varchar(max)'
        ) as LastName
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        'attributeCollection/attribute[name = "firstName"]/value'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')
  • Query #5:
    select ANV.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromNameValue_Query(
      ANV.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesNameValue ANV
     where (dbo.fnGet_Value_byAttributeName_fromNameValue_Query(
        ANV.Attribute
        ,'firstName'
        ) = 'Karen')
  • Query #6:
    select ANV.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromNameValue_Query(
      ANV.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesNameValue ANV
     where (ANV.Attribute.query(
        '/attributeCollection/attribute[name = "firstName"][1]/value'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')
  • Query #7:
    select dbo.AttributesTyped.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromTyped(
      dbo.AttributesTyped.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesTyped
     where (dbo.fnGet_Value_byAttributeName_fromTyped(
        dbo.AttributesTyped.Attribute
        ,'firstName'
        ) = 'Karen')
  • Query #8:
    select AT.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromTyped(
      AT.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        'attributeCollection/firstName'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')
  • Query #9:
    select AT.AttributeId as AttributeId
     ,AT.Attribute.query(
      'attributeCollection/lastName'
      ).value(
       '.'
       ,'varchar(max)'
       ) as LastName
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        'attributeCollection/firstName'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')
  • Query #10:
    select AT.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromTyped_Query(
      AT.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesTyped AT
     where (dbo.fnGet_Value_byAttributeName_fromTyped_Query(
        AT.Attribute
        ,'firstName'
        ) = 'Karen')
  • Query #11:
    select AT.AttributeId as AttributeId
     ,dbo.fnGet_Value_byAttributeName_fromTyped_Query(
      AT.Attribute
      ,'lastName'
      ) as LastName
     from dbo.AttributesTyped AT
     where (AT.Attribute.query(
        'attributeCollection/firstName'
        ).value(
         '.'
         ,'varchar(max)'
         ) = 'Karen')