SQL Server trigger's cursor is not working - sql

I created a cursor inside a trigger and it is not working properly. Please help me fix it
Create trigger Posts_Raw_To_Queue_Trigger ON SendNotificationPostsRaw FOR INSERT
AS
BEGIN
DECLARE #PostID uniqueidentifier
DECLARE #UserID uniqueidentifier
DECLARE #ProfID int
DECLARE #Email nvarchar(100)
DECLARE #CreationTime datetime
DECLARE #SpecialityID int
SELECT #ProfID= ProfessionalID,#Email= Email from Professionals where UserID=#UserID
SELECT #PostID = I.PostID,#UserID = I.UserID ,#CreationTime =I.CreationTime FROM INSERTED I
DECLARE post_relation_cursor CURSOR FOR select CategoryId from PostCategoryRelations where PostId=#PostID;
OPEN post_relation_cursor;
FETCH NEXT FROM post_relation_cursor INTO #SpecialityID
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO SendNotificationPostsQueue (UserID,PostID,SpecialityID,TemplateID,CreationTime,SendTime,JSONParameters) Values(#UserID,#PostID,1,1,'04/11/2013','04/11/2013','')
FETCH NEXT FROM post_relation_cursor INTO #SpecialityID;
END;
CLOSE post_relation_cursor;
DEALLOCATE post_relation_cursor;
END
If I remove cursor and insert dummy values into SendNotificationPostsQueue, it works. So there is problem with my cursor... Please tell me why cursor is not working?

It doesn't appear that you need to use a cursor at all and would be better off not using one in almost all cases. Simply replace the body of your trigger (the part between begin and end) with a standard insert:
INSERT INTO SendNotificationPostsQueue (UserID,PostID,SpecialityID,TemplateID,CreationTime,SendTime,JSONParameters)
SELECT
i.UserID,
i.PostID,
1,
1,
'04/11/2013', -- Might want i.CreationTime or current_timestamp
'04/11/2013',
''
FROM INSERTED i
-- possibly want "LEFT JOIN Professionals p on i.UserID = p.UserID" here to grab other info
Note how I am not using the values clause of an insert which can only insert one row. I'm putting a select statement as part of the insert, thus inserting as many rows as the select returns. This means that we don't need to use a cursor, and don't need a bunch of variables to power the cursor.
One issue with your current code, as #automatic has mentioned, is that you're assuming INSERTED only holds one row. If it has more than one, then you'll throw an error when you try to assign a column to a variable.
For reasons of elegance, maintainability, and performance, I strongly urge you to abandon this cursor and run a simple insert (since that's all that your cursor is doing anyway).

Possible problem is here -
CREATE TRIGGER dbo.Posts_Raw_To_Queue_Trigger
ON SendNotificationPostsRaw
-- for view
INSTEAD OF INSERT
-- OR
-- for table
AFTER INSERT
AS BEGIN
DECLARE
#PostID UNIQUEIDENTIFIER
, #UserID UNIQUEIDENTIFIER
, #ProfID INT
, #Email NVARCHAR(100)
, #CreationTime DATETIME
, #SpecialityID INT
SELECT #ProfID = ProfessionalID
, #Email = Email
FROM Professionals
WHERE UserID = #UserID
-- this posible return invalid result (random record from inserted sequence)
SELECT #PostID = I.PostID
, #UserID = I.UserID
, #CreationTime = I.CreationTime
FROM INSERTED I
DECLARE post_relation_cursor CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT CategoryID
FROM dbo.PostCategoryRelations
WHERE PostId = #PostID;
OPEN post_relation_cursor;
FETCH NEXT FROM post_relation_cursor INTO #SpecialityID
WHILE ##FETCH_STATUS=0 BEGIN
INSERT INTO SendNotificationPostsQueue (
UserID
, PostID
, SpecialityID
, TemplateID
, CreationTime
, SendTime
, JSONParameters
)
SELECT
#UserID
, #PostID
, #SpecialityID --- !!!
, 1
, '04/11/2013'
, '04/11/2013'
, ''
FETCH NEXT FROM post_relation_cursor INTO #SpecialityID;
END;
CLOSE post_relation_cursor;
DEALLOCATE post_relation_cursor;
END
Update
If I understand you correctly, the business logic must be like this:
CREATE TRIGGER dbo.Posts_Raw_To_Queue_Trigger
ON dbo.SendNotificationPostsRaw
[INSTEAD OF]/[AFTER] INSERT
AS BEGIN
SET NOCOUNT ON;
DECLARE
#PostID UNIQUEIDENTIFIER
, #UserID UNIQUEIDENTIFIER
, #ProfID INT
, #Email NVARCHAR(100)
, #CreationTime DATETIME
, #SpecialityID INT
DECLARE cur CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
i.PostID
, i.UserID
, ProfID = p.ProfessionalID
, p.Email
, i.CreationTime
, pcr.CategoryID
FROM INSERTED i
JOIN dbo.Professionals p ON i.UserID = p.UserID
JOIN dbo.PostCategoryRelations pcr ON i.PostID = pcr.PostID
OPEN cur
FETCH NEXT FROM cur INTO
#PostID
, #UserID
, #ProfID
, #Email
, #CreationTime
, #SpecialityID
WHILE ##FETCH_STATUS = 0 BEGIN
INSERT INTO dbo.SendNotificationPostsQueue
(
UserID
, PostID
, SpecialityID
, TemplateID
, CreationTime
, SendTime
, JSONParameters
)
SELECT
#UserID
, #PostID
, #SpecialityID
, 1
, #CreationTime
, #CreationTime
, ''
FETCH NEXT FROM cur INTO
#PostID
, #UserID
, #ProfID
, #Email
, #CreationTime
, #SpecialityID
END
CLOSE cur
DEALLOCATE cur
END

Related

SQL server issue - Create Function must be the only statement in the batch

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

SQL Cursor to Insert into Temporary Table and Fetch From Temparory Table

Sir,
I have build a SQL query using Cursor as #AllRecords to insert values into Temporary Table & then Fetch values from that temporary table. But it showing me an error at last statement when I am fetching values from table (Error: incorrect syntax near #AllRecords). Below is my code:
DECLARE #ColName varchar(20)=null,
#Query varchar(MAX)=null,
#DepartmentName varchar(50)=null,
#deptt_code varchar(4)=null,
#DistrictId varchar(4)='0001',
#Deptt_Id char(4)=null,
#stYear varchar(4)=null,
#cYear varchar(4)=null,
#yr varchar(9)='2017-2018',
#tno int
BEGIN
set #stYear = SUBSTRING(#yr,0,5)
set #cYear = SUBSTRING(#yr,6,4)
--DECLARE & SET COUNTER
DECLARE #counter int
SET #counter = 1
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords table
(
department_name varchar(50),
project_name varchar(100),
department_code varchar(4)
)
--*** Declare Cursor
DECLARE cur_FetchDepartmentName CURSOR READ_ONLY
FOR
select deptt_code,deptt_name+'('+ RTRIM(LTRIM(deptt_short))+')' as dept_name from m_Department
where deptt_code in (select distinct department_code from t_Project_Details where district_id=#DistrictId
and financial_year=#yr)
OPEN cur_FetchDepartmetName
fetch next from cur_FetchDepartmetName into
#deptt_code, #DepartmentName
--LOOP UNTIL RECORDS ARE AVAILABLE
while ##FETCH_STATUS=0
BEGIN
if(#tno=0)
BEGIN
set #tno=1
insert into #AllRecords values(#DepartmentName,#deptt_code)
fetch next from cur_FetchDepartmetName into
#deptt_code,#DepartmentName
END
else
BEGIN
set #tno=#tno+1
insert into #AllRecords values(#DepartmentName,#deptt_code)
fetch next from cur_FetchDepartmetName into
#deptt_code,#DepartmentName
END
END
--CLOSE CURSOR
CLOSE cur_FetchDepartmetName
DEALLOCATE cur_FetchDepartmetName
select department_name, department_code from #AllRecords
Instead of answering what is error in this solution, I would like to offer a better solution to the problem. Use of cursor in this example is completely unnecessary, query can be more easily be written without it. It's a simple INSERT..SELECT statement and counting of records to set #tno can easily be done in the end.
BEGIN
set #stYear = SUBSTRING(#yr,0,5);
set #cYear = SUBSTRING(#yr,6,4);
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords table
(
department_name varchar(50),
project_name varchar(100), --what's the use of this column?
department_code varchar(4)
);
INSERT INTO #AllRecords (department_code, department_name)
select deptt_code,deptt_name+'('+ RTRIM(LTRIM(deptt_short))+')' as dept_name from m_Department
where deptt_code in (select distinct department_code from t_Project_Details where district_id=#DistrictId
and financial_year=#yr);
SELECT #tNo = COALESCE(#tno,0) + COUNT(*) FROM #AllRecords;
select department_name, department_code from #AllRecords;
END
Please check this article about cursors and how to avoid them:
Cursors and How to Avoid Them
Your SQL Query as below :
BEGIN
DECLARE #ColName VARCHAR(20)= NULL, #Query VARCHAR(MAX)= NULL, #DepartmentName VARCHAR(50)= NULL, #deptt_code VARCHAR(4)= NULL, #DistrictId VARCHAR(4)= '0001', #Deptt_Id CHAR(4)= NULL, #stYear VARCHAR(4)= NULL, #cYear VARCHAR(4)= NULL, #yr VARCHAR(9)= '2017-2018', #tno INT;
SET #stYear = SUBSTRING(#yr, 0, 5);
SET #cYear = SUBSTRING(#yr, 6, 4);
--DECLARE & SET COUNTER
DECLARE #counter INT;
SET #counter = 1;
--CREATE DYNAMIC TABLE WITH COLs
DECLARE #AllRecords TABLE
(department_name VARCHAR(50),
project_name VARCHAR(100),
department_code VARCHAR(4)
);
--*** Declare Cursor
DECLARE cur_FetchDepartmentName CURSOR READ_ONLY
FOR
SELECT deptt_code,
deptt_name+'('+RTRIM(LTRIM(deptt_short))+')' AS dept_name
FROM m_Department
WHERE deptt_code IN
(
SELECT DISTINCT
department_code
FROM t_Project_Details
WHERE district_id = #DistrictId
AND financial_year = #yr
);
OPEN cur_FetchDepartmetName;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
--LOOP UNTIL RECORDS ARE AVAILABLE
WHILE ##FETCH_STATUS = 0
BEGIN
IF(#tno = 0)
BEGIN
SET #tno = 1;
INSERT INTO #AllRecords
(department_name,
department_code
)
SELECT #DepartmentName,
#deptt_code;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
END;
ELSE
BEGIN
SET #tno = #tno + 1;
INSERT INTO #AllRecords
(department_name,
department_code
)
SELECT #DepartmentName,
#deptt_code;
FETCH NEXT FROM cur_FetchDepartmetName INTO #deptt_code, #DepartmentName;
END;
END;
--CLOSE CURSOR
CLOSE cur_FetchDepartmetName;
DEALLOCATE cur_FetchDepartmetName;
select department_name, department_code from #AllRecords
END;

Dedup and Combine Customer Data

I'm trying to combine records based on the ClusterID to have an enriched data of customer records.
How will I be able to group the following using MS SQL? Coalesce won't work as records need to be on the same row to make it work, and if I have more than 2 matches per cluster it's going to be a tedious processing. Using max by ClusterId in all columns is a workaround I'm doing but I was hoping there is a more efficient way to do this.
Have:
ClusterID,CustomerNo,Name,Email,Mobile,Address,PostalCode,Passport,ProfileNo
100,NULL,Person,person#email.com,NULL,OfficeAdd,12345,NULL,123
100,456,Person,person#email.com,98765,HomeAdd,34567,P12345,NULL
**This is a result of the SSIS DQS Matching node (https://ssisdqsmatching.codeplex.com/). It can do the match, but cannot handle the survivorship portion to get the golden record.
Want:
ClusterID,CustomerNo,Name,Email,Mobile,Address,PostalCode,Passport,ProfileNo
100,456,Person,person#email.com,98765,OfficeAdd,12345,P12345,123
Any thoughts would be much appreciated. Thank you!
DECLARE #CLUSTERID VARCHAR(MAX),#CUSTOMERNO VARCHAR(MAX),#NAME VARCHAR(MAX),#EMAIL VARCHAR(MAX),#MOBILE VARCHAR(MAX)
DECLARE #POSTALCODE VARCHAR(MAX),#PASSPORT VARCHAR(MAX),#PROFILENO VARCHAR(MAX),#ADDRESS VARCHAR(MAX)
DECLARE #NCLUSTERID VARCHAR(MAX),#NCUSTOMERNO VARCHAR(MAX),#NNAME VARCHAR(MAX),#NEMAIL VARCHAR(MAX),#NMOBILE VARCHAR(MAX)
DECLARE #NPOSTALCODE VARCHAR(MAX),#NPASSPORT VARCHAR(MAX),#NPROFILENO VARCHAR(MAX),#NADDRESS VARCHAR(MAX)
DECLARE #NEW_TABLE TABLE ( ClusterID varchar(max) ,
CustomerNo varchar(max) ,
Name varchar(max) ,
Email varchar(max) ,
Mobile varchar(max) ,
Address varchar(max) ,
PostalCode varchar(max) ,
Passport varchar(max) ,
ProfileNo varchar(max)
)
DECLARE C CURSOR FOR
SELECT DISTINCT CLUSTERID FROM CUSTOMER
OPEN C
FETCH NEXT FROM C INTO #CLUSTERID
WHILE ##FETCH_STATUS=0
BEGIN
DECLARE D CURSOR FOR
select CustomerNo,Name,Email,Mobile,Address,PostalCode,Passport,ProfileNo from customer where ClusterID=#CLUSTERID
OPEN D
FETCH NEXT FROM D INTO #CustomerNo,#Name,#Email,#Mobile,#Address,#PostalCode,#Passport,#ProfileNo
WHILE ##FETCH_STATUS=0
BEGIN
IF #CustomerNo is not null SET #NCustomerNo=#CustomerNo
IF #CustomerNo IS NOT NULL SET #NCustomerNo= #CustomerNo ;
IF #Name IS NOT NULL SET #NName = #Name ;
IF #Email IS NOT NULL SET #NEmail = #Email ;
IF #Mobile IS NOT NULL SET #NMobile = #Mobile ;
IF #Address IS NOT NULL SET #NAddress = #Address ;
IF #PostalCode IS NOT NULL SET #NPostalCode= #PostalCode ;
IF #Passport IS NOT NULL SET #NPassport = #Passport ;
IF #ProfileNo IS NOT NULL SET #NProfileNo = #ProfileNo ;
FETCH NEXT FROM D INTO #CustomerNo,#Name,#Email,#Mobile,#Address,#PostalCode,#Passport,#ProfileNo
END
CLOSE D
DEALLOCATE D
INSERT INTO #NEW_TABLE VALUES (
#CLUSTERID,
#NCustomerNo ,
#NName ,
#NEmail ,
#NMobile ,
#NAddress ,
#NPostalCode ,
#NPassport ,
#NPROFILENO
)
FETCH NEXT FROM C INTO #CLUSTERID
END
CLOSE C
DEALLOCATE C
SELECT * FROM #NEW_TABLE
I guess this query solves your requirement
DECLARE #CLUSTERID VARCHAR(MAX),#CUSTOMERNO VARCHAR(MAX),#NAME VARCHAR(MAX),#EMAIL VARCHAR(MAX),#MOBILE VARCHAR(MAX)
DECLARE #POSTALCODE VARCHAR(MAX),#PASSPORT VARCHAR(MAX),#PROFILENO VARCHAR(MAX),#ADDRESS VARCHAR(MAX)
DECLARE #NCLUSTERID VARCHAR(MAX)=NULL,#NCUSTOMERNO VARCHAR(MAX)=NULL,#NNAME VARCHAR(MAX)=NULL,#NEMAIL VARCHAR(MAX)=NULL,#NMOBILE VARCHAR(MAX)=NULL
DECLARE #NPOSTALCODE VARCHAR(MAX)=NULL,#NPASSPORT VARCHAR(MAX)=NULL,#NPROFILENO VARCHAR(MAX)=NULL,#NADDRESS VARCHAR(MAX)=NULL
DECLARE #NEW_TABLE TABLE ( ClusterID varchar(max) ,
CustomerNo varchar(max) ,
Name varchar(max) ,
Email varchar(max) ,
Mobile varchar(max) ,
Address varchar(max) ,
PostalCode varchar(max) ,
Passport varchar(max) ,
ProfileNo varchar(max)
)
DECLARE C CURSOR FOR
SELECT DISTINCT CLUSTERID FROM CUSTOMER
OPEN C
FETCH NEXT FROM C INTO #CLUSTERID
WHILE ##FETCH_STATUS=0
BEGIN
DECLARE D CURSOR FOR
select CustomerNo,Name,Email,Mobile,Address,PostalCode,Passport,ProfileNo from customer where ClusterID=#CLUSTERID
OPEN D
FETCH NEXT FROM D INTO #CustomerNo,#Name,#Email,#Mobile,#Address,#PostalCode,#Passport,#ProfileNo
WHILE ##FETCH_STATUS=0
BEGIN
IF #CustomerNo is not null SET #NCustomerNo=#CustomerNo
IF #CustomerNo IS NOT NULL SET #NCustomerNo= #CustomerNo ;
IF #Name IS NOT NULL SET #NName = #Name ;
IF #Email IS NOT NULL SET #NEmail = #Email ;
IF #Mobile IS NOT NULL SET #NMobile = #Mobile ;
IF #Passport IS NOT NULL SET #NPassport = #Passport ;
IF #ProfileNo IS NOT NULL SET #NProfileNo = #ProfileNo ;
IF (#ADDRESS IS NOT NULL AND #NADDRESS IS NOT NULL)
BEGIN
SET #NAddress = #Address ;
SET #NPostalCode= #PostalCode ;
END
ELSE IF(#ADDRESS IS NOT NULL AND #NADDRESS IS NULL)
BEGIN
SET #NAddress = #Address ;
SET #NPostalCode= NULL ;
END
ELSE IF(#ADDRESS IS NULL AND #NADDRESS IS NOT NULL)
BEGIN
SET #NAddress = NULL ;
SET #NPostalCode= #PostalCode ;
END
FETCH NEXT FROM D INTO #CustomerNo,#Name,#Email,#Mobile,#Address,#PostalCode,#Passport,#ProfileNo
END
CLOSE D
DEALLOCATE D
INSERT INTO #NEW_TABLE VALUES (
#CLUSTERID,
#NCustomerNo ,
#NName ,
#NEmail ,
#NMobile ,
#NAddress ,
#NPostalCode ,
#NPassport ,
#NPROFILENO
)
FETCH NEXT FROM C INTO #CLUSTERID
END
CLOSE C
DEALLOCATE C
SELECT * FROM #NEW_TABLE

Call Procedure for each Row without using a cursor and set the row with the result of the procedure

I have this procedure :
CREATE PROC dbo.##HTMLtoMARKDOWN #text nvarchar(500),
#returnText nvarchar(500) output
AS
BEGIN
DECLARE #counter tinyint
SET #counter=1
WHILE CHARINDEX('**', #text, 1) > 0
BEGIN
SELECT #text = STUFF(#text,
CHARINDEX('**', #text, 1),
2,
IIF(#counter%2=0,'<br><b>','</b>')),
#counter = #counter + 1
END
SET #returnText = #text
END
GO
Which can be run like this:
DECLARE #returnText nvarchar(500)
EXEC dbo.##HTMLtoMARKDOWN '**a** **b** **c**', #returnText output
I'm using this kind of query:
Select, IIF(IsUniversal=0,'TRUE','FALSE') as [Is Universal?],
MarkdownMini as [Off Topic Reason]
From CloseAsOffTopicReasonTypes
group by IsUniversal, MarkdownMini
Ifdbo.##HTMLtoMARKDOWNwas declared as a function (CREATE FUNCTION dbo.HTMLtoMARKDOWN #text nvarchar(500))), I could have written this:
Select, IIF(IsUniversal=0,'TRUE','FALSE') as [Is Universal?],
dbo.HTMLtoMARKDOWN(MarkdownMini) as [Off Topic Reason]
From CloseAsOffTopicReasonTypes
group by IsUniversal, MarkdownMini
I'm not allowed to use functions, so how I can do that kind of thing with a temporary procedure?
This one works by applying the stored procedure to the distinct reasons rather than processing the whole set.
CREATE PROC dbo.##HTMLtoMARKDOWN #text nvarchar(500),
#returnText nvarchar(500) output
AS
BEGIN
DECLARE #counter tinyint
SET #counter=1
WHILE CHARINDEX('**', #text, 1) > 0
BEGIN
SELECT #text = STUFF(#text,
CHARINDEX('**', #text, 1),
2,
IIF(#counter%2=0,'<br><b>','</b>')),
#counter = #counter + 1
END
SET #counter=1
WHILE CHARINDEX('*', #text, 1) > 0
BEGIN
SELECT #text = STUFF(#text,
CHARINDEX('*', #text, 1),
1,
IIF(#counter%2=0,'<br><i>','</i>')),
#counter = #counter + 1
END
-- SET #returnText = #text
SET #returnText = #text
END
GO
DECLARE #returnText nvarchar(500)
--taken from http://meta.stackexchange.com/a/237237/242800
;with ReasonsPerPost as
(
-- get the count of each close/flag reason per post
select TOP 100 PERCENT -- Only use with clustered index.
Posts.Id,
PendingFlags.CloseReasonTypeId,
IIF(CloseReasonTypeId<>102,CloseReasonTypes.Name,MarkdownMini) as Name,
count(PendingFlags.CloseReasonTypeId) as TotalByCloseReason
from Posts
INNER JOIN PendingFlags on PendingFlags.PostId = Posts.Id
INNER JOIN CloseReasonTypes on CloseReasonTypes.Id=PendingFlags.CloseReasonTypeId
LEFT OUTER JOIN CloseAsOffTopicReasonTypes on CloseAsOffTopicReasonTypes.id=PendingFlags.CloseAsOffTopicReasonTypeId
where Posts.ClosedDate IS NULL -- The question is not closed.
and PendingFlags.FlagTypeId in (14,13) -- Exclude reopen votes
group by Posts.id, CloseReasonTypes.Name, MarkdownMini, PendingFlags.CloseReasonTypeId
order by TotalByCloseReason desc
),
TopPerPost as
(
-- create a row number to order the results by the close reason totals
select Id,
CloseReasonTypeId,
Name,
ReasonsPerPost.TotalByCloseReason,
row_number() over(partition by Id order by TotalByCloseReason desc) seq
from ReasonsPerPost
where Name is NOT NULL
)
select TOP ##Limit:int?38369## -- This number may grow, or get removed the day the server will have enough RAM.
Posts.Id as [Post Link], -- Question title.
Count(PendingFlags.PostId) as [Number of pending flags], -- Number of pending flags per questions.
TopPerPost.Name as [The most common vote reason],
Posts.OwnerUserId as [User Link], -- Let click on the colum to see if the same user ask off-topic questions often.
Reputation as [User Reputation], -- Interesting to see that such questions are sometimes asked by high rep users.
Posts.Score as [Votes], -- Interesting to see that some questions have more than 100 upvotes.
Posts.AnswerCount as [Number of Answers], -- I thought we shouldn't answer on off-topic post.
Posts.ViewCount,
Posts.FavoriteCount as [Number of Stars], -- Some questions seems to be very helpfull :) .
Posts.CreationDate as [Asked on], -- The older is the question, the more is the chance that flags on them can't get reviewed.
Posts.LastActivityDate as [last activity], -- Similar effect as with Posts.CreationDate.
Posts.LastEditDate as [modified on]
into #results
from Posts
INNER JOIN PendingFlags on PendingFlags.PostId = Posts.Id
LEFT OUTER JOIN Users on Users.id = posts.OwnerUserId
LEFT OUTER JOIN TopPerPost on Posts.id=TopPerPost.id
where seq=1
group by Posts.id, Posts.OwnerUserId, TopPerPost.Name, Reputation, Posts.Score, Posts.FavoriteCount, Posts.AnswerCount, Posts.CreationDate, Posts.LastActivityDate, Posts.LastEditDate, Posts.ViewCount
order by [Number of pending flags] desc, [The most common vote reason], Score desc, Reputation desc, FavoriteCount desc, ViewCount desc, Posts.CreationDate asc, LastActivityDate, LastEditDate -- Questions with more flags have more chance to get them handled, and the higher is the probabilty that the question is off-topic (since several users already reviewed the question).
select distinct [The most common vote reason] into #reasons from #results
ALTER TABLE #reasons
ADD id INT IDENTITY(1,1), html nvarchar(500)
create nonclustered index results_reasons_index
on #results ([The most common vote reason]);
create unique nonclustered index reasons_index
on #reasons ([The most common vote reason]);
declare #id int
declare #maxId as int
declare #markdown as nvarchar(500)
declare #html as nvarchar(500)
select #maxId = max(id) from #reasons
set #id = 0
while ( #id < #maxId )
begin
set #id = #id + 1
select #markdown = [The most common vote reason] from #reasons where id = #id
exec dbo.##HTMLtoMARKDOWN #text = #markdown, #returnText = #html output
update #reasons set html = #html where id = #id
end
update #results set [The most common vote reason] = #reasons.html
from #results
inner join #reasons
on #results.[The most common vote reason]
= #reasons.[The most common vote reason]
select * from #results
To update rows using a stored procedure, you need cursor:
DEClARE #akey int, #text NVARCHAR(500),#retText NVARCHAR(500);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR SELECT aid, MarkdownMini
FROM CloseAsOffTopicReasonTypes;
OPEN c;
FETCH NEXT FROM c into #akey, #text;
WHILE ##FETCH_STATUS=0 BEGIN
EXEC dbo.##HTMLtoMARKDOWN #TEXT, #retText output;
UPDATE CloseAsOffTopicReasonTypes
SET MarkDown = #retText WHERE aid = #akey;
FETCH NEXT FROM c into #akey, #text;
END;
DEALLOCATE c;
If you intent to return a record set (like select), you need a temp table or in-memory table:
DECLARE #TMP TABLE (akey int, MarkDown nvarchar(800) );
SET NOCOUNT ON;
DEClARE #akey int, #text NVARCHAR(500),#retText NVARCHAR(500);
DECLARE c CURSOR LOCAL FAST_FORWARD FOR SELECT aid, MarkdownMini
FROM CloseAsOffTopicReasonTypes;
OPEN c;
FETCH NEXT FROM c into #akey, #text;
WHILE ##FETCH_STATUS=0 BEGIN
EXEC dbo.##HTMLtoMARKDOWN #TEXT, #retText output;
--UPDATE CloseAsOffTopicReasonTypes SET MarkDown = #retText WHERE aid = #akey;
INSERT INTO #TMP (akey, MarkDown) values(#akey, #retText);
FETCH NEXT FROM c into #akey, #text;
END;
DEALLOCATE c;
SET NOCOUNT OFF;
SELECT * FROM #TMP;
The SET NOCOUNT ON/OFF are required if you want to return the row to a caller such as C#, PHP, where you also want to make above lines into one stored procedure.

procedure that returns varchar

I tried to make a function that returns varchar, but I can't because I'm using CREATE TABLE inside, and when I'm creating it with a procedure I can't return a value.
I wanted to know if you have some advice.
I made this just to make a string with emails separated by ";" so I can have all the "manager" mails in one varchar (for the recipients).
ALTER procedure [dbo].[Manager_email]
AS
BEGIN
declare #mails varchar (max),
#number_of_mails int,
#counter int
set #counter=2
create table #temp ( id int identity, email varchar(30))
insert into #temp (email)
select Email
from hr.Employees
where lower (EmpRole) like 'manager'
set #number_of_mails=##ROWCOUNT
set #mails = (select email from #temp where id =1 ) + ';'
while #counter <= #number_of_mails
BEGIN
set #mails = #mails + (select email from #temp where id =#counter ) + ';'
set #counter = #counter+1
END
drop table #temp
return cast (#mails as varchar (200))
END
You can only return integer value back from the procedure, If you want to return varchar value from procedure its good to make use of output variable in procedure.
Example
CREATE PROCEDURE Sales.uspGetEmployeeSalesYTD
#SalesPerson nvarchar(50),
#SalesYTD money OUTPUT
AS
SET NOCOUNT ON;
SELECT #SalesYTD = SalesYTD
FROM Sales.SalesPerson AS sp
JOIN HumanResources.vEmployee AS e ON e.BusinessEntityID = sp.BusinessEntityID
WHERE LastName = #SalesPerson;
RETURN
like in above procedure return #SalesYTD from procedure.
you can check full post on MSDN : Returning Data by Using OUTPUT Parameters
You can use function instead
CREATE FUNCTION Manager_email ()
RETURNS varchar(max)
AS
BEGIN
declare #email varchar(30)
declare #emails varchar(max)
set #emails = ''
declare cur cursor for
select Email
from hr.Employees
where lower (EmpRole) like 'manager'
open cur
fetch next from cur into #email
while ##fetch_status = 0
begin
set #emails = #emails + #email + ';'
fetch next from cur into #email
end
close cur
deallocate cur
return #emails
END
You can use table variable instead of temporary table. In that case you can continue to use UDF.