Alternative for a WITH inside FROM - sql

I`m trying to make a query in ssms that shows all errors and the amount of them.
The error message is in XML but in a nvarchar field, thats why I have to do a "with".
;with CastToXML as (
select CAST(tableName.XMLField as xml) as x
from tableName
group by tableName.XMLField
)
select distinct h.ep.value('(./logMessage)[1]', 'VARCHAR(max)') as log1
from CastToXML
cross apply x.nodes('xmlfield') as h(ep)
This doesn't allow me to do a group by log1.
Please help me.

If you want to add a COUNT(*) to your final results, just move your current query into another CTE and query and group by its results (after removing the DISTINCT, obviously):
;with CastToXML as (
select CAST(tableName.XMLField as xml) as x
from tableName
group by tableName.XMLField
), Errors as (
select h.ep.value('(./logMessage)[1]', 'VARCHAR(max)') as log1
from CastToXML
cross apply
x.nodes('xmlfield') as h(ep)
)
select log1,COUNT(*)
from Errors
group by log1

If I get this correctly, you want to count the messages within your XML.
Besides the fact, that you really should store your XML in a natively typed column (the repeated cast to XML is very expensive!), you can use simple XQuery to achieve the same:
DECLARE #SomeXML XML=
N'<xmlfield>
<logMessage>Message 1</logMessage>
<logMessage>Message 2</logMessage>
<logMessage>Message 3</logMessage>
<logMessage>Message 1</logMessage>
</xmlfield>';
SELECT #SomeXML.value('count(/xmlfield/logMessage)','int') AS CountOfLogMessages
,#SomeXML.value('count(distinct-values(/xmlfield/logMessage))','int') AS DistinctCountOfLogMessages
Hint You can use this in your query in the same place as you've got the other .value() call.

Related

Query with XML Path, Elements XSINIL and other functions

I am looking for guidance in understanding the logic and meaning of this script below. I have tried to break it down but I haven't been successful. I noticed that it merges the table result into one single column with the first part of the query providing the column headers only. I'm unsure though how it does cross apply when there is only one table involved. Where did the "nodes" come from when there is no node column in the table. What does fn:local-name(.)[1] mean as well as the T1.N.Nodes('/*').
Appreciate all the help. Admin here and never had developed a query before.
select top 1 (
select '^^'+T2.N.value('fn:local-name(.)[1]', 'varchar(max)')
from (
select T.*
for xml path(''), type, ELEMENTS XSINIL
) as T1(N)
cross apply T1.N.nodes('/*') as T2(N)
for xml path(''), type, ELEMENTS XSINIL
).value('substring((.)[1], 3)', 'varchar(max)') as systemname
from dbo.tablename as T with (nolock)
UNION ALL
select (
select '^^'+ISNULL(T2.N.value('./text()[1]', 'varchar(max)'),'')
from (
select T.*
for xml path(''), type, ELEMENTS XSINIL
) as T1(N)
cross apply T1.N.nodes('/*') as T2(N)
for xml path(''), type, ELEMENTS XSINIL
).value('substring(./text()[1], 3)', 'varchar(max)')
from dbo.tablename as T with (nolock)
where 1=1
As mentioned, this is a pretty convoluted way of generating a flat-file.
Breaking it down step-by-step:
We start with a single row of values:
select top 1 ...
from dbo.tablename as T with (nolock)
We then make a correlated sub-query as follows:
Take all columns, and turn it into a single XML, keeping null elements and returning it as the xml type
from (
select T.*
for xml path(''), type, ELEMENTS XSINIL
) as T1(N)
Shred it into separate rows, one per node (column), the * selects all nodes. Since , type was used, T1.N is an xml data type, and we can shred it into rows with .nodes
cross apply T1.N.nodes('/*') as T2(N)
From that, select a string ^^ plus the node name, and create another XML. fn:local-name(.)[1] gets you the name of the current node, rather than the inner text value.
Since this is an unnamed column, all the nodes get lumped together. Then use .value('substring((.)[1], 3)' to get that whole string, stripping off the extra ^^. Effectively, this is the same as STRING_AGG
(
select '^^'+T2.N.value('fn:local-name(.)[1]', 'varchar(max)')
for xml path(''), type, ELEMENTS XSINIL
).value('substring((.)[1], 3)', 'varchar(max)') as systemname
Then we union that with the same thing again, this time selecting all rows, and the values rather than the node names
UNION ALL
select (
select '^^'+ISNULL(T2.N.value('./text()[1]', 'varchar(max)'),'')
from (
select T.*
for xml path(''), type, ELEMENTS XSINIL
) as T1(N)
cross apply T1.N.nodes('/*') as T2(N)
for xml path(''), type, ELEMENTS XSINIL
).value('substring(./text()[1], 3)', 'varchar(max)')
from dbo.tablename as T with (nolock)
where 1=1
The results is a big flat-file delimited by ^^
Notes:
I leave you to think about the consequences of nolock, it's generally a very bad idea.
I have no idea why where 1=1 is there, it does nothing.
ELEMENTS XSINIL was not necessary on the outer for xml, because the value is never null.
varchar(max) should be nvarchar(max) if there is any chance of other languages getting in here.
As mentioned, STRING_AGG would have been a better idea on newer versions of SQL Server
OPENJSON can do all of the above in a less convoluted fashion
And to be honest, bcp.exe and other tools are much better at this anyway

SQL Server: display whole column only if substring found

Working with SQL Sever 2016. I am constrained by the fact we cannot create functions or stored procedures. I am trying to find %word% in many columns across a table (75). Right now, I have a very large clump of
and (fieldname1 like %word%
or fieldname2 like %word%
or fieldname3 like %word%) etc.
While cumbersome, this does provide me the correct results. However:
I am looking to simplify this and
in the select, I want to display the whole column if and only if it finds %word% (or even just the column name would work)
Thank you in advance for any thoughts.
--...slow...
declare #searchfor varchar(100) = '23';
select #searchfor as [thevalue],
thexml.query('for $a in (/*[contains(upper-case(.), upper-case(sql:variable("#searchfor")))])
return concat(local-name($a[1]), ",")').value('.', 'nvarchar(max)') as [appears_in_columns],
*
from
(
select *, (select o.* for xml path(''), type) as thexml
from sys.all_objects as o --table goes here
) as src
where thexml.exist('/*[contains(upper-case(.), upper-case(sql:variable("#searchfor")))]') = 1;
One option uses cross apply to unpivot the table and then search:
select v.*
from mytable t
cross apply (values
('fieldname1', fieldname1),
('fieldname2', fieldname2),
('fieldname3', fieldname3)
) v(fieldname, fieldvalue)
where v.fieldvalue like '%word%'
Note that if more than one column contains the search word, you will get several rows in the resultset. I am unsure how you want to handle this use case (there are options).
SELECT OBJECT_NAME(id) ObjectName , [Text]
FROM syscomments
WHERE TEXT LIKE '%word%'

How to read data from multiple XML files in SQL Server?

Background :
I want to obtain data from multiple XML files (stored in database) and fetch them into one result set. The basic working solution, with single XML file looks similar to this one :
DECLARE #xml xml
SET #xml =
(SELECT TOP 1 convert(varchar(max), convert(varbinary(max), [XML_FILE]))
FROM [SOME_TABLE])
SELECT
b.value('(./SomeNode/text())[1]','nvarchar(100)')) as [Some_Text],
b.value('(./SomeOtherNode/#VAL)[1]','int')) as [Some_Val]
FROM #xml.nodes('Example/File') as a(b)
Obviously this won't work with SELECT that returns many rows (many XML files). Sub-optimal solution could be achieved using cursor (iterating over collection -> pushing data into temporary table -> SELECT (*) FROM temporary_table) however, I believe thats not necessary and more straightforward solution can be achieved.
Question :
How to fetch data from multiple XML files, obtained via SELECT query, into a single result-set, without using cursor?
FILE_NAME || Value 1 || Value 2 || ...
----------------------------------------------
XML_FILE_1 || Node1Value || Node2Value || ...
XML_FILE_2 || Node1Value || Node2Value || ...
I've found solution thanks to #Shnugo answer.
If the type of xml-container column is different then XML MS-SQL dedicated one, then double CROSS APPLY should be performed. Example below :
DECLARE #mockup TABLE(ID INT IDENTITY, [XML_DATA] VARBINARY(MAX));
INSERT INTO #mockup VALUES('<Example><File><SomeNode>blah</SomeNode><SomeOtherNode VAL="1"/></File></Example>')
,('<Example><File><SomeNode>blub</SomeNode><SomeOtherNode VAL="2"/></File></Example>')
SELECT
ID,
b.value('(SomeNode/text())[1]','nvarchar(100)') as [Some_Text],
b.value('(SomeOtherNode/#VAL)[1]','int') as [Some_Val]
FROM #mockup
CROSS APPLY (SELECT CAST(convert(varbinary(max), [XML_DATA]) as XML)) as RAW_XML(xml_field)
CROSS APPLY RAW_XML.xml_field.nodes('Example/File') as a(b)
For sure the CURSOR approach is not needed and would be wrong entirely...
The general approach should be something like this:
SELECT
b.value('(./SomeNode/text())[1]','nvarchar(100)') as [Some_Text],
b.value('(./SomeOtherNode/#VAL)[1]','int') as [Some_Val]
FROM [SOME_TABLE]
CROSS APPLY [XML_FILE].nodes('Example/File') as a(b);
But there are questions open:
Speaking about xml files is a bit bewildering... I hope to get this correctly, that all these XMLs are living in a table's column.
If the first is true: Are all these XMLs of the same structure? if not you will need some kind of filtering.
is the XML in your table's column a native XML-type already? Your example uses CONVERT extensivly... You will need a native XML in order to use .nodes()
If there's no native XML: Do you have to deal with invalid / uncastable data?
Are there rows with no data but you want to see them anyway? In this case you can try OUTER APPLY instead of CROSS APPLY.
For demonstration a running stand-alone mockup:
DECLARE #mockup TABLE(ID INT IDENTITY, [XML_FILE] XML);
INSERT INTO #mockup VALUES('<Example><File><SomeNode>blah</SomeNode><SomeOtherNode VAL="1"/></File></Example>')
,('<Example><File><SomeNode>blub</SomeNode><SomeOtherNode VAL="2"/></File></Example>')
SELECT
ID,
b.value('(SomeNode/text())[1]','nvarchar(100)') as [Some_Text],
b.value('(SomeOtherNode/#VAL)[1]','int') as [Some_Val]
FROM #mockup
CROSS APPLY [XML_FILE].nodes('Example/File') as a(b)

Retrieve data from a For XML subquery

I am creating a table as follows:
CREATE TABLE dbo.Test
(
A int,
B int
)
GO
INSERT INTO Test VALUES (1, 11)
GO
INSERT INTO Test VALUES (5, 55)
GO
INSERT INTO Test VALUES (4, 44)
GO
I have a query which converts this into XML as :
SELECT A,B
FROM Test
ORDER BY A
FOR XML AUTO, ROOT ('myroot'), ELEMENTS
I need to use the above query as a subquery to get the following result:
A B
1 11
4 44
5 55
I am trying a query like this but it gives an error:
SELECT Z.Value('#A', 'INT'),
Z.Value('#B', 'INT')
FROM (SELECT A, B
FROM Test
ORDER BY A
FOR XML AUTO,Elements, ROOT ('myroot')) Doc(Z)
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "Z" or the user-defined function or aggregate "Z.Value", or the > name is ambiguous.
I can write a simple query like below to get the result but the requirement is that I have to convert it into XMl and then retrieve the same result from it using the subquery.
Select * from test order by A
I know that I can insert the records returned by For XML in a table variable and then use Cross apply to fetch the result but as said above, I am looking to get this done in a single query without any temporary table or temporary variable.
There're a several issues here. First, your xml looks like this:
<myroot>
<Test>
<A>1</A><B>11</B>
</Test>
<Test>
<A>4</A><B>44</B>
</Test>
<Test>
<A>5</A><B>55</B>
</Test>
</myroot>
And you're trying to fetch data as attributes (#A, #B). You need to fetch it as elements (A[1] or (A/text())[1]).
Second, you have to use type keyword if you want your xml to be xml type.
Third, to split data by rows you need nodes() function. So your query becomes:
select
D.Z.value('(A/text())[1]', 'int'),
D.Z.value('(B/text())[1]', 'int')
from (
select A, B
from Test
order by A
for xml auto, elements, root('myroot'), type
) as Doc(Z)
outer apply Doc.Z.nodes('myroot/Test') as D(Z)
BTW, I'd better to use attributes, like this:
select
D.Z.value('#A', 'int'),
D.Z.value('#B', 'int')
from (
select A, B
from Test
order by A
for xml raw('Test'), root('myroot'), type
) as Doc(Z)
outer apply Doc.Z.nodes('myroot/Test') as D(Z)
sql fiddle demo
You forgot TYPE mode (without it you get nvarchar instead of xml) and value keyword should be in lower-case.
Try this:
SELECT Z.Z.value('#A', 'INT'),
Z.Z.value('#B', 'INT')
FROM (
SELECT A, B
FROM Test
ORDER BY A
FOR XML AUTO, ROOT ('myroot'), TYPE
) Doc(Doc)
CROSS APPLY Doc.nodes('/myroot/Test')Z(Z)
But I would prefer generating XML without AUTO mode (your query would break down if you write dbo.Test instead of Test), in more declaretive way using PATH keyword:
SELECT Z.Z.value('#A', 'INT'),
Z.Z.value('#B', 'INT')
FROM (
SELECT A AS '#A', B AS '#B'
FROM dbo.Test
ORDER BY A
FOR XML PATH('Test'), ROOT ('myroot'), TYPE
) Doc(Doc)
CROSS APPLY Doc.nodes('/myroot/Test')Z(Z)

Query a table to find records with given text

I have below 3 records in my table(TAG_DATA) column(TAGS).
car,bus,van
bus,car,ship,van
ship
I wrote a query to get records which has car and bus as below.
SELECT * FROM TAG_DATA
where TAGS like '%car, bus%'
But above query return only below record.
car,bus,van
But i need to get output as below. because both records have car and bus
car,bus,van
bus,car,ship,van
How can i write a query for this ? I'm using MS SQL Server.
UPDATED
I'm selecting tags from multi select combobox in my application. so i need to give text in that. so can't use and/ or in my query.
Please try:
DECLARE #input NVARCHAR(MAX)='car, bus'
SELECT DISTINCT B.*
FROM(
SELECT
LTRIM(Split.a.value('.', 'VARCHAR(100)')) AS CVS
FROM
(
SELECT
CAST ('<M>' + REPLACE(#input, ',', '</M><M>') + '</M>' AS XML) AS CVS
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
)x INNER JOIN TAG_DATA b on TAGS like '%'+CVS+'%'
Here, only records having car, bus tags in sequence will be fetched.
For the desired result, below query is beneficial :
SELECT * FROM TAG_DATA
where TAGS like '%car, bus%' or TAGS like '%bus, car%'
SQL FIDDLE
SELECT * FROM TAG_DATA
where TAGS like '%car,%' and TAGS like '%bus,%'
I intentionally used the commas. It depends on your data The following query will also work atleast for the example above.
SELECT * FROM TAG_DATA
where TAGS like '%car,%' and TAGS like '%bus,%'
This works
SELECT * FROM TAG_DATA
where TAGS like '%[BUS,CAR],[CAR,BUS]%'