Script to get all numbers between Characters - sql

I need to find a way to get the numbers between the dashes. This is the only way I know to do it, but I know that not all of our accounts are the same length. So I am just looking for a way to get everything between before, after and between the dashes. This is an example of the types of accounts we have. '2-0-200-325-0' and '1-0-1105-1500-1520' The non-digit characters are only dashes and nothing else.
declare #Department Int
declare #Account Int
declare #Company Int
declare #Location Int
declare #SubAccount Int
declare #AccountNo varchar(24) = '2-0-200-325-0'
declare #CommaPos Int
select #CommaPos = charindex('-',#accountno)
set #Company = substring(#accountno,1,#CommaPos-1)
select #Company as Company
set #Location = Substring(#AccountNo, #CommaPos+1, 1)
select #Location as Location
set #Department = Substring(#AccountNo, #CommaPos+3, 4)
select #Department as Department
set #Account = Substring(#AccountNo, #CommaPos+8, 4)
select #Account as Account
set #SubAccount = Substring(#AccountNo, #CommaPos+13, 4)
select #SubAccount as SubAccount

One option uses a recursive query for parsing. This properly handles the variable lenght of each part - and can easily be extended to handle more parts if needed.
-- declare the variables
declare #AccountNo varchar(24) = '2-0-200-325-0';
declare #Department Int;
declare #Account Int;
declare #Company Int;
declare #Location Int;
declare #SubAccount Int;
-- parse and assign values to variables
with cte as (
select
substring(#AccountNo + '-', 1, charindex('-', #AccountNo + '-') - 1) val,
substring(#AccountNo + '-', charindex('-', #AccountNo + '-') + 1, len(#AccountNo)) rest,
1 lvl
union all
select
substring(rest, 1, charindex('-', rest) - 1),
substring(rest, charindex('-', rest) + 1, len(rest)),
lvl + 1
from cte
where charindex('-', rest) > 0
)
select
#Company = max(case when lvl = 1 then val end),
#Location = max(case when lvl = 2 then val end),
#Department = max(case when lvl = 3 then val end),
#Account = max(case when lvl = 4 then val end),
#SubAccount = max(case when lvl = 5 then val end)
from cte;
-- check the results
select
#AccountNo AccountNo,
#Company Company,
#Location Location,
#Department Department,
#Account Account,
#SubAccount SubAccount
;
Demo on DB Fiddle:
AccountNo | Company | Location | Department | Account | SubAccount
:------------ | ------: | -------: | ---------: | ------: | ---------:
2-0-200-325-0 | 2 | 0 | 200 | 325 | 0

This was my approach:
--first I use a declared table variable to simulate your issue:
DECLARE #tbl TABLE(ID INT IDENTITY,ACCOUNT_NO VARCHAR(24))
INSERT INTO #tbl VALUES('2-0-200-325-0');
--The query
SELECT t.ID
,t.ACCOUNT_NO
,casted.value('x[1]','int') AS Company
,casted.value('x[2]','int') AS Location
,casted.value('x[3]','int') AS Department
,casted.value('x[4]','int') AS Account
,casted.value('x[5]','int') AS SubAccount
FROM #tbl t
CROSS APPLY(VALUES(CAST('<x>' + REPLACE(t.ACCOUNT_NO,'-','</x><x>') + '</x>' AS XML))) A(casted);
The idea in short:
We use simple string operations to transform your dash-separated list of numbers into XML.
Now we can use XQuery to retrieve each element by its position (typesafe!).
Find details here. In this link there is also a faster approach using JSON support (needs v2016+):
SELECT t.ID
,t.ACCOUNT_NO
,A.*
FROM #tbl t
CROSS APPLY OPENJSON(CONCAT('[[',REPLACE(t.ACCOUNT_NO,'-',','),']]'))
WITH(Company INT '$[0]'
,Location INT '$[1]'
,Department INT '$[2]'
,Account INT '$[3]'
,SubAccount INT '$[4]') A;
The idea of this JSON approach:
Agains we use some simple string operations to transform your string into a JSON array.
Using two array brackets ([[) allows to use OPENJSON() with a WITH clause.
The WITH clause allows to grab each fragment by its (zero-based) position (typesafe).
The WITH clause is some kind of implicit pivoting.

/*
-- First Create this function. This is what you need.
-- It will split a sentence into words, given a defined separator
CREATE FUNCTION [dbo].[udf_SplitString] (#Sentence varchar(max), #Separator char(1))
RETURNS #WordList TABLE (Word varchar(50))
AS
BEGIN
SET #Separator = ISNULL(#Separator, ' ')
DECLARE #Word varchar(50)
SET #Sentence = LTRIM(#Sentence) + #Separator
WHILE (CHARINDEX(#Separator, #Sentence) > 0)
BEGIN
SET #Word = SUBSTRING(#Sentence, 1, CHARINDEX(#Separator, #Sentence) - 1)
INSERT INTO #WordList SELECT LTRIM(#Word)
-- Remove word added to the List from the sentence.
SET #Sentence = SUBSTRING(#Sentence, CHARINDEX(#Separator, #Sentence) + 1, LEN(#Sentence))
SET #Sentence = LTRIM(#Sentence)
END
RETURN
END
GO
*/
DECLARE #AccountList TABLE (AccountNo varchar(20), Variable varchar(20))
INSERT INTO #AccountList VALUES
('1-0-1105-1200-1290','')
, ('1-0-1105-1500-1520','')
, ('1-0-1105-1500-1620','')
, ('1-0-1106-1200-1250','')
, ('1-0-1106-1200-1290','')
, ('1-0-1106-1500-1520','')
;
DECLARE #VariableList TABLE (OrderNo int, VariableName varchar(20))
INSERT INTO #VariableList VALUES
(1, 'Company ')
, (2, 'Location ')
, (3, 'Department ')
, (4, 'Account ')
, (5, 'SubAccount ')
;
SELECT
AccountNo
, Variable = (SELECT VariableName FROM #VariableList WHERE RowNo = OrderNo)
, Value = Word
FROM (
SELECT
RowNo = ROW_NUMBER() OVER(PARTITION BY AccountNo ORDER BY AccountNo)
, AccountNo = L.AccountNo
, Variable = ''
, Word = W.Word
FROM #AccountList L
CROSS APPLY dbo.udf_SplitString(L.AccountNo, '-') W -- Here how to use the function
) R

Related

Using string_split to store data in multiple columns instead of just one?

I am trying to save a data of string2 ='DOB;Mar 1199;passport;AW1234567' into multiple columns of the table but it only move it to 1st column. I am using string_split function to separate all 4 string parts separated by ";".
What should I do to move this data into a single row across 4 columns?
Please see the details and result image below:
use TEST
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
DECLARE #myTableVariable TABLE (id INT IDENTITY(1,1) PRIMARY KEY, name varchar(max))
insert into #myTableVariable
(name)
SELECT value FROM STRING_SPLIT(#string2, #sep);
print #string2;
insert into EMPLOYEE (dob1)
select name from #myTableVariable
To guarantee column ordering you can't rely on string_split so need a different user defined function. This one returns the same value column and also a seq column for row ordering:
create function dbo.SplitString(#string varchar(max), #Delimiter varchar(1))
returns table
as
return(
select j.[value], 1 + Convert(tinyint,j.[key]) Seq
from OpenJson(Concat('["',replace(#string, #delimiter , '","'),'"]')) j
);
You can then make use of it as follows to create the columns from the sample string and insert into the target table:
declare #string2 varchar(max)='DOB;Mar 1199;passport;AW1234567', #sep char(1)=';'
insert into Employee(Dob1, DobNum, Pass1, PassNum)
select
Max(case when Seq=1 then Value end) Dob1,
Max(case when Seq=2 then Value end) DobNum,
Max(case when Seq=3 then Value end) Pass1,
Max(case when Seq=4 then Value end) PassNum
from dbo.SplitString(#string2, #sep);
Example Fiddle
SQL Server built-in PARSENAME() function could be handy for the task.
SQL
DECLARE #string2 varchar(max) = 'DOB;Mar 1199;passport;AW1234567';
DECLARE #sep char(1) = ';'
, #dot CHAR(1) = '.';
DECLARE #employee TABLE (
id INT IDENTITY(1,1) PRIMARY KEY,
Dob1 varchar(20),
DobNum VARCHAR(20),
Pass1 VARCHAR(20),
PassNum VARCHAR(20)
);
WITH rs AS
(
SELECT REPLACE(#string2, #sep, #dot) AS tokenList
)
INSERT INTO #employee (Dob1, DobNum, Pass1, PassNum)
SELECT PARSENAME(tokenList,4)
, PARSENAME(tokenList,3)
, PARSENAME(tokenList,2)
, PARSENAME(tokenList,1)
FROM rs;
-- test
SELECT * FROM #employee;
Output
+----+------+----------+----------+-----------+
| id | Dob1 | DobNum | Pass1 | PassNum |
+----+------+----------+----------+-----------+
| 1 | DOB | Mar 1199 | passport | AW1234567 |
+----+------+----------+----------+-----------+
It is possible to do this a number of ways, it is generally frowned upon, but because you have specifically requested STRING_SPLIT then I offer you this mechanism to parse the components of the string:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
SELECT ROW_NUMBER() OVER (order by (SELECT 1)) as RN, Value
FROM STRING_SPLIT(#string2, #sep, 1)
RN
Value
1
DOB
2
Mar 1199
3
passport
4
AW1234567
NOTE: This hack for the ORDER BY is generally advised against
From the docs: The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string
However you could use this are your own risk if you really needed to use STRING_SPLIT
You could now access these tokens via their ordinal:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT ROW_NUMBER() OVER (order by (select 1)) as RN, Value
FROM STRING_SPLIT(#string2, #sep)
)
SELECT s1.value as DOBKey, s2.value as DOBValue, s3.value as PassportKey, s4.value as PassportValue
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.RN = 1
AND s2.RN = 2
AND s3.RN = 3
AND s4.RN = 4
Results in this:
DOBKey
DOBValue
PassportKey
PassportValue
DOB
Mar 1199
passport
AW1234567
SQL Azure Supports an enable_ordinal flag
As an update to SQL 2019 and expected to be released soon, there is an enable_ordinal argument that can be used to ensure the sequence of the string tokens and it will include the ordinal value in an output column named ordinal.
HOWEVER as of 11/06/2021 enable_ordinal argument and ordinal output column are currently only supported in Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics (serverless SQL pool only).
When available we can simplify the previous query:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT Ordinal, Value
FROM STRING_SPLIT(#string2, #sep, 1)
)
SELECT s1.Value as DOBKey, s2.Value as DOBValue, s3.Value as PassportKey, s4.Value as PassportValue
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.Ordinal = 1
AND s2.Ordinal = 2
AND s3.Ordinal = 3
AND s4.Ordinal = 4
You can use the same CTE to extract the DOBValue and the PassportValue in the INSERT:
DECLARE #string2 varchar(max);
DECLARE #sep char(1);
set #string2 = 'DOB;Mar 1199;passport;AW1234567';
set #sep = ';'
;WITH Split as (
SELECT Ordinal, Value
FROM STRING_SPLIT(#string2, #sep, 1)
)
insert into EMPLOYEE (dob1, pass1)
select s2.Value, s4.Value
FROM Split as s1, Split as s2, Split as s3, Split as s4
WHERE s1.Ordinal = 1
AND s2.Ordinal = 2
AND s3.Ordinal = 3
AND s4.Ordinal = 4
Should result in this:
ID
dob1
pass1
1
Mar 1199
AW1234567

SQL - Replace characters using mapping, without a loop

Is there a way to replace characters in SQL Server from a string using a mapping table and without using a loop.
I have mapping that can go like this:
a => b
b => c
...
z => a
This mapping is not static and can change.
I tried the solution from https://stackoverflow.com/a/45202933/3161817 and https://stackoverflow.com/a/13051989/3161817 but I only end up having a string that are just a, like 'aaaaaaaa'
My current solution is like:
DECLARE #NextChar NCHAR(1)
DECLARE #Position int = 1
DECLARE #StrLength int = LEN(#str)
DECLARE #Result nvarchar(1000) = ''
WHILE (#Position <= #StrLength)
BEGIN
SET #NextChar = SUBSTRING(#str, #Position, 1)
SET #Result = #Result + ISNULL((SELECT ToChar FROM CharMapping
WHERE #NextChar COLLATE Latin1_General_BIN = FromChar COLLATE Latin1_General_BIN
), #NextChar)
SET #Position= #Position + 1
END
but I'm looking for a possible solution without a loop.
DECLARE #t TABLE(
src char
,dest char
)
INSERT INTO #t VALUES
('a', 'b')
,('b', 'c')
,('d', 'e')
DECLARE #TestString nvarchar(100) = 'aabbcdacbezzz';
WITH cte AS(
SELECT 1 lvl, SUBSTRING(#TestString, 1, 1) AS TestPosChar, SUBSTRING(#TestString, 2, LEN(#TestString)-1) AS TestStringRemain
UNION ALL
SELECT lvl + 1, SUBSTRING(TestStringRemain, 1, 1), SUBSTRING(TestStringRemain, 2, LEN(TestStringRemain)-1)
FROM cte
WHERE LEN(TestStringRemain) >= 1
)
SELECT #TestString AS OldString
,SUBSTRING((SELECT ( '' + ISNULL(t.dest, TestPosChar))
FROM cte c
LEFT JOIN #t AS t ON t.src = c.TestPosChar
ORDER BY lvl
FOR XML PATH( '' )
), 1, 1000 ) AS NewString
I made this test :
declare #MyTab table(
letter char
)
declare #MyTab2 table(
letter char
)
insert into #MyTab
select substring(a.b, v.number+1, 1)
from (select 'ABCDEFGHZZZ' b) a
join master..spt_values v on v.number < len(a.b)
where v.type = 'P'
insert into #MyTab2
select NewLetter
from (
select case letter when 'Z' then 'A'
when 'z' then 'a'
else char(ascii(letter)+1) end NewLetter
from #MyTab
) MyView
select stuff(
(select ''+letter from #MyTab2
for xml path('')),1,0,'')
SQL Server 2017 introduces a TRANSLATE function, which is similar to nested REPLACE functions. You didn't specify what version of SQL Server you are using so I don't know if that's an option.
SELECT TRANSLATE(#SourceString, 'abcdefghijklmnopqrstuvwxyz', 'bcdefghijklmnopqrstuvwxyza');
Try (and expand) following query, which use XML PATH, a tally number table and a decode table:
CREATE TABLE #TTMMPP (ORIG CHAR(1), NEWC CHAR(1));
/* add all values to shift */
INSERT INTO #TTMMPP VALUES ('a','b'),('b','c'),('c','d'),('d','e'),('e','f') /*, ....*/
;
/* N as max len of your string */
CREATE TABLE #TTMMPP2 (N smallint);
DECLARE #I INT
DECLARE #ROWS INT
SET #I = 1
SET #ROWS = 1000
WHILE #I < #ROWS
BEGIN
INSERT INTO #TTMMPP2 VALUES (#I)
SET #I = #I + 1
END
----------------------------------------
DECLARE #my_str VARCHAR(100) = 'abcd';
SELECT #my_str AS ORIGINAL,
(
SELECT ''+C.NEWC
FROM (
SELECT N, SUBSTRING( #my_str, N,1) AS X, B.NEWC
FROM #TTMMPP2 A
INNER JOIN #TTMMPP B ON SUBSTRING(#my_str,A.N,1)= B.ORIG
WHERE N<=LEN(#my_str)
) C
FOR XML PATH('')
) AS SHIFTED;
Output:
ORIGINAL SHIFTED
abcd bcde
Updated version: if you want "mark" character not found in decode table you can use this (little changes to query: LEFT JOIN and COALESCE):
DECLARE #my_str VARCHAR(100) = 'abcdefg';
SELECT #my_str AS ORIGINAL,
(
SELECT ''+C.NEWC
FROM (
SELECT N, SUBSTRING( #my_str, N,1) AS X, COALESCE(B.NEWC,'*') AS NEWC
FROM #TTMMPP2 A
LEFT JOIN #TTMMPP B ON SUBSTRING(#my_str,A.N,1)= B.ORIG
WHERE N<=LEN(#my_str)
) C
ORDER BY N
FOR XML PATH('')
) AS SHIFTED;
Output (* substitute character not found in decode table):
ORIGINAL SHIFTED
abcdefg bcde***
New update (as your last comment added):
SELECT #my_str AS ORIGINAL,
(
SELECT ''+C.NEWC
FROM (
SELECT N, SUBSTRING( #my_str, N,1) AS X, COALESCE(B.NEWC,SUBSTRING(#my_str,A.N,1)) AS NEWC
FROM ##TTMMPP2 A
LEFT JOIN #TTMMPP B ON SUBSTRING(#my_str,A.N,1) COLLATE Latin1_General_BIN = B.ORIG COLLATE Latin1_General_BIN
WHERE N<=LEN(#my_str)
) C
ORDER BY N
FOR XML PATH('')
) AS SHIFTED
Output:
ORIGINAL SHIFTED
abcdefgA bcdeefgA

Join table using columns which contains other table's id's separated by commas

I have FACULTY table which contain the column with other table's id's separated by commas. I want to join those with respective table.
faculty table:
id | name | course_id | subject_id
a | smith | 2,3 | 1,2
course table:
id | name
1 | bcom
2 | mcom
3 | bba
subject table:
id | name
1 | account
2 | state
3 | economics
I want to get result from these table like..
faculty.id, faculty.name, course.name(using faculty.course_id), subject.name(using faculty.subject_id)
I have tried a lot of queries and also finds from Google but it didn't gave me proper result.
I do not think the performance will be too nice but worths trying. This solution would work in SQL SERVER:
SELECT *
FROM faculty F
JOIN course C
ON ','+F.course_id+',' LIKE '%,'+CONVERT(VARCHAR,C.ID) +',%'
JOIN subject S
ON ','+F.subject_id_id+',' LIKE '%,'+CONVERT(VARCHAR,S.ID) +',%'
Based on Albin Sunnanbo's comment i would also sugget you add some many too many tables:
fcourses
facultyId
courseId
and
fsubjects
facultyId
subjectId
That way you could do a proper join :
SELECT *
FROM faculty F
JOIN fcourses FC
ON F.Id = FC.facultyId
JOIN course C
ON FC.courseId = C.ID
JOIN fsubjects FS
ON F.Id = FS.facultyId
JOIN subject S
ON FS.courseId = S.ID
You can do the following query
select * from faculty F
JOIN course C
on CHARINDEX((','+CAST(c.id as varchar(10))+','), (','+f.courseid+',')) > 0
JOIN subject s
on CHARINDEX((','+CAST(s.id as varchar(10))+','), (','+f.subjectid+',')) > 0
I've done something similar like this:
select f.id, f.lname, f.fname, U.useridlist
from TABLE1 F, TABLE2 U
where ',' || U.useridlist || ',' like '%,' || f.id || ',%'
If you can create a 'string to int table' function, I would look at the following:
Create the function
CREATE FUNCTION [dbo].[udf_ConvertIntListToTable] (#list varchar(MAX))
RETURNS #tbl TABLE (val int) AS
BEGIN
DECLARE #ix int,
#pos int,
#str varchar(MAX),
#num int
SET #pos = 1
SET #ix = 1
WHILE #ix > 0
BEGIN
SET #ix = charindex(',', #list, #pos)
IF #ix > 0
SET #str = substring(#list, #pos, #ix - #pos)
ELSE
SET #str = substring(#list, #pos, len(#list))
SET #str = ltrim(rtrim(#str))
IF #str LIKE '%[0-9]%' AND
(#str NOT LIKE '%[^0-9]%' OR
#str LIKE '[-+]%' AND
substring(#str, 2, len(#str)) NOT LIKE '[-+]%[^0-9]%')
BEGIN
SET #num = convert(int, #str)
INSERT #tbl (val) VALUES(#num)
END
SET #pos = #ix + 1
END
RETURN
END
Then query using CROSS APPLY
declare #FacultyTable table(id int PRIMARY KEY, name nvarchar(50), course_id varchar(50))
declare #CourseTable table(id int PRIMARY KEY, name nvarchar(50))
insert into #FacultyTable values(1, 'Peter Sagal', '11,22')
insert into #FacultyTable values(2, 'Carl Kasell', '22,33')
insert into #CourseTable values(11,'News')
insert into #CourseTable values(22,'News')
insert into #CourseTable values(33,'News')
insert into #CourseTable values(44,'News')
select *
from #FacultyTable f
CROSS APPLY(
SELECT *
FROM #CourseTable c
WHERE
c.id IN (SELECT * FROM dbo.udf_ConvertIntListToTable(f.course_id))
) tCourses

SQL - Storing multiple records in a variable separated by commas

I have two variables, 1 varchar named cust_ref, and 1 int named associated_ids. What I'm trying to accomplish is the following:
You provide cust_ref with a value which will usually result in between 1+ rows being returned from the Customer table. I am concerned with gathering all customer_id records for that cust_ref and storing them in the associated_ids variable seperated by commars.
This is the SQL I have so far, and obviously is only loading one of the customer_id records into the variable. Based on this example I would like select #associated_ids to return the following 75458,77397,94955
declare #cust_ref varchar(20) = 'ABGR55532'
declare #associated_ids int
select distinct #associated_ids = customer_id
from dbo.Customer
where cust_ref = #cust_ref
select #associated_ids
select *
from dbo.Customer
where cust_ref = #cust_ref
Here is the results from the above, as you can see there are actually 3 associated_ids that I need stored in the variable in this example but my command is capturing the largest, I want all 3 seperated by commars.
declare #cust_ref varchar(20) = 'ABGR55532' --from your code
DECLARE #result varchar(100)
set #result =
(SELECT distinct (customer_id + ' ')
FROM dbo.Customer
where cust_ref = #cust_ref --from your code
ORDER BY (customer_id + ' ')
FOR XML PATH (''))
SELECT REPLACE(RTRIM(#result),' ',',')
You could try something like this ... obviously, some adjustment will be needed:
create table x (id varchar(50),num int)
insert into x (id,num) values ('75458','20')
insert into x (id,num) values ('77397','20')
insert into x (id,num) values ('94955','20')
and then,
create function GetList (#num as varchar(10))
returns varchar(100)
as
begin
declare #List varchar(100)
select #List = COALESCE(#List + ', ', '') + id
from x
where num = #num
return #List
end
Then, use something like this to get the values:
select distinct num,dbo.GetList(num) from x
Here you go
DECLARE #cust_ref varchar(20) = 'ABGR55532' --from your code
DECLARE #result varchar(100)
set #result =
(SELECT distinct (cast(customer_id as varchar) + ' ')
FROM dbo.Customer
where cust_ref = #cust_ref --from your code
ORDER BY (cast(customer_id as varchar) + ' ')
FOR XML PATH (''))
SELECT REPLACE(RTRIM(#result),' ',',')

Convert Comma Delimited String to bigint in SQL Server

I have a varchar string of delimited numbers separated by commas that I want to use in my SQL script but I need to compare with a bigint field in the database. Need to know to convert it:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = 'CPN'
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN (#RegionID)
AND Viewable = 'Y'
This fails with the following error:
Error converting data type varchar to bigint.
RegionID In the database is a bigint field.. need to convert the varchar to bigint.. any ideas..?
Many thanks in advance,
neojakey
create this function:
CREATE function [dbo].[f_split]
(
#param nvarchar(max),
#delimiter char(1)
)
returns #t table (val nvarchar(max), seq int)
as
begin
set #param += #delimiter
;with a as
(
select cast(1 as bigint) f, charindex(#delimiter, #param) t, 1 seq
union all
select t + 1, charindex(#delimiter, #param, t + 1), seq + 1
from a
where charindex(#delimiter, #param, t + 1) > 0
)
insert #t
select substring(#param, f, t - f), seq from a
option (maxrecursion 0)
return
end
change this part:
AND a.RegionID IN (select val from dbo.f_split(#regionID, ','))
Change this for better overall performance:
AND DATEDIFF(d, 0, GETDATE()) <= ExpirationDate
Your query does not know that those are separate values, you can use dynamic sql for this:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #sql nvarchar(Max)
set #sql = 'SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = ''CPN''
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN ('+#RegionID+')
AND Viewable = ''Y'''
exec sp_executesql #sql
I use this apporach sometimes and find it very good.
It transfors your comma-separated string into an AUX table (called #ARRAY) and then query the main table based on the AUX table:
declare #RegionID varchar(50)
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #S varchar(20)
if LEN(#RegionID) > 0 SET #RegionID = #RegionID + ','
CREATE TABLE #ARRAY(region_ID VARCHAR(20))
WHILE LEN(#RegionID) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#RegionID, 1, CHARINDEX(',', #RegionID) - 1))
INSERT INTO #ARRAY (region_ID) VALUES (#S)
SELECT #RegionID = SUBSTRING(#RegionID, CHARINDEX(',', #RegionID) + 1, LEN(#RegionID))
END
select * from your_table
where regionID IN (select region_ID from #ARRAY)
It avoids you from ahving to concatenate the query string and then use EXEC to execute it, which I dont think it is a very good approach.
if you need to run the code twice you will need to drop the temp table
I think the answer should be kept simple.
Try using CHARINDEX like this:
DECLARE #RegionID VARCHAR(200) = NULL
SET #RegionID =
'853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT 1
WHERE Charindex('834', #RegionID) > 0
SELECT 1
WHERE Charindex('999', #RegionID) > 0
When CHARINDEX finds the value in the large string variable, it will return it's position, otherwise it return 0.
Use this as a search tool.
The easiest way to change this query is to replace the IN function with a string function. Here is what I consider the safest approach using LIKE (which is portable among databases):
AND ','+#RegionID+',' like '%,'+cast(a.RegionID as varchar(255))+',%'
Or CHARINDEX:
AND charindex(','+cast(a.RegionID as varchar(255))+',', ','+#RegionID+',') > 0
However, if you are explicitly putting the list in your code, why not use a temporary table?
declare #RegionIds table (RegionId int);
insert into #RegionIds
select 853 union all
select 834 union all
. . .
select 303
Then you can use the table in the IN clause:
AND a.RegionId in (select RegionId from #RegionIds)
or in a JOIN clause.
I like Diego's answer some, but I think my modification is a little better because you are declaring a table variable and not creating an actual table. I know the "in" statement can be a little slow, so I did an inner join since I needed some info from the Company table anyway.
declare #companyIdList varchar(1000)
set #companyIdList = '1,2,3'
if LEN(#companyIdList) > 0 SET #companyIdList = #companyIdList + ','
declare #CompanyIds TABLE (CompanyId bigint)
declare #S varchar(20)
WHILE LEN(#companyIdList) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#companyIdList, 1, CHARINDEX(',', #companyIdList) - 1))
INSERT INTO #CompanyIds (CompanyId) VALUES (#S)
SELECT #companyIdList = SUBSTRING(#companyIdList, CHARINDEX(',', #companyIdList) + 1, LEN(#companyIdList))
END
select d.Id, d.Name, c.Id, c.Name
from [Division] d
inner join [Company] c on d.CompanyId = c.Id
inner join #CompanyIds cids on c.Id = cids.CompanyId