Iterative Query for Spatial Records - sql

I have created following query in SQL Server
declare #x XML
set #x='<Dataset_Extent>
<EXTENT_TYPE>Bounding_Polygon</EXTENT_TYPE>
<Vertex>
<LON>66.91292909247741</LON>
<LAT>30.27001012181008</LAT>
<X>299232</X>
<Y>3350549</Y>
<COL>1</COL>
<ROW>1</ROW>
</Vertex>
<Vertex>
<LON>66.99456841960638</LON>
<LAT>30.27128639618252</LAT>
<X>307089.5</X>
<Y>3350549</Y>
<COL>15715</COL>
<ROW>1</ROW>
</Vertex>
<Vertex>
<LON>66.99700791329992</LON>
<LAT>30.1509623521339</LAT>
<X>307089.5</X>
<Y>3337207.5</Y>
<COL>15715</COL>
<ROW>26683</ROW>
</Vertex>
<Vertex>
<LON>66.91546772378466</LON>
<LAT>30.14969219541345</LAT>
<X>299232</X>
<Y>3337207.5</Y>
<COL>1</COL>
<ROW>26683</ROW>
</Vertex>
<Center>
<LON>66.9549932872921</LON>
<LAT>30.21048776638499</LAT>
<X>303160.75</X>
<Y>3343878.25</Y>
<COL>7858</COL>
<ROW>13342</ROW>
</Center>
</Dataset_Extent>';
declare #wkt varchar(8000);
select #wkt=CONVERT(varchar(7000),#x.query('distinct-values(
for $lon in /Dataset_Extent/Vertex/LON/text()
for $lat in /Dataset_Extent/Vertex/LAT/text()
return ($lon cast as xs:string?,$lat cast as xs:string?,","))
'));
--concatenate the word POLYGON
set #wkt='POLYGON(('+#wkt +'))';
print #wkt
This gives me the following output:
POLYGON((66.91292909247741 66.99456841960638 30.27128639618252 66.99700791329992 66.91546772378466 30.27001012181008,30.1509623521339 30.14969219541345))
Each Lon/lat pair is not in proper order, I required output in following format:
POLYGON((66.91292909247741 30.27001012181008,66.99456841960638 30.27128639618252,66.99700791329992 30.1509623521339,66.91546772378466 30.14969219541345,66.91292909247741 30.27001012181008))
The starting vertex Lon/Lat should also repeat at end to make polygon close.
What do I need to do this to fix the problems?

this will do the job...
DECLARE #wkt NVARCHAR(MAX)
SELECT #wkt = COALESCE(#wkt + ',' + char(13), '')
+ concat(convert(varchar, x.query('./LON/text()')),' ', convert(varchar,x.query('./LAT/text()')))
from #x.nodes('/Dataset_Extent/Vertex') as t(x)
select #wkt = COALESCE(#wkt + ',' + char(13), '')
+ concat(convert(varchar, x.query('./LON/text()')),' ', convert(varchar,x.query('./LAT/text()')))
from #x.nodes('/Dataset_Extent/Vertex[1]') as t(x)
set #wkt='POLYGON(('+#wkt +'))';
print #wkt
not exactly beautiful but i couldnt figure out how to subquery the first node... good luck! i try to avoid xpath as much as possible if i can :)

Shred the XML on /Dataset_Extent/Vertex and build the string using the for xml path trick.
declare #wkt varchar(8000);
set #wkt = 'POLYGON((' +
stuff((select ','+T.X.value('(LON/text())[1]', 'varchar(50)')+
' '+T.X.value('(LAT/text())[1]', 'varchar(50)')
from #x.nodes('/Dataset_Extent/Vertex') as T(X)
for xml path('')), 1, 1, '')+
','+#x.value('(/Dataset_Extent/Vertex/LON/text())[1]', 'varchar(50)')+
' '+#x.value('(/Dataset_Extent/Vertex/LAT/text())[1]', 'varchar(50)')+
'))';
Against a table it would look like this instead.
select 'POLYGON((' +
stuff((select ','+T.X.value('(LON/text())[1]', 'varchar(50)')+
' '+T.X.value('(LAT/text())[1]', 'varchar(50)')
from X.XMLData.nodes('/Dataset_Extent/Vertex') as T(X)
for xml path('')), 1, 1, '')+
','+X.XMLData.value('(/Dataset_Extent/Vertex/LON/text())[1]', 'varchar(50)')+
' '+X.XMLData.value('(/Dataset_Extent/Vertex/LAT/text())[1]', 'varchar(50)')+
'))'
from XMLFiles as X;

Related

SQL Server - parse GPX file

I'm trying to parse a GPX file within SQL Server 2019, but I'm hitting a snag with namespaces, I think.
From what I can see - if the GPX file contains :
xmlns="http://www.topografix.com/GPX/1/1"
SQL returns a NULL. But if I remove that from the GPX file, the SQL returns a string of coords - as expected.
SQL code :
DECLARE #XML TABLE (XML_COLUMN XML)
DECLARE #sqlstmt NVARCHAR(255)
DECLARE #file NVARCHAR(255) = 'd:\demo_2.gpx'
SET #sqlstmt= 'SELECT * FROM OPENROWSET (BULK ''' + #file + ''', SINGLE_CLOB) AS xmlData'
INSERT INTO #XML
EXEC (#sqlstmt)
;WITH XMLNAMESPACES ('http://www.topografix.com/GPX/1/1' AS ns), X_CTE AS
(
SELECT
T1.Name.query('.') AS Name,
T2.X_Content.query('.') AS X_Content
FROM
#XML AS X
CROSS APPLY
XML_Column.nodes('/gpx/trk') AS T1(Name)
CROSS APPLY
XML_Column.nodes('/gpx/trk/trkseg/trkpt') AS T2(X_Content)
),
XML_Data AS
(
SELECT
X_Content.value('(/trkpt/#lat)[1]', 'VARCHAR(50)') AS LAT,
X_Content.value('(/trkpt/#lon)[1]', 'VARCHAR(50)') AS LON
FROM
X_CTE
)
SELECT
STUFF((SELECT '[' + LON + ',' + LAT + ']' + ','
FROM XML_Data
WHERE 1 = 1
FOR XML PATH('')), 1, 0, '') AS mapString;
GPX file content (demo_2.gpx)
<?xml version="1.0" encoding="UTF-8"?>
<gpx creator="Garmin Connect" version="1.1"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/11.xsd"
xmlns:ns3="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.garmin.com/xmlschemas/GpxExtensions/v3">
<metadata>
<link href="connect.garmin.com">
<text>Garmin Connect</text>
</link>
<time>2022-05-29T08:37:21.000Z</time>
</metadata>
<trk>
<name>My Route</name>
<type>e_bike_mountain</type>
<trkseg>
<trkpt lat="54.37033147551119327545166015625" lon="-3.075514398515224456787109375">
<ele>65.8000030517578125</ele>
<time>2022-05-29T11:37:02.000Z</time>
<extensions>
<ns3:TrackPointExtension>
<ns3:atemp>17.0</ns3:atemp>
<ns3:hr>155</ns3:hr>
</ns3:TrackPointExtension>
</extensions>
</trkpt>
<trkpt lat="54.37033147551119327545166015625" lon="-3.075514398515224456787109375">
<ele>65.8000030517578125</ele>
<time>2022-05-29T11:37:03.000Z</time>
<extensions>
<ns3:TrackPointExtension>
<ns3:atemp>17.0</ns3:atemp>
<ns3:hr>155</ns3:hr>
</ns3:TrackPointExtension>
</extensions>
</trkpt>
</trkseg>
</trk>
</gpx>
Really pulling the last bits of remaining hair out with this one, if anyone can assist, that would be totally awesome!
Your XML defines a default namespace, that is applied to all XML nodes - as long as no other namespace is defined explicitly, by means of a prefix:
<gpx creator="Garmin Connect" version="1.1"
...
xmlns="http://www.topografix.com/GPX/1/1"
The xmlns= declaration, without an alias (like xmlns:ns=...), is the default XML namespace for your XML document.
Now if you actually define your XML namespace in the query, with an alias ns like so:
;WITH XMLNAMESPACES ('http://www.topografix.com/GPX/1/1' AS ns)
then you must also use that alias in all your relevant XPath queries:
SELECT
...
FROM
#XML AS X
CROSS APPLY
XML_Column.nodes('/ns:gpx/ns:trk') AS T1(Name)
CROSS APPLY
XML_Column.nodes('/ns:gpx/ns:trk/ns:trkseg/ns:trkpt') AS T2(X_Content)
Alternatively, and much simpler - define the XML namespace as your default namespace in the XQuery in T-SQL; then you do not need to apply the namespace alias everywhere:
;WITH XMLNAMESPACES (DEFAULT 'http://www.topografix.com/GPX/1/1')
And in the end - you could write your XQuery much simpler - try this:
WITH XMLNAMESPACES (DEFAULT 'http://www.topografix.com/GPX/1/1')
SELECT
LAT = XC.value('#lat', 'VARCHAR(50)'),
LON = XC.value('#lon', 'VARCHAR(50)')
FROM
#Xml AS X
CROSS APPLY
XML_Column.nodes('/gpx/trk/trkseg/trkpt') AS XT(XC)
This should return the same value - with much less code and indirections....
Sorted, with many thanks to marc_s - my final working code ...
DECLARE #XML TABLE (XML_COLUMN XML)
DECLARE #sqlstmt NVARCHAR(255)
DECLARE #file NVARCHAR(255) = 'd:\erc\gpx\demo_4.gpx'
SET #sqlstmt= 'SELECT * FROM OPENROWSET ( BULK ''' + #file + ''', SINGLE_CLOB) AS xmlData'
INSERT INTO #XML
EXEC (#sqlstmt)
;WITH XMLNAMESPACES (DEFAULT 'http://www.topografix.com/GPX/1/1'), X_CTE AS
(
SELECT
LAT = XC.value('#lat', 'VARCHAR(50)'),
LON = XC.value('#lon', 'VARCHAR(50)')
FROM
#XML AS X
CROSS APPLY
XML_Column.nodes('/gpx/trk/trkseg/trkpt') AS XT(XC)
),
XML_Data AS
(
SELECT * FROM X_CTE
)
SELECT Stuff
((
SELECT
'[' + LON + ',' + LAT + ']' + ','
FROM XML_Data
WHERE 1 = 1
FOR XML PATH('')), 1, 0, '') AS mapString;

SQL - First and last name from full name

I have a field, in as SQL table, with the fullname of the users that shows the employee number and then the full name (20284 - JOAQUIM MIGUEL SAMPAIO PEREIRA)
I only want to show "JOAQUIM PEREIRA".
Right now am trying to use the following code:
SELECT left(NMSTRING, CHARINDEX(' ',NMSTRING,CHARINDEX(' ',NMSTRING,CHARINDEX(' ',NMSTRING)+1)+1)-1) +
substring(WFPROCATTRIB.NMSTRING, len(WFPROCATTRIB.NMSTRING)-CHARINDEX(' ', REVERSE(WFPROCATTRIB.NMSTRING))+1, len(WFPROCATTRIB.NMSTRING))
FROM WHATEVER
And the result i am getting is: "20284 - JOAQUIM PEREIRA"
How can i remove the "20284 - " part?
Try like this,
DECLARE #sql VARCHAR(50) = '20284 - JOAQUIM MIGUEL SAMPAIO PEREIRA'
SELECT substring(#sql, charindex('-', #sql) + 2, charindex(' ', substring(#sql, charindex('-', #sql) + 2, len(#sql)))) + ' ' + substring(#sql, LEN(#sql) - CHARINDEX(' ', REVERSE(#sql)) + 2, LEN(#sql))
If the employee number is always five characters, you could simply do:
select substring(NMString, 9, len(NMString))
Another method would be to use charindex():
select substring(NMString,
charindex(' - ', NMString) + 3,
len(NMString)
)
You can use substring to get the desired result
DECLARE #Name varchar(100) = '20284 - JOAQUIM PEREIRA'
SELECT SUBSTRING(#Name, CHARINDEX('-', #Name) + 1, LEN(#Name))
More examples:
DECLARE #TestTable TABLE
(
EmployeeDetails varchar(100)
)
INSERT INTO #TestTable VALUES ('20284 - JOAQUIM PEREIRA'),
('123 - Name1') , ('12312344 - Some Other Name')
SELECT SUBSTRING(EmployeeDetails, CHARINDEX('-', EmployeeDetails) + 1, LEN(EmployeeDetails)) FROM #TestTable
DEMO - ON Stackexchange data explorer
A little bit overkill, but should work for you:
--Declare table with example
DECLARE #WHATEVER TABLE (
NMSTRING nvarchar(max)
)
INSERT INTO #WHATEVER VALUES
(N'20284 - JOAQUIM MIGUEL SAMPAIO PEREIRA'),
(N'20285 - JOAQUIM SAMPAIO PEREIRA'),
(N'20286 - JOAQUIM PEREIRA')
--Declare xml variable
DECLARE #xml xml
--Put data in xml variable
SELECT #xml = (
SELECT CAST('<s><n>' + REPLACE(NMSTRING,' ','</n><n>') + '</n></s>' as xml)
FROM #WHATEVER
FOR XML PATH('')
)
The data in #xml will look like this:
<s>
<n>20284</n>
<n>-</n>
<n>JOAQUIM</n>
<n>MIGUEL</n>
<n>SAMPAIO</n>
<n>PEREIRA</n>
</s>
<s>
<n>20285</n>
<n>-</n>
<n>JOAQUIM</n>
<n>SAMPAIO</n>
<n>PEREIRA</n>
</s>
<s>
<n>20286</n>
<n>-</n>
<n>JOAQUIM</n>
<n>PEREIRA</n>
</s>
Then run this
SELECT t.v.value('n[3]','nvarchar(max)') + ' ' +
COALESCE(t.v.value('n[6]','nvarchar(max)'),t.v.value('n[5]','nvarchar(max)'),t.v.value('n[4]','nvarchar(max)'))
FROM #xml.nodes('/s') as t(v)
Output
JOAQUIM PEREIRA
JOAQUIM PEREIRA
JOAQUIM PEREIRA

Concat column values with comma as the separator

I am trying to figure out the result of the below query on how to concat all the columns into a single string with comma separated values. The number of values can be dynamic.
SELECT 'Sc4','Sc5','Sc8','Sc7','Sc2'
I want the result to be as below:
Sc4,Sc5,Sc8,Sc7,Sc2
I have tried using stuff but did manage to concat the string but unable to insert comma in between.
Below is what i have tried
SELECT Stuff((SELECT 'Sc4','Sc5','Sc8','Sc7','Sc2' FOR XML PATH('')), 2, 0, '');
UPDATE
I would like to rephrase my question, is there any way the out shown below can be converted to csv
i assume Sc4, Sc5 etc are your column name and not string constant ?
SELECT Stuff(
(
SELECT ',' + Sc4 + ',' + Sc5 + ',' + Sc8 + ',' + Sc7
+ ',' + Sc2
FROM yourtable
FOR XML PATH('')
), 1, 1, '');
Try concatenation like
SELECT 'Sc4' + ',' + 'Sc5' + ',' + 'Sc8' + ',' + 'Sc7' + ',' + 'Sc2'

Concatenation of strings by for xml path

Hi!
Today I learned for xml path technique to concatenate strings in mssql. Since I've never worked with xml in mssql and google hasn't helped, I need to ask you.
Let's imagine the default case. We need to concatenate some strings from a table:
declare #xmlRepNames xml = (
select
', [' + report_name + ']'
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('.', 'nvarchar(max)')), 1, 1, '')
So I get smth like this:
[str1], [str2], [strn]
Ok. It works fine. But I have two very similar concatenate blocks. The difference is just in the way the result string looks like:
[str1], [str2], [strn]
and
isnull([str1], 0) as [str1], isnull([str2], 0) as [str2], isnull([strn], 0) as [strn]
So I can write 2 very similar code blocks (already done, btw) with different string constructors or to try extend previous code to has xml variable containing 2 kind of constructors and then concatenate by xml node type. Doing 2nd way I met some problems - I wrote this:
declare #xmlRepNames xml = (
select
', [' + report_name + ']' as name,
', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('/name', 'nvarchar(max)')), 1, 1, ''),
stuff((select #xmlRepNames.value('/res', 'nvarchar(max)')), 1, 1, '')
but it raise error "XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'".
To replace, e.g., '/name' to '/name[1]' or any other '/name[x]', will return just x-th 'name' record but not all 'name' records concatenated.
[question]: is it possible to solve problem 2nd way like I want and if it's possible then how?
[disclaimer]: the problem isn't really serious for me now (1st way just a little bit uglier but also fine), but it seems very interesting how to come over :)
Thanks!
Your subquery cannot return two values. If you just want to concatenate strings, you do not need the xml data type at all. You can do the stuff() and subquery in a single statement:
declare #Rep1Names nvarchar(max) = (
stuff((select ', [' + report_name + ']' as name
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
declare #Rep2Names nvarchar(max) = (
stuff(select ', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
Ok, so I haven't been completely satisfied the way Gordon Linoff suggested and since I've found this kind of problem actual for me I'm adding here another solution without using for xml path:
declare
#pivot_sequence nvarchar(max),
#columns_sequence nvarchar(max)
select
#pivot_sequence = coalesce(#pivot_sequence + ', [', '[')
+ col_name + ']',
#columns_sequence = coalesce(#columns_sequence + ', ', '')
+ 'isnull([' + col_name + '], 0) as [' + col_name + ']'
from some_table
order by
/* some_columns if needed to order concatenation */
Obviously, it'll work much slower but in cases when you haven't many rows it won't drastically affect on performance. In my case I have dynamic pivot query and these strings are built for it - I won't have many columns in pivot so it's fine for me.

TSQL Reverse FOR XML Encoding

I am using FOR XML in a query to join multiple rows together, but the text contains quotes, "<", ">", etc. I need the actual character instead of the encoded value like """ etc. Any suggestions?
Basically what you're asking for is invalid XML and luckly SQL Server will not produce it. You can take the generated XML and extract the content, and this operation will revert the escaped characters to their text representation. This revert normally occurs in the presnetaitonlayer, but it can occur in SQL Server itslef by instance using XML methods to extract the content of the produced FOR XML output. For example:
declare #text varchar(max) = 'this text has < and >';
declare #xml xml;
set #xml = (select #text as [node] for xml path('nodes'), type);
select #xml;
select x.value(N'.', N'varchar(max)') as [text]
from #xml.nodes('//nodes/node') t(x);
I have a similar requirement to extract column names for use in PIVOT query.
The solution I used was as follows:
SELECT #columns = STUFF((SELECT '],[' + Value
FROM Table
ORDER BY Value
FOR XML PATH('')), 1, 2, '') + ']'
This produces a single string:
[Value 1],[Value 2],[Value 3]
I hope this points you in the right direction.
--something like this?
SELECT * INTO #Names FROM (
SELECT Name='<>&' UNION ALL
SELECT Name='ab<>'
) Names;
-- 1)
SELECT STUFF(
(SELECT ', ' + Name FROM #Names FOR XML PATH(''))
,1,2,'');
-- 2)
SELECT STUFF(
(SELECT ', ' + Name FROM #Names FOR XML PATH(''),TYPE).value('text()[1]','nvarchar(max)')
,1,2,'');
-- 2) is slower but will not return encoded value.
Hope it help.