I am not sure why this is happening, but when I run some code that takes a long multi-value sub-value string, I'm getting a random error. The error is supposedly that I'm trying to do a insert into a temp table that already exists, though I'm not sure how this is possible since inside the top of the first base loop, I'm dropping the tables if they do exist. That way the prior run can use them, them drop and run through again if need be.
Now I'm almost certain another error is failing, then causing a misleading return message but not sure.
Here is the full stored procedure. Also, I'm a stack dev so if you have any good pointers for this query please feel free to make note. Thanks in advance.
DATA string for testing that would possibly come into this procedure:
MITUTOYO~103-217~DESC~ ^BROWN & SHARPE~73~DESC~ ^MITUTOYO~103-188~DESC~ ^MITUTOYO~103-224A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-225A~DESC~ ^MITUTOYO~103-189A~DESC~ ^
For each one of the main multi values within the ^ character, we would look up the Manufacturer and Model and see if it exist. If so return it, if not return it with a new suggestions list of multi-values in the suggestionsList column. Here is the example of one item sent in, then no matches found so does the fuzzy lookup.
Manufacturer ManufacturerPartNumber ManufacturerDescription Price ItemType MfrFound ModelFound Score SuggestionGroup
Analyzer Network, 0.00, NA, 0, 1, 0, STANDARD,,USE FOR HYPRO STANDARD BORE
GAGES,31.00,6, ^ Harvey Wells,,FORCE GAUGE 0 35 GMS,93.50,0, ^
The full stored procedure:
ALTER PROCEDURE DATA_FuzzyAssetMatchLARGE
/*
PROP: #LISTIN - The entire multi file string sent in from the data file of records.
I=Since the records sent in are a multi value record of sub values meaning (v1.Mfr, v1.Model, v1.Description ^ v2.Mfr, v2.Model, v2.Description ^ ....)
we could in theory have thousands of records to n(3) of string length unknown (n). This being said, we set the value to varchar(max).
varchar(max) unlike varchar which holds (8000 characters max about 65k bytes), varchar(max) can hold 2 gigs worth of data. That equates to about
1 billion characters since in .NET and others one CHAR is a 2 byte system. This holds more than enough.
*/
#LISTIN VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SET ARITHABORT OFF
SET ANSI_WARNINGS OFF
SET FMTONLY OFF
declare #errorMessage varchar(150) -- ideally would be nice to append to each row so we knew what failed but not break and continue. Not like that at moment
-- ITEMS USED FOR TOP LEVEL RECURSIVE SPLIT LOOP --
declare #pos INT;
declare #len INT;
declare #value varchar(max); -- the value of the first splits (objects) delimed by the ^ character
-- Counter variables used to loop and build the one column Suggestion of all matching items
declare #MaxCount integer;
declare #Count integer;
declare #suggestionsStringBuilder varchar(350); --holds the built list of any suggestions.
declare #objectManufacturer varchar(100);
declare #objectModel varchar(150);
declare #objectDescription varchar(150); --the context for this instance to run the params selects for Mfr, Model, Desc
declare #counterIndex int = 0;
declare #objectString varchar(250);
--USED FOR SECOND LEVEL RECURSIVE SPLIT LOOP
declare #objectPosVal INT;
declare #objectLenVal INT;
declare #objectSubstring varchar(250); -- the value used for the secondary split (each object properties such as the actual Mfr, Model, Description, so on....)
--cursor declarations
DECLARE #suggestionsListCursor CURSOR;
DECLARE #CURSOR_Mfr varchar(150);
DECLARE #CURSOR_Model varchar(150);
DECLARE #CURSOR_Desc varchar(150);
DECLARE #CURSOR_Type varchar(20);
DECLARE #CURSOR_Price MONEY;
DECLARE #CURSOR_Score int;
--TEMP TABLE TO STORE FUZZY PREDFINED TABLES IN
IF OBJECT_ID('BASE') IS NOT NULL DROP TABLE BASE
--CREATE THE TEMPTABLE TO WORK WITH
CREATE TABLE BASE
(
Manufacturer varchar(150) null,
ManufacturerPartNumber Varchar(100) null,
ManufacturerDescription varchar(150) null,
Score int ,
Price money,
ItemType varchar(20),
ModelFound bit not null,
MfrFound bit not null,
SuggestionGroup varchar(MAX) null
)
--THIS IS THE LOOP TO PREPARE THE DELIM STRING AND DO A FUZZY MATH OR STRAIGHT MATCH ON EACH OBJECT ROW
set #pos = 0;
set #len = 0;
while charindex('^', #LISTIN, #pos+1)>0
begin
if object_id('tempdb..#TEMPPMFR') is not null drop table #TEMPPMFR
if object_id('tmpdb..#TEMPPMFRMODEL') is not null drop table #TEMPPMFRMODEL
-- stop if there was an error --
if(#errorMessage is not null and #errorMessage != '')
break;
set #suggestionsStringBuilder = '';
set #len = charindex('^', #LISTIN, #pos+1) - #pos;
set #VALUE = substring(#LISTIN,#POS,#LEN-1);
set #objectLenVal = 0;
set #objectPosVal = 0;
set #counterIndex = 0;
set #suggestionsStringBuilder = ''
set #objectManufacturer = ''
set #objectModel = ''
set #objectDescription = ''
--THE LIST COMING IN IS A MULTI-DEMENSIONAL ARRAY OR MULTI-VALUE. IT IS A GROUP OF ENTITYIES, THEN EACH
--ENTITYT IS A GROUP OF PROPERTIES. OUR OUTTER LOOP WE SPLIT ON ENTITITIES.
-- EXAMPLE: #F, #M, #D = Manufacturer, odel, Description
while charindex('~', #value, #objectPosVal+1)>0
begin
set #objectLenVal = charindex('~', #value, #objectPosVal+1) - #objectPosVal
set #objectSubstring = substring(#value, #objectPosVal, #objectLenVal)
if(#counterIndex=0)
set #objectManufacturer = LTRIM(RTRIM(#objectSubstring))
else if(#counterIndex=1)
set #objectModel = LTRIM(RTRIM(#objectSubstring))
else
begin
set #objectDescription = LTRIM(RTRIM(#objectSubstring))
break
end
set #objectPosVal = charindex('~', #value, #objectPosVal+#objectLenVal) +1
end
-- ****
-- **** WE HAVE THE MANUFACTURER AND THE MODEL SO JUST RETURN THE DATA
-- ****
if((select top 1 1 FROM Products_OurProducts_Products where Manufacturer = #objectManufacturer and ManufacturerPartNumber = #objectModel) > 0)
begin try
insert into BASE (
Manufacturer
,ManufacturerPartNumber
,ManufacturerDescription
,Price,ItemType
,MfrFound
,ModelFound
,Score
,SuggestionGroup
)
select
POP.Manufacturer
,POP.ManufacturerPartNumber as Model
,POP.Description
,CONVERT(money,POP.Price) as Price
,POP.ItemType
,CAST('1' as bit) as MfrFound
,CAST('1' as bit) as ModelFound
,CAST('-1' as int) as Score
,'' as SuggestionGroup
from
Products_OurProducts_Products as POP
where
POP.Manufacturer = #objectManufacturer
and
POP.ManufacturerPartNumber = #objectManufacturer
end try
begin catch
SET #errorMessage = (
select
'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage
);
end catch
else
-- ****
-- **** WE EITHER FOUND MANUFACTURER SO FUZZY ON MODEL OR VICE VERSA
-- ****
begin try
if((select top 1 1 from Products_OurProducts_Products where Manufacturer = #objectManufacturer) > 0)
begin
--we have to build these temp tables os dynamic columns exist such as MfrFound, ModelFound
select
PMFR.Manufacturer
,PMFR.ManufacturerPartNumber
,PMFR.Description AS ManufacturerDescription
,convert(money,PMFR.Price) as Price
,PMFR.ItemType
,cast('1' as bit) as MfrFound
,cast('0' as bit) as ModelFound
,'' as SuggestionGroup
into
#TEMPPMFR
from
Products_OurProducts_Products as PMFR
where
PMFR.Manufacturer = #objectManufacturer
set #SuggestionsListCursor = cursor for
select top 5
P.Manufacturer
,P.ManufacturerPartNumber as Model
,P.ManufacturerDescription AS 'Description'
,P.Price
,fms.score as Score
from #TEMPPMFR as P
cross apply (
select
dbo.FuzzyControlMatch(#objectModel, P.ManufacturerPartNumber) AS score
) as fms
where
P.Manufacturer = #objectManufacturer
order by
fms.score
desc
open #SuggestionsListCursor
fetch next from
#SuggestionsListCursor
into
#CURSOR_Mfr
,#CURSOR_Model
,#CURSOR_Desc
,#CURSOR_Price
,#CURSOR_Score
while ##FETCH_STATUS = 0
begin
if #suggestionsStringBuilder!=''
set #suggestionsStringBuilder=#suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
else
set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
fetch next from
#SuggestionsListCursor
into
#CURSOR_Mfr
,#CURSOR_Model
,#CURSOR_Desc
,#CURSOR_Price
,#CURSOR_Score
end
--Now we insert the original Mfr, Model, Desc, and the suggestions list we build
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'1'
,'0'
,#suggestionsStringBuilder
)
close #SuggestionsListCursor
deallocate #SuggestionsListCursor
end
else
--IF HAVE A FUZZY AT MFR, THEN WE NEED TO GRAB BEST CHOICE AND GO DOWN.
--WE COULD HAVE POSIBLY CANDIDATES FOR THIS SO WHEN TO STOP RECURSIVENESS AND SAY ADDING NEW ENTRY?
begin
--AT MOMENT JUST RETURN TOP FOUND MFR THEN SELECT FROM THAT TO SEE RESULT TESTS
--FIRST LETS SEE IF SENT MODEL EXIST AND IF SO, PULL THAT THEN RANK AGAINST MFR FOR IT
if((select top 1 1 from Products_OurProducts_Products where ManufacturerPartNumber = #objectModel) > 0)
begin
select
Manufacturer
,ManufacturerPartNumber
,Description AS ManufacturerDescription
,CONVERT(money,Price) AS Price
,ItemType
,CAST('0' as bit) as MfrFound
,CAST('1' as bit) as ModelFound
,'' as SuggestionGroup
into
#TEMPPMFRMODEL
from
Products_OurProducts_Products
where
ManufacturerPartNumber = #objectModel
set #SuggestionsListCursor = cursor for
select top 5
P.Manufacturer
,P.ManufacturerPartNumber as Model
,P.ManufacturerDescription as 'Description'
,P.Price
,fms.score as Score
from #TEMPPMFRMODEL AS P
CROSS APPLY (
select
dbo.FuzzyControlMatch(#objectManufacturer, P.Manufacturer) AS score
) AS fms
where
P.ManufacturerPartNumber = #objectModel
order by
fms.score
desc
--OPEN CURSOR NOW--
open #SuggestionsListCursor
-- NOW LOOP THE RESULTS AND BUILD DEMLIMETER STRING OF SUGESTIONS FOR MFR--
fetch next from #SuggestionsListCursor into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score
while ##FETCH_STATUS = 0
begin
if #suggestionsStringBuilder != ''
set #suggestionsStringBuilder = #suggestionsStringBuilder + #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
else
set #suggestionsStringBuilder = #CURSOR_Mfr + ',' + #CURSOR_Model + ',' + #CURSOR_Desc + ',' + convert(varchar(20),#CURSOR_Price) + ',' + convert(varchar(4),#CURSOR_Score) + ', ^ '
fetch next from #SuggestionsListCursor
into #CURSOR_Mfr, #CURSOR_Model, #CURSOR_Desc, #CURSOR_Price, #CURSOR_Score
end
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'1'
,'0'
,#suggestionsStringBuilder
)
close #SuggestionsListCursor ;
deallocate #SuggestionsListCursor ;
end
else
insert into BASE values(
#objectManufacturer
,#objectModel
,#objectDescription
,'0'
,'0'
,'NA'
,'0'
, '0'
, ''
)
end
--THEN SEE IF EXIST USING FUZZY FOUND MFR, TO SEEK MODEL NUMBER ETC.
END TRY
BEGIN CATCH
SET #errorMessage = (
select
'Number: ' + CAST(ERROR_NUMBER() as varchar(15)) + ' Message:' + ERROR_MESSAGE() AS ErrorMessage
);
END CATCH
SET #pos = CHARINDEX('^', #LISTIN, #pos+#len) +1
END
if(#errorMessage is not null and #errorMessage != '')
select #errorMessage as 'ErrorInfo'
else
select
Manufacturer
,ManufacturerPartNumber
,ManufacturerDescription
,Price
,ItemType
,MfrFound
,ModelFound
,Score
,SuggestionGroup
from
BASE
--NOW REMOVE THE TEMP TABLE
If(OBJECT_ID('BASE') Is Not Null)
BEGIN
DROP TABLE BASE
END
IF(OBJECT_ID('tempdb..#TEMPPMFR') IS NOT NULL)
BEGIN
DROP TABLE #TEMPPMFR
END
IF(OBJECT_ID('tmpdb..#TEMPPMFRMODEL') IS NOT NULL)
BEGIN
DROP TABLE #TEMPPMFRMODEL
END
END
GO
I have a function which I have created in SQL but I am getting this error 'SQL server issue - Create Function must be the only statement in the batch'. I checked other similar topics but couldn't find anything wrong. I am using SQL Server 2012
CREATE FUNCTION GETLLPATH(#objectid FLOAT)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #dir VARCHAR(MAX);
DECLARE #obj_id FLOAT;
DECLARE Name_Cursor CURSOR LOCAL FOR
SELECT A.Name, A.ParentID FROM OTCS_User.DTree A
WHERE A.DataID = #obj_id;
DECLARE
SET #dir = NULL;
SET #obj_id = #objectid;
WHILE 1=1 BEGIN
OPEN Name_Cursor;
FETCH Name_Cursor INTO #name;
IF ##FETCH_STATUS <> 0 BREAK or #name_NAME = 'Enterprise';
IF #dir IS NOT NULL BEGIN
SET #dir = (ISNULL(#name_NAME, '') + ':' + isnull(#dir, '')) ;
END
IF #dir IS NULL BEGIN
SET #dir = #name_NAME;
END
SET #obj_id = #name_PARENTID;
CLOSE Name_Cursor;
DEALLOCATE Name_Cursor;
END;
return(#dir);
END;
GO
I am also getting error for variables as 'Must declare Scalar variable', In the end there is one more error - 'Expecting conversation', request you to please help.
I believe you just have some bad code.
Try this:
CREATE FUNCTION GETLLPATH(
#objectid FLOAT
)
RETURNS VARCHAR(4000)
AS
BEGIN
DECLARE #dir VARCHAR(MAX);
DECLARE
#obj_id FLOAT
, #name_NAME VARCHAR(50) -- or whatever your field size is.
, #name_PARENTID VARCHAR(50) -- again, whatever your field size is.
DECLARE Name_Cursor CURSOR LOCAL FOR
SELECT A.Name, A.ParentID FROM OTCS_User.DTree A WHERE A.DataID = #obj_id;
SET #dir = NULL; -- redundant.
SET #obj_id = #objectid; -- this can be set at declaration ( e.g. DELARE #obj_id FLOAT = #obj_id ).
WHILE ( 1 = 1 ) BEGIN
OPEN Name_Cursor;
FETCH Name_Cursor INTO #name;
IF ( ##FETCH_STATUS <> 0 OR #name_NAME = 'Enterprise' )
BREAK;
IF ( #dir IS NOT NULL ) BEGIN
SET #dir = (ISNULL(#name_NAME, '') + ':' + isnull(#dir, '')) ;
END
IF #dir IS NULL BEGIN
SET #dir = #name_NAME;
END
SET #obj_id = #name_PARENTID;
CLOSE Name_Cursor;
DEALLOCATE Name_Cursor;
END
RETURN #dir;
END
GO
On a personal note, I am never fond of using WHILE (1=1). Are you guaranteed to have an exit?
Also, I would highly recommend using an alternative to a cursor. Perhaps use a TABLE variable and loop through that like so:
CREATE FUNCTION GETLLPATH(
#objectid FLOAT
)
RETURNS VARCHAR(4000)
AS
BEGIN
-- declare variables --
DECLARE #id INT
, #dir VARCHAR(MAX)
, #obj_id FLOAT = #objectid
, #name_NAME VARCHAR(50)
, #name_PARENTID VARCHAR(50)
-- declare table variable --
DECLARE #data TABLE( [name] VARCHAR(50), [parent_id] VARCHAR(50), [id] INT IDENTITY (1,1) );
-- insert data --
INSERT INTO #data ( [name], [parent_id] )
SELECT A.Name, A.ParentID FROM OTCS_User.DTree A WHERE A.DataID = #obj_id;
-- for-each row... --
SET #id = 1;
WHILE ( #id <= ( SELECT MAX( id ) FROM #data ) )
BEGIN
-- current row --
SELECT
#name_NAME = [name]
, #name_PARENTID = [parent_id]
FROM #data WHERE [id] = #id;
-- do your work here...
-- next row --
SET #id = ( #id + 1 );
END
RETURN #dir;
END
GO
There are a couple of syntax errors even before I test your query:
The word 'DECLARE' should not appear in the middle without a variable name,
#name_Name and #name_PARENTID are undeclared
DECLARE #name_NAME varchar, #name_PARENTID int
(check for the variable types according to the source table)
IF ##FETCH_STATUS <> 0 BREAK or #name_NAME = 'Enterprise' is incorrect
IF ##FETCH_STATUS <> 0 or #name_NAME = 'Enterprise'
BREAK
"Create Function must be the only statement in the batch" - usually
means there is some syntax error.
This is, honestly, a total guess, and (most importantly) completely untested; due to the absence of sample data and expected results.
Anyway, like I said in the comments, a CURSOR and a Scalar-value Function are both really bad performers here. If you're trying to simply delimit your data with a colon (:), then you can use STUFF and FOR XML PATH.
Like I said, this is completed untested, but might get you on the right path:
CREATE FUNCTION dbo.GetllPath (#objectID int) --Is it really a float? I've used an int
RETURNS table
AS RETURN
SELECT STUFF((SELECT ':' + DT.[Name]
FROM OTCS_User.DTree DT
WHERE DT.DataID = #obj_id
--ORDER BY SOMETHING HERE!!!
FOR XML PATH('')),1,1,'') AS llPath;
GO
I would like to know the way I could concatenate between two dates in a calculated column horizontally, for example:
date_ini,date_end,result
2016-04-01,2016-04-05,2016-04-01|2016-04-02|2016-04-03|2016-04-04|2016-04-05
2016-04-03,2016-04-06,2016-04-03|2016-04-04|2016-04-05|2016-04-06
2016-04-05,2016-04-05,2016-04-05
2016-04-05,2016-04-06,2016-04-05|2016-04-06
The result is the column I would like to create
Thanks,
A function like this could do the trick. Please change the size of the return varchar to fit your needs.
CREATE FUNCTION dbo.date_concatenate(#start_date DATETIME, #end_date DATETIME)
RETURNS VARCHAR(255)
AS
BEGIN
DECLARE #result VARCHAR(255);
SET #result = '';
IF(#end_date < #start_date)
SET #result = 'Error: End Date can not be less than start date';
ELSE
BEGIN
WHILE (#start_date < #end_date)
BEGIN
SET #result = #result + CONVERT(varchar(10),#start_date,120) + '|'
SET #start_date = DATEADD(dd,1,#start_date)
END
END
SET #result = #result + CONVERT(varchar(10),#end_date,120)
RETURN #result
END
This code in a store procedure worked for years, now on the OPEN line I am getting A cursor with the name ... does not exist.
Did something change in sp_executesql that might have caused this to break?
Is there another way of doing this (the need for dynamic SQL is because the #Types param is passed in as a pre-formatted string for the IN clause?)
Select #Query = 'DECLARE cur_person CURSOR FOR
SELECT *
FROM MyTable
WHERE PersonID = #PersonnelID
AND Type in ' + #Types + ' <== formatted list for IN clause
EXEC sp_executesql #Query
OPEN cur_person <== get cursor doesn't exist error
In your example, that means that the cursor is defined locally.
You can define it globally with database option (CURSOR_DEFAULT) but that might not be a good idea.
Another thing that you can do is put all code in the query and execute it.
I don't know why it fails, but here's a split function that avoids the need for the dynamic query:
CREATE FUNCTION StringToTable
(
#p_list varchar(MAX),
#p_separator varchar(5) = ',',
#p_distinct bit = null
)
RETURNS
#ParsedList table
(
element varchar(500)
)
AS
BEGIN
DECLARE #v_element varchar(500), #Pos int, #v_insert_ind bit
SET #p_list = LTRIM(RTRIM(#p_list))+ #p_separator
SET #Pos = CHARINDEX(#p_separator, #p_list, 1)
IF REPLACE(#p_list, #p_separator, '') <> ''
BEGIN
WHILE #Pos > 0
BEGIN
SET #v_insert_ind = 1
SET #v_element = LTRIM(RTRIM(LEFT(#p_list, #Pos - 1)))
IF #v_element <> ''
BEGIN
IF (#p_distinct = 1)
AND (SELECT count(element) FROM #ParsedList WHERE element = #v_element) > 0
SET #v_insert_ind = 0
IF #v_insert_ind = 1
BEGIN
INSERT INTO #ParsedList (element)
VALUES (#v_element)
END
END
--
SET #p_list = RIGHT(#p_list, LEN(#p_list) - #Pos)
SET #Pos = CHARINDEX(#p_separator, #p_list, 1)
END
END
RETURN
END
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