Distance calculated wrongly with the ST_Distance function in PostgreSQL database? - postgresql

We are developing an application that involves calculating the shortest distance for a given point against the polygons stored within the PostgreSQL table.
We are using the ST_Distance function from PostgreSQL database.
When we compare the calculated distance with the Google Earth, there is vast difference between them.
Of course, neither Google nor PostgreSQL could be wrong (or are they?), so we are obviously missing something here.
Any ideas what is going wrong?
I have given below the sample query that we have tested with PostgreSQL along with the screenshots from Google Earth.
SELECT ST_Distance(Place1, Place2) As Place1ToPlace2
, ST_Distance(Place1, Spot1) As Place1ToSpot1
, ST_Distance(Place1, Spot2) As Place1ToSpot2
, ST_Distance(Place2, Spot1) As Place2ToSpot1
, ST_Distance(Place2, Spot2) As Place2ToSpot2
FROM (SELECT
ST_PolygonFromText('SRID=4326;POLYGON((-74.0050636293915 40.75265123968514,-74.00500355126653 40.75268991743845,-74.00498169169283 40.75267084386348,-74.00503571044075 40.75263867886528,-74.0050636293915 40.75265123968514))') as Spot1
,ST_PolygonFromText('SRID=4326;POLYGON((-74.00503571044075 40.75263867886528,-74.00498225451273 40.75267084385684,-74.00495878551709 40.75265859837483,-74.00501023946696 40.75262521978885,-74.00503571044075 40.75263867886528))') as Spot2
,ST_GeogFromText('SRID=4326;POINT(-74.00489 40.752894)') As Place1
,ST_GeogFromText('SRID=4326;POINT(-74.004774 40.752846)') As Place2
) As foo ;
It results in following values:
place1toplace2 |place1tospot1 |place1tospot2 |place2tospot1 |place2tospot2 |
---------------|--------------|--------------|--------------|--------------|
11.152362504 |24.608417285 |25.977083731 |26.004190091 |26.011579435 |
Following are the screenshots from Google Earth:
Place1ToPlace2
Place1ToSpot1
Place1ToSpot2
Place2ToSpot1
Place2ToSpot2
Thanks in advance!!
Following are the KMLs exported from Google Earth:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Spot 1.kml</name>
<Style id="inline">
<LineStyle>
<color>ff0000ff</color>
<width>2</width>
</LineStyle>
<PolyStyle>
<fill>0</fill>
</PolyStyle>
</Style>
<Style id="inline0">
<LineStyle>
<color>ff0000ff</color>
<width>2</width>
</LineStyle>
<PolyStyle>
<fill>0</fill>
</PolyStyle>
</Style>
<StyleMap id="inline1">
<Pair>
<key>normal</key>
<styleUrl>#inline</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#inline0</styleUrl>
</Pair>
</StyleMap>
<Placemark>
<name>Spot 1</name>
<styleUrl>#inline1</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-74.0050636293915,40.75265123968514,0 -74.00500355126653,40.75268991743845,0 -74.00498169169283,40.75267084386348,0 -74.00503571044075,40.75263867886528,0 -74.0050636293915,40.75265123968514,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Document>
</kml>
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Spot 2.kml</name>
<Style id="inline">
<LineStyle>
<color>ff0000ff</color>
<width>2</width>
</LineStyle>
<PolyStyle>
<fill>0</fill>
</PolyStyle>
</Style>
<StyleMap id="inline0">
<Pair>
<key>normal</key>
<styleUrl>#inline1</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#inline</styleUrl>
</Pair>
</StyleMap>
<Style id="inline1">
<LineStyle>
<color>ff0000ff</color>
<width>2</width>
</LineStyle>
<PolyStyle>
<fill>0</fill>
</PolyStyle>
</Style>
<Placemark>
<name>Spot 2</name>
<styleUrl>#inline0</styleUrl>
<Polygon>
<tessellate>1</tessellate>
<outerBoundaryIs>
<LinearRing>
<coordinates>
-74.00503571044075,40.75263867886528,0 -74.00498225451273,40.75267084385684,0 -74.00495878551709,40.75265859837483,0 -74.00501023946696,40.75262521978885,0 -74.00503571044075,40.75263867886528,0
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>
</Document>
</kml>
!EDIT 2!
I have tried one more thing. I have taken the point from Spot1 that visually looks nearest to the Place1 and marked it as Place3.
When we look it on the map, it looks like Place3 is nearer to Place1 than Place2, but when checking with the query, distance to Place3 gives higher value.
I have checked with following query:
SELECT ST_Distance(Place1, Place2) As Place1ToPlace2
,ST_Distance(Place1, Place3) As Place1ToPlace3
FROM (SELECT
ST_GeogFromText('SRID=4326;POINT(-74.00489 40.752894)') As Place1
,ST_GeogFromText('SRID=4326;POINT(-74.004774 40.752846)') As Place2
,ST_GeogFromText('SRID=4326;POINT(-74.00500355126653 40.75268991743845)') As Place3
) As foo;
And it gives following result:
place1toplace2 |place1toplace3 |
---------------|---------------|
11.152362504 |24.608417285 |
While on the Map:
Place1ToPlace3
Following is the KML for Place1,Place2 and Place3 (I have removed the style related tags from KML)
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>Distance Comparison.kml</name>
<Folder>
<name>Distance Comparison</name>
<open>1</open>
<Style>
<ListStyle>
<listItemType>check</listItemType>
<bgColor>00ffffff</bgColor>
<maxSnippetLines>2</maxSnippetLines>
</ListStyle>
</Style>
<Placemark>
<name>Place 1 - 40°45&apos;9.78"N 74° 0&apos;18.07"W (40.752894, -74.00489)</name>
<open>1</open>
<LookAt>
<longitude>-74.00500758183839</longitude>
<latitude>40.75269419172616</latitude>
<altitude>0</altitude>
<heading>-0.0008536233435993688</heading>
<tilt>29.8433509629012</tilt>
<range>47.16429940085073</range>
<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
</LookAt>
<styleUrl>#msn_1</styleUrl>
<Point>
<gx:drawOrder>1</gx:drawOrder>
<coordinates>-74.00501944444444,40.75271666666666,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Place 2 - 40°45&apos;9.54"N 74° 0&apos;17.70"W (40.752846, -74.004774)</name>
<open>1</open>
<LookAt>
<longitude>-74.00500758183839</longitude>
<latitude>40.75269419172616</latitude>
<altitude>0</altitude>
<heading>-0.0008536233435993688</heading>
<tilt>29.8433509629012</tilt>
<range>47.16429940085073</range>
<gx:altitudeMode>relativeToSeaFloor</gx:altitudeMode>
</LookAt>
<styleUrl>#msn_2</styleUrl>
<Point>
<gx:drawOrder>1</gx:drawOrder>
<coordinates>-74.00491666666667,40.75265,0</coordinates>
</Point>
</Placemark>
<Placemark>
<name>Place 3 - 40°45&apos;9.68"N 74° 0&apos;18.01"W (40.75268991743845 -74.00500355126653)</name>
<open>1</open>
<styleUrl>#msn_3</styleUrl>
<Point>
<coordinates>-74.00500277777778,40.75268888888889,0</coordinates>
</Point>
</Placemark>
</Folder>
</Document>
</kml>

Your features in PostGIS don't appear to be where they are in Google... Here's what I got by visualising it.
Now there are two more issues. First, the difference between geometry and geography. Second, distance calculations.
st_distance on geographies returns values in metres. That's why the output of your first query is basically what you'd expect, given the location of the features (because your points were geographies, Postgres cast the polygons to geography for you). However, st_distance on geometries returns values in the units of the underlying projection, in this case degrees. That explains the output of your second query.
On to distance calculation. Trying to be concise here, but the biggest thing affecting your calculation is the underlying projection. From my understanding, the geography type is better for larger areas - you'd get more accurate results using geometries in a suitable projection for the area you're analysing. The "suitable projection" depends on what parts of the world you're looking at and what your purpose is.
Also, don't assume Google's figures are correct - I did a quick search but didn't find anything on how they calculate their distances. If they use a different method you'll get a different answer...
EDIT
Here is the query with the coordinates as they appear in your screenshots. Note the differences in the coordinate values! Also, note that the degrees-minutes-seconds displayed in your screenshots do not match with the decimal degree values in brackets!
SELECT ST_Distance(Place1, Place2) As Place1ToPlace2
, ST_Distance(Place1, Spot1) As Place1ToSpot1
, ST_Distance(Place1, Spot2) As Place1ToSpot2
, ST_Distance(Place2, Spot1) As Place2ToSpot1
, ST_Distance(Place2, Spot2) As Place2ToSpot2
FROM (SELECT
ST_PolygonFromText('SRID=4326;POLYGON((-74.0050636293915 40.75265123968514,-74.00500355126653 40.75268991743845,-74.00498169169283 40.75267084386348,-74.00503571044075 40.75263867886528,-74.0050636293915 40.75265123968514))') as Spot1
,ST_PolygonFromText('SRID=4326;POLYGON((-74.00503571044075 40.75263867886528,-74.00498225451273 40.75267084385684,-74.00495878551709 40.75265859837483,-74.00501023946696 40.75262521978885,-74.00503571044075 40.75263867886528))') as Spot2
,ST_GeogFromText('SRID=4326;POINT( -74.005019 40.752717)') As Place1
,ST_GeogFromText('SRID=4326;POINT(-74.004917 40.752650)') As Place2
) As foo ;
The values returned are 11.38223433, 3.27827391, 5.99175215, 5.93327383, 3.65564537. They are within cm of the results from Google.

I potentially major problem I see is that you are mixing geometries and geographies in your call to ST_Distance. From a glance at Postgres' API, the ST_Distance function takes either geometries or geographies, but not both.
Please try the following query. Here, I have replaced your calls to ST_GeogFromText, which returns a geography, with ST_GeometryFromText, which returns a geometry.
SELECT ST_Distance(Place1, Place2) AS Place1ToPlace2,
ST_Distance(Place1, Spot1) AS Place1ToSpot1
ST_Distance(Place1, Spot2) AS Place1ToSpot2
ST_Distance(Place2, Spot1) AS Place2ToSpot1
ST_Distance(Place2, Spot2) AS Place2ToSpot2
FROM
(
SELECT ST_PolygonFromText('SRID=4326;POLYGON((-74.0050636293915 40.75265123968514,-74.00500355126653 40.75268991743845,-74.00498169169283 40.75267084386348,-74.00503571044075 40.75263867886528,-74.0050636293915 40.75265123968514))') AS Spot1,
ST_PolygonFromText('SRID=4326;POLYGON((-74.00503571044075 40.75263867886528,-74.00498225451273 40.75267084385684,-74.00495878551709 40.75265859837483,-74.00501023946696 40.75262521978885,-74.00503571044075 40.75263867886528))') AS Spot2,
ST_GeometryFromText('SRID=4326;POINT(-74.00489 40.752894)') AS Place1
ST_GeometryFromText('SRID=4326;POINT(-74.004774 40.752846)') AS Place2
) AS foo;

Related

Postgres XML parsing - Calculate the sum of multiple nodes of the same name using XPath

I have an xml snippet as below:
<OpCodeLaborInfo JobStatus="F" UpSellFlag="N" JobNo="1" OpCode="02TTZ10K" OpCodeDesc="10K SERVICE">
<TechInfo ActualHrsWorked="2.50" CustTechRate="27.00" TechHrs="0.00" TechName="JEFF SELLERS" TechNo="4816" />
<TechInfo ActualHrsWorked="0.00" CustTechRate="27.00" TechHrs="0.70" TechName="JEFF SELLERS" TechNo="4816" />
<BillTimeRateHrs BillRate="129.97" />
<CCCStmts Correction="PERFORMED 10K SERVICE" Complaint="LUBE OIL FILTER CHANGE, TIRE ROTATION, PERFORM MULTI POINT" />
<CCCStmts Correction="X" Complaint="INSPECTION, INSPECT FILTERS AND RECOMMEND, INSPECT BRAKES," />
<CCCStmts Complaint="BELTS AND HOSES" />
<RoAmts DlrCost="18.90" PayType="Cust" AmtType="Job" TotalAmt="59.12" />
</OpCodeLaborInfo>
<OpCodeLaborInfo JobStatus="F" UpSellFlag="N" JobNo="2" OpCode="02TTZ10K" OpCodeDesc="10K SERVICE">
<TechInfo ActualHrsWorked="2.50" CustTechRate="27.00" TechHrs="1.00" TechName="JEFF SELLERS" TechNo="4816" />
<TechInfo ActualHrsWorked="0.00" CustTechRate="27.00" TechHrs="0.00" TechName="JEFF SELLERS" TechNo="4816" />
<BillTimeRateHrs BillRate="129.97" />
<CCCStmts Correction="PERFORMED 10K SERVICE" Complaint="LUBE OIL FILTER CHANGE, TIRE ROTATION, PERFORM MULTI POINT" />
<CCCStmts Correction="X" Complaint="INSPECTION, INSPECT FILTERS AND RECOMMEND, INSPECT BRAKES," />
<CCCStmts Complaint="BELTS AND HOSES" />
<RoAmts DlrCost="18.90" PayType="Cust" AmtType="Job" TotalAmt="59.12" />
</OpCodeLaborInfo>
I need to calculate the sum of the TechInfo/#TechHrs for each OpCodeLaborInfo. I tried the following:
unnest(xpath('sum(//dns:RepairOrder/dns:RoRecord/dns:Rolabor/dns:OpCodeLaborInfo/dns:TechInfo/#TechHrs[1])'::text,
data_detail.ro_data_xml,
ARRAY[ARRAY['dns'::text, 'http://www.starstandards.org/STAR'::text]]))::text::numeric AS lbrsoldhours
but this seems to return the sum of the Tech Hours inside both the OpCodeLaborInfo nodes. Could someone be able to tell me how I can tweak the xpath so as to get the desired result.
So basically I need :
Job
Tech Hrs
1
sum(0.00+0.70)
2
sum(1.00+0.00)
I would solve this using xmltable()
select d.job, sum(t.hours)
from data_detail d
cross join xmltable (
'/OpCodeLaborInfo/TechInfo'
passing d.ro_data_xml
columns hours numeric path '#TechHrs') as t
group by d.job;
Online example
The XPath is probably not correct as the XPath you have shown doesn't match your sample XML data. Your sample XML also doesn't contain a namespace, so I am not sure why you are passing one to xpath()
But if you need one, you can use something like this:
cross join xmltable (
xmlnamespaces ('http://www.starstandards.org/STAR' as dns),
'/dns:OpCodeLaborInfo/dns:TechInfo'
passing d.ro_data_xml
columns hours numeric path '#TechHrs') as t
xpath can also work fine:
SELECT job
, SUM((xpath('//#TechHrs', element))[1]::text::decimal)
FROM data_detail
, LATERAL (SELECT unnest(xpath('/OpCodeLaborInfo/TechInfo', ro_data_xml))) u(element)
GROUP BY job;
fiddle
(based on the fiddle of #a_horse_with_no_name)

KML to Postgis table

I'm trying to save some KML files to PostgreSQL.
I'm using postgis and this is a sample of what I've done so far.
INSERT INTO police_crime_boundaries (name, polygon) VALUES (
'A',
ST_GeomFromKML('<LinearRing>
<coordinates>-2.69927798816,52.9954289967,0 -2.70075298816,52.9932649967,0
</coordinates>
</LinearRing>')
);
Anyone have any idea of what am I doing wrong?
Getting the data from here: https://data.police.uk/data/boundaries/
LinearRing can not be stored to/retrieved from a Geometry column (see the document)
So you should either use <Polygon> tag instead of <LinearRing> or wrap it with OuterBoundary and Polygon tags like below :
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>0,0 0,1 1,1 1,0 0,0</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>

SQL Server 2016 XML Shredding

Have been trying to figure this out for a while without success, read like 10 posts and some other examples and the MS help, not resonating, need to shred some xml data with the following format:
<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://cp.com/rules/client">
<admin>
<quoteback name="abcd">ABCD A</quoteback>
<product_group>Abcd ABcd Abcd</product_group>
<pnc_account>123456</pnc_account>
<pnc_account_name>ABC</pnc_account_name>
<product_reference>123456789</product_reference>
<report_type>ABCDE</report_type>
<status>ABCDE</status>
<ownership>ABCD</ownership>
<report_code>1234</report_code>
<report_description>Abcde/report_description>
<purpose>ABCDEFGH</purpose>
<date_request_ordered>05/05/2020</date_request_ordered>
<date_request_received>05/05/2020</date_request_received>
<date_request_completed>05/05/2020</date_request_completed>
<time_report_processed>1028</time_report_processed>
<multiple_scores_ordered>false</multiple_scores_ordered>
<vendor name="Abcd" address="Abcd" />
<report>
<sequence>0000000001</sequence>
<count>0000000001</count>
</report>
</admin>
<report>
<alerts_scoring>
<scoring>
<score status="Abcd">
<model_label>ABCD</model_label>
<score>123</score>
<rating_state>AB</rating_state>
<classification> ABCD </classification>
<reason_codes>
<code>12</code>
<description>ABCD</description>
</reason_codes>
<reason_codes>
<code>12</code>
<description>ABCD</description>
</reason_codes>
<reason_codes>
<code>12</code>
<description>ABCD ABCD ABCD</description>
</reason_codes>
<reason_codes>
<code>12</code>
<description>ABCD ABCD ABCD</description>
</reason_codes>
</score>
</scoring>
<general>ABCD ABCD ABCD ORIGINAL REPORT DATE: 12/12/2000</general>
<general>ABCD ABCD ABCD</general>
<general> ABCD ABCD ABCD</general>
<general narrativeCode="Abcd Abcd">ABCD ABCD ABCD</general>
<general narrativeCode=" Abcd Abcd">ABCD ABCD ABCD</general>
<general narrativeCode=" Abcd Abcd">ABCD ABCD ABCD</general>
</alerts_scoring>
<vendor_dataset>
<subjects>
<subject type="Abcd" relationship_to_data="Abcd">
<name type="Abcd">
<first>XXXX</first>
<middle>X</middle>
<last>XXXX</last>
</name>
<birth_date>01/01/1900</birth_date>
<ssn>999999999</ssn>
<address type="Abcd" ref="1" />
<address type="Abcd" ref="2" />
<address type="Abcd" ref="3" />
</subject>
</subjects>
<addresses>
<address id="1">
<street1>ABCD</street1>
<city>ABCD</city>
<state>AB</state>
<postalcode>12345</postalcode>
<zip4>1234</zip4>
<date_first_at_address>01/02/1900</date_first_at_address>
<date_last_at_address>01/02/1900</date_last_at_address>
</address>
<address id="2">
<house>123</house>
<street1>ABCDE</street1>
<city>ABCDE</city>
<state>AB</state>
<postalcode>12345</postalcode>
<zip4>1234</zip4>
<date_first_at_address>00/00/1900</date_first_at_address>
<date_last_at_address>00/00/1900</date_last_at_address>
</address>
<address id="3">
<street1>ABCDE</street1>
<city>ABCDE</city>
<state>AB</state>
<postalcode>12345</postalcode>
<zip4>1234</zip4>
<date_first_at_address>00/00/1900</date_first_at_address>
<date_last_at_address>00/00/1900</date_last_at_address>
</address>
</addresses>
</vendor_dataset>
<summary>
<date_oldest_trade>00/00/1900</date_oldest_trade>
<date_latest_trade>00/00/1900</date_latest_trade>
<date_latest_activity>00/00/1900</date_latest_activity>
<includes_bankruptcies flag="true" date="02/02/2009" />
<includes_other_records public_records="false" collection="true" consumer_statement="false" />
<credit_range high="123456" low="1234" number_trade_lines="12" />
**<account_status_counters>
<account type="current" description="Pays Account as Agreed" status="1">12</account>
<account type="current" description="Status Not Known" status=" ">7</account>
<account type="former" description="Pays/Paid 30-60 Days or Max 2 Payments Past Due" status="2">5</account>
<account type="former" description="Pays/Paid 60-90 Days or Max 3 Payments Past Due" status="3">4</account>
<account type="former" description="Bad Debt" status="9">6</account>
</account_status_counters>**
I currently going down the path of trying to use the xml procedure but I could not get to the finish line with openxml as well. Trying to extract data in highlighted at bottom of xml
EXEC sp_xml_preparedocument #hdoc OUTPUT, #CreditScoreXML
SELECT * FROM OPENXML(#hdoc, '/<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://cp.com/rules/client">/admin/summary/account_status_counters')
WITH
(
[Ref_Number] VARCHAR(10) 'product_reference',
[current_account_type] VARCHAR(10) './account/#type',
[current_account_type_description] VARCHAR(50) './account/#description',
[current_account_type_description] VARCHAR(1) './account/#status'
You can define the namespace for your XML using WITH XMLNAMESPACES statement, then you can extract the values you need with .value().
I don't understand exactly the information you are trying to extract, but this should put you on the right track (I only put the first row of your xml to save space, you should put the entire XML fragment in the #xml variable):
declare #xml xml set #xml='
<ncf_report xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://cp.com/rules/client">
...
'
;WITH XMLNAMESPACES ('http://cp.com/rules/client' as ns1)
select
#xml.value('(ns1:ncf_report/ns1:admin/ns1:product_reference)[1]', 'varchar(10)') as Ref_Number
,#xml.value('(ns1:ncf_report/ns1:report/ns1:summary/ns1:account_status_counters/ns1:account[#type="current" and #status ="1"]/#description)[1]', 'varchar(50)') as CurrentDescription
,#xml.value('(ns1:ncf_report/ns1:report/ns1:summary/ns1:account_status_counters/ns1:account[#type="current" and #status ="1"])[1]', 'int') as CurrentStatus
,#xml.value('(ns1:ncf_report/ns1:report/ns1:summary/ns1:account_status_counters/ns1:account[#type="current" and #status =" "]/#description)[1]', 'varchar(50)') as CurrentDescription_2
,#xml.value('(ns1:ncf_report/ns1:report/ns1:summary/ns1:account_status_counters/ns1:account[#type="current" and #status =" "])[1]', 'int') as CurrentStatus_2
This sample query would extract:

using INSERT for an xml statement

how would you go about inserting an xml document of information into an existing table, I cannot figure out how the insert statement would work my code is below:
USE MyGuitarShop;
DECLARE #CustomerUpdate XML;
SET #CustomerUpdate = '
<NewCustomers>
<Customer LastName="Chan" FirstName="Isabella" Password="" EmailAddress="izzychan#yahoo.com"/>
<Customer LastName="Prine" FirstName="John" Password="" EmailAddress="johnprine#gmail.com"/>
<Customer LastName="Kitchen" FirstName="Kathy" Password="" EmailAddress="kathykitchen#sbcglobal.net"/>
</NewCustomers>
'
;
INSERT INTO Customers (LastName, Password, EmailAddress)
VALUES (#CustomerUpdate.value('(/NewCustomers/LastName)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/FirstName)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/Password)[1]', 'varchar(50)'),
(#CustomerUpdate.value('(/NewCustomers/EmailAddress)[1]', 'varchar(50)');
You're selecting the node LastName from NewCustomers, while NewCustomers contains only Customer nodes, which then contain LastName attribute.
In order to select the last name, use the following query instead:
value('(/NewCustomers/Customer/#LastName)[1]', 'varchar(50)')
Since you're extracting the data from a single XML value, the selection is pretty straightforward:
declare #CustomerUpdate xml;
set #CustomerUpdate = '
<NewCustomers>
<Customer LastName="Chan" FirstName="Isabella" Password="" EmailAddress="izzychan#yahoo.com"/>
<Customer LastName="Prine" FirstName="John" Password="" EmailAddress="johnprine#gmail.com"/>
<Customer LastName="Kitchen" FirstName="Kathy" Password="" EmailAddress="kathykitchen#sbcglobal.net"/>
</NewCustomers>';
select
t.Customer.value('#LastName', 'nvarchar(50)') as [LastName],
t.Customer.value('#FirstName', 'nvarchar(50)') as [FirstName],
t.Customer.value('#Password', 'nvarchar(50)') as [Password],
t.Customer.value('#EmailAddress', 'nvarchar(50)') as [EmailAddress]
from #CustomerUpdate.nodes('(/NewCustomers/Customer)') t(Customer)
If you were selecting the data from a row of XML values, you would have used cross apply instead.
Important note: DON'T STORE PASSWORDS IN PLAIN TEXT IN A DATABASE. If you're actually doing it, you do it wrong. If you don't understand why, learn about hash and salt, or, better, let others handle personal information for you: OpenID is one of the ways of moving the responsibility of securing sensitive data from you to Google-scale companies.

How to avoid namespace in child nodes using FOR XML PATH?

I want to create a sitemap xml file (including images) directly from the database without another process (like transformation or another trick).
My query is:
;WITH XMLNAMESPACES(
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9',
'http://www.google.com/schemas/sitemap-image/1.1' as [image] )
SELECT
(SELECT
'mysite' as [loc],
(select
'anotherloc'
as [image:loc]
for XML path('image:image'), type
)
for xml path('url'), type
)
for xml path('urlset'), type
Returns:
<urlset xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<loc>mysite</loc>
<image:image xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>
But I need this output, without repeated namespace declaration:
<urlset xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>mysite</loc>
<image:image>
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>
I'm sure you realise that the additional otiose namespace declarations don't change the meaning of the XML document, so if the result is going to be consumed by an XML-conformant tool, they shouldn't matter. Nevertheless I know there are some tools out there which don't do XML Namespaces correctly, and in a large XML instance superfluous repeated namespace declarations can bloat the size of the result significantly, which may cause its own problems.
In general there is no getting around the fact that each SELECT...FOR XML statement within the scope of a WITH XMLNAMESPACES prefix will generate namespace declarations on the outermost XML element(s) in its result set, in all XML-supporting versions of SQL Server up to SQL Server 2012.
In your specific example, you can get fairly close to the desired XML by separating the SELECTs rather than nesting them, and using the ROOT syntax for the enveloping root element, thus:
DECLARE #inner XML;
WITH XMLNAMESPACES('http://www.google.com/schemas/sitemap-image/1.1' as [image])
SELECT #inner =
(
SELECT
'anotherloc' AS [image:loc]
FOR XML PATH('image:image'), TYPE
)
;WITH XMLNAMESPACES(
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9'
)
SELECT
'mysite' AS [loc],
#inner
FOR XML PATH('url'), ROOT('urlset'), TYPE
The result being:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>mysite</loc>
<image:image xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns="">
<image:loc>anotherloc</image:loc>
</image:image>
</url>
</urlset>
But this approach doesn't provide a completely general solution to the problem.
You can use UDF. Example:
ALTER FUNCTION [dbo].[udf_get_child_section] (
#serviceHeaderId INT
)
RETURNS XML
BEGIN
DECLARE #result XML;
SELECT #result =
(
SELECT 1 AS 'ChildElement'
FOR XML PATH('Child')
)
RETURN #result
END
GO
DECLARE #Ids TABLE
(
ID int
)
INSERT INTO #Ids
SELECT 1 AS ID
UNION ALL
SELECT 2 AS ID
;WITH XMLNAMESPACES (DEFAULT 'http://www...com/content')
SELECT
[dbo].[udf_get_child_section](ID)
FROM
#Ids
FOR XML PATH('Parent')
Result:
<Parent xmlns="http://www...com/content">
<Child xmlns="">
<ChildElement>1</ChildElement>
</Child>
</Parent>
<Parent xmlns="http://www...com/content">
<Child xmlns="">
<ChildElement>1</ChildElement>
</Child>
</Parent>
Maybe too late for answer, but this is a quick solution.
`DECLARE #PageNumber Int = 1;
DECLARE #siteMapXml XML ;
;WITH XMLNAMESPACES (
'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd' as "schemaLocation",
'http://www.w3.org/2001/XMLSchema-instance' as xsi,
'http://www.google.com/schemas/sitemap-image/1.1' as [image],
DEFAULT 'http://www.sitemaps.org/schemas/sitemap/0.9'
)
SELECT #siteMapXml = (
SELECT
Slug loc,
convert(varchar(300),[Image]) as [image:image/image:loc]
,
Convert(char(10), UpdatedOnUtc, 126) as lastmod,
'hourly' as changefreq,
'0.5' as priority
FROM Products(NOLOCK)
WHERE Pagenumber = #PageNumber
FOR XML PATH ('url'), ROOT ('urlset'))
SELECT #siteMapXml = REPLACE(CAST(#siteMapXml AS NVARCHAR(MAX)), ' xmlns:schemaLocation=', ' xsi:schemaLocation=')
SELECT #siteMapXml`