Replace columns separated by string with id from another table - SQL Server - sql

I have following 2 tables in SQL Server
Category table:
Category
--------------------------
Delivery;Gauges;Book;Table
Category id:
id name
-----------------
13183 Delivery
88781 Gauges
88782 Book
12512 Table
Intended result is to have category table replaced with category id, as:
Category
-----------------------
13183;88781;88782;12512
I approached this by first separating category columns into separate columns using :
ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
and so on. Then used left join and replace on each new column. Isn't there an easier way to do this? I searched on the net and stackoverflow but can't seem to find anything similar.

You can try to make a function to split your string value by a character.
CREATE FUNCTION Split_fun
( #Words nvarchar(MAX)
, #splitStr varchar(50)
)
RETURNS #Result_Table TABLE
(
[word] nvarchar(max) NULL
)
BEGIN
Declare #TempStr nvarchar(MAX)
WHILE (CHARINDEX(#splitStr,#Words)>0)
BEGIN
Set #TempStr=SUBSTRING(#Words,1,CHARINDEX(#splitStr,#Words)-1)
Insert into #Result_Table (word) Values (#TempStr)
Set #Words = REPLACE(#Words,#TempStr+#splitStr,'')
END/*End While*/
IF(LEN(RTRIM(LTRIM(#Words)))>0 And CHARINDEX(#splitStr,RTRIM(LTRIM(#Words)))=0)
Begin
Set #TempStr=#Words
Insert into #Result_Table (word) Values (#TempStr)
End
RETURN
END
you can use this function to make a result set by ';'.
do self-join with Category id table.
final you can use FOR XML connect all string by ; to get your expectation result.
;with cte as (
SELECT id
FROM T CROSS APPLY Split_fun(Category,';') v
JOIN T1 on v.word = t1.Category
)
select STUFF((
select distinct ';'+ cast(id as varchar(10))
FROM cte
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
sqlfiddle

Related

Split string with '+ 'and search whether all the strings exist in one row in table

I have a table Rules with 2 columns:
RID
RuleValue
DECLARE #RuleType VARCHAR(MAX)= 'DDDD+FFFF' ;
I want to split and search the above variable 'DDDD+FFFF' in rulevalue column.
Below image is the rules table:
after splitting and searching in rulevalue column the output should be as below:
If order does not matter you can create a condition from your variable and then use it with a like filter in your where clause:
declare #Rules table (RID int, RuleValue varchar(50))
insert into #Rules
values
(1,'DDDD+FFFF')
,(2,'DDDD+EEEE+FFFF')
,(3,'BBBB+CCCC')
,(4,'BBBB+DDDD')
,(5,'CCCC+EEEE')
,(6,'BBBB+DDDD')
DECLARE #RuleType VARCHAR(MAX)= 'DDDD+FFFF' ;
select *
from #Rules
where RuleValue like '%' + replace(#RuleType, '+','%') + '%'
Results:
Edit after comment from OP
If order matters, then the solution is a bit trickier.
declare #Rules table (RID int, RuleValue varchar(50))
insert into #Rules
values
(1,'DDDD+FFFF')
,(2,'DDDD+EEEE+FFFF')
,(3,'BBBB+CCCC')
,(4,'BBBB+DDDD')
,(5,'CCCC+EEEE')
,(6,'BBBB+DDDD')
DECLARE #RuleType VARCHAR(MAX)= 'DDDD+FFFF+EEEE' ;
--define a table variable to hold every component of the rule type
declare #splittedRules table (SplittedRule nvarchar(max))
--fill the table variable with each component of the rule type
--since you use SQL Server 2012 you must use xml syntax to split the string
insert into #splittedRules
SELECT Split.a.value('.', 'NVARCHAR(MAX)') as SplittedRule
FROM
(
SELECT CAST('<X>'+REPLACE(#RuleType, '+', '</X><X>')+'</X>' AS XML) AS String
) AS A
CROSS APPLY String.nodes('/X') AS Split(a)
--now you can see if each rule matches a single component of the rule type
;with compare as
(
select
r.RID
,r.RuleValue
,spl.SplittedRule
, case when CHARINDEX(spl.SplittedRule, r.RuleValue) > 0 then 1 else 0 end as ok
from
#Rules as r
cross apply
#splittedRules spl
)
--finally you can perform a group by checking
--which rule matches all the components of the rule type
select
RID, RuleValue
from
compare
group by
RID, RuleValue
having
sum (ok)=(select count(*) from #splittedRules)
order by RID
Results:

Function which takes a column values and convert it into a comma separated values

I want an SQL function which takes a result of a select statement as parameter and return a string of comma separated values for the result. If there is a NULL value then it should leave a space and continue with the result.
I tried using the COALESCE() expression, but this takes out the NULL values and returns only valid values.
declare #str varchar(MAX)
SELECT #str= coalesce(#str + ',', '')+ a.D8_BOOK_YEAR_END
FROM (select D8_BOOK_YEAR_END from CUST_PRODUCT_ACCOUNTS
WHERE CUST_PRODUCT_ID=1) a
print #str
For example: In the image, I need to pass the column NAME into the function and it should return me the values as Mango, ,Apple,Grape.
I want an SQL function which takes a result of a select statement as parameter and return a string of comma separated values for the result. If there is a null value then it should leave a space and continue with the result.
You can do that as
--Create a table (just for test)
CREATE TABLE T(
Str VARCHAR(45)
);
INSERT INTO T VALUES
('One'),
(NULL),
('Two'),
('Three');
-- Create a type
CREATE TYPE MyData AS TABLE (Str NVARCHAR(300));
-- Create the function
CREATE FUNCTION dbo.MyFunc(
#Data MyData READONLY
)
RETURNS NVARCHAR(300)
AS
BEGIN
DECLARE #Result NVARCHAR(300) = N'';
SELECT #Result = STUFF(
(
SELECT ',' + ISNULL(Str, '') --Or ' ' as you like
FROM #Data
FOR XML PATH('')
), 1, 1, ''
);
RETURN (#Result);
END
-- Finally, use it
DECLARE #Test MyData;
INSERT INTO #Test SELECT * FROM T;
SELECT dbo.MyFunc(#Test);
Returns:
+------------------+
| (No column name) |
+------------------+
| One,,Two,Three |
+------------------+
And here is a live demo
DECLARE #TABLE TABLE (
ID INT
,Info VARCHAR(MAX)
)
INSERT INTO #TABLE
VALUES (1,'mango')
INSERT INTO #TABLE
VALUES (1,'apple')
INSERT INTO #TABLE
VALUES (1,null)
INSERT INTO #TABLE
VALUES (1,'grape')
INSERT INTO #TABLE
VALUES (2,'mango1')
INSERT INTO #TABLE
VALUES (2,null)
INSERT INTO #TABLE
VALUES (2,null)
INSERT INTO #TABLE
VALUES (2,'grape1')
SELECT ID
,STUFF((
SELECT ',' + isnull(info, '')
FROM #TABLE T1
WHERE T1.id = T2.ID
FOR XML PATH('')
), 1, 1, '')
FROM #TABLE T2
GROUP BY ID
Use ISNULL(field,' ') somewhere to avoid the NULL value behavior.
You can use SQL Concatenation using XML PATH() method as illustrated with following sample query
SELECT
STUFF(
(
SELECT
',' + [user]
FROM dbo.UserCategoryValues
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, ''
) As concatenated_string
This will take care of NULL values. Those NULLs will not be concatenated in to the list
But you can not wrap this code into a function as dynamic SQL that will take the field name and table name and build a SQL statement dynamically. SQL engine will throw an exception in that case

How to concatenate all the rows of a single int column to get a single string?

Table: I have a database table table_1 in SQL Server 2012 with data as:
CREATE TABLE table_1
(
name nvarchar(128) not null,
state tinyint null,
state_desc nvarchar(60) null
);
INSERT INTO table_1
VALUES ('text1',1,'ONLINE'),
('text2',0,'ONLINE'),
('text3',0,'ONLINE'),
('text4',0,'ONLINE'),
('TEXTTE',0,'ONLINE'),
('TEXTTEXT',0,'ONLINE'),
('EXTTEXT',0,'ONLINE'),
('TEXTex_EX_Ext',0,'ONLINE'),
('TEXTex_TEX_Ext',0,'ONLINE'),
('TEXTTEXTText',0,'ONLINE'),
('Texttextext',0,'ONLINE'),
('TextTextext',0,'ONLINE'),
('TEXTER',1,'ONLINE');
I want to select all the values in column state and concatenate them to get a single string.
So desired result is a single row and a single column with data as:
1000000000001
What I have tried: Using substring but it skips first row (1) and gives 13 rows (000000000001 in each row) instead of just 1.
Select
substring(
(
Select ''+ST1.[state] AS [text()]
From dbo.table_1 ST1
For XML PATH ('')
), 2, 1000) [state]
From dbo.table_1 ST2;
Is there any other way to do this?
I will not know the number of rows and I want to keep the sequence while concatenating. (First row should be first digit, second row second digit, etc)
It doesn't matter if the first row goes rightmost or leftmost after concatenation, just it needs to be consistent and in sequence.
Your query is almost correct. You just do not need the part with substring. Also I suggest you to order rows while concatenating with for xml path. Do you have some ID column? I have slightly modified your query:
select result = (
Select ''+ST1.[state] AS [text()]
From dbo.table_1 ST1
For XML PATH ('')
)
--Try this query
SELECT replace([state],',','')
FROM(
SELECT stuff( (SELECT ',' + CONVERT(VARCHAR(1000), ST1.[state])
FROM table_1 ST1
FOR XML PATH(''), TYPE).value('.', 'varchar(max)')
,1,1,'')
AS [state]
)t
try using variable
see this
declare #Str varchar(1000)
set #Str = ''
update table_1
set #Str = #Str + cast(state as varchar)
select #Str

Custom aggregate function (concat) in SQL Server

Question: I want to write a custom aggregate function that concatenates string on group by.
So that I can do a
SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2) as f2
FROM TABLE_XY
GROUP BY FIELD1, FIELD2
All I find is SQL CRL aggregate functions, but I need SQL, without CLR.
Edit:1
The query should look like this:
SELECT SUM(FIELD1) as f1, MYCONCAT(FIELD2) as f2
FROM TABLE_XY
GROUP BY FIELD0
Edit 2:
It is true that it isn't possible without CLR.
However, the subselect answer by astander can be modified so it doesn't XML-encode special characters.
The subtle change for this is to add this after "FOR XML PATH":
,
TYPE
).value('.[1]', 'nvarchar(MAX)')
Here a few examples
DECLARE #tT table([A] varchar(200), [B] varchar(200));
INSERT INTO #tT VALUES ('T_A', 'C_A');
INSERT INTO #tT VALUES ('T_A', 'C_B');
INSERT INTO #tT VALUES ('T_B', 'C_A');
INSERT INTO #tT VALUES ('T_C', 'C_A');
INSERT INTO #tT VALUES ('T_C', 'C_B');
INSERT INTO #tT VALUES ('T_C', 'C_C');
SELECT
A AS [A]
,
(
STUFF
(
(
SELECT DISTINCT
', ' + tempT.B AS wtf
FROM #tT AS tempT
WHERE (1=1)
--AND tempT.TT_Status = 1
AND tempT.A = myT.A
ORDER BY wtf
FOR XML PATH, TYPE
).value('.[1]', 'nvarchar(MAX)')
, 1, 2, ''
)
) AS [B]
FROM #tT AS myT
GROUP BY A
SELECT
(
SELECT
',äöü<>' + RM_NR AS [text()]
FROM T_Room
WHERE RM_Status = 1
ORDER BY RM_NR
FOR XML PATH('')
) AS XmlEncodedNoNothing
,
SUBSTRING
(
(
SELECT
',äöü<>' + RM_NR AS [data()]
FROM T_Room
WHERE RM_Status = 1
ORDER BY RM_NR
FOR XML PATH('')
)
,2
,10000
) AS XmlEncodedSubstring
,
(
STUFF
(
(
SELECT ',äöü<>' + RM_NR + CHAR(10)
FROM T_Room
WHERE RM_Status = 1
ORDER BY RM_NR
FOR XML PATH, TYPE
).value('.[1]', 'nvarchar(MAX)')
, 1, 1, ''
)
) AS XmlDecodedStuffInsteadSubstring
You cannot write custom aggregates outside of the CLR.
The only type of functions you can write in pure T-SQL are scalar and table valued functions.
Compare the pages for CREATE AGGREGATE, which only lists CLR style options, with CREATE FUNCTION, which shows T-SQL and CLR options.
Have a look at something like. This is not an aggregate function. If you wish to implement your own aggregate function, it will have to be CLR...
DECLARE #Table TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #Table (ID,Val) SELECT 1, 'A'
INSERT INTO #Table (ID,Val) SELECT 1, 'B'
INSERT INTO #Table (ID,Val) SELECT 1, 'C'
INSERT INTO #Table (ID,Val) SELECT 2, 'B'
INSERT INTO #Table (ID,Val) SELECT 2, 'C'
--Concat
SELECT t.ID,
SUM(t.ID),
stuff(
(
select ',' + t1.Val
from #Table t1
where t1.ID = t.ID
order by t1.Val
for xml path('')
),1,1,'') Concats
FROM #Table t
GROUP BY t.ID
Starting from 2017 there is built-in concatenate aggregate function STRING_AGG :)
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017
Found this link around concatenation which covers methods like
Concatenating values when the number of items are not known
Recursive CTE method
The blackbox XML methods
Using Common Language Runtime
Scalar UDF with recursion
Table valued UDF with a WHILE loop
Dynamic SQL
The Cursor approach
Non-reliable approaches
Scalar UDF with t-SQL update extension
Scalar UDF with variable concatenation in SELECT
Though it doesn't cover aggerate functions there may be some use around concatenation in there to help you with your problem.
This solution works with no need of deploy from Visual studio or dll file in server.
Copy-Paste and it Work!
https://github.com/orlando-colamatteo/ms-sql-server-group-concat-sqlclr
dbo.GROUP_CONCAT(VALUE )
dbo.GROUP_CONCAT_D(VALUE ), DELIMITER )
dbo.GROUP_CONCAT_DS(VALUE , DELIMITER , SORT_ORDER )
dbo.GROUP_CONCAT_S(VALUE , SORT_ORDER )
You could do something like what I have done below to create a custom aggregate concatenation function in pure T-SQL. Obviously I have gone with a hard coded table name and group by column but it should illustrate the approach. There is probably some way to make this a truly generic function using dynamic TSQL constructed from input parameters.
/*
User defined function to help perform concatenations as an aggregate function
Based on AdventureWorks2008R2 SalesOrderDetail table
*/
--select * from sales.SalesOrderDetail
IF EXISTS (SELECT *
FROM sysobjects
WHERE name = N'fnConcatenate')
DROP FUNCTION fnConcatenate
GO
CREATE FUNCTION fnConcatenate
(
#GroupByValue int
)
returnS varchar(8000)
as
BEGIN
DECLARE #SqlString varchar(8000)
Declare #TempStore varchar(25)
select #SqlString =''
Declare #MyCursor as Cursor
SET #MyCursor = CURSOR FAST_FORWARD
FOR
Select ProductID
From sales.SalesOrderDetail where SalesOrderID = #GroupByValue
order by SalesOrderDetailID asc
OPEN #MyCursor
FETCH NEXT FROM #MyCursor
INTO #TempStore
WHILE ##FETCH_STATUS = 0
BEGIN
select #SqlString = ltrim(rtrim(#TempStore )) +',' + ltrim(rtrim(#SqlString))
FETCH NEXT FROM #MyCursor INTO #TempStore
END
CLOSE #MyCursor
DEALLOCATE #MyCursor
RETURN #SqlString
END
GO
select SalesOrderID, Sum(OrderQty), COUNT(*) as DetailCount , dbo.fnConcatenate(salesOrderID) as ConCatenatedProductList
from sales.SalesOrderDetail
where salesOrderID= 56805
group by SalesOrderID

SQL Server query with multiple values in one column relating to another column

Situation: This table holds the relation information between a Documents table and an Users table. Certain Users need to review or approve documents (Type). I would like to have it to where I could get all of the reviewers on one line if needed. So if three users review Document 1, then a row would have 346, 394, 519 as the value, since those are the reviewers
Table: xDocumentsUsers
DocID..UserID....Type...
1........386......approver
1........346......reviewer
1........394......reviewer..
1........519......reviewer..
4........408......reviewer..
5........408......reviewer..
6........408......reviewer..
7........386......approver..
7........111......readdone..
7........346......reviewer..
8........386......approver..
8........346......reviewer..
9........386......approver..
9........346......reviewer..
10.......386......approver..
11.......386......approver..
11......346......reviewer..
12......386......approver..
12......346......reviewer..
13......386......approver..
13......346......reviewer..
14......386......approver..
14......346......reviewer..
15......386......approver
So desired result would be...
DocID..UserID................Type...
1........386....................approver
1........346,394,519......reviewer.
4........408....................reviewer..
5........408....................reviewer..
6........408....................reviewer..
7........386....................approver..
7........111....................readdone..
7........346....................reviewer..
8........386....................approver..
8........346....................reviewer..
9........386....................approver..
9........346....................reviewer..
10......386....................approver..
11......386....................approver..
11......346....................reviewer..
12......386....................approver..
12......346....................reviewer..
13......386....................approver..
13......346....................reviewer..
14......386....................approver..
14......346....................reviewer..
15......386....................approver
The FOR XML PATH is a great solution. You need to be aware, though, that it will convert any special characters in the inner SELECTs result set into their xml equivalent - i.e., & will become & in the XML result set. You can easily revert back to the original character by using the REPLACE function around the inner result set. To borrow from astander's previous example, it would look like (note that the SELECT as the 1st argument to the REPLACE function is enclosed in ():
--Concat
SELECT t.ID,
REPLACE((SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')), '&', '&'))
FROM #Table t
GROUP BY t.ID
Have a look at
Emulating MySQL’s GROUP_CONCAT() Function in SQL Server 2005
Is there a way to create a SQL Server function to “join” multiple rows from a subquery into a single delimited field?
A simple example is
DECLARE #Table TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #Table (ID,Val) SELECT 1, 'A'
INSERT INTO #Table (ID,Val) SELECT 1, 'B'
INSERT INTO #Table (ID,Val) SELECT 1, 'C'
INSERT INTO #Table (ID,Val) SELECT 2, 'B'
INSERT INTO #Table (ID,Val) SELECT 2, 'C'
--Concat
SELECT t.ID,
(
SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')
)
FROM #Table t
GROUP BY t.ID
Does this help?
SELECT DocID
, [Type]
, (SELECT CAST(UserID + ', ' AS VARCHAR(MAX))
FROM [xDocumentsUsers]
WHERE (UserID = x1.UserID)
FOR XML PATH ('')
) AS [UserIDs]
FROM [xDocumentsUsers] AS x1