I have two stored procedures that return result sets.
How can I use them to populate a string and update another column?
exec getEmailSignatureDetails 'Jane', 'Doe'
exec getFeaturedAccount 'June'
These both return columns that I would like to map to variables.
I would then like to put the variables into a string.
Then update a column in another table with that string.
Output from getEmailSignatureDetails:
addCity | addLine | addSt | addZip | fName | lName
--------------+-------------+-------+---------+-------+------
San Francisco | 777 SV Lane | CA | 94016 | Jane | Doe
Output from getFeaturedAccount:
month | img
------+----------
June | base64...
I would like to turn this into a string like
Your package has been delivered to
#fName #lName
#addLine
#addCity #addSt, #addZip
#img
And then update a column with this string matching on name.
If I understand your question your are looking to dynamically fill-in a template via macro substitution
Example
-- Create some Sample Data
Declare #getEmailSignatureDetails Table ([addCity] varchar(50),[addLine] varchar(50),[addSt] varchar(50),[addZip] varchar(50),[fName] varchar(50),[lName] varchar(50))
Insert Into #getEmailSignatureDetails Values
('San Francisco','777 SV Lane','CA',94016,'Jane','Doe')
Declare #getFeaturedAccount Table ([month] varchar(50),[img] varchar(50))
Insert Into #getFeaturedAccount Values
('June','base64..')
-- Declare the Template
Declare #Template varchar(max) ='
Your package has been delivered to
#fName #lName
#addLine
#addCity #addSt, #addZip
#img
'
-- Populate the Template
Select #Template = replace(#Template,'#'+Field,Value)
From (
Select C.*
From (values (convert(XML,(Select * From #getEmailSignatureDetails Join #getFeaturedAccount on [month]='June' For XML Raw ) ) ) ) A(XMLData)
Cross Apply (
Select Field = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)')
From A.XMLData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('local-name(.)','varchar(100)') not in ('Column1','Column2')
) C
) A
Updated Template
Your package has been delivered to
Jane Doe
777 SV Lane
San Francisco CA, 94016
base64..
If it helps with the visualization, the sub-query is a "dynamic" unpivot and generates the following:
Field Value
addCity San Francisco
addLine 777 SV Lane
addSt CA
addZip 94016
fName Jane
lName Doe
month June
img base64.. -- (presumably would be the image)
Related
this is my first time posting so hopefully I get all the information needed across correctly.
I'm attempting to do an update replace statement on a row where there is a set of characters but a wildcard statement won't work, below is a select statement example
SELECT replace(column, '[aA][bB][cC]-', '')
FROM table
where column like '%[aA][bB][cC]-%'
an example row would be "abc-123 aBc-123 abC-abc Def-123", but I need it to return "123 123 abc"
this is a basic example, but I'm trying to get rid of a set of 3 characters and a "-" character anywhere in a string. the abc could change to def but the "-" character will always come after.
I've done some googling and can't find an appropriate solution as most examples will only remove one example of abc- where I need to get rid of all versions. I'm running version 12.0 of sql server (SQL Server 2014) so I think some functions I wouldn't be able to use.
I think the closest example I could find was Using Wildcard For Range of Characters In T-SQL but I can't use the translate function.
Edit: below is an example of a created table
CREATE TABLE String_Removal_Example(
someValue VARCHAR(100)
)
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123')
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123 ABC-123')
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123 ABc-123 123-ABC DEF-123')
select statement brings back
someValue
abc-123
abc-123 ABC-123
abc-123 ABc-123 123-ABC DEF-123
Edit2: If this isn't possible to do in this manner, a possible alternative would be to remove 3 characters and the - character. I've tried the below
select Substring (someValue, Charindex( '-', someValue ) + 1, Len(someValue)) From String_Removal_Example
but this returns the below, which is only affecting the first instance of nnn-.
123
123 ABC-123
123 ABc-123 123-ABC DEF-123
Edit3: The string I need to replace is nnn- for clarification. I was trying for the [aA][bB][cC]- format in case I needed to change it. It will always be 3 characters followed by a "-" Character
You can create a function like this:
(I named it ThreeLetters, but what's in a name...)
CREATE FUNCTION [dbo].[ThreeLetters]
(
#p0 VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #p1 VARCHAR(20) = '%[A-Z][A-Z][A-Z]-%';
DECLARE #Result VARCHAR(MAX) = #p0;
DECLARE #pos INT = PATINDEX(#p1,#Result COLLATE SQL_Latin1_General_CP1_CS_AS);
DECLARE #pos2 INT ;
DECLARE #i INT =0;
WHILE #pos>0 and #i<10
BEGIN
SET #pos2 = CHARINDEX('-',#Result,#pos)-#pos;
SELECT #Result = SUBSTRING(#Result,1,#pos-1)+SUBSTRING(#Result, #pos+#pos2+1, len(#Result));
SET #pos = PATINDEX(#p1,#Result COLLATE SQL_Latin1_General_CP1_CS_AS);
SET #i = #i + 1;
END
RETURN #Result
END
output with your sample data:
someValue
(No column name)
abc-123
123
abc-123 ABC-123
123 123
abc-123 ABc-123 123-ABC DEF-123
123 123 123-ABC 123
see DBFIDDLE
P.S. The check for #i was introduced while debugging. It might not be needed, but was tooo lazy to remove it.
P.P.S I am removing ThreeLetters followed by a -, that is why 123-ABC remains unchanged.
Here's an XML trick that'll also work in Sql Server 2014
(In Sql Server 2016 and beyond, the string_split function would be better.)
declare #String_Removal_Example table (
someValue varchar(100) not null
);
insert into #String_Removal_Example (someValue) VALUES
('abc-123')
, ('abc-123 ABC-123')
, ('abc-123 ABC-123 123-Abc DEF-123');
select ca.value as someValue
from #String_Removal_Example t
cross apply
(
select rtrim((
select node.n.value('.','varchar(30)')+' '
from
(
select cast('<x><a>' + replace(replace(t.someValue,
' ', '</n><a>'),
'-', '</a><n>') +
'</n></x>' as xml) as x
) q
cross apply x.nodes ('/x/n') AS node(n)
for xml path('')
)) as value
) ca
where lower(t.someValue) like '%[a-z0-9]-[a-z0-9]%';
GO
| someValue |
| :-------------- |
| 123 |
| 123 123 |
| 123 123 Abc 123 |
db<>fiddle here
Please try the following solution. It is a variation of #LukStorms solution.
It is based on XML and XQuery. Their data model is based on ordered sequences.
XML allows us to tokenize the input string.
It is looking for 'nnn-' strings and filters them out.
For example, 2nd row is converted to XML like this:
<root>
<r>abc-</r>
<r>123</r>
<r>ABC-</r>
<r>123</r>
</root>
SQL
-- DDL and sample data population, start
declare #String_Removal_Example table (
someValue varchar(100) not null
);
insert into #String_Removal_Example (someValue) VALUES
('abc-123')
, ('abc-123 ABC-123')
, ('abc-123 ABC-123 123-Abc DEF-123');
-- DDL and sample data population, end
SELECT someValue AS [before], [after]
FROM #String_Removal_Example
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(someValue, '-', '- '), SPACE(1), ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
.query('data(/root/r[not(contains(text()[1],"-"))])')
.value('.','VARCHAR(255)')) AS t1(after);
Output
+---------------------------------+-----------------+
| before | after |
+---------------------------------+-----------------+
| abc-123 | 123 |
| abc-123 ABC-123 | 123 123 |
| abc-123 ABC-123 123-Abc DEF-123 | 123 123 Abc 123 |
+---------------------------------+-----------------+
This is basically dividing up the string where there are spaces, then looking for "words" that have a '-' in the 4th position, removing the prefix and then stitching it back together:
SELECT s.someValue,
STUFF((SELECT ' ' + SUBSTRING(value, 5, LEN(value) - 4)
FROM STRING_SPLIT(someValue, ' ')
WHERE CHARINDEX('-', value) = 4
FOR XML PATH('')),1,1,'') a
FROM String_Removal_Example s
As there is no regex replace in SQL Server, I think the only way you can reliably use a pattern replace is through an iterative process. One such way is with a recursive CTE:
WITH Data AS
( SELECT SomeValue
FROM (VALUES
('abc-123'),
('abc-123 ABC-123'),
('abc-123 aBc-123 abC-abc Def-123'),
('abc-123 ABCD-123'),
('abc-123 A$C-123')
) x (SomeValue)
), RegexReplace AS
( SELECT SomeValue, NewValue = CONVERT(VARCHAR(100), Data.SomeValue), Level = 1
FROM Data
UNION ALL
SELECT SomeValue, CONVERT(VARCHAR(100), STUFF(NewValue, PATINDEX('% [A-z][A-z][A-z]-%', CONCAT(' ', NewValue)), 4, '')), Level + 1
FROM RegexReplace AS d
WHERE PATINDEX('% [A-z][A-z][A-z]-%', CONCAT(' ', NewValue)) > 0
), RankedData AS
( SELECT *, RowNumber = ROW_NUMBER() OVER(PARTITION BY rr.SomeValue ORDER BY rr.Level DESC)
FROM RegexReplace AS rr
)
SELECT rd.SomeValue, rd.NewValue
FROM RankedData AS rd
WHERE rd.RowNumber = 1;
Which gives:
SomeValue
New Value
abc-123
123
abc-123 ABC-123
123 123
abc-123 aBc-123 abC-abc Def-123
123 123 abc 123
abc-123 A$C-123
123 A$C-123
abc-123 ABCD-123
123 ABCD-123
This basically uses PATINDEX() to find the first instance of the pattern, then STUFF() to remove the 4 characters after the index found. This process then repeats until the PATINDEX() fails to find a match.
Probably also of note is that when doing the PATINDEX() I am adding a space to the start of the string, and including a space at the start of the pattern. This ensures that it is limited to only 3 characters followed by a hyphen, if it is more than 3 (e.g. ABCD-123) the entire string is left intact rather than leaving A123 as shown in the last row above. Adding the space to the start of the string being searched ensures that the pattern is still found even if it is at the start of the string.
SELECT replaceAll(column, '[aA][bB][cC]-', '')
FROM table
where column like '%[aA][bB][cC]-%'
I have a column with comma-separated strings, I need to compare it with another comma-separated column and return only matching values.
For example
column 1 = John, james, steve
column 2 = john, smith, will, james
I need a result like John,james since it is available in both column 1 and column 2. Is this possible in SQL?
As I'm using SQL Server 2012, I'm not able to use the String_split function
You can try the following solution.
It is using XQuery and its FLWOR expression.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, col1 VARCHAR(MAX), col2 VARCHAR(MAX));
INSERT INTO #tbl (col1, col2) VALUES
('John, james, steve', 'john, smith, will, james'),
('Mary, Lisa', 'Mary');
-- DDL and sample data population, end
DECLARE #separator CHAR(2) = ', ';
SELECT *
, REPLACE(TRY_CAST('<root>' +
'<source><r><![CDATA[' + REPLACE(col1, #separator, ']]></r><r><![CDATA[') +
']]></r></source>' +
'<target><r><![CDATA[' + REPLACE(col2, #separator, ']]></r><r><![CDATA[') +
']]></r></target>' +
'</root>' AS XML)
.query('
for $x in /root/source/r/text(),
$y in /root/target/r/text()
where lower-case($y) eq lower-case($x)
return data($x)
').value('.', 'NVARCHAR(MAX)'), SPACE(1), ',') AS result
FROM #tbl;
Output
+----+--------------------+--------------------------+------------+
| ID | col1 | col2 | result |
+----+--------------------+--------------------------+------------+
| 1 | John, james, steve | john, smith, will, james | John,james |
| 2 | Mary, Lisa | Mary | Mary |
+----+--------------------+--------------------------+------------+
This can certainly be done using STRING_SPLIT():
DECLARE #a VARCHAR(MAX) = 'john,james,steve'
DECLARE #b VARCHAR(MAX) = 'john,smith,will,james'
SELECT a.value
FROM string_split(#a, ',') a
JOIN string_split(#b, ',') b
ON a.value = b.value
But I think the broader question is if you should be doing this in SQL at all. SQL is much better and more efficient at set-based operations, and this approach is very iterative going row by row. I would consider restructing your data so it's easier to do this or read it into memory and use C# to do this type of manipulation.
I have a column in a sql server table named [City_St_Zip] that contains records that look like this
Dallas, TX 12345
What I would like to do is separate the column into three different columns (i.e. City, State and Zip)
like this:
Dallas
TX
12345
I am not sure how to go about this in SQL
I have tried the following
DECLARE #X NVARCHAR(100),
DECLARE #T NVARCHAR(100),
SELECT
#X = [City_St_Zip],
#T = [NewDivision]
FROM
dbo.Invoice
CROSS APPLY STRING_SPLIT(#X, ',');
This yielded 0 results so I am pretty sure I did that incorrectly
Any suggestions? I am using SQL Server 2019
EDIT:
I also tried this which is closer to what I want
SELECT
value
FROM
dbo.Invoice
CROSS APPLY STRING_SPLIT([City_St_Zip], ',');
That gives me a result set of:
Dallas
TX 12345
So I guess this is convoluted and needs both a comma and a space delimiter. Would I just put the value through another STRING_SPLIT?
SQL Server has poor string processing support. And, string_split() is not guaranteed to keep the values in order. And string searches are dangerous -- think New York, New York.
So, a brute force method:
select left(col, charindex(',', col) - 1) as city,
substring(col, charindex(',', col) + 2, 2) as state,
right(col, 5) as zipcode
Here is a db<>fiddle.
If you want to use STRING_SPLIT then this will work without variables.
Sample data:
create table dbo.Invoice
(
id int identity(1,1) primary key,
[City_St_Zip] nvarchar(100)
);
insert into dbo.Invoice
([City_St_Zip]) values
('Dallas, TX 12345'),
('Fort Worth, TX 12345')
GO
2 rows affected
Query:
SELECT inv.*, a.*
FROM dbo.Invoice inv
OUTER APPLY
(
SELECT
[1] AS [City],
LTRIM(LEFT([2], 3)) AS [State],
TRIM(SUBSTRING([2],4,LEN([2]))) AS [Zip]
FROM
( SELECT spl.value
, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rn
FROM STRING_SPLIT(inv.[City_St_Zip],',') spl
) s
PIVOT (MAX(value) FOR rn IN ([1],[2])) p
) a;
Result:
id | City_St_Zip | City | State | Zip
-: | :------------------- | :--------- | :---- | :----
1 | Dallas, TX 12345 | Dallas | TX | 12345
2 | Fort Worth, TX 12345 | Fort Worth | TX | 12345
db<>fiddle here
Extra:
Using the XML type, this SQL will also work in an earlier version like Sql Server 2012.
SELECT inv.*
, a.City
, RTRIM(LEFT(a.StateZip, CHARINDEX(' ',a.StateZip))) AS State
, LTRIM(SUBSTRING(a.StateZip, CHARINDEX(' ',a.StateZip),LEN(a.StateZip))) AS Zip
FROM dbo.Invoice inv
OUTER APPLY
(
SELECT X.x AS CityStateZipXml
, X.x.value('/x[1]','nvarchar(max)') AS City
, LTRIM(X.x.value('/x[2]','nvarchar(max)')) AS StateZip
FROM (
SELECT CAST(CONCAT('<x>', REPLACE(inv.[City_St_Zip],',','</x><x>'),'</x>') AS XML)
) AS X(x)
) a;
* Updated as per SQL Server*
create table ctry
(
city_st_zip nvarchar(100)
);
insert into ctry values('Dallas, TX 12345');
--SQL USED--
SELECT
LEFT([city_st_zip], CHARINDEX(',', [city_st_zip]) - 1) AS [City],
SUBSTRING([city_st_zip], CHARINDEX(',', [city_st_zip]) + 2, 2) as [State],
RIGHT([city_st_zip], CHARINDEX(' ', [city_st_zip]) - 2) AS [Zip]
FROM ctry;
--Result--
City State Zip
Dallas TX 12345
I have compiled the data in a table called Employees. Table definition is
Name Age
Sam 25
Mike 28
Is it possible to write a query that can give me the output in format
SAM
25
MIKE
28
I am unable to write this query. Is it possible to do it.
If not, how can i achieve that.
I can do it using a cursor but it will largely degrade the performance of my proc.
Easily modified to suite your needs
Declare #User table (id int,First_Name varchar(50),Last_Name varchar(50),EMail varchar(50))
Insert into #User values
(1,'John','Smith','john.smith#gmail.com'),
(2,'Jane','Doe' ,'jane.doe#gmail.com')
Declare #XML xml
Set #XML = (Select * from #User for XML RAW)
Select ID = r.value('#id','int')
,Item = Attr.value('local-name(.)','varchar(100)')
,Value = Attr.value('.','varchar(max)')
From #XML.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*[local-name(.)!="id"]') as B(Attr)
Returns
ID Item Value
1 First_Name John
1 Last_Name Smith
1 EMail john.smith#gmail.com
2 First_Name Jane
2 Last_Name Doe
2 EMail jane.doe#gmail.com
As requested, but I see little value in it
Declare #Table varchar(150) = 'YourTableName'
Declare #SQL varchar(max) = '>>>'
Select #SQL = Replace(#SQL + SQL ,'>>>Union All ','')
From (Select Seq=ORDINAL_POSITION,SQL='Union All Select Value=cast(['+Column_Name+'] as varchar(500)) From ['+Table_Schema+'].['+Table_Name+']' From INFORMATION_SCHEMA.COLUMNS where Table_Name=#Table) A
Order By Seq
--Print #SQL
Exec(#SQL)
Sample Return
Value
22 Star Ave, Riverside, RI 02915
22 Planet Ave, Riverside, RI 02915
100 Peck Ave, Riverside, RI 02915
1086 Willett Ave, Riverside, RI 02915
4
5
6
I am selecting from a table that has an XML column using T-SQL. I would like to select a certain type of node and have a row created for each one.
For instance, suppose I am selecting from a people table. This table has an XML column for addresses. The XML is formated similar to the following:
<address>
<street>Street 1</street>
<city>City 1</city>
<state>State 1</state>
<zipcode>Zip Code 1</zipcode>
</address>
<address>
<street>Street 2</street>
<city>City 2</city>
<state>State 2</state>
<zipcode>Zip Code 2</zipcode>
</address>
How can I get results like this:
Name City State
Joe Baker Seattle WA
Joe Baker Tacoma WA
Fred Jones Vancouver BC
Here is your solution:
/* TEST TABLE */
DECLARE #PEOPLE AS TABLE ([Name] VARCHAR(20), [Address] XML )
INSERT INTO #PEOPLE SELECT
'Joel',
'<address>
<street>Street 1</street>
<city>City 1</city>
<state>State 1</state>
<zipcode>Zip Code 1</zipcode>
</address>
<address>
<street>Street 2</street>
<city>City 2</city>
<state>State 2</state>
<zipcode>Zip Code 2</zipcode>
</address>'
UNION ALL SELECT
'Kim',
'<address>
<street>Street 3</street>
<city>City 3</city>
<state>State 3</state>
<zipcode>Zip Code 3</zipcode>
</address>'
SELECT * FROM #PEOPLE
-- BUILD XML
DECLARE #x XML
SELECT #x =
( SELECT
[Name]
, [Address].query('
for $a in //address
return <address
street="{$a/street}"
city="{$a/city}"
state="{$a/state}"
zipcode="{$a/zipcode}"
/>
')
FROM #PEOPLE AS people
FOR XML AUTO
)
-- RESULTS
SELECT [Name] = T.Item.value('../#Name', 'varchar(20)'),
street = T.Item.value('#street' , 'varchar(20)'),
city = T.Item.value('#city' , 'varchar(20)'),
state = T.Item.value('#state' , 'varchar(20)'),
zipcode = T.Item.value('#zipcode', 'varchar(20)')
FROM #x.nodes('//people/address') AS T(Item)
/* OUTPUT*/
Name | street | city | state | zipcode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Joel | Street 1 | City 1 | State 1 | Zip Code 1
Joel | Street 2 | City 2 | State 2 | Zip Code 2
Kim | Street 3 | City 3 | State 3 | Zip Code 3
Here's how I do it generically:
I shred the source XML via a call such as
DECLARE #xmlEntityList xml
SET #xmlEntityList =
'
<ArbitrarilyNamedXmlListElement>
<ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>1</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement>
<ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>2</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement>
<ArbitrarilyNamedXmlItemElement><SomeVeryImportantInteger>3</SomeVeryImportantInteger></ArbitrarilyNamedXmlItemElement>
</ArbitrarilyNamedXmlListElement>
'
DECLARE #tblEntityList TABLE(
SomeVeryImportantInteger int
)
INSERT #tblEntityList(SomeVeryImportantInteger)
SELECT
XmlItem.query('//SomeVeryImportantInteger[1]').value('.','int') as SomeVeryImportantInteger
FROM
[dbo].[tvfShredGetOneColumnedTableOfXmlItems] (#xmlEntityList)
by utilizing the scalar-valued function
/* Example Inputs */
/*
DECLARE #xmlListFormat xml
SET #xmlListFormat =
'
<ArbitrarilyNamedXmlListElement>
<ArbitrarilyNamedXmlItemElement>004421UB7</ArbitrarilyNamedXmlItemElement>
<ArbitrarilyNamedXmlItemElement>59020UH24</ArbitrarilyNamedXmlItemElement>
<ArbitrarilyNamedXmlItemElement>542514NA8</ArbitrarilyNamedXmlItemElement>
</ArbitrarilyNamedXmlListElement>
'
declare #tblResults TABLE
(
XmlItem xml
)
*/
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 01/02/3003
-- Description: Shreds a list of XML items conforming to
-- the expected generic #xmlListFormat
-- =============================================
CREATE FUNCTION [dbo].[tvfShredGetOneColumnedTableOfXmlItems]
(
-- Add the parameters for the function here
#xmlListFormat xml
)
RETURNS
#tblResults TABLE
(
-- Add the column definitions for the TABLE variable here
XmlItem xml
)
AS
BEGIN
-- Fill the table variable with the rows for your result set
INSERT #tblResults
SELECT
tblShredded.colXmlItem.query('.') as XmlItem
FROM
#xmlListFormat.nodes('/child::*/child::*') as tblShredded(colXmlItem)
RETURN
END
--SELECT * FROM #tblResults
In case this is useful to anyone else out there looking for a "generic" solution, I created a CLR procedure that can take an Xml fragment as above and "shred" it into a tabular resultset, without you providing any additional information about the names or types of the columns, or customizing your call in any way for the given Xml fragment:
http://architectshack.com/ClrXmlShredder.ashx
There are of course some restrictions (the xml must be "tabular" in nature like this sample, the first row needs to contain all the elements/columns that will be supported, etc) - but I do hope it's a few steps ahead of what's available built-in.
Here's an alternate solution:
;with cte as
(
select id, name, addresses, addresses.value('count(/address/city)','int') cnt
from #demo
)
, cte2 as
(
select id, name, addresses, addresses.value('((/address/city)[sql:column("cnt")])[1]','nvarchar(256)') city, cnt-1 idx
from cte
where cnt > 0
union all
select cte.id, cte.name, cte.addresses, cte.addresses.value('((/address/city)[sql:column("cte2.idx")])[1]','nvarchar(256)'), cte2.idx-1
from cte2
inner join cte on cte.id = cte2.id and cte2.idx > 0
)
select id, name, city
from cte2
order by id, city
FYI: I've posted another version of this SQL on the code review site here: https://codereview.stackexchange.com/questions/108805/select-field-in-an-xml-column-where-both-xml-and-table-contain-multiple-matches
If you can use it, the linq api is convenient for XML:
var addresses = dataContext.People.Addresses
.Elements("address")
.Select(address => new {
street = address.Element("street").Value,
city = address.Element("city").Value,
state = address.Element("state").Value,
zipcode = address.Element("zipcode").Value,
});