SQL select join on xml field with xpath expression - sql

I have a following query that can return a result from an xml:
declare #xml xml
select #xml = data from files where id = 1234
select
children.p.value('./speed[1]','float')
from #xml.nodes('root/children') as children(p)
where
children.p.value('./name[1]','nvarchar(max)') = 'something'
This in my case returns a single value, for example 3141
However, I'd like to do multiple selects like this from multiple XMLs.
I can select the xml data as
select id, cast(data as xml) as xml
from files
where id in (1005,51,968,991,992,993,969,970) --for example
I imagine there must be some kind of JOIN that will apply my expression and return a single item for each xml variable in the table, but I am not sure how.

Use apply:
select
f.id, children.p.value('./speed[1]','float')
from files as f
outer apply (select cast(f.data as xml) as xml) as x
outer apply x.xml.nodes('root/children') as children(p)
where
f.id in (1005,51,968,991,992,993,969,970) and
children.p.value('./name[1]','nvarchar(max)') = 'something'

Related

Cannot use column of outside reference in my inside query

I am using SQL query like below:
SELECT distinct uf.SystemName as System ,uf.SystemId,
(SELECT SUM(CASE WHEN c.SystemId = uf.SystemTypeId THEN 1 ELSE 0 END)
FROM OPENJSON((SELECT TOP 1 CAST(JsonStringColumn AS NVARCHAR(MAX)) FROM RefTable where TagId = (Select top 1 TagId from CSSTAGS where projectid='9abbeecf-15a4-412f-ba0c-358b8f09ac9e')))
WITH (Data NVARCHAR(MAX) AS JSON) a
CROSS APPLY OPENJSON(a.Data)
WITH (System NVARCHAR(MAX) AS JSON) b
CROSS APPLY OPENJSON(b.System)
WITH (SystemId NVARCHAR(MAX) '$.SystemId') c
) As SystemIdExists
FROM Table1 uf
I am not able to use uf.SystemId inside my second select query which throwing below error: Multiple columns are specified in an aggregated expression containing an outer reference.
How can I achieve this?
Adding Where condition to second select query resolved my issue

How to simplify correlated subquery in sql server 2008?

I have query in sql server which I want to simplify so that it can be Hive compatible.
This is the query written in SQL format
SELECT session_id,
Substring ((SELECT ( ';' + tag_name )
FROM session_tag st2
WHERE st2.session_id = st.session_id
FOR xml path ( '' )), 2, 1000) AS tags
FROM session_tag
GROUP BY session_id;
This is the result of this query
Once again I don't want to pass select query inside the substring function.
So I tried to simplify these query to get the same result the first query shows nothing and second one throws an error that subquery returned more than one result.
SELECT SUBSTRING(';'+tag_name,2,1000) as tag from session_tag st1
where st1.session_id = (select st2.session_id from session_tag st2 where st1.session_id = st2.session_id for xml path (''))
and
SELECT SUBSTRING(';'+tag_name,2,1000) as tag from session_tag st1
where st1.session_id = (select st2.session_id from session_tag st2 where st1.session_id = st2.session_id) for xml path('')
Finally I got the solution. The simplified version of this query is
SELECT session_id, Substring(d.tagList,2,1000) as tag from (
select distinct session_id from session_tag) a
cross apply
(
select ';'+tag_name from session_tag as b
where a.session_id = b.session_id for xml path ('')
) d (tagList)

SQL: Filter XML retrieving using xpath

I have documents table, each row has a XML column Document:
<Document>
<Good GroupId="..."/>
<Good GroupId="..."/>
...
</Document>
I also have temp table with subset of GroupId values:
DECLARE #Groups TABLE (groupId VARCHAR(MAX));
Next, I wrote a select query to documents table, goal - retrieve XML from Document:
SELECT
(SELECT CAST(Document.data as XML)).query('/Document/Good') AS Goods
FROM
Documents as Document
JOIN
#Numbers n ON n.number = Document.number
WHERE
Document.type = #type
--FOR XML AUTO, ROOT('Documents')
As a result, in column Goods I got all Good items for each Document
Task:
In step 3, I need filter Good elements by GroupId attribute (using #Groups) - I need all Good for which GroupId value is not contained in #Groups
Thanks!
Do CROSS APPLY to extract the attribute values from the XML
Need to use LEFT JOIN to find Goods in XML but not in #Goods table.
Here is the SQL Fiddle: http://www.sqlfiddle.com/#!3/a6b9e/5
SELECT G.value('#GroupId', 'varchar(max)') FROM
(
SELECT
CAST(Document.data as XML) AS Goods
FROM
Documents as Document
WHERE type = 1
) T
CROSS APPLY T.Goods.nodes('Document/Good') D(G)
LEFT JOIN #Groups GS
ON G.value('#GroupId', 'varchar(max)') = GS.groupId
WHERE GS.groupId IS NULL

SQL Server: How to add count to nested select

I have two tables, one containing a list of files and one containing a list of tags which are linked by a fileID.
Currently I select this as follows which works fine so far.
How do I have to amend this if I want to count the tags per file and show this in addition to the selected data ?
What I want to do is show how many tags are assigned to each file.
My SP:
SELECT C.fileTitle,
C.fileID,
(
SELECT T.fileTag
FROM Files_Tags T
WHERE T.fileID = C.fileID
ORDER BY T.fileTag
FOR XML PATH(''), ELEMENTS, TYPE
) AS tags
FROM Files C
ORDER BY C.fileTitle
FOR XML PATH('files'), ELEMENTS, TYPE, ROOT('root')
Many thanks for any help with this, Tim.
You can add a subquery:
SELECT C.fileTitle,
C.fileID,
(
SELECT COUNT(*)
FROM Files_Tags T
WHERE T.fileID = C.fileID
) AS NumTags,
(
SELECT T.fileTag
FROM Files_Tags T
WHERE T.fileID = C.fileID
ORDER BY T.fileTag
FOR XML PATH(''), ELEMENTS, TYPE
) AS tags
You could also put in a join and aggregation in the outer query. But, your query already has to use a nested select for the concatenation, so you might as well use the same structure for the count.
Can't you just do this?:
SELECT C.fileTitle,
C.fileID,
(
SELECT T.fileTag
FROM Files_Tags T
WHERE T.fileID = C.fileID
ORDER BY T.fileTag
FOR XML PATH(''), ELEMENTS, TYPE
) AS tags,
(
SELECT
COUNT(*)
FROM
Files_Tags T
WHERE
T.fileID = C.fileID
) AS NumberOfTages
FROM Files C
ORDER BY C.fileTitle
FOR XML PATH('files'), ELEMENTS, TYPE, ROOT('root')
Anding a sub query for the count

Assign Values of a column in SubQuery in SQL

I am trying to do following in SQL Server:
SELECT
PRODUCER_NAME, PRODUCER_ID,
(SELECT #X = #X + PRODUCT_NAME
FROM PRODUCT
WHERE PRODUCER_ID = PRODUCER.ID)
FROM
PRODUCER
There are two tables. Producer table is list of all producers. Product table stores product produced by producers. #x is varchar variable
Basically I want a list of all products, comma-separated by producer.
For example
Producer Products
-------- --------------------------
P1 ProductA,ProductB,ProductC
P2 ProductD,ProductE
I don't know if this is possible this way. Do anyone know how to do this without joining tables?
I don't have a way for you to assign multiple output comma-separated lists to a single varchar variable, but maybe you don't actually need that anyway. Try this:
SELECT Producer = PRODUCER.PRODUCER_NAME,
Products = STUFF(
(
SELECT N',' + PRODUCT.PRODUCT_NAME
FROM dbo.PRODUCT
WHERE PRODUCT.PRODUCER_ID = PRODUCER.ID
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'nvarchar(max)'),1,1,N'')
FROM dbo.PRODUCER;
On a large table, this kind of correlated subquery can be quite expensive. On SQL Server 2017+ we can use STRING_AGG() in a single pass:
SELECT Producer = PRODUCER.PRODUCER_NAME,
Products = STRING_AGG(PRODUCT.PRODUCT_NAME, N',')
FROM dbo.PRODUCT
INNER JOIN dbo.PRODUCER
ON PRODUCT.PRODUCER_ID = PRODUCER.ID
GROUP BY PRODUCER.PRODUCER_NAME;
Example db<>fiddle
If you want to concatenate names, you can do this:
select
P.PRODUCER_NAME, P.PRODUCER_ID,
stuff(
(
select ',' + T.PRODUCT_NAME
from PRODUCT as T
where T.PRODUCER_ID = P.PRODUCER_ID
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 1, '') as PRODUCT_NAMES
from PRODUCER as P
Two notes:
Always use the table aliases for such a queries. To see why - delete alias name from query and drop PRODuCER_ID from PRODUCT table.
Use value method instead of implicit conversion to nvarchar to correctly work with names like 'Product & 1'.