Related
Trying to create a temp table where the AlphaSeq column would go through an "array" of alphanumeric characters that do not repeat and follow a flow as shown in the example below.
Example:
AlphaSeq
NumericSeq
A
1
A1
2
B
3
B1
4
AA
5
AB
6
BA
7
BB
8
[ETC]
[ETC]
Here's what I have so far (Obviously the "IG" is static as it is written now, so Im trying to make that field more fluid)
CREATE TABLE #Index
(
AlphaSeq VARCHAR(2),
NumericSeq TINYINT
)
DECLARE #ArrayNum AS TINYINT;
DECLARE #ReceiptSeq AS VARCHAR(2);
SET #ArrayNum = 0
SET #ReceiptSeq = 'IG'
WHILE #ArrayNum < 100
BEGIN
SET #ArrayNum = #ArrayNum + 1
INSERT INTO #Index (AlphaSeq, NumericSeq)
VALUES (#ReceiptSeq, #ArrayNum)
END
SELECT *
FROM #Index
You can simply cross-join the list of characters
CREATE TABLE #Index
(
AlphaSeq VARCHAR(2) NOT NULL,
NumericSeq SMALLINT NOT NULL IDENTITY
);
WITH Chars AS (
SELECT *
FROM (VALUES
('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('Z')
) v(chr)
),
CharsWithNums AS (
SELECT *
FROM (VALUES
('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9'),('0')
) v(chr)
UNION ALL
SELECT *
FROM Chars
)
INSERT #Index (AlphaSeq)
SELECT chr AS AlphaSeq
FROM Chars
UNION ALL
SELECT a.chr + b.chr
FROM Chars a
CROSS JOIN CharsWithNums b
ORDER BY AlphaSeq;
I am trying to implement search functionality with list of values in SQL variable, including range. Appreciate any guidance/links pointing to correct approach for this.
Below is the dataset:
CREATE TABLE [dbo].[Books]
(
[ID] [NCHAR](10) NOT NULL,
[AUTHCODE] [NCHAR](10) NULL,
[TITLE] [NCHAR](10) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Books] ([ID], [AUTHCODE], [TITLE])
VALUES (N'1', N'nk', N'Book1'),
(N'2', N'an', N'Book2'),
(N'3', N'mn', N'Book3'),
(N'4', N'ra', N'Book4'),
(N'5', N'kd', N'Book5'),
(N'6', N'nk', N'Book6'),
(N'7', N'an', N'Book7'),
(N'8', N'ra', N'Book8'),
(N'9', N'kd', N'Book9'),
(N'10', N'mn', N'Book10 ')
GO
Below I am trying to filter using the SQL IN clause but this does not return desired result.
select * from books
declare #List1 varchar(max) = '2,4,6,7,8,9' --simple list
select *
from books
where id in (#List1)
declare #List2 varchar(max) = '2,4-7,9' --list with range
select *
from books
where id in (#List2)
You cannot directly use strings as lists, but you can do use STRING_SPLIT (Transact-SQL) if you really need to pass filtering parameters as strings:
declare #list varchar(max) = '2,4,6-8,9'
declare #filter table (id1 int, id2 int)
insert into #filter (id1,id2)
select
case when b.pos > 0 then left(a.[value], pos - 1) else a.[value] end as id1,
case when b.pos > 0 then right(a.[value], len(a.[value]) - pos) else a.[value] end as id2
from string_split(#list, ',') as a
cross apply (select charindex('-', a.[value]) as pos) as b
select *
from [dbo].[Books] as b
where
exists (select * from #filter as tt where b.id between tt.id1 and tt.id2)
Also it might be an idea to pass your filter as json and OPENJSON (Transact-SQL) so you can make parsing part simplier:
declare #list varchar(max) = '[2,4,[6,8],9]'
select
case when a.[type] = 4 then json_value(a.[value], '$[0]') else a.[value] end,
case when a.[type] = 4 then json_value(a.[value], '$[1]') else a.[value] end
from openjson(#list) as a
All above, of course, only applicable if you have Sql Server 2016 or higher
IN() operator determines whether a specified value matches any value in a subquery or a list. not a string and that what you are doing.
What you are trying to do can be done as
DECLARE #List1 AS TABLE (ID INT);
INSERT INTO #List1
SELECT 2 UNION SELECT 4 UNION SELECT 6 UNION
SELECT 7 UNION SELECT 8 UNION SELECT 9;
SELECT *
FROM Books
WHERE ID IN (SELECT ID FROM #List1);
DECLARE #List2 As TABLE (AFrom INT, ATo INT);
INSERT INTO #List2
SELECT 2, 4 UNION SELECT 7, 9;
SELECT *
FROM Books B CROSS APPLY #List2 L
WHERE B.ID BETWEEN L.AFrom AND L.ATo;
Live Demo
I would like to extract only the last three values from a delimited string and generate a delimited substring with those three values. Could anyone suggest what is the best way to do this. I tried using STRING_SPLIT and was able to successfully split the string into multiple values but I am not sure how to proceed further. Any thoughts would be appreciated.
SELECT value FROM STRING_SPLIT('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL',',')
Output:
CatalogTypeCode VARCHAR(10) NOT NULL
EventID INT NOT NULL
ModelCode TINYINT NOT NULL
YearID INT NOT NULL
PerilSetCode INT NOT NULL
GrossLoss FLOAT NULL
GrossSD FLOAT NULL
GrossMaxLoss FLOAT NULL
Expected Output :
'GrossLoss FLOAT NULL,GrossSD FLOAT NULL,GrossMaxLoss FLOAT NULL'
Perhaps another option using a little XML in concert with reverse() ... twice
Example
Declare #YourTable table (ID int,SomeCol varchar(150))
Insert Into #YourTable values
(1,'Val1,Val2,Val3,Val4')
,(2,'Val1,Val2,Val3,Val4,Val5,Val6')
,(3,'Val1,Val2')
,(4,'Val1')
,(5,null)
Select A.ID
,LastThree = reverse(concat(Pos1,','+Pos2,','+Pos3))
From #YourTable A
Cross Apply (
Select Pos1 = n.value('/x[1]','varchar(max)')
,Pos2 = n.value('/x[2]','varchar(max)')
,Pos3 = n.value('/x[3]','varchar(max)')
From (Select cast('<x>' + replace(reverse(SomeCol),',','</x><x>')+'</x>' as xml) as n) X
) B
Returns
ID LastThree
1 Val2,Val3,Val4
2 Val4,Val5,Val6
3 Val1,Val2 -- Notice only 2 values
4 Val1 -- Notice only 1 value
5 -- Notice value was null
Unfortunately, split_string() doesn't return the position of the values within the string.
This will work, assuming there are no duplicates among the lines:
SELECT string_agg(line, ',') within group (order by pos) as lines_3
FROM (SELECT TOP (3) s.line, CHARINDEX(line, lines) as pos
FROM (VALUES ('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL')
) v(lines) OUTER APPLY
STRING_SPLIT(v.lines, ',') s(line)
ORDER BY pos
) s
EDIT:
Oops, the above works on SQL Server 2017, but not 2016. You can use conditional aggregation:
SELECT (MAX(CASE WHEN seqnum = 1 THEN line END) + ','
MAX(CASE WHEN seqnum = 2 THEN line END) + ','
MAX(CASE WHEN seqnum = 3 THEN line END)
) as lines_3
FROM (SELECT TOP (3) s.line,
ROW_NUMBER() OVER (ORDER BY CHARINDEX(line, lines)) as seqnum
FROM (VALUES ('CatalogTypeCode VARCHAR(10) NOT NULL
,EventID INT NOT NULL
,ModelCode TINYINT NOT NULL
,YearID INT NOT NULL
,PerilSetCode INT NOT NULL
,GrossLoss FLOAT NULL
,GrossSD FLOAT NULL
,GrossMaxLoss FLOAT NULL')
) v(lines) OUTER APPLY
STRING_SPLIT(v.lines, ',') s(line)
ORDER BY pos
) s;
I am using this table-valued function for years now. Works fine. After creating function call select top 3 * from lma.dbo.split_test('a,b,c,d,e',',') order by id desc
CREATE FUNCTION [dbo].[Split]
(
#String VARCHAR(MAX),
#Delimiter VARCHAR(10)
)
RETURNS #Temptable TABLE (id int identity, items varchar(MAX))
AS
BEGIN
DECLARE #Idx INT
DECLARE #Slice VARCHAR(MAX)
DECLARE #Delimiterlen INT=LEN(#Delimiter)
--,#String VARCHAR(MAX)='N'
--,#Delimiter VARCHAR(10)=';;'
IF (#String like '%' + #Delimiter + '%')
BEGIN
SELECT #Idx = 1
IF LEN(#String)<#Delimiterlen or #String is null RETURN
WHILE #Idx!= 0
BEGIN
SET #Idx = CHARINDEX(#Delimiter,#String)
IF #Idx!=0
SET #Slice = left(#String,#idx-1)
ELSE
SET #Slice = #String
IF(LEN(#Slice)>0)
INSERT INTO #Temptable(Items) values(#Slice)
--290913 : IF WE WANT TO USE DELIMETER LENGTH GREATER THAN 2
IF LEN(#String) >= (#Idx -1 + #Delimiterlen)
SET #String = RIGHT(#String,LEN(#String) - (#Idx-1+#Delimiterlen))
IF LEN(#String) = 0 BREAK
END
END
ELSE
BEGIN
INSERT INTO #Temptable(Items) values(#String)
END
DELETE #Temptable WHERE items =''
RETURN
END
I have a table UserPermission which has a number of columns of TINYINT type. e.g Read, Write, Update, Delete, Access etc.
I get three parameters in the stored procedure: #UserId, #ColNames, #ColValues where #ColNames and #ColValues are comma separated values.
How can I insert or update the table row (if already exists) with the passed column names and corresponding values.
I try to write the dynamic query which runs fine for INSERT but I was unable to write the UPDATE query dynamically with each column and its value to be concatenate.
Any response would be appreciated
Thanks in advance.
This is a somewhat dirty way to do what you require. However, if you create the following Stored Procedure:
CREATE FUNCTION [dbo].[stringSplit]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
You can then use that Procedure to join the data together:
DECLARE #TotalCols INT
DECLARE #TotalVals INT
SET #TotalCols = (
SELECT COUNT(ID) AS Total
FROM dbo.stringSplit('department, teamlead', ',')
);
SET #TotalVals = (
SELECT COUNT(ID) AS Total
FROM dbo.stringSplit('IT, Bob', ',')
);
IF #TotalCols = #TotalVals
BEGIN
IF OBJECT_ID('tempdb..#temptable') IS NOT NULL
DROP TABLE #temptable
CREATE TABLE #temptable (
ColName VARCHAR(MAX) NULL
,ColValue VARCHAR(MAX) NULL
)
INSERT INTO #temptable
SELECT a.DATA
,b.DATA
FROM dbo.stringSplit('department, teamlead', ',') AS a
INNER JOIN dbo.stringSplit('IT, Bob', ',') AS b ON a.Id = b.Id
SELECT *
FROM #temptable;
END
It's not very efficient, but it will bring you the desired results.
You can then use the temp table to update, insert and delete as required.
Instead of having a comma delimited list I would create a separate parameter for each Column and make its default value to NULL and in the code update nothing if its null or insert 0. Something like this....
CREATE PROCEDURE usp_UserPermissions
#UserID INT
,#Update INT = NULL --<-- Make default values NULL
,#Delete INT = NULL
,#Read INT = NULL
,#Write INT = NULL
,#Access INT = NULL
AS
BEGIN
SET NOCOUNT ON;
Declare #t TABLE (UserID INT, [Update] INT,[Read] INT
,[Write] INT,[Delete] INT,[Access] INT)
INSERT INTO #t (Userid, [Update],[Read],[Write],[Delete],[Access])
VALUES (#UserID , #Update , #Read, #Write , #Delete, #Access)
IF EXISTS (SELECT 1 FROM UserPermission WHERE UserID = #UserID)
BEGIN
UPDATE up -- Only update if a value was provided else update to itself
SET up.[Read] = ISNULL(t.[Read] , up.[Read])
,up.[Write] = ISNULL(t.[Write] , up.[Write])
,up.[Update] = ISNULL(t.[Update] , up.[Update])
,up.[Delete] = ISNULL(t.[Delete] , up.[Delete])
,up.[Access] = ISNULL(t.[Access] , up.[Access])
FROM UserPermission up
INNER JOIN #t t ON up.UserID = t.UserID
END
ELSE
BEGIN
-- if already no row exists for that User add a row
-- If no value was passed for a column add 0 as default
INSERT INTO UserPermission (Userid, [Update],[Read],[Write],[Delete],[Access])
SELECT Userid
, ISNULL([Update], 0)
, ISNULL([Read], 0)
, ISNULL([Write], 0)
, ISNULL([Delete], 0)
, ISNULL([Access], 0)
FROM #t
END
END
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