Select comma separated string depending on existing columns - sql

I have a table in my database which have a set of bit value columns. I want to insert a comma separated string in a new column, depending on the values of these bit columns.
Lets say the columns are named:
c1,c2,c3,c4
If the value in a column is equal to 1, I want to include the string mapped as following:
c1: 'campaign1'
c2: 'campaign2'
c3: 'campaign3'
c4: 'campaign4'
So for exampel, if a row contains the following values:
c1 = 1, c2 = 0, c3 = 1, c4 = 0
I want to insert the following in a new column named 'Campaigns'
'campaign1,campaign3'
Any suggestions on how I can accomplish this?

I would do this using stuff() and some string manipulation:
select t.*,
stuff( ((case when c1 = 1 then ',campaign1' else '' end) +
(case when c2 = 1 then ',campaign2' else '' end) +
(case when c3 = 1 then ',campaign3' else '' end) +
(case when c4 = 1 then ',campaign4' else '' end)
), 1, 1, ''
) as campaigns
from t;

This works, but it might not best the best solution:
DECLARE #table TABLE (id INT, c1 INT, c2 INT, c3 INT, c4 INT);
INSERT INTO #table
SELECT 1, 1, 0, 1, 0
UNION ALL
SELECT 2, 0, 0, 1, 1;
WITH cte AS (
SELECT
id,
CASE WHEN c1 = 1 THEN 'campaign1,' ELSE '' END +
CASE WHEN c2 = 1 THEN 'campaign2,' ELSE '' END +
CASE WHEN c3 = 1 THEN 'campaign3,' ELSE '' END +
CASE WHEN c4 = 1 THEN 'campaign4,' ELSE '' END AS campaigns
FROM
#table)
SELECT
id,
CASE
WHEN LEN(campaigns) > 0 THEN LEFT(campaigns, LEN(campaigns) - 1)
ELSE ''
END AS campaigns
FROM
cte;
Gordon beat me to the STUFF method, which I was hastily writing when I realised this was better, but here's an answer that also shows how the UPDATE might work, as this is what you originally asked for:
DECLARE #table TABLE (id INT, c1 INT, c2 INT, c3 INT, c4 INT, campaigns VARCHAR(512));
INSERT INTO #table
SELECT 1, 1, 0, 1, 0, NULL
UNION ALL
SELECT 2, 0, 0, 1, 1, NULL;
UPDATE
t
SET
campaigns = STUFF(CASE WHEN c1 = 1 THEN ',campaign1' ELSE '' END +
CASE WHEN c2 = 1 THEN ',campaign2' ELSE '' END +
CASE WHEN c3 = 1 THEN ',campaign3' ELSE '' END +
CASE WHEN c4 = 1 THEN ',campaign4' ELSE '' END, 1, 1, '')
FROM
#table t;
SELECT * FROM #table;
Results:
id c1 c2 c3 c4 campaigns
1 1 0 1 0 campaign1,campaign3
2 0 0 1 1 campaign3,campaign4

Just to give another option, you can use concat and replace also
DECLARE #table TABLE (id INT, c1 INT, c2 INT, c3 INT, c4 INT)
INSERT INTO #table
SELECT 1, 1, 0, 1, 0 UNION ALL SELECT 2, 0, 0, 1, 1
select t.*,
replace(replace(c1, '1', 'Campaing1,'), '0', '') +
replace(replace(c2, '1', 'Campaing2,'), '0', '') +
replace(replace(c3, '1', 'Campaing3,'), '0', '') +
replace(replace(c4, '1', 'Campaing4,'), '0', '')
from #table t
The result is
id c1 c2 c3 c4 COLUMN1
-- -- -- -- -- -------
1 1 0 1 0 Campaing1,Campaing3,
2 0 0 1 1 Campaing3,Campaing4,
But I would go for Gordon's answer

You may use IIF function ( provided you're using SQL Server 2012+ ) as in the following :
select IIF(c1=1, 'Campaign1', '')+IIF(c2=1 and c1>0,',','')+
IIF(c2=1, 'Campaign2', '')+IIF(c3=1 and c1+c2>0,',','')+
IIF(c3=1, 'Campaign3', '')+IIF(c4=1 and c1+c2+c3>0,',','')+
IIF(c4=1, 'Campaign4', '') as 'Campaigns'
from tab t;
SQL Fiddle Demo

Related

SQL Server - Return All possible combinations of a 4 digit number passed to a stored procedure

I have a task to write a stored procedure or a function to return all possible combinations of a 4 digit number.
For example, if I pass 1234 to the stored procedure or function, it should return 4 digit numbers (all possible combinations), like
1123, 1112, 1324, 1342, 2134, 2234
and so on.
It can be of 4 digits only.
I have been doing this with using LIKE operator:
select *
from Table
where mynumber like '%1%'
and mynumber like '%2%'
and mynumber like '%3%'
and mynumber like '%4%'
but the problem is, I have hardcoded the numbers 1,2,3 and 4.
The number can be anything.
And these many LIKE operators can also impact the performance on a large table.
Can anybody give me some generic query to get the combinations?
Thanks in advance.
You can use a cross join:
with digits as (
select substring(num, 1, 1) as d union all
select substring(num, 2, 1) as d union all
select substring(num, 3, 1) as d union all
select substring(num, 4, 1) as d
)
select (d1.d + d2.d + d3.d + d4.d)
from digits d1 cross join
digits d2 cross join
digits d3 cross join
digits d4;
Note: This assumes that the number is a string (based on the fact that you use like in your question).
First you need to be able to break a four-digit number into separate digits. I suggest using a table variable and the modulus operator. Assuming we have an integer input named #input, we can break it into its digits using this:
DECLARE #Digits Table(Number int)
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
Now we have a table with four rows, one row per digit.
To create a combination of four digits, we need to include the table four times, meaning we need three joins. The joins have to be set up so no digit is duplicated. Thus our FROM and JOIN clauses will look like this:
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
Now to take the values and make a new, four-digit integer:
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
The complete solution:
CREATE PROC Combine(#input AS int)
AS
BEGIN
DECLARE #Digits Table(Number int)
;
INSERT INTO #Digits(Number)
VALUES (#input % 10),
(#input / 10 % 10),
(#input / 100 % 10),
(#input / 1000 % 10)
;
SELECT Number = D1.Number * 1000
+ D2.Number * 100
+ D3.Number * 10
+ D4.Number
FROM #Digits D1
JOIN #Digits D2 ON D2.Number <> D1.Number
JOIN #Digits D3 ON D3.Number <> D1.Number
AND D3.Number <> D2.Number
JOIN #Digits D4 ON D4.Number <> D1.Number
AND D4.Number <> D2.Number
AND D4.Number <> D3.Number
ORDER BY Number
;
END
Usage:
EXEC Combine 1234
Resultset:
Number
------
1234
1243
1324
1342
1423
1432
2134
2143
2314
2341
2413
2431
3124
3142
3214
3241
3421
4123
4132
4213
4231
4312
4321
24 row(s)
Click here to run the above code on RexTester
Improving #GordonLinoff's answer, you can add an additional column in you CTE so that you can only make sure that each number is only used once:
declare #num varchar(max);
set #num = '1234';
with numCTE as (
select SUBSTRING(#num, 1,1) as col, 1 as cnt union
select SUBSTRING(#num, 2,1) as col, 3 as cnt union
select SUBSTRING(#num, 3,1) as col, 9 as cnt union
select SUBSTRING(#num, 4,1) as col, 27 as cnt
)
select DISTINCT (a1.col+a2.col+a3.col+a4.col)
from numCTE a1
cross join numCTE a2
cross join numCTE a3
cross join numCTE a4
where a1.cnt + a2.cnt + a3.cnt + a4.cnt = 40
Additionally, you can remove the WHERE to allow each number to be used more than once.
Don't forget the DISTINCT keyword. :)
You can try this.
select * from Table where mynumber like '%[1234][1234][1234][1234]%'
if it should only be 4 digit
select * from Table where mynumber like '[1234][1234][1234][1234]'
Also, you can use [1-4] instead of [1234]
Here's query to return all combinations of four digits (characters in general):
select A.col + B.col + C.col + D.col [Combinations] from
(values ('1'),('2'),('3'),('4')) as A(col) cross join
(values ('1'),('2'),('3'),('4')) as B(col) cross join
(values ('1'),('2'),('3'),('4')) as C(col) cross join
(values ('1'),('2'),('3'),('4')) as D(col)
Taking inspiration from this answer:
WITH n AS (
SELECT n FROM (VALUES (1), (2), (3), (4)) n (n)
) SELECT ones.n + 10*tens.n + 100*hundreds.n + 1000*thousands.n
FROM n ones, n tens, n hundreds, n thousands
You can define a table in your stored procedure will all possible combinations but using letters for codding:
DECLARE #Combinations TABLE
(
[value] CHAR(4)
);
INSERT INTO #Combinations ([value])
VALUES ('AAAA')
,('AAAB')
,('AAAC')
,('AAAD')
...
Then update every latter with the input number:
DECLARE #Numner1 TINYINT = 2
,#Numner2 TINYINT = 5
,#Numner3 TINYINT = 1
,#Numner4 TINYINT = 3;
UPDATE #Combinations
SET [value] = REPLACE([value], 'A', #Numner1);
UPDATE #Combinations
SET [value] = REPLACE([value], 'B', #Numner2);
UPDATE #Combinations
SET [value] = REPLACE([value], 'C', #Numner3);
UPDATE #Combinations
SET [value] = REPLACE([value], 'D', #Numner4);
Then just join the table with your table:
select *
from Table A
INNER JOIN #Combinations B
ON A.[mynumber] = B.[value];
Try This approach
DECLARE #Num INT = 5432
;WITH CTE
AS
(
SELECT
SeqNo = 1,
Original = CAST(#Num AS VARCHAR(20)),
Num = SUBSTRING(CAST(#Num AS VARCHAR(20)),1,1)
UNION ALL
SELECT
SeqNo = SeqNo+1,
Original,
Num = SUBSTRING(Original,SeqNo+1,1)
FROM CTE
WHERE SeqNo < LEN(Original)
)
SELECT
MyStr = C1.Num+C2.Num+C3.Num+C4.Num
FROM CTE C1
CROSS JOIN CTE C2
CROSS JOIN CTE C3
CROSS JOIN CTE C4
WHERE
(
C1.SeqNo <> C2.SeqNo
AND
C3.SeqNo <> C4.SeqNo
AND
C4.SeqNo <> C1.SeqNo
AND
C2.SeqNo <> C3.SeqNo
AND
C1.SeqNo <> C3.SeqNo
AND
C4.SeqNo <> C2.SeqNo
)
ORDER BY 1
My Result
MyStr
2345
2354
2435
2453
2534
2543
3245
3254
3425
3452
3524
3542
4235
4253
4325
4352
4523
4532
5234
5243
5324
5342
5423
5432
Please try this. SET BASED Approach to generate all Possible combinations of a number-
IF OBJECT_ID('Tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
DECLARE # AS INT = 1234
IF LEN(#) <= 7
BEGIN
DECLARE #str AS VARCHAR(100)
SET #str = CAST(# AS VARCHAR(100))
DECLARE #cols AS VARCHAR(100) = ''
SELECT DISTINCT SUBSTRING(#str,NUMBER,1) n INTO #T FROM MASTER..spt_values WHERE number > 0 AND number <= LEN(#)
SELECT #cols = #cols + r
FROM ( SELECT DISTINCT CONCAT(', o',number,'.n') r FROM MASTER..spt_values WHERE number > 0 AND number <= (LEN(#)-1) )q
DECLARE #ExecStr AS VARCHAR(1000) = ''
SET #ExecStr = 'SELECT CAST(CONCAT( a.n' + #cols + ' ) AS INT) Combinations FROM #T a'
SELECT #ExecStr = #ExecStr + r FROM
(
SELECT DISTINCT CONCAT(' CROSS APPLY ( SELECT * FROM #T b' , number , ' WHERE ( b' , number, '.n' , ' <> a.n ) ',
CASE WHEN number = 1 then ''
WHEN number = 2 then ' AND ( b2.n <> o1.n )'
WHEN number = 3 then ' AND ( b3.n <> o1.n ) AND ( b3.n <> o2.n ) '
WHEN number = 4 then ' AND ( b4.n <> o1.n ) AND ( b4.n <> o2.n ) AND ( b4.n <> o3.n ) '
WHEN number = 5 then ' AND ( b5.n <> o1.n ) AND ( b5.n <> o2.n ) AND ( b5.n <> o3.n ) AND ( b5.n <> o4.n ) '
WHEN number = 6 then ' AND ( b6.n <> o1.n ) AND ( b6.n <> o2.n ) AND ( b6.n <> o3.n ) AND ( b6.n <> o4.n ) AND ( b6.n <> o5.n ) '
END
,') o' , number ) r FROM
MASTER..spt_values
WHERE number > 0 AND number <= (LEN(#)-1)
)p
EXEC (#ExecStr)
END
IF OBJECT_ID('tempdb..#T') IS NOT NULL
DROP TABLE tempdb..#T
OUTPUT
1432
1342
1423
1243
1324
1234
2431
2341
2413
2143
2314
2134
3421
3241
3412
3142
3214
3124
4321
4231
4312
4132
4213
4123
from - https://msbiskills.com/2016/05/20/sql-puzzle-generate-possible-combinations-of-a-number-puzzle/
You can try following alternative SQL Script as well
declare #param varchar(4) = '1234'
;with combination as (
select
distinct rn = DENSE_RANK() over (Order By num), num
from (
select substring(#param,1,1) as num
union all
select substring(#param,2,1)
union all
select substring(#param,3,1)
union all
select substring(#param,4,1)
) t
)
select
c1.num, c2.num, c3.num, c4.num,
cast(c1.num as char(1)) + cast(c2.num as char(1)) + cast(c3.num as char(1)) + cast(c4.num as char(1)) as number
from combination c1, combination c2, combination c3, combination c4
It produces 256 numbers for 4 digits
Actually this code is from SQL code which returns non-repeatable combinations in SQL of given set of items, but modified it to enable repeats of items in the output

How to insert a hyphen between blocks of alpha and numeric characters

I need to insert hyphens between blocks of alpha and numeric text in a string.
I am not even sure how to start on this problem.
ABC123 -> ABC-123
ABC123XYZ -> ABC-123-XYZ
D123 -> D-123
123C -> 123-C
This one will work for you with single value.
DECLARE #CODE VARCHAR(50) = '12ABC123XYZ'
,#NEWCODE VARCHAR(100) = ''
;WITH CTE
AS (
SELECT NUMBER
,SUBSTRING(#CODE, NUMBER, 1) AS VAL
FROM master.dbo.spt_values
WHERE TYPE = 'P'
AND number BETWEEN 1
AND LEN(#CODE)
)
SELECT #NEWCODE = #NEWCODE + CASE
WHEN ISNUMERIC(C1.VAL) <> ISNUMERIC(ISNULL(C2.VAL, C1.VAL))
THEN '-' + C1.VAL
ELSE C1.VAL
END
FROM CTE C1
LEFT JOIN CTE C2 ON C1.number = C2.number + 1
SELECT #NEWCODE
Result : 12-ABC-123-XYZ
And If you want this to work with table column, you need to create a scalar function.
CREATE FUNCTION CODE_SPLIT
(
#CODE VARCHAR(50)
)
RETURNS VARCHAR(100)
AS
BEGIN
DECLARE #NEWCODE VARCHAR(100) ='';
;WITH CTE
AS (
SELECT NUMBER
,SUBSTRING(#CODE, NUMBER, 1) AS VAL
FROM master.dbo.spt_values
WHERE TYPE = 'P'
AND number BETWEEN 1
AND LEN(#CODE)
)
SELECT #NEWCODE = #NEWCODE + CASE
WHEN ISNUMERIC(C1.VAL) <> ISNUMERIC(ISNULL(C2.VAL, C1.VAL))
THEN '-' + C1.VAL
ELSE C1.VAL
END
FROM CTE C1
LEFT JOIN CTE C2 ON C1.number = C2.number + 1
RETURN #NEWCODE
END
GO
And call it on your actual table
Schema:
SELECT * INTO #TAB FROM(
SELECT 'ABC123' AS CODE
UNION ALL
SELECT 'ABC123XYZ'
UNION ALL
SELECT 'D123'
UNION ALL
SELECT '123C'
)A
SELECT CODE, dbo.CODE_SPLIT(CODE) AS NEWCODE FROM #TAB
Result:
+-----------+-------------+
| CODE | NEWCODE |
+-----------+-------------+
| ABC123 | ABC-123 |
| ABC123XYZ | ABC-123-XYZ |
| D123 | D-123 |
| 123C | 123-C |
+-----------+-------------+
patindex('%[0-9]%') return index of first number.
patindex('%[^0-9]%') return index of first non-numeric character.
You could use recursive CTE and PATINDEX like this.
DECLARE #SampleData AS TABLE
(
TextValue varchar(100)
)
INSERT INTO #SampleData
VALUES ('ABC124'), ('ABC123XYZ'), ('123C'), ('ABC'), ('1A2B3C')
;WITH cte AS
(
SELECT sd.TextValue AS RootText,
sd.TextValue AS CurrentText,
CAST('' AS varchar(100)) AS Result
FROM #SampleData sd
UNION ALL
SELECT
c.RootText,
CASE
WHEN patindex('%[0-9]%', c.CurrentText) = 0 OR patindex('%[^0-9]%', c.CurrentText) = 0
THEN ''
WHEN patindex('%[0-9]%', c.CurrentText) > patindex('%[^0-9]%', c.CurrentText)
THEN RIGHT(c.CurrentText, len(c.CurrentText) - patindex('%[0-9]%', c.CurrentText) + 1)
ELSE RIGHT(c.CurrentText, len(c.CurrentText) - patindex('%[^0-9]%', c.CurrentText) + 1)
END AS CurrentText,
CAST(
CASE
WHEN patindex('%[0-9]%', c.CurrentText) = 0 OR patindex('%[^0-9]%', c.CurrentText) = 0
THEN Result + '-' + c.CurrentText
WHEN patindex('%[0-9]%', c.CurrentText) > patindex('%[^0-9]%', c.CurrentText)
THEN Result + '-' + LEFT(CurrentText, patindex('%[0-9]%', c.CurrentText) - 1)
ELSE Result + '-' + LEFT(CurrentText, patindex('%[^0-9]%', c.CurrentText) - 1)
END AS varchar(100)
) AS Result
FROM cte c
WHERE LEN(CurrentText) > 0
)
SELECT cte.RootText, STUFF(cte.Result, 1,1,'') AS Result FROM cte
WHERE cte.CurrentText = ''
Demo link: http://rextester.com/FTYA72053

Splitting single row into more columns based on column value

I've a requirement to get 3 similar set of row data replacing the column value if any certain value exists in the given column('[#]' in this case). For example
---------------------
Type Value
---------------------
1 Apple[#]
2 Orange
3 Peach[#]
I need to modify the query to get value as below
----------------------
Type Value
--------------------
1 Apple1
1 Apple2
1 Apple3
2 Orange
3 Peach1
3 Peach2
3 Peach3
I could not come up with logic how to get this
You can also get the same result without recursivity :
select Type, Value from MyTable where Right(Value, 3) <> '[#]'
union
select Type, Replace(Value, '[#]', '1') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '2') from MyTable where Right(Value, 3) = '[#]'
union
select Type, Replace(Value, '[#]', '3') from MyTable where Right(Value, 3) = '[#]'
order by 1, 2
Assuming there is only one digit (as in your example), then I would go for:
with cte as (
select (case when value like '%\[%%' then left(right(value, 2), 1) + 0
else 1
end) as cnt, 1 as n,
left(value, charindex('[', value + '[')) as base, type
from t
union all
select cnt, n + 1, base, type
from cte
where n + 1 <= cnt
)
select type,
(case when cnt = 1 then base else concat(base, n) end) as value
from cte;
Of course, the CTE can be easily extended to any number of digits:
(case when value like '%\[%%'
then stuff(left(value, charindex(']')), 1, charindex(value, '['), '') + 0
else 1
end)
And once you have the number, you can use another source of numbers. But the recursive CTE seems like the simplest solution for the particular problem in the question.
Try this query
DECLARE #SampleData AS TABLE
(
Type int,
Value varchar(100)
)
INSERT INTO #SampleData
VALUES (1, 'Apple[#]'), (2, 'Orange'), (3, 'Peach[#]')
SELECT sd.Type, cr.Value
FROM #SampleData sd
CROSS APPLY
(
SELECT TOP (IIF(Charindex('[#]', sd.Value) > 0, 3, 1))
x.[Value] + Cast(v.t as nvarchar(5)) as Value
FROM
(SELECT Replace(sd.Value, '[#]', '') AS Value) x
Cross JOIN (VALUES (1),(2),(3)) v(t)
Order by v.t asc
) cr
Demo link: Rextester
Using a recursive CTE
CREATE TABLE #test
(
Type int,
Value varchar(50)
)
INSERT INTO #test VALUES
(1, 'Apple[#]'),
(2, 'Orange'),
(3, 'Peach[#]');
WITH CTE AS (
SELECT
Type,
IIF(RIGHT(Value, 3) = '[#]', LEFT(Value, LEN(Value) - 3), Value) AS 'Value',
IIF(RIGHT(Value, 3) = '[#]', 1, NULL) AS 'Counter'
FROM
#test
UNION ALL
SELECT
B.Type,
LEFT(B.Value, LEN(B.Value) - 3) AS 'Value',
Counter + 1
FROM
#test AS B
JOIN CTE
ON B.Type = CTE.Type
WHERE
RIGHT(B.Value, 3) = '[#]'
AND Counter < 3
)
SELECT
Type,
CONCAT(Value, Counter) AS 'Value'
FROM
CTE
ORDER BY
Type,
Value
DROP TABLE #test

Check anagrams using sql server

ACT and CAT are anagrams
I have to Write a function in sql server that takes 2 strings and given a Boolean output that indicates whether the both of them are anagram or not.
This doesnt make sense to do it in sql server,but,it is for learning purpose only
SQL Server is not good at this kind of things, but here you are:
WITH Src AS
(
SELECT * FROM (VALUES
('CAT', 'ACT'),
('CAR', 'RAC'),
('BUZ', 'BUS'),
('FUZZY', 'MUZZY'),
('PACK', 'PACKS'),
('AA', 'AA'),
('ABCDEFG', 'GFEDCBA')) T(W1, W2)
), Numbered AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) Num
FROM Src
), Splitted AS
(
SELECT Num, W1 Word1, W2 Word2, LEFT(W1, 1) L1, LEFT(W2, 1) L2, SUBSTRING(W1, 2, LEN(W1)) W1, SUBSTRING(W2, 2, LEN(W2)) W2
FROM Numbered
UNION ALL
SELECT Num, Word1, Word2, LEFT(W1, 1) L1, LEFT(W2, 1) L2, SUBSTRING(W1, 2, LEN(W1)) W1, SUBSTRING(W2, 2, LEN(W2)) W2
FROM Splitted
WHERE LEN(W1)>0 AND LEN(W2)>0
), SplitOrdered AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Num ORDER BY L1) LNum1,
ROW_NUMBER() OVER (PARTITION BY Num ORDER BY L2) LNum2
FROM Splitted
)
SELECT S1.Num, S1.Word1, S1.Word2, CASE WHEN COUNT(*)=LEN(S1.Word1) AND COUNT(*)=LEN(S1.Word2) THEN 1 ELSE 0 END Test
FROM SplitOrdered S1
JOIN SplitOrdered S2 ON S1.L1=S2.L2 AND S1.Num=S2.Num AND S1.LNum1=S2.LNum2
GROUP BY S1.Num, S1.Word1, S1.Word2
And results:
1 CAT ACT 1
2 CAR RAC 1
3 BUZ BUS 0
4 FUZZY MUZZY 0
5 PACK PACKS 0
6 AA AA 1
7 ABCDEFG GFEDCBA 1
First split (T-SQL Split Word into characters) both words into temporary tables. Then perform an outer join and check for nulls.
Edit thanks to George's comment:
split (T-SQL Split Word into characters) both words into temporary tables
Modify temporary tables or use CTEs to add a column with count(*) with group by letters clause
Perform a full outer join on two temporary tables using a letter and it's count in join condition
Check for nulls in the output - if there are none, you have an anagram
The first get in my mind:
DECLARE #word1 nvarchar(max) = NULL,
#word2 nvarchar(max) = 'Test 1',
#i int = 0, #n int
DECLARE #table TABLE (
id int,
letter int
)
SELECT #word1 = ISNULL(LOWER(#word1),''), #word2 = ISNULL(LOWER(#word2),'')
SELECT #n = CASE WHEN LEN(#word1) > LEN(#word2) THEN LEN(#word1) ELSE LEN(#word2) END
WHILE #n > 0
BEGIN
INSERT INTO #table
SELECT 1, ASCII(SUBSTRING(#word1,#n,1))
UNION ALL
SELECT 2, ASCII(SUBSTRING(#word2,#n,1))
SET #n=#n-1
END
SELECT CASE WHEN COUNT(*) = 0 THEN 1 ELSE 0 END isAnagram
FROM (
SELECT id, letter, COUNT(letter) as c
FROM #table
WHERE id = 1
GROUP BY id, letter)as t
FULL OUTER JOIN (
SELECT id, letter, COUNT(letter) as c
FROM #table
WHERE id = 2
GROUP BY id, letter) as p
ON t.letter = p.letter and t.c =p.c
WHERE t.letter is NULL OR p.letter is null
Output:
isAnagram
0
You can also use loops in functions, and they can work fast. I am not able to get any of the of other answers even close to the performance of this function:
CREATE FUNCTION IsAnagram
(
#value1 VARCHAR(255)
, #value2 VARCHAR(255)
)
RETURNS BIT
BEGIN
IF(LEN(#value1) != LEN(#value2))
RETURN 0;
DECLARE #firstChar VARCHAR(3);
WHILE (LEN(#value1) > 0)
BEGIN
SET #firstChar = CONCAT('%', LEFT(#value1, 1), '%');
IF(PATINDEX(#firstChar, #value2) > 0)
SET #value2 = STUFF(#value2, PATINDEX(#firstChar, #value2), 1, '');
ELSE
RETURN 0;
SET #value1 = STUFF(#value1, 1, 1, '');
END
RETURN (SELECT IIF(#value2 = '', 1, 0));
END
GO
SELECT dbo.IsAnagram('asd', 'asd')
--1
SELECT dbo.IsAnagram('asd', 'dsa')
--1
SELECT dbo.IsAnagram('assd', 'dsa')
--0
SELECT dbo.IsAnagram('asd', 'dssa')
--0
SELECT dbo.IsAnagram('asd', 'asd')
This is something a numbers table can help with.
Code to create and populate a small numbers table is below.
CREATE TABLE dbo.Numbers
(
Number INT PRIMARY KEY
);
WITH Ten(N) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
INSERT INTO dbo.Numbers
SELECT ROW_NUMBER() OVER (ORDER BY ##SPID) AS Number
FROM Ten T10,
Ten T100,
Ten T1000
Once that is in place you can use
SELECT W1,
W2,
IsAnagram = CASE
WHEN LEN(W1) <> LEN(W2)
THEN 0
ELSE
CASE
WHEN EXISTS (SELECT SUBSTRING(W1, Number, 1),
COUNT(*)
FROM dbo.Numbers
WHERE Number <= LEN(W1)
GROUP BY SUBSTRING(W1, Number, 1)
EXCEPT
SELECT SUBSTRING(W2, Number, 1),
COUNT(*)
FROM dbo.Numbers
WHERE Number <= LEN(W2)
GROUP BY SUBSTRING(W2, Number, 1))
THEN 0
ELSE 1
END
END
FROM (VALUES
('CAT', 'ACT'),
('CAR', 'RAC'),
('BUZ', 'BUS'),
('FUZZY', 'MUZZY'),
('PACK', 'PACKS'),
('AA', 'AA'),
('ABCDEFG', 'GFEDCBA')) T(W1, W2)
Or an alternative implementation could be
IsAnagram = CASE
WHEN LEN(W1) <> LEN(W2)
THEN 0
ELSE
CASE
WHEN EXISTS (SELECT 1
FROM dbo.Numbers N
CROSS APPLY (VALUES(1,W1),
(2,W2)) V(Col, String)
WHERE N.Number <= LEN(W1)
GROUP BY SUBSTRING(String, Number, 1)
HAVING COUNT(CASE WHEN Col = 1 THEN 1 END) <>
COUNT(CASE WHEN Col = 2 THEN 1 END))
THEN 0
ELSE 1
END
END

tsql function split string

I wonder if anyone can help me.
I need a tsql function to split a given value such as:
1) 00 Not specified
3) 01-05 Global WM&BB | Operations
2) 02-05-01 Global WM&BB | Operations | Operations n/a
I need to get a result like this:
cat1 cat1descr cat2 cat2descr cat3 cat3descr
----------------------------------------------------------------
00 Not especified null null null null
01 Global WM&BB 05 Operations null null
01 Global WM&BB 05 Operations 01 Operations n/a
Result will have always 6 columns
select funcX('00 Not specified');
cat1 cat1descr cat2 cat2descr cat3 cat3descr
----------------------------------------------------------------
00 Not especified null null null null
This will work on SQL Server 2005 and SQL Server 2008. I have assumed that your first sequence of digits is fixed to 2-digit groups of 1, 2, or 3. You can do this with fewer cascading CTEs but I find the SUBSTRING/CHARINDEX/LEN syntax can quickly become very difficult to read and debug.
DECLARE #foo TABLE
(
bar VARCHAR(4000)
);
INSERT #foo(bar) SELECT '00 Not specified'
UNION ALL SELECT '01-05 Global WM&BB | Operations'
UNION ALL SELECT '02-05-01 Global WM&BB | Operations | Operations n/a';
WITH split1 AS
(
SELECT
n = SUBSTRING(bar, 1, CHARINDEX(' ', bar)-1),
w = SUBSTRING(bar, CHARINDEX(' ', bar)+1, LEN(bar)),
rn = ROW_NUMBER() OVER (ORDER BY bar)
FROM
#foo
),
split2 AS
(
SELECT
rn,
cat1 = LEFT(n, 2),
wl = RTRIM(SUBSTRING(w, 1,
COALESCE(NULLIF(CHARINDEX('|', w), 0)-1, LEN(w)))),
wr = LTRIM(SUBSTRING(w, NULLIF(CHARINDEX('|', w),0) + 1, LEN(w))),
cat2 = NULLIF(SUBSTRING(n, 4, 2), ''),
cat3 = NULLIF(SUBSTRING(n, 7, 2), '')
FROM
split1
),
split3 AS
(
SELECT
rn,
cat1descr = wl,
cat2descr = RTRIM(SUBSTRING(wr, 1,
COALESCE(NULLIF(CHARINDEX('|', wr), 0)-1, LEN(wr)))),
cat3descr = LTRIM(SUBSTRING(wr,
NULLIF(CHARINDEX('|', wr),0) + 1, LEN(wr)))
FROM
split2
)
SELECT
s2.cat1, s3.cat1descr,
s2.cat2, s3.cat2descr,
s2.cat3, s3.cat3descr
FROM split2 AS s2
INNER JOIN split3 AS s3
ON s2.rn = s3.rn;
You can do this using PatIndex and SubString
If #In is the value of the string you're trying to parse, then try this:
Select
Case When firstDash = 0 Then zz.sKey
Else left(zz.sKey, FirstDash-1) End cat1,
Ltrim(RTrim(Case When firstPipe = 0 Then zz.Vals
Else Left(zz.Vals, firstPipe -1) End)) ca1Desc,
Case When firstDash = 0 Then Null
When secondDash = 0
Then SubString(zz.sKey, FirstDash+1, Len(zz.skey))
Else SubString(zz.sKey, FirstDash+1, secondDash-firstDash-1) End cat2,
Ltrim(RTrim(Case When firstPipe = 0 Then Null
When secondPipe = 0
Then SubString(zz.Vals, firstPipe+1, Len(zz.Vals))
Else SubString(zz.Vals, firstPipe+1,
secondPipe-firstPipe-1) End)) cat2Desc,
Case When secondDash > 0
Then Substring(zz.sKey, secondDash+1, len(sKey)-seconddash) End cat3,
Ltrim(RTrim(Case When secondPipe > 0
Then Substring(zz.Vals, secondPipe+1,
len(Vals)-secondPipe) End)) cat3Desc
From (Select Z.sKey, Z.Vals,
charIndex('-', Z.skey) firstDash,
charIndex('-', Z.skey, 1 + charIndex('-', Z.skey)) secondDash,
charIndex('|', Z.Vals) firstPipe,
charIndex('|', Z.Vals, 1 + charIndex('|', Z.Vals)) secondPipe
From (Select Left(#In, CharIndex(' ', #In)-1) skey,
substring(#In, CharIndex(' ', #In)+ 1, Len(#In)) vals) Z) ZZ