Trouble searching for specific values using XMLNAMESPACES - sql-server

I've taken a look and wasn't able to find an answer that would help me with my issue. (Most probably due to my poor skills)
However was hoping that someone would be able to point me in the right direction.
The issue:
I have an XML column in the table that I am querying and I need the query to return rows all rows with a specific value.
An example from the xml column
<EventD xmlns="http://example1" xmlns:e3q1="http://example2" xmlns:xsi="http://example3" xsi:type="e3q1:Valuechange">
<e3q1:NewValue>Running</e3q1:NewValue>
<e3q1:OldValue>Stopped</e3q1:OldValue>
</EventD>
What I would need to do is to return all rows that have "NewValue" as "Running"
;WITH XMLNAMESPACES ('example2' as e3q1)
select top 100
Xml.value('(EventD/NewValue)[1]', 'varchar(100)'),
* from Table1
and Xml.value('(EventD/NewValue)[1]', 'varchar(100)') like 'Running'
Yet this does not seem to return any rows at all, would be really grateful if someone could point out what am i doing wrong here.
Thanks in advance,

You do declare the namespace e3q1 (although it's missing the http:// and you don't use it later...), but you did not declare the default namespace
DECLARE #tbl TABLE([Xml] XML);
INSERT INTO #tbl VALUES
(
N'<EventD xmlns="http://example1" xmlns:e3q1="http://example2" xmlns:xsi="http://example3" xsi:type="e3q1:Valuechange">
<e3q1:NewValue>Running</e3q1:NewValue>
<e3q1:OldValue>Stopped</e3q1:OldValue>
</EventD>'
);
;WITH XMLNAMESPACES (DEFAULT 'http://example1', 'http://example2' as e3q1)
SELECT [Xml].value('(EventD/e3q1:NewValue)[1]', 'varchar(100)')
from #tbl AS Table1
WHERE Xml.value('(EventD/e3q1:NewValue)[1]', 'varchar(100)') like 'Running';
But this approach is - at least I think so - not what you really want. I think you are looking for .nodes(). The next lines show as alternative an approach to replace namespaces with a wildcard. But I would recommend to be as specific as possible.
SELECT Only.Running.value('text()[1]', 'varchar(100)')
from #tbl AS Table1
CROSS APPLY Xml.nodes('*:EventD/*:NewValue[text()="Running"]') AS Only(Running)

Looks like  Jeroen Mostert already says all necessary
I can only add - name of namespace is not important, only uri
declare #xml xml='<EventD xmlns="http://example1" xmlns:e3q1="http://example2" xmlns:xsi="http://example3" xsi:type="e3q1:Valuechange">
<e3q1:NewValue>Running</e3q1:NewValue>
<e3q1:OldValue>Stopped</e3q1:OldValue>
<xsi:test>test</xsi:test>
<test1>test1</test1>
</EventD>'
;WITH XMLNAMESPACES ('http://example2' as test)
select
#xml.query('(*/test:*)')
compare with result of
select
#xml.query('(*/*)')

Related

Understanding CTE Semicolon Placement

When I run this CTE in SQL Server it says the syntax is incorrect by the declare statement.
;WITH cte as
(
SELECT tblKBFolders.FolderID
from tblKBFolders
where FolderID = #FolderID
UNION ALL
SELECT tblKBFolders.FolderID
FROM tblKBFolders
INNER JOIN cte
ON cte.FolderID = tblKBFolders.ParentFolderID
)
declare #tblQueryFolders as table (FolderID uniqueidentifier)
insert into #tblQueryFolders
SELECT FolderID From cte;
But if I move the declare to before the CTE, it runs just fine.
declare #tblQueryFolders as table (FolderID uniqueidentifier)
;WITH cte as
(
SELECT tblKBFolders.FolderID
from tblKBFolders
where FolderID = #FolderID
UNION ALL
SELECT tblKBFolders.FolderID
FROM tblKBFolders
INNER JOIN cte
ON cte.FolderID = tblKBFolders.ParentFolderID
)
insert into #tblQueryFolders
SELECT FolderID From cte;
Why is that?
The answer you ask for was given in a comment already: This has nothing to do with the semicolon's placement.
Important: The CTE's WITH cannot follow right after a statement without an ending semicolon. There are many statments, where a WITH-clause would add something to the end of the statement (query hints, the WITH after OPENJSON etc.). The engine would have to guess, whether this WITH adds to the statment before or if it is a CTE's start. That's the reason, why we often see
;WITH cte AS (...)
That's actually the wrong usage of a semicolon. People put it there, just not to forget about it. Anyway it is seen as better style and best practice to end T-SQL statements always with a semicolon (and do not use ;WITH, as it adds an empty statement actually).
A CTE is not much more than syntactical sugar. Putting the CTE's code within a FROM(SELECT ...) AS SomeAlias would be roughly the same. In most cases this would lead to the same execution plan. It helps in cases, where you'd have to write the same FROM(SELECT ) AS SomeAlias in multiple places. And - in general - it makes things easier to read and understand. But it is not - by any means - comparable to a temp table or a table variable. The engine will treat it as inline code and you can use it in the same statement exclusively.
So this is the same:
WITH SomeCTE AS(...some query here...)
SELECT SomeCTE.* FROM SomeCTE;
SELECT SomeAlias.*
FROM (...some query here...) AS SomeAlias;
Your example looks like you think of the CTE as kind of a temp table definition, which you can use in the following statements. But this is not correct.
After the CTE the engine expects another CTE or a final statement like SELECT or UPDATE.
WITH SomeCTE AS(...some query here...)
SELECT * FROM SomeCTE;
or
WITH SomeCTE AS( ...query... )
,AnotherCTE AS ( ...query... )
SELECT * FROM AnotherCTE;
...or another content added with the WITH clause:
WITH XMLNAMESPACES( ...namespace declarations...)
,SomeCTE AS( ...query... )
SELECT * FROM SomeCTE;
All of these examples are one single statement.
Putting a DECLARE #Something in the middle, would break this concept.

If clause in sql

Declare #a varchar(100);
If #a = select productname from product
The above mentioned query is throwing error as my sql query (select product name from product) is returning multiple values.
Can someone help me with this?
Try something like:
Declare #a varchar(100);
SELECT #a = 'MyProduct'
Then either
If #a = select TOP 1 productname from product ORDER BY <some field>
OR
If #a IN (select productname from product)
However, how do you know which product(s) to match to; you might need a WHERE clause. Some sample data and desired results would help.
Please note that you need to put the SELECT query inside the parenthesis in this case! Also you should note that your select query, in this case should not return more than one value. So to resolve these you need to write it as:
Declare #a varchar(100);
If #a = (select TOP 1 productname from product)
However your query logically seems to be invalid and you should rethink about it, for example you need to say that, you are going to check #a with which product? You might need to add some filters to query and/or add ELSE to your if, etc.
You might also need to read the #PeterSmith's answer too(Using IN...)

Does the WITH on OpenJson has something to do with the WITH CTE?

Learning OpenJson on sql-server 130, I come to the WITH clause
https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql
searching deeper, I come to the WITH common_table_expression
https://learn.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql
While it has help me to learn about the CTE to understand better how OpenJson works, and the syntasis is similar, I am not sure whether both WITHs are related, or it is just a causality the usage of same word
On the other hand, if not related, I would have expected another name for the with clause, like "AS", "STRUCTURED_AS", "WITHSTRUCTURE", "CASTED", "INSHAPEOF", "LIKE" etc...
OpenJson ( jsonExpresion [, path] ) [STRUCTURED_AS ({colName type ...}...)
Not sure, but I think OPENJSON WITH is similar to query hints. This makes sense when some queries can be helped to achieve better performance when WITH is used.:
-- this requires two JSON parses
SELECT count(*)
FROM OPENJSON(#json)
WHERE JSON_VALUE(value, '$.model') = 'Golf'
-- this requires one JSON parse only
SELECT count(*)
FROM OPENJSON(#json) WITH(model nvarchar(20) )
WHERE model = 'Golf'
In this case, WITH behaves similarly to a hint (it is not required, but helps the engine to perform better).
As a side note, CTE can be used along with JSON WITH clause and it might lead to better readability. E.g.:
;with Comp as (
select * from OPENJSON (
( SELECT BulkColumn
FROM OPENROWSET (BULK 'companies.json', SINGLE_CLOB) as j)
)
WITH (
CompanyId INT '$.companyId' ,
CompanyName NVARCHAR(128) '$.companyName',
CountryName NVARCHAR(128) '$.countryName',
FoundedDate DATETIME '$.foundedDate'
))
SELECT *
FROM Comp

Filter xml column in T-SQL from WhoIsActive

Simple question, how do I query an XML column containing values like this:
<?query --
select statistic_name_id, convert(varchar(1024), statistic_name), datasource_id, statistic_class_id, datatype_guide, derived, using_wide_string from spotlight_stat_names
--?>
I use WhoIsActive from Adam Mechanic with a destination table. I got a lot of rows and I want to filter out some. Please help.
select * from WhoIsActivetable
where cast(sql_text as varchar(max)) like '%statistic_name_id%'
select sql_text, cast(sql_text as varchar(max)) from WhoIsActivetable

How to use a column name in CONTAINSTABLE for the search condition?

I'm surprised to find that neither CONTAINS or CONTAINSTABLE seems to support syntax like the following where you pass a column name in for the last Search Condition parameter.
SELECT *
FROM dbo.Articles AS a
WHERE EXISTS
(
SELECT *
FROM dbo.Terms AS t
INNER JOIN CONTAINSTABLE(dbo.Articles, (ArticleBody), t.FulltextTerm)
AS ct ON ct.[Key] = a.ArticleId
)
The above query returns an "Incorrect syntax near 't'" error message.
The Terms table contains multiple rows with a FulltextTerm column, and if any of those FulltextTerm values is in the ArticleBody, it should be a match so that particular Article is selected. This is what I'm trying to achieve.
CONTAINS and CONTAINSTABLE appear to only support string literals or variables for the Search Condition parameter, which is very limiting. If that's the only option, it requires a lot more code and will certainly be much slower if I need to iterate thru the Terms table with a cursor or loop.
Am I missing a trick here, or any workarounds someone can suggest - preferably a set-based solution, i.e. avoiding loops.
What about merging all your terms in one variable, and then using the CONTAINSTABLE, as below:-
declare #term as table(
FulltextTerm nvarchar(60)
)
insert into #term values ('light NEAR aluminum')
insert into #term values ('lightweight NEAR aluminum')
Declare #FulltextTerm nvarchar(max)=''
select #FulltextTerm=#FulltextTerm+' OR ('+FulltextTerm+')' from #term
set #FulltextTerm=SUBSTRING(#FulltextTerm,5,99999)
-- #FulltextTerm will have the below value:-
-- (light NEAR aluminum) OR (lightweight NEAR aluminum)
SELECT *
FROM dbo.Articles AS a
INNER JOIN
CONTAINSTABLE (dbo.Articles,ArticleBody,#FulltextTerm) AS ct
ON ct.[Key] = a.ArticleId
off course in your case you dont need the table variable #term, you could replace it with your Term table, but I only used it here to show the idea.
I believe this may be better than looping.
Note: I dont know the database version you have but you could even use the below if you can use STRING_AGG function
select #FulltextTerm=STRING_AGG('('+FulltextTerm+')',' OR ') from #term

Resources