SQL: How to CONCAT value - sql-server

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.

Related

csv from subquery in IN() causes conversion error

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

Arrow(-->) does not show in SQL Server SELECT when use as xml path

I am trying to get all route path separating with arrow (-->). But the arrow (-->) does not work, it is shown as -->.
SELECT
vrmd.LocationName + '--> '
FROM
dbo.tbl_VehicleRouteMapDetail vrmd
WHERE
vrmd.VrmapId = 1
ORDER BY
vrmd.VrmapId
FOR XML PATH('')
But it's working perfectly this way:
DECLARE #Route VARCHAR(MAX)= ''
SELECT #Route = #Route + vrmd.LocationName + '--> '
FROM dbo.tbl_VehicleRouteMapMaster vrmm
INNER JOIN dbo.tbl_VehicleRouteMapDetail vrmd ON vrmd.VrmapId = vrmm.VrmapId
ORDER BY vrmd.VrmapDetailId ASC
Why choose first query?
I need to use this query as subquery. In this case second query does not work for me:
SELECT
COUNT(ewva.VehicleId) Allocation, vii.Capacity,
STUFF((SELECT vrmd.LocationName + '=> '
FROM dbo.tbl_VehicleRouteMapDetail vrmd
WHERE vrmd.VrmapId = vrmm.VrmapId
ORDER BY vrmd.VrmapId
FOR XML PATH('')), 1, 1, '') AS name_csv
FROM
dbo.tbl_VehicleInfoInternal vii
INNER JOIN
dbo.tbl_EmpWiseVehicleAllocation ewva ON ewva.VehicleId = vii.VehicleId
INNER JOIN
dbo.tbl_VehicleRouteMapMaster vrmm ON vrmm.VehicleId = vii.VehicleId
GROUP BY
ewva.VehicleId, vii.Capacity, vrmm.VrmapId
Any help would be appreciated.
It's as simple as adding the TYPE directive to your FOR XML clause (docs here). This will return the result as an instance of the XML type rather than text. Text will have certain characters (e.g. > and <) translated (> and <) to fit within an XML element.
SELECT
vrmd.LocationName + '--> '
FROM
dbo.tbl_VehicleRouteMapDetail vrmd
WHERE
vrmd.VrmapId = 1
ORDER BY
vrmd.VrmapId
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.

How to merge several records in one using COALESCE in MS SQL 2008

I'm guering three tables from the DataBase with the idea to extract information for a certain Client so I get single values from all columns except one.
My tables are :
Client :: (ClientId | ClientName)
Notifications :: (NotificationId | NotificiationText)
ClientsNotifications :: (ClientId | NotificationId)
A single client may have multiple notifications related to him, but I want to get them in a single row so after little research I decied that I should use COALESCE.
I made this query :
SELECT c.ClientName, (COALESCE(n.NotificiationText,'') + n.NotificiationText + ';')
FROM [MyDB].[dbo].[Client] AS c
LEFT JOIN [MyDB].[dbo].[ClientsNotifications] AS cn
ON c.ClientId = cn.ClientId
LEFT JOIN [MyDB].[dbo].[Notifications] AS n
ON c.ClientId = cn.ClientId
AND cn.NotificationId = n.NotificationId
WHERE c.ClientId = 1
For this particular user I have two notifications, the result I get is - two rows, on the first row I have the first notification concatenated for itself (I have two times the same string) on the second row I have the second notification concateneated for itself again.
So There are three things that I want but don't know how to do -
Right now for column name I get (No column name) so I want to give it one
I want the two notifications (or as many as they are) concatenated in a single row
I want to determine some delimeter so when I fetch the records I can perform split. In my example I use this - ';') which I think should act as delimeter but the concatenated strings that I have are not separeted by ; or anything.
You can give your column name an alias in the same way you do for a table, e.g.
SELECT <expression> AS ColumnAlias
However, for reasons detailed here I prefer using:
SELECT ColumnAlias = <expression>
Then to get multiple rows into columns you can use SQL Servers XML extensions to achieve this:
SELECT c.ClientName,
Notifications = STUFF(( SELECT ';' + n.NotificationText
FROM [MyDB].[dbo].[ClientsNotifications] AS cn
INNER JOIN [MyDB].[dbo].[Notifications] AS n
ON n.NotificationId = cn.NotificationId
WHERE c.ClientId = cn.ClientId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM [MyDB].[dbo].[Client] AS c
WHERE c.ClientId = 1;
Example on SQL Fiddle
An explanation of how this method works can be found in this answer so I shalln't repeat it here.
There's a trick to doing what you want to do. As it's written right now, you're just grabbing stuff off the same row. Also, multiple conditions on the second left join are unnecessary.
DECLARE #clientName VARCHAR(MAX) = '';
DECLARE #text VARCHAR(MAX) = '';
SELECT #clientName = c.ClientName
, #text = (CASE
WHEN n.NotificationText IS NOT NULL THEN #text + ';' + n.NotificationText
ELSE #text
END)
FROM [MyDB].[dbo].[Client] AS c
LEFT JOIN [MyDB].[dbo].[ClientsNotifications] AS cn
ON c.ClientId = cn.ClientId
LEFT JOIN [MyDB].[dbo].[Notifications] AS n
ON cn.NotificationId = n.NotificationId
WHERE c.ClientId = 1
SELECT #clientName AS ClientName, #text AS Notifications

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