I have table similar to:
Create Table #test (Letter varchar(5), Products varchar(30))
Insert into #test (Letter, Products)
Values ('A','B,C,D,E,F'),('B','B,C,D,E,F'),('G','B,C,D,E,F'),('Z','B,C,D,E,F'),('E','B,C,D,E,F')
Is it possible to write a CASE Statement which will check if the list in the 'Products' column contain a letter from 'Letter' column?
Thanks
Here is the query
Select *, Case When (charindex(Letter, Products, 0)>0) Then 'Yes' Else 'No' End AS [Y/N] from #test
I think i managed to do the trick:
CASE WHEN Products LIKE '%'+Letter+'%' THEN 'TRUE' ELSE 'FALSE' END
You could use EXISTS with a split-function:
SELECT Letter, Products,
ContainsLetter = CASE WHEN EXISTS
(
SELECT 1 FROM dbo.Split(t1.Products, ',')s
WHERE s.Item = Letter
)
THEN 'Yes' ELSE 'No' END
FROM #test t1
Demo
Here is the one i'm using on SQL-Server 2005:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #ItemTable TABLE (Item VARCHAR(250))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #ItemTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
Related
I am trying to write a query that should return the following output:
val1 = 'GREAT'
val2 = 'TAGER'
See above have the same characters and it should return 'Exist' if the match is found. Otherwise, if there is a character missing, then 'Not exist'. I am using CASE to get the required output and was wondering if it could be done using SUBSTRING, CHARINDEX etc. Hoping to get a solution or idea to do so. Thanks.
You can make it as a function / procedure according to your needs.
DECLARE #S1 VARCHAR(100)
DECLARE #S2 VARCHAR(100)
SELECT #S1 = val1
,#S2 = val2
FROM <TABLE_NAME>
DECLARE #c CHAR(1)
DECLARE #i TINYINT
DECLARE #o1 VARCHAR(100) = ''
DECLARE #o2 VARCHAR(100) = ''
WHILE DataLength(#s1) > 0
BEGIN
SET #c = Left(#s1, 1)
SET #s1 = Substring(#s1, 2, len(#s1))
SET #i = 1
WHILE #i <= Len(#o1)
AND #c > substring(#o1, #i, 1)
SET #i += 1
SET #o1 = left(#o1, #i - 1) + #c + substring(#o1, #i, len(#o1))
END
WHILE DataLength(#s2) > 0
BEGIN
SET #c = Left(#s2, 1)
SET #s2 = Substring(#s2, 2, len(#s2))
SET #i = 1
WHILE #i <= Len(#o2)
AND #c > substring(#o2, #i, 1)
SET #i += 1
SET #o2 = left(#o2, #i - 1) + #c + substring(#o2, #i, len(#o2))
END
SELECT CASE
WHEN #o1 = #o2
THEN 'Exist'
ELSE 'Not Exist'
END
This is a custom script for you
Run this SP first
IF(OBJECT_ID('CharSplit')) IS NOT NULL
DROP PROCEDURE CharSplit;
GO
CREATE PROC CharSplit
#Words VARCHAR(MAX)
AS
BEGIN
IF OBJECT_ID('tempdb..#temp1') IS NOT NULL
DROP TABLE #temp1;
CREATE TABLE #temp1
(
letter CHAR(1), freq INT
);
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
DROP TABLE #temp2;
CREATE TABLE #temp2
(
letter CHAR(1), freq INT
);
DECLARE #t VARCHAR(MAX);
DECLARE #I INT;
--SET #Words = 'sanuantony';
SELECT #I = 0;
WHILE(#I < LEN(#Words) + 1)
BEGIN
SELECT #t = SUBSTRING(#words, #I, 1);
INSERT INTO #temp1
(letter, freq
)
VALUES
(#t, 0
);
SET #I = #I + 1;
END;
TRUNCATE TABLE #temp2;
INSERT INTO #temp2
(letter, freq
)
SELECT letter, COUNT(freq)
FROM #temp1
GROUP BY letter;
SELECT *
FROM #temp2
ORDER BY letter;
END;
Now Just try your business logic
DECLARE #t1 AS TABLE
(
letter CHAR(1), freq INT
);
DECLARE #t2 AS TABLE
(
letter CHAR(1), freq INT
);
INSERT INTO #t1
EXEC charSplit 'alammalay';
INSERT INTO #t2
EXEC charSplit 'malayalam';
IF(
(
SELECT COUNT(1)
FROM #t1
) =
(
SELECT COUNT(1)
FROM #t2
)
AND
(
SELECT COUNT(1)
FROM #t2
) =
(
(
SELECT COUNT(1)
FROM #t1
)
UNION
(
SELECT COUNT(1)
FROM #t2
)
)
)
SELECT 'Both are matching' AS result;
ELSE
SELECT 'Both are not matching' AS result;
my data is :
Id Case ID#
1. QCCR1A105369, QCCR1A104986 ,QCCR1A103717
2. QCIM1A106902,4613410733 QCIM1A106902; 4613344523 QCIM1A105842; 4614004212 QCIM1A106580; 4614060189 QCIM1A106676
3. QCCR1D93616, QCCR1D92488, QCCR1D58461
4. QCCR1B40216 .... ,QCCR1B39080, QCCR1B40216, QCCR1B39745, QCCR1B38463 , QCCR1B38618, QCCR1B38619, QCCR1B38620, QCCR1B38621, QCCR1B38622, QCCR1B38465, QCCR1B38623
5. QCCR2A30221 QCCR2A30223 QCCR2A30222 QCIM2A30416
My output will be Id 1,3,4,5. I want only that rows, which have starting value QC not any numeric value. For ID 2 you can see there are some numeric values, please tell me how can I achieve it.
You could use a table valued function to split your value by a delimiter like this:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #ItemTable TABLE (Item VARCHAR(250))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #ItemTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
Then this query returns the expected result:
SELECT t.*
FROM dbo.TableName t
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.Split(t.CaseID, ',') split
WHERE NOT LEFT(LTRIM(split.Item), 2) = 'QC'
OR ISNUMERIC(split.Item) = 1
)
Demo with your sample data. But in general you should avoid multiple values in one column. Instead store it in separate rows in a linked table. That's called database normalization.
How to return all the characters from a string and count it in sql.
if the string is "how are you"
it should return
char count
2
h 1
o 2
w 1
a 1
r 1
e 1
y 1
u 1
You can use this script. It will give you exactly what you need.
This one counts just the letters in the string.
declare #c int
declare #ch varchar(10)
declare #str varchar(max)
set #str = 'how are you'
declare #letter int
declare #i int
set #i = 1
create table #tbl(ch varchar(10), cnt int)
while (#i <= len(#str))
begin
set #letter = 0
set #ch = substring(#str, #i, 1)
select #c = count(*) from #tbl
where ch = #ch
if ( (#ch >= 'a' and #ch <= 'z') or (#ch >= 'A' and #ch <= 'Z') )
begin
set #letter = 1
end
if (#c = 0)
begin
if (#letter = 1)
begin
insert into #tbl (ch, cnt) values (#ch, 1)
end
end
else
begin
update #tbl set cnt = cnt + 1 where ch = #ch
end
set #i = #i + 1
end
select * from #tbl
drop table #tbl
And if you want to count all chars (not just letters),
this makes it even easier. Use this script.
declare #c int
declare #ch varchar(10)
declare #str varchar(max)
set #str = 'how are you'
declare #i int
set #i = 1
create table #tbl(ch varchar(10), cnt int)
while (#i <= len(#str))
begin
set #ch = substring(#str, #i, 1)
select #c = count(*) from #tbl
where ch = #ch
if (#c = 0)
begin
insert into #tbl (ch, cnt) values (#ch, 1)
end
else
begin
update #tbl set cnt = cnt + 1 where ch = #ch
end
set #i = #i + 1
end
select * from #tbl
drop table #tbl
You can use a customer tsql function, see http://gallery.technet.microsoft.com/scriptcenter/T-SQL-Script-to-Split-a-308206f3.
And you can make a query solve your issue using group by and count statements ?
This will return the result set you have requested. It does this by taking each letter and adding it to a new row within a temporary table and then querying the results to return the counts for each occurrence of the character.
DECLARE #individual CHAR(1);
DECLARE #text NVARCHAR(200)
SET #text = 'how are you';
IF OBJECT_ID('tempdb..#tmpTable') IS NOT NULL
DROP TABLE #tmpTable
CREATE TABLE #tmpTable (letter char(1));
WHILE LEN(#text) > 0
BEGIN
SET #individual = SUBSTRING(#text, 1, 2)
INSERT INTO #tmpTable (letter) VALUES (#individual);
SET #text = SUBSTRING(#text, 2, LEN(#text))
END
SELECT letter, COUNT(*) AS [count]
FROM #tmpTable
GROUP BY letter;
Need help on how to improve my SQL script for better performance. dbo.Products table has a million rows. I'm hesitant to rewrite it using dynamic SQL. Thanks!
DECLARE
#Brand varchar(MAX) = 'Brand 1, Brand 2, Brand 3',
#ItemCategory varchar(MAX) = 'IC1, IC2, IC3, IC4, IC5'
--will return all records if params where set to #Brand = NULL, #ItemCategory = NULL
SELECT
[Brand],
SUM([Amount]) AS [Amount]
FROM dbo.Products (NOLOCK)
LEFT JOIN [dbo].[Split](#Brand, ',') FilterBrand ON Brand = [FilterBrand].[Items]
LEFT JOIN [dbo].[Split](#ItemCategory, ',') FilterItemCategory ON ItemCategory = [FilterItemCategory].[Items]
WHERE
(#Brand IS NULL OR (#Brand IS NOT NULL AND [FilterBrand].[Items] IS NOT NULL)) AND
(#ItemCategory IS NULL OR (#ItemCategory IS NOT NULL AND [FilterItemCategory].[Items] IS NOT NULL))
GROUP BY
[Brand]
Below is the split table-valued function that I found on the web:
CREATE function [dbo].[Split]
(
#String varchar(8000),
#Delimiter char(1)
)
RETURNS #Results TABLE (Items varchar(4000))
AS
BEGIN
IF (#String IS NULL OR #String = '') RETURN
DECLARE #i int, #j int
SELECT #i = 1
WHILE #i <= LEN(#String)
BEGIN
SELECT #j = CHARINDEX(#Delimiter, #String, #i)
IF #j = 0
BEGIN
SELECT #j = len(#String) + 1
END
INSERT #Results SELECT RTRIM(SUBSTRING(#String, #i, #j - #i))
SELECT #i = #j + LEN(#Delimiter)
END
RETURN
END
Following solution are with out using functions
Declare #IDs Varchar(100)
SET #IDs = '2,4,6'
Select IsNull(STUFF((Select ', '+ CAST([Name] As Varchar(100)) From [TableName]
Where CharIndex(','+Convert(Varchar,[ID])+',', ','+#IDs+',')> 0
For XML Path('')),1,1,''),'') As [ColumnName]
Here is the function I use. I also have another that wraps this to return numeric values which I find helpful as well.
Edit: Sorry, as for how to improve the performance of the query, I usually split the values into table variables and perform my joins to that but that probably won't change your performance, just your readability. The only thing I can see in terms of performance is your double checking whether your joins produce anything. You really can't get much better performance with two conditional left joins on two tables. It basically boils down to indexes at that point.
(#Brand IS NULL OR [FilterBrand].[Items] IS NOT NULL)
Function:
ALTER FUNCTION [dbo].[fn_SplitDelimittedList]
(
#DelimittedList varchar(8000),
#Delimitter varchar(20)
)
RETURNS
#List TABLE
(
Item varchar(100)
)
AS
BEGIN
DECLARE #DelimitterLength INT
SET #DelimitterLength = LEN(#Delimitter)
-- Tack on another delimitter so we get the last item properly
set #DelimittedList = #DelimittedList + #Delimitter
declare #Position int
declare #Item varchar(500)
set #Position = patindex('%' + #Delimitter + '%' , #DelimittedList)
while (#Position <> 0)
begin
set #Position = #Position - 1
set #Item = LTRIM(RTRIM(left(#DelimittedList, #Position)))
INSERT INTO #List (Item) VALUES (#Item)
set #DelimittedList = stuff(#DelimittedList, 1, #Position + #DelimitterLength, '')
set #Position = patindex('%' + #Delimitter + '%' , #DelimittedList)
end
RETURN
END
Hey just try the split function I have created without using any while loops here.And just use this in place of your split function and use col to match in LEFT join.
ALTER function dbo.SplitString(#inputStr varchar(1000),#del varchar(5))
RETURNS #table TABLE(col varchar(100))
As
BEGIN
DECLARE #t table(col1 varchar(100))
INSERT INTO #t
select #inputStr
if CHARINDEX(#del,#inputStr,1) > 0
BEGIN
;WITH CTE as(select ROW_NUMBER() over (order by (select 0)) as id,* from #t)
,CTE1 as (
select id,ltrim(rtrim(LEFT(col1,CHARINDEX(#del,col1,1)-1))) as col,RIGHT(col1,LEN(col1)-CHARINDEX(#del,col1,1)) as rem from CTE
union all
select c.id,ltrim(rtrim(LEFT(rem,CHARINDEX(#del,rem,1)-1))) as col,RIGHT(rem,LEN(rem)-CHARINDEX(#del,rem,1))
from CTE1 c
where CHARINDEX(#del,rem,1)>0
)
INSERT INTO #table
select col from CTE1
union all
select rem from CTE1 where CHARINDEX(#del,rem,1)=0
END
ELSE
BEGIN
INSERT INTO #table
select col1 from #t
END
RETURN
END
DECLARE #Brand varchar(MAX) = 'Brand 1,Brand 2,Brand 3',
#ItemCategory varchar(MAX) = ' IC1 A ,IC2 B , IC3 C, IC4 D' --'IC1, IC2, IC3, IC4, IC5'
select * from dbo.SplitString(#ItemCategory,',')
I have a test function which would sanitize phone nos and allow only nos and characters "x" or "X" to be stored. I have it to where it does most of it other than it allows multiple x's which I don't want. Can anybody help me add it to the regular expression also let me know if you spot potential issues ?
CREATE Function [dbo].[RemoveAlphaCharacters](#Temp VarChar(1000))
Returns VarChar(1000)
AS
Begin
While PatIndex('%[^0-9,x,X]%', #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex('%[^0-9,x,X]%', #Temp), 1, '')
Return #TEmp
End
The problem with PATINDEX here is that it can't really determine that the pattern should change after it hits a string for the first time. So maybe this approach will be simpler:
CREATE FUNCTION [dbo].[RemoveAlphaCharacters]
(
#Temp VARCHAR(1000)
)
RETURNS VARCHAR(1000)
AS
BEGIN
DECLARE #i INT, #hitX BIT, #t VARCHAR(1000), #c CHAR(1);
SELECT #i = 1, #hitX = 0, #t = '';
WHILE #i <= LEN(#Temp)
BEGIN
SET #c = SUBSTRING(#Temp, #i, 1);
IF LOWER(#c) = 'x' AND #hitX = 0
BEGIN
SET #t = #t + #c;
SET #hitX = 1;
END
IF #c LIKE '[0-9]'
BEGIN
SET #t = #t + #c;
END
SET #i = #i + 1;
END
RETURN(#t);
END
GO
SELECT dbo.RemoveAlphaCharacters('401-867-9092');
SELECT dbo.RemoveAlphaCharacters('401-867-9092x32');
SELECT dbo.RemoveAlphaCharacters('401-867-9092x32x54');
Results:
4018679092
4018679092x32
4018679092x3254