Handling larger strings in Sql Server 2008 - sql-server-2008

We have an stored procedure that we created so that user can write comma separated search tags in their software product's admin. So he can add comma-separated tags and in case if he wants to edit them, we read from the table all the tags, recreate them as comma-separated values (CSV) in stored procedure and returns that to the calling code. What happened recently, the user complained that he could not see the new CSVs he wrote. I looked into it and found out that the stored procedure is truncating the string when it reads values from database and creates CSV string. The string is of type nvarchar, and because its exceeding the max characters of 4000 limit, the values gets truncated. Any ideas on how to work out that problem.
Find my code underneath.
BEGIN
BEGIN
Declare #Synonyms Table
(
RowID int Identity(1,1),
SynonymID int,
[Synonym] nvarchar(4000)
);
SET NOCOUNT ON;
Insert #Synonyms(SynonymID, [Synonym])
Select distinct SynonymID, [Synonym] From RF_SearchSynonyms with(nolock) Where SearchTermID = #SearchTermID And ActiveInd = 1
If((Select COUNT(RowID) From #Synonyms) <> 0)
BEGIN
Declare #CurrentRow int = (Select MIN(RowID) From #Synonyms),
#TotalRows int = (Select MAX(RowID) From #Synonyms),
#Synonyms_CSV nvarchar(4000) = '';
WHILE #CurrentRow <= #TotalRows
BEGIN
Declare #TempSyn nvarchar(500);
Select #TempSyn = [Synonym] + ',' From #Synonyms Where RowID = #CurrentRow;
Set #Synonyms_CSV = #Synonyms_CSV + LTRIM(RTRIM(LOWER(#TempSyn)));
SET #CurrentRow = #CurrentRow + 1
END
END
Else
BEGIN
Set #Synonyms_CSV = '';
END
END
BEGIN
Declare #SKUs Table
(
RowID int Identity(1,1),
SkuID int,
SKU nvarchar(15)
);
SET NOCOUNT ON;
Insert #SKUs(SkuID, SKU)
Select distinct SkuID, SKU From RF_SearchSkus with(nolock) Where SearchTermID = #SearchTermID And ActiveInd = 1
If((Select COUNT(RowID) From #SKUs) <> 0)
BEGIN
Declare #CurrentRow1 int = (Select MIN(RowID) From #SKUs),
#TotalRows1 int = (Select MAX(RowID) From #SKUs),
#Skus_CSV nvarchar(4000) = '';
WHILE #CurrentRow1 <= #TotalRows1
BEGIN
Declare #TempSku nvarchar(15);
Select #TempSku = SKU + ',' From #SKUs Where RowID = #CurrentRow1;
Set #Skus_CSV = #Skus_CSV + LTRIM(RTRIM(#TempSku));
SET #CurrentRow1 = #CurrentRow1 + 1
END
END
Else
BEGIN
Set #Skus_CSV = '';
END
END
BEGIN
Declare #Combined varchar(8000),
#syn_len int = 0,
#sku_len int = 0;
Select #syn_len = LEN(#Synonyms_CSV);
Select #sku_len = LEN(#Skus_CSV);
Select #Combined = #Synonyms_CSV + '-_-' + #Skus_CSV;
Select #Synonyms_CSV + '-_-' + #Skus_CSV;
END
END
I can't use text and ntext as they do not play nice with concatenation operations.
Thanks.

How are your declaring the string parameter?
nvarchar(max)
supports up to 2^32-1 (2GB)
See this link.

Related

Visual Studio 2010 Reporting Services - how to populate something when nothing is returned

I'm using a query with two parameters (#campaign,#resultcode) to populate a table with 3 columns ("Campaignname","Disposition","Count"), but when either one of those parameters don't exist in the database, nothing populates in the table. Is there a way to make it populate the two parameters with a count of 0? Also I have it set so that multiple parameters can be selected. I've tried IIF(IsNothing()..., IIF(***.value = null or ""). Still doesn't do what I want it to do. Some help?
Included code from comment response:
SELECT databasename, callresultdescription, count(*) as Count
FROM bpsql00.[histCallCenterStats].[dbo].[CallResults]
WHERE databasename IN(#campaign) AND callresultcode IN(#resultcode)
GROUP BY databasename, callresultdescription
The callresultdescription is AKA disposition
You could union them together:
--create table [CallResults] (databasename varchar(10),callresultdescription
varchar(10),myvalue int)
--insert into [CallResults]
--values ('a','AA',1),
--('b','BB',2),
--('c','CC',3)
--select * from [CallResults]
declare #campaign varchar(10)='d',#resultcode varchar(10)='dd' ;
SELECT databasename, callresultdescription,
count(1) as [Count]
FROM [CallResults]
WHERE databasename IN (#campaign)
AND callresultdescription IN (#resultcode)
GROUP BY databasename, callresultdescription
UNION
SELECT databasename=#campaign,
callresultdescription=#resultcode,
0 as [Count]
from [CallResults]
where databasename not IN (#campaign)
AND callresultdescription not IN (#resultcode)
You can accomplish this with an IF statement in the SQL query:
IF EXISTS (SELECT 1 FROM bpsql00.[histCallCenterStats].[dbo].[CallResults] WHERE databasename IN ( #campaign ) AND callresultcode IN ( #resultcode ))
SELECT databasename
, callresultdescription
, [Count] = COUNT(*)
FROM bpsql00.[histCallCenterStats].[dbo].[CallResults]
WHERE databasename IN ( #campaign )
AND callresultcode IN ( #resultcode )
GROUP BY databasename ,
callresultdescription;
ELSE
SELECT databasename = #campaign
, callresultdescription = #resultcode
, [Count] = 0
Edit per question in the comment:
It gets tricky when you need to return a multi-valued parameter. If you're on SQL 2016, you can use the new TSQL STRING_SPLIT function to split out their comma-separated selections. There are also splitter functions you can find on the interwebs for prior versions of SQL. The simplest solution, though, is to let the query return nothing and set the NoRowsMessage of the tablix to inform the client. You can use an expression like
="No records found in the selected campaigns (" & _
Parameters!campaign.Value & ") and result codes (" & _
Parameters!resultcode.Value & ")."
That gives the user a record of what was searched and that nothing was found to match their criteria.
So finally figured it out. I have concluded that the program is limited what I wanted to do. So... why not let SQL do it for me and I can just call a stored procedure. BINGO. I had to create a function as well. So for anyone who needs something like this.
Stored procedure I created:
alter procedure [dbo].[rs_Query]
#campaign varchar (100),
#resultcode varchar (100)
as
Begin
declare #var_campaign varchar(100)
declare #var_resultcode varchar(100)
declare #c table(ID int identity, databasename varchar(100))
declare #r table(ID int identity, callresultcode varchar(100))
insert into #c select element from dbo.func_split(#campaign, ',')
insert into #r select element from dbo.func_split(#resultcode,',')
declare #dbcnt int --count of campaigns selected
declare #crcnt int --count of dispositions selected
declare #crrow int --row id for campaigns selected
declare #dbrow int --row id for dispositions selected
declare #tempdbname varchar(50) --temp campaign name
declare #tempcr varchar(50) --temp call result name
declare #t table (databasename varchar(100), callresultdescription varchar (100), Count int)
declare #count int
select #dbcnt = count(*) from #c
select #crcnt = count(*) from #r
select #dbrow = 1
select #crrow = 1
while #dbcnt >= #dbrow
begin
set #tempdbname = (select databasename
from bpsql00.callcenteraux.dbo.DailyReportsCampaign
where databasename = (select databasename from #c where id = #dbrow))
set #crrow = 1
while #crcnt >= #crrow
begin
set #tempcr = (select CallResultDescription
from CallResultCode
where CallResultCode = (select CallResultCode from #r where id = #crrow));
if exists(select 1 from bpsql00.[histCallCenterStats].[dbo].[CallResults]
where CallResultCode = (select CallResultCode from #r where id = #crrow) and databasename = #tempdbname)
begin
select #count = count(*) from bpsql00.[histCallCenterStats].[dbo].[CallResults]
where CallResultCode = (select CallResultCode from #r where id = #crrow) and databasename = #tempdbname
insert into #t values(#tempdbname,#tempcr,#count)
end
else
begin
insert into #t values(#tempdbname,#tempcr,0)
end
set #crrow = #crrow + 1
end
set #dbrow = #dbrow + 1
end
select * from #t
end
And function I created:
ALTER FUNCTION [dbo].[func_Split]
(
#DelimitedString varchar(8000),
#Delimiter varchar(100)
)
RETURNS #tblArray TABLE
(
ElementID int IDENTITY(1,1), -- Array index
Element varchar(1000) -- Array element contents
)
AS
BEGIN
-- Local Variable Declarations
-- ---------------------------
DECLARE #Index smallint,
#Start smallint,
#DelSize smallint
SET #DelSize = LEN(#Delimiter + 'x') - 1
-- Loop through source string and add elements to destination table array
-- ----------------------------------------------------------------------
WHILE LEN(#DelimitedString) > 0
BEGIN
SET #Index = CHARINDEX(#Delimiter, #DelimitedString)
IF #Index = 0
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(#DelimitedString)))
BREAK
END
ELSE
BEGIN
INSERT INTO
#tblArray
(Element)
VALUES
(LTRIM(RTRIM(SUBSTRING(#DelimitedString, 1,#Index - 1))))
SET #Start = #Index + #DelSize
SET #DelimitedString = SUBSTRING(#DelimitedString, #Start , LEN(#DelimitedString) - #Start + 1)
END
END
RETURN
END

Passing values to dynamic query in SQL Server procedure

CREATE TYPE TableVariable AS TABLE
(
id int identity(1,1),
field_ids INT,
value VARCHAR(MAX)
)
Type created successfully.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Alter PROCEDURE Testing
(
#TableVar TableVariable READONLY,
#C_id INT
)
AS
Declare #maxPK INT;
Declare #pk INT;
Declare #fid INT;
Declare #val VARCHAR(max);
Declare #Where VARCHAR(max);
Declare #SQL VARCHAR(MAX);
Set #pk = 1
BEGIN
BEGIN TRY
SET NOCOUNT ON;
Select #maxPK = count(*) From #TableVar
SELECT #maxPK
SELECT #C_id
Set #SQL = 'SELECT DISTINCT v1.e_id from values v1 inner join listings l ON v1.e_id =
l.e_id WHERE l.c_id='+ #C_id
SELECT #SQL
While #pk <= #maxPK
BEGIN
SELECT #fid= field_ids FROM #TableVar where id=#pk;
SELECT #val= value FROM #TableVar where id=#pk;
SELECT #fid
SELECT #val
SET #SQL += ' and exists (select 1 from values v#pk+1 where v1.e_id = #pk+1.e_id and #pk+1.f_id=#fid and(value=#val))'
Select #pk = #pk + 1
END
SELECT #SQL
EXECUTE SP_EXECUTESQL #SQL
END TRY
BEGIN CATCH
END CATCH
END
DECLARE #DepartmentTVP AS TableVariable;
INSERT INTO #DepartmentTVP VALUES (14567, 'first')
INSERT INTO #DepartmentTVP VALUES (145678, 'second')
SELECT *
FROM #DepartmentTVP
EXEC Testing #DepartmentTVP, 83
I executed the above procedure.Values are not passed to the query
This is my orginal query
select distinct
v1.e_id
from
values v1
inner join
listings l on v1.e_id = l.e_id
where
l.c_id = 83
and exists (select 1
from values v2
where v1.e_id = v2.e_id
and v2.f_id = 1780
and (value = N'Peppermint Whole Care Toothpaste'))
and exists (select 1
from values v3
where v1.e_id = v3.e_id
and v3.f_id = 22483
and (value = N'sasdfa'))
order by
l.id desc
and exists part will add dynamically.
If you are wanting a variable to be part of the text such as:
'select 1 from values v#pk+1 where v1.e_id = #pk+1.e_id'
Then you need to escape the string and add the variable -- but beware this is prone to SQL Injection attacks if you don't strictly control the source of the variable data!
Example:
'select 1 from values v'+convert(varchar(15),#pk+1)+' where v1.e_id = '+convert(varchar(15),#pk+1)+'.e_id'
If you simply want to pass the variable into the string such as:
'and v5.f_id=#fid and(value=#val))'
Then you leave those variables unescaped, and pass them into sp_executesql
Example:
exec sp_executesql #SQL, N'#fid int, #val varchar(max)', #fid=#fid, #val=#val
More info on sp_executesql and its parameters:
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql

Get data from comma separated string input

I have a stored procedure where input is a comma separated string say '12341,34567,12446,12997' and it is not sure that the input string always carries numerical data. It may be '12341,34as67,12$46,1we97' so I need to validate them and use only the valid data in query.
Say my query is (Where the column OrderCode is int type)
select * from dbo.DataCollector where OrderCode in (12341,34567,12446,12997)
or only the valid data if other are invalid
select * from dbo.DataCollector where OrderCode in (12341)
For such situation what would be a good solution.
One way that works also in SQl-Server 2005 would be to create a split-function, then you can use ISNUMERIC to check if it's a number:
DECLARE #Input VARCHAR(MAX) = '12341,34as67,12$46,1we97'
SELECT i.Item FROM dbo.Split(#Input, ',')i
WHERE IsNumeric(i.Item) = 1
Demo
Your complete query:
select * from dbo.DataCollector
where OrderCode in ( SELECT i.Item FROM dbo.Split(#Input, ',')i
WHERE IsNumeric(i.Item) = 1 )
Here is the split-function which i use:
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
Edit according to the comment of Damien that ISNUMERIC has it's issues. You can use this function to check if it's a real integer:
CREATE FUNCTION dbo.IsInteger(#Value VarChar(18))
RETURNS Bit
AS
BEGIN
RETURN IsNull(
(Select Case When CharIndex('.', #Value) > 0
Then Case When Convert(int, ParseName(#Value, 1)) <> 0
Then 0
Else 1
End
Else 1
End
Where IsNumeric(#Value + 'e0') = 1), 0)
END
Here is another example with damien's "bad" input which contains £ and 0d0:
Demo

Select UNION from multiple tables via variable

I have a query that selects from multiple tables using a join. I want to execute this query from different databases via a loop.
I have accomplished that via (simplified query):
DECLARE #intCounter int
SET #intCounter = 1
DECLARE #tblBedrijven TABLE (ID int identity(1,1),
CompanyName varchar(20),
DatabaseTable varchar(100))
INSERT INTO #tblBedrijven VALUES ('001-CureCare', '<TABLE/ DATABASE1> AUS'),
('002-Cleaning', '[global_nav5_prod].[dbo].<TABLE/ DATABASE2>] AUS')
DECLARE #strCompany varchar(20)
DECLARE #strTable varchar(100)
WHILE (#intCounter <= (SELECT MAX(ID) FROM #tblBedrijven))
BEGIN
SET #strTable = (SELECT DatabaseTable FROM #tblBedrijven
WHERE ID = #intCounter)
SET #strCompany = (SELECT CompanyName FROM #tblBedrijven
WHERE ID = #intCounter)
EXEC('SELECT ''' + #strCompany + ''' as Company,
AUS.[User],
AUS.[E-mail]
FROM' + #strTable)
SET #intCounter = #intCounter + 1
END
My problem is that the result generates 2 separate tables (for every loop). I want to union the results but have no clue how.
Any suggestions?
Thanks in advance.
Can't you use something like the below code where you append all the sqls with union and finally execute the sql once only without executing in a loop. I am not an expert in SQL Server but I have written many other similar stored procedures using other RDBMS. So please bear any syntax errors.
DECLARE #intCounter int
DECLARE #maxId int
SET #intCounter = 1
DECLARE #tblBedrijven TABLE (ID int identity(1,1),
CompanyName varchar(20),
DatabaseTable varchar(100))
INSERT INTO #tblBedrijven VALUES ('001-CureCare', '<TABLE/ DATABASE1> AUS'),
('002-Cleaning', '[global_nav5_prod].[dbo].<TABLE/ DATABASE2>] AUS')
DECLARE #strCompany varchar(20)
DECLARE #strTable varchar(100)
DECLARE #strSql varchar(5000)
SET #maxId = (SELECT MAX(ID) FROM #tblBedrijven)
WHILE (#intCounter <= #maxId)
BEGIN
SET #strTable = (SELECT DatabaseTable FROM #tblBedrijven
WHERE ID = #intCounter)
SET #strCompany = (SELECT CompanyName FROM #tblBedrijven
WHERE ID = #intCounter)
SET #strSql = #strSql + ('SELECT ''' + #strCompany + ''' as Company,
AUS.[User],
AUS.[E-mail]
FROM' + #strTable)
IF #intCounter < #maxId THEN
BEGIN
SET #strSql = #strSql + ' UNION '
END
SET #intCounter = #intCounter + 1
END
EXEC(#strSql)

T-SQL: split and aggregate comma-separated values

I have the following table with each row having comma-separated values:
ID
-----------------------------------------------------------------------------
10031,10042
10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039
10009
20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041
I need to get a count for each ID (a groupby).
I am just trying to avoid cursor implementation and stumped on how to do this without cursors.
Any Help would be appreciated !
You will want to use a split function:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 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)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
And then you can query the data in the following manner:
select items, count(items)
from table1 t1
cross apply dbo.split(t1.id, ',')
group by items
See SQL Fiddle With Demo
Well, the solution i always use, and probably there might be a better way, is to use a function that will split everything. No use for cursors, just a while loop.
if OBJECT_ID('splitValueByDelimiter') is not null
begin
drop function splitValueByDelimiter
end
go
create function splitValueByDelimiter (
#inputValue varchar(max)
, #delimiter varchar(1)
)
returns #results table (value varchar(max))
as
begin
declare #delimeterIndex int
, #tempValue varchar(max)
set #delimeterIndex = 1
while #delimeterIndex > 0 and len(isnull(#inputValue, '')) > 0
begin
set #delimeterIndex = charindex(#delimiter, #inputValue)
if #delimeterIndex > 0
set #tempValue = left(#inputValue, #delimeterIndex - 1)
else
set #tempValue = #inputValue
if(len(#tempValue)>0)
begin
insert
into #results
select #tempValue
end
set #inputValue = right(#inputValue, len(#inputValue) - #delimeterIndex)
end
return
end
After that you can call the output like this :
if object_id('test') is not null
begin
drop table test
end
go
create table test (
Id varchar(max)
)
insert
into test
select '10031,10042'
union all select '10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039'
union all select '10009'
union all select '20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041'
select value
from test
cross apply splitValueByDelimiter(Id, ',')
Hope it helps, although i am still looping through everything
After reiterating the comment above about NOT putting multiple values into a single column (Use a separate child table with one value per row!),
Nevertheless, one possible approach: use a UDF to convert delimited string to a table. Once all the values have been converted to tables, combine all the tables into one table and do a group By on that table.
Create Function dbo.ParseTextString (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dlLen TinyInt -- Length of delimiter
Declare #wind VarChar(8000) -- Will Contain Window into text string
Declare #winLen Integer -- Length of Window
Declare #isLastWin TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #roVal VarChar(8000)-- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dlPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
-- ---------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- --------------------------------------------
Select #dlLen = DataLength(#delim),
#Strt = 1, #wPos = 1,
#wind = Substring(#S, 1, #BtchSiz)
Select #winLen = DataLength(#wind),
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, #Strt)
-- --------------------------------------------
While #Strt <= #winLen
Begin
If #dlPos = 0 Begin -- No More delimiters in window
If #isLastWin = 1 Set #dlPos = #winLen + 1
Else Begin
Set #wPos = #wPos + #Strt - 1
Set #wind = Substring(#S, #wPos, #BtchSiz)
-- ----------------------------------------
Select #winLen = DataLength(#wind), #Strt = 1,
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, 1)
If #dlPos = 0 Set #dlPos = #winLen + 1
End
End
-- -------------------------------
Insert #tOut (sVal)
Select LTrim(Substring(#wind,
#Strt, #dlPos - #Strt))
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dlPos + #dlLen
Set #dlPos = CharIndex(#delim, #wind, #Strt)
End
Return
End
Then write, (using your table schema),
Declare #AllVals VarChar(8000)
Select #AllVals = Coalesce(#allVals + ',', '') + ID
From Table Where ID Is Not null
-- -----------------------------------------
Select sVal, Count(*)
From dbo.ParseTextString(#AllVals, ',')
Group By sval