csv from subquery in IN() causes conversion error - sql-server

CROSS APPLY (
SELECT
ISNULL(
(
SELECT
SUM (R.CALORIE)
FROM
TA_RECIPE AS R
WHERE
R.NO_RECIPE IN ([mp].recipe_ids)
OR R.NO_RECIPE IN ([tags2].recipe_ids)
),
0
) AS caloriePerPortion
) AS [CAL]
The problem is in the part
R.NO_RECIPE IN ([mp].recipe_ids)
OR R.NO_RECIPE IN ([tags2].recipe_ids)
The recipe_ids contain comma seperated lists
when there's only one id in the recipe_ids it works fine but if there is actually a list in recipe_ids sql server interprets this as a varchar instead of a list of integers
wrapping them in STRING_SPLIT causes the query to become very slow (adds seconds to the execution time)

wrapping them in STRING_SPLIT causes the query to become very slow
But that's the only way it can work.
select *
from t
where c in ('1,2,3,4,5')
just doesn't do what you want.

the data isn't stored as csv but was retrieved like that throug cross apply with for xml path
I changed it to
SELECT DISTINCT
MM.MAP_ID AS mapId,
MM.MENU_ID AS menuId,
stuff((select ',' + CAST(RECIPES.recipe_ids AS VARCHAR(MAX)) for xml path('')), 1, 1, '') AS planningRecipe_ids
FROM MM
LEFT JOIN MP ON MM.MENU_ID = MP.MENU_ID
LEFT JOIN RECIPES ON RECIPES.PLANNING_MENU_ID = MP.PLANNING_MENU_ID
but then the for xml path doesn't work

Related

Invalid object name in a XML Path / Stuff combination

I am trying to get some values from different rows into a single column, and I keep getting this error :
Invalid object name 't'
The query is rather big and complicated so I narrowed it down to a simple part that still gives me the error.
select
IDs = stuff( ( select ',' + convert(varchar, t2.ChassisID)
from t as t2
where t2.ChassisID = 42 --t.ChassisID
for XML path('')
)
, 1, 1, ''
)
from ( select ch.ChassisID, p.GPS
from tblChassis ch
inner join tblPlace p on ch.BestemmingID = p.PlaceID
) t
group by t.Gps
I tried changing the where clause to a fixed number (42) instead of t.ChassidID and still get the error, so there is only one place left that could cause the error I assume, but I cant see why.
I probably am missing something simple but I just cannot see it.
What is wrong with this query ?
I am using Sql Server 2014
Try declaring your filtered table in a CTE, then referencing this CTE both times.
;WITH FilteredChassis AS
(
select
ch.ChassisID,
p.GPS
from
tblChassis ch
inner join tblPlace p on ch.BestemmingID = p.PlaceID
)
select
t.Gps,
IDs = stuff( ( select ',' + convert(varchar, t2.ChassisID)
from FilteredChassis as t2
where t2.Gps = t.Gps
for XML path('')
)
, 1, 1, ''
)
from
FilteredChassis AS t
group by
t.Gps
I've made the link through gps, I believe that's what you need.

Surrounding query with parenthesis cause error For XML

When trying to wrap query for further processing I get an error by just putting parenthesis around the query. See query below, if I simply remove the wrapping parenthesis, the query works. But when I use the parenthesis I get this error:
Incorrect syntax near the keyword 'for'
(
select '; ' + [NAME]
from (select a.[NAME] as 'NAME'
from [MS].[dbo].[A_POSITIONS] a
where a.ID = 208418
except
select top 1 b.[NAME]
from [MS].[dbo].[A_POSITIONS] b
where b.ID = 208418) as c
for XML PATH(''),TYPE
)
Anyone have an idea on why this is happening?
Thanks!
Adding the parenthesis alone around your query leads to an error, but if you type a SELECT before the opening parenthesis the code will run, meaning that you can use it for further processing:
if OBJECT_ID('A_POSITIONS') is not null
drop table [dbo].[A_POSITIONS]
create table [dbo].[A_POSITIONS]([ID] int,[Name] varchar(100))
insert into [dbo].[A_POSITIONS] values
(208418, 'one'),(208418, 'two'),(208418, 'three'),(208418, 'four'),(208418, 'five')
select
(
select '; ' + [NAME]
from (select a.[NAME] as 'NAME'
from [MS].[dbo].[A_POSITIONS] a
where a.ID = 208418
except
select top 1 b.[NAME]
from [MS].[dbo].[A_POSITIONS] b
where b.ID = 208418) as c
for XML PATH(''),TYPE
)
One last consideration: TOP keyword without an ORDER BY clause is non-deterministic.
From Microsoft Docs (more info here):
When TOP is used in conjunction with the ORDER BY clause, the result set is limited to the first N number of ordered rows; otherwise, it returns the first N number of rows in an undefined order
So you should really consider adding an ORDER BY to the second part of your query:
select '; ' + [NAME]
from (select a.[NAME] as 'NAME'
from [MS].[dbo].[A_POSITIONS] a
where a.ID = 208418
except
select top 1 b.[NAME]
from [MS].[dbo].[A_POSITIONS] b
where b.ID = 208418
--Add an ORDER BY clause here
) as c
for XML PATH(''),TYPE

SQL Server Regular expression extract pattern from DB colomn

I have a question about SQL Server: I have a database column with a pattern which is like this:
up to 10 digits
then a comma
up to 10 digits
then a semicolon
e.g.
100000161, 100000031; 100000243, 100000021;
100000161, 100000031; 100000243, 100000021;
and I want to extract within the pattern the first digits (up to 10) (1.) and then a semicolon (4.)
(or, in other words, remove everything from the semicolon to the next semicolon)
100000161; 100000243; 100000161; 100000243;
Can you please advice me how to establish this in SQL Server? Im not very familiar with regex and therefore have no clue how to fix this.
Thanks,
Alex
Try this
Declare #Sql Table (SqlCol nvarchar(max))
INSERT INTO #Sql
SELECT'100000161,100000031;100000243,100000021;100000161,100000031;100000243,100000021;'
;WITH cte
AS (SELECT Row_number()
OVER(
ORDER BY (SELECT NULL)) AS Rno,
split.a.value('.', 'VARCHAR(1000)') AS Data
FROM (SELECT Cast('<S>'
+ Replace( Replace(sqlcol, ';', ','), ',',
'</S><S>')
+ '</S>'AS XML) AS Data
FROM #Sql)AS A
CROSS apply data.nodes('/S') AS Split(a))
SELECT Stuff((SELECT '; ' + data
FROM cte
WHERE rno%2 <> 0
AND data <> ''
FOR xml path ('')), 1, 2, '') AS ExpectedData
ExpectedData
-------------
100000161; 100000243; 100000161; 100000243
I believe this will get you what you are after as long as that pattern truly holds. If not it's fairly easy to ensure it does conform to that pattern and then apply this
Select Substring(TargetCol, 1, 10) + ';' From TargetTable
You can take advantage of SQL Server's XML support to convert the input string into an XML value and query it with XQuery and XPath expressions.
For example, the following query will replace each ; with </b><a> and each , to </a><b> to turn each string into <a>100000161</a><a>100000243</a><a />. After that, you can select individual <a> nodes with /a[1], /a[2] :
declare #table table (it nvarchar(200))
insert into #table values
('100000161, 100000031; 100000243, 100000021;'),
('100000161, 100000031; 100000243, 100000021;')
select
xCol.value('/a[1]','nvarchar(200)'),
xCol.value('/a[2]','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
-------------------------
A1 A2
100000161 100000243
100000161 100000243
value extracts a single value from an XML field. nodes returns a table of nodes that match the XPath expression. The following query will return all "keys" :
select
a.value('.','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
cross apply xCol.nodes('a') as y(a)
where a.value('.','nvarchar(200)')<>''
------------
100000161
100000243
100000161
100000243
With 200K rows of data though, I'd seriously consider transforming the data when loading it and storing it in indivisual, indexable columns, or add a separate, related table. Applying string manipulation functions on a column means that the server can't use any covering indexes to speed up queries.
If that's not possible (why?) I'd consider at least adding a separate XML-typed column that would contain the same data in XML form, to allow the creation of an XML index.

SQL: How to CONCAT value

How can I return the values of MainEmail in the query below, delimited by commas and still count MDCselect?
declare #MainHospital varchar(50)='hospital 1'
select distinct mainhospital , f.Item, count(*) Count
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
To be clear the above returns this: http://i.imgur.com/F1oPU6P.jpg
So there were 3 separate entries/people that selected "02-Eye". I want to list out their emails(MainEmail) comma delimited. Please let me know if I am unclear.
Assuming from your use of CROSS APPLY that you are using SQL Server, and that it is at least version 2005, you can use XML to do the concatenation as follows:
declare #MainHospital varchar(50)='hospital 1';
select mainhospital , f.Item, count(*) Count
,Stuff(
(select distinct ', ' + m.MainEmail
from SurveyPicList m
where m.MainHospital = #MainHospital
and ':' + m.MDCselect + ':' like '%:' + f.Item + ':%'
FOR XML PATH ('')),
1, 2, '') as Emails
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
From the name I am assuming that splitstrings splits its first argument into items separated by its second argument. Hence I used like to check for f.Item in m.MDCselect in the WHERE condition of the subselect. Actually, what this WHERE condition is doing is collecting all the rows from another instance of the same table that match one record in the final grouped output.

Deleting *multiple* items from a comma delimted list in SQL

I have a legacy field which contains a comma delimited list of userID's, I will be replacing this is at some point but for now I just need to maintain it, I have set up a trigger so when a user is added to a group it sticks it in to the list no problem, however when a user is deleted I need a trigger to remove it from the list. I used this bit of code which works fine when you delete just one member at a time, however I need to make it work for multiple deletes in one go, I normally join to the deleted table and boom, I'm away but in this instance it won't work.
Can you give me an idea of the best way to delete multiple userID's from multiple comma delimited strings on a trigger?
Here is the current trigger.
DECLARE #MemberID int
SELECT #MemberID = MemberID FROM Deleted
UPDATE MemberGroups SET MembersList = SUBSTRING(
REVERSE (SUBSTRING (REVERSE (REPLACE(',' + mgs.MembersList + ',', ',' + CAST(#MemberID AS varchar) + ',', ',') ) , 2, 8000) ), 2, 8000)
FROM MemberGroups mgs
WHERE ',' + mgs.MembersList + ',' LIKE (',%' + CAST(#MemberID AS Varchar) + '%,')
If you want to play a bit with Xml, you can transform the comma-separated list of values into an Xml, apply the modifications, then re-create the list. In my example I transform the string into a well-formed Xml, use the nodes and value functions to return the rowset, join with deleted and keep the remaining member ids, the re-create the string using FOR XML PATH. Here is the code:
--update the groups if the member list contains more then one item
;WITH results(GroupID, MemberID) AS (
SELECT t.GroupID, t.MemberID FROM (
SELECT GroupID, t.c.value('text()[1]', 'INT') AS MemberID
FROM (SELECT GroupID, CONVERT(XML, '<ms><m>' + REPLACE(MemberList, ',', '</m><m>') + '</m></ms>') AS ms FROM MemberGroups) x
CROSS APPLY x.ms.nodes('//m') AS t(c)
) t
LEFT JOIN deleted d ON t.MemberID = d.MemberID
WHERE d.MemberID IS NULL
) UPDATE mg SET
MemberList = SUBSTRING((SELECT ',' + CONVERT(VARCHAR(10), MemberID) FROM results WHERE GroupID = r.GroupID FOR XML PATH('')), 2, 8000)
FROM MemberGroups mg
INNER JOIN results r ON mg.GroupID = r.GroupID
--update the groups if the member list contains one item only
UPDATE mg SET
MemberList = NULL
FROM MemberGroups mg
INNER JOIN deleted d ON mg.MemberList = CONVERT(VARCHAR(10), d.MemberID)
SELECT * FROM MemberGroups
What I would have done in this case:
Store the list of IDs to be deleted in a temp table named #todelete.
Declare a new list. #newlist varchar(max)
Parse the original member list and extract the member ids one by one and put it into #newlist. But while inserting make sure it is not present in #todelete.
Replace the content of original member list with the content of #newlist.

Resources