OPENXML Leaves out xml rows that do not have the specified elements - tsql

I am running below query on SQL Server 2014 server but I can't understand why I am getting only one row in result.
DECLARE #idoc int, #doc varchar(1000);
SET #doc ='
<ROOT>
<Customer CustomerID="VINET" ContactName="Paul Henriot">
<Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00">
<OrderDetail OrderID="10248" ProductID="11" Quantity="12"/>
<OrderDetail OrderID="10248" ProductID="42" Quantity="10">
<ReturnDetail ReturnOrderID="1111" ReturnDate="1996-08-04T00:00:00"/>
</OrderDetail>
</Order>
</Customer>
<Customer CustomerID="LILAS" ContactName="Carlos Gonzlez">
<Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00">
<OrderDetail OrderID="10283" ProductID="72" Quantity="3"/>
</Order>
</Customer>
</ROOT>';
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc;
-- SELECT stmt using OPENXML rowset provider
SELECT *
FROM OPENXML (#idoc, '/ROOT/Customer/Order/OrderDetail/ReturnDetail',2)
WITH ( OrderID int '../#OrderID',
order_CustomerID varchar(10) '../../#CustomerID',
EmployeeID varchar(10) '../../#EmployeeID',
ContactName varchar(100) '../../../#ContactName',
CustomerID varchar(10) '../../#CustomerID',
OrderDate datetime '../../#OrderDate',
ProdID int '../#ProductID',
Qty int '../#Quantity',
ReturnOrderID int '#ReturnOrderID',
ReturnDate datetime '#ReturnDate'
);
How do I get all 3 records returned like below

You should use the built-in, native XQuery support (instead of the legacy OPENXML stuff...).
Use this code to get all the details down to the <OrderDetail> node (define your #doc variable as XML):
DECLARE #doc XML;
SET #doc = '......';
SELECT
OrderID = XOD.value('#OrderID', 'int'),
CustomerID = XCus.value('#CustomerID', 'varchar(20)'),
ContactName = XCus.value('#ContactName', 'varchar(50)'),
EmployeeID = XOrder.value('#EmployeeID', 'int'),
OrderDate = XOrder.value('#OrderDate', 'datetime'),
ProductID = XOD.value('#ProductID', 'int'),
Quantity = XOD.value('#Quantity', 'int'),
ReturnOrderID = RetD.value('#ReturnOrderID', 'int'),
ReturnDate = RetD.value('#ReturnDate', 'datetime')
FROM
#doc.nodes('/ROOT/Customer') AS XT(XCus)
CROSS APPLY
XCus.nodes('Order') AS XT2(XOrder)
CROSS APPLY
XOrder.nodes('OrderDetail') AS XT3(XOD)
OUTER APPLY
XOD.nodes('ReturnDetail') AS XT4(RetD)
For each "level" that might contain multiple nodes (like <Customer> under <ROOT> etc.), you need to use CROSS APPLY and the .nodes() XQuery function to get all the child nodes - not just one (first or arbitrary).

Related

How to split a field that has carriage return

I have a field in my database table called ADDRESSFORMAT
1,The Lodge
Street
Town
Postcode
Where the contents are separated by a CHAR (13) and CHAR (10)
How would I go about creating fields in a query that would only pull back either the first line, second line...and so on?
The following is an in-line approach.
The Cross Apply B generates a "clean string". It will eliminate any number of repeating CRLFs and create a pipe delimited string to be processed by Cross Appy C.
I should note that this method of eliminating repeating strings was demonstrated by Gordon Linoff several weeks back. Sorry I can't find the original post.
Example
Declare #YourTable table (ID int,ADDRESSFORMAT varchar(max))
Insert Into #YourTable values
(1,'The Lodge
Street
Town
Postcode')
Select A.ID
,C.*
From #YourTable A
Cross Apply (
Select CleanString = replace(replace(replace(replace(replace(ADDRESSFORMAT,char(13),'|'),char(10),'|'),'|','><'),'<>',''),'><','|')
) B
Cross Apply (
Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace(B.CleanString,'|','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) C
Returns
ID Pos1 Pos2 Pos3 Pos4 Pos5 Pos6 Pos7 Pos8 Pos9
1 The Lodge Street Town Postcode NULL NULL NULL NULL NULL

Column is of type timestamp without time zone but expression is of type character

I'm trying to insert records on my trying to implement an SCD2 on Redshift
but get an error.
The target table's DDL is
CREATE TABLE ditemp.ts_scd2_test (
id INT
,md5 CHAR(32)
,record_id BIGINT IDENTITY
,from_timestamp TIMESTAMP
,to_timestamp TIMESTAMP
,file_id BIGINT
,party_id BIGINT
)
This is the insert statement:
INSERT
INTO ditemp.TS_SCD2_TEST(id, md5, from_timestamp, to_timestamp)
SELECT TS_SCD2_TEST_STAGING.id
,TS_SCD2_TEST_STAGING.md5
,from_timestamp
,to_timestamp
FROM (
SELECT '20150901 16:34:02' AS from_timestamp
,CASE
WHEN last_record IS NULL
THEN '20150901 16:34:02'
ELSE '39991231 11:11:11.000'
END AS to_timestamp
,CASE
WHEN rownum != 1
AND atom.id IS NOT NULL
THEN 1
WHEN atom.id IS NULL
THEN 1
ELSE 0
END AS transfer
,stage.*
FROM (
SELECT id
FROM ditemp.TS_SCD2_TEST_STAGING
WHERE file_id = 2
GROUP BY id
HAVING count(*) > 1
) AS scd2_count_ge_1
INNER JOIN (
SELECT row_number() OVER (
PARTITION BY id ORDER BY record_id
) AS rownum
,stage.*
FROM ditemp.TS_SCD2_TEST_STAGING AS stage
WHERE file_id IN (2)
) AS stage
ON (scd2_count_ge_1.id = stage.id)
LEFT JOIN (
SELECT max(rownum) AS last_record
,id
FROM (
SELECT row_number() OVER (
PARTITION BY id ORDER BY record_id
) AS rownum
,stage.*
FROM ditemp.TS_SCD2_TEST_STAGING AS stage
)
GROUP BY id
) AS last_record
ON (
stage.id = last_record.id
AND stage.rownum = last_record.last_record
)
LEFT JOIN ditemp.TS_SCD2_TEST AS atom
ON (
stage.id = atom.id
AND stage.md5 = atom.md5
AND atom.to_timestamp > '20150901 16:34:02'
)
) AS TS_SCD2_TEST_STAGING
WHERE transfer = 1
and to short things up, I am trying to insert 20150901 16:34:02 to from_timestamp and 39991231 11:11:11.000 to to_timestamp.
and get
ERROR: 42804: column "from_timestamp" is of type timestamp without time zone but expression is of type character varying
Can anyone please suggest how to solve this issue?
Postgres isn't recognizing 20150901 16:34:02 (your input) as a valid time/date format, so it assumes it's a string.
Use a standard date format instead, preferably ISO-8601. 2015-09-01T16:34:02
SQLFiddle example
Just in case someone ends up here trying to insert into a postgresql a timestamp or a timestampz from a variable in groovy or Java from a prepared statement and getting the same error (as I did), I managed to do it by setting the property stringtype to "unspecified". According to the documentation:
Specify the type to use when binding PreparedStatement parameters set
via setString(). If stringtype is set to VARCHAR (the default), such
parameters will be sent to the server as varchar parameters. If
stringtype is set to unspecified, parameters will be sent to the
server as untyped values, and the server will attempt to infer an
appropriate type. This is useful if you have an existing application
that uses setString() to set parameters that are actually some other
type, such as integers, and you are unable to change the application
to use an appropriate method such as setInt().
Properties props = [user : "user", password: "password",
driver:"org.postgresql.Driver", stringtype:"unspecified"]
def sql = Sql.newInstance("url", props)
With this property set, you can insert a timestamp as a string variable without the error raised in the question title. For instance:
String myTimestamp= Instant.now().toString()
sql.execute("""INSERT INTO MyTable (MyTimestamp) VALUES (?)""",
[myTimestamp.toString()]
This way, the type of the timestamp (from a String) is inferred correctly by postgresql. I hope this helps.
Inside apache-tomcat-9.0.7/conf/server.xml
Add "?stringtype=unspecified" to the end of url address.
For example:
<GlobalNamingResources>
<Resource name="jdbc/??" auth="Container" type="javax.sql.DataSource"
...
url="jdbc:postgresql://127.0.0.1:5432/Local_DB?stringtype=unspecified"/>
</GlobalNamingResources>

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`

XML DML query for attribute

declare #myDoc xml
set #myDoc = '<Form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.mydomain.org/MySchema.xsd" SectionId="ABCD" Description="Some stuff">
<ProductDescription ProductID="1" ProductName="Road Bike">
<Features>
<Warranty>1 year parts and labor</Warranty>
<Maintenance>3 year parts and labor extended maintenance is available</Maintenance>
</Features>
</ProductDescription>
</Form>'
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT #myDoc.value('/Form[#SectionId][0]', 'varchar')
I need to obtain the attribute value of SectionId as a nvarchar ? how do I do it ?...
T and R
Mark
You could write it even simpler:
;WITH XMLNAMESPACES(DEFAULT 'http://www.mydomain.org/MySchema.xsd')
SELECT #myDoc.value('(/Form/#SectionId)[1]', 'VARCHAR(100)') AS SectionId
Since you're never using/referring to any of the xsi or xsd namespaces, there's no need to declare those.
And since you're only fetching one attribute from one element, there's really no point in using the .nodes() function to create an internal "dummy table", either.
;WITH XMLNAMESPACES( 'http://www.w3.org/2001/XMLSchema-instance' as xsi, 'http://www.w3.org/2001/XMLSchema' as xsd, DEFAULT 'http://www.mydomain.org/MySchema.xsd' )
SELECT Node.value('#SectionId', 'VARCHAR(100)') AS SectionId
FROM #myDoc.nodes('/Form') TempXML (Node);

TSQL - How to join 1..* from multiple tables in one resultset?

A location table record has two address id's - mailing and business addressID that refer to an address table.
Thus, the address table will contain up to two records for a given addressID.
Given a location ID, I need an sproc to return all tbl_Location fields, and all tbl_Address fields in one resultset:
LocationID INT,
ClientID INT,
LocationName NVARCHAR(50),
LocationDescription NVARCHAR(50),
MailingAddressID INT,
BillingAddressID INT,
MAddress1 NVARCHAR(255),
MAddress2 NVARCHAR(255),
MCity NVARCHAR(50),
MState NVARCHAR(50),
MZip NVARCHAR(10),
MCountry CHAR(3),
BAddress1 NVARCHAR(255),
BAddress2 NVARCHAR(255),
BCity NVARCHAR(50),
BState NVARCHAR(50),
BZip NVARCHAR(10),
BCountry CHAR(3)
I've started by creating a temp table with the required fields, but am a bit stuck on how to accomplish this.
I could do sub-selects for each of the required address fields, but seems a bit messy.
I've already got a table-valued-function that accepts an address ID, and returns all fields for that ID, but not sure how to integrate it into my required result.
Off hand, it looks like 3 selects to create this table - 1: Location, 2: Mailing address, 3: Billing address.
What I'd like to do is just create a view and use that.
Any assistance would be helpful.
Thanks.
something along the lines of the following would work:
select L.*,
a1.Address1 as MAddress1, a1.Address2 as MAddress2,
a2.Address1 as BAddress1, a2.Address2 as BAddress2
from location L
inner join Address a1 on (a1.AddressId = L.MailingAddressId)
inner join Address a2 on (a2.AddressId = L.BillingAddressId)
I didn't put in all of the fields, but you get the idea.
Note that if either of the address ids could be null, the you might use a left join instead.
If I understand your question correctly you want something like:
SELECT
L.*,
MAddress1 = M.Address1,
MAddress2 = M.Address2,
MCity = M.City,
MState = M.State,
MZip = M.Zip,
MCountry = M.Country
BAddress1 = B.Address1,
BAddress2 = B.Address2,
BCity = B.City,
BState = B.State,
BZip = B.Zip,
BCountry = B.Country
FROM
tbl_Location L
INNER JOIN tbl_Address M
ON L.MailingAddressID = M.MailingAddressID
INNER JOIN tbl_Address B
ON L.BillingAddressID = B.BillingAddressID
WHERE
L.LocationID = #LocationID