UDF replace function - failing to recognize strings - sql

I am trying to replace names found in 'xml' fieldname as hyperlinks, by matching with the names in database_tags.
I have created a simple UDF, but it does not execute the query correctly, as it creates duplicate name tags within the XML fieldname.
ALTER FUNCTION [dbo].[Tags](#XML VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
SELECT #XML = REPLACE(#XML,[Name],''+[name]+'')
FROM [dbo].[database_tags]
where UploadDate >= '2014-09-01'
RETURN #XML
END
UPDATE:
I manage to update my function to the following below, however the function only is only recognizing one name from the XML fieldname data.
DECLARE #N VARCHAR(MAX)
SELECT #N = [Name] FROM [dbo].[database_tags]
WHERE #XML LIKE '%'+[Name]+'%'
AND UploadDate >= '2014-09-01'
IF #N IS NOT NULL
BEGIN
SELECT #XML = REPLACE(#XML,
#N,
''+#N+'')
END
RETURN #XML
END
Please advice further if possible. Many thanks

You can use a cursor to loop through values in the table replace each name with the hyper link. But replace can force unwanted outcome. For example if a configured name is part of a separate string function will replace it with a hyperlink.
Create FUNCTION [dbo].[ReplaceTags](#XML VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #Name VARCHAR(MAX)
DECLARE CUR CURSOR FAST_FORWARD FOR
SELECT name
FROM database_tags
Where UploadDate >= '2014-09-01'
OPEN CUR
FETCH NEXT FROM CUR INTO #Name
WHILE ##FETCH_STATUS = 0
BEGIN
IF #Name IS NOT NULL
BEGIN
SELECT #XML = REPLACE(#XML,
#Name,
''+#Name+'')
END
FETCH NEXT FROM CUR INTO #Name
END
CLOSE CUR;
DEALLOCATE CUR;
RETURN #XML
END

Related

How to write a stored procedure, so that it will run the last column and save that in a table?

I have a table which has last column is the code. The table looks like below:
Rule_ID Simple_English_Description Source_Attribute Dependent_Attribute View text
-------------------------------------------------------------------------------------------
39 Material description Mandatory for all Material types Material Description Basic Data
Code:
INSERT INTO GDQ_PRODUCT_ERROR_TABLE
SELECT
'39' as RULE_ID,
vw.MATNR, VW.REGION_CODE, VW.COUNTRY_CODE, VW.[CLUSTER_CODE],
VW.[COMMON_COUNTRY],
'MATERIAL DESCRIPTION' as SRC_ATTR,
VW.MAKTX AS SOURCE_VALUE,
'' AS WERKS,
VW.MTART, VW.MAKTX, VW.NUMTP, VW.EAN11, VW.MEINS, VW.MSTAE, VW.PRDHA,
CASE
WHEN (vw.MAKTX IS NULL OR RTRIM(LTRIM(vw.MAKTX)) = ''
THEN 'I'
ELSE 'V'
END as DQ_INVALID
FROM
T_U2K2_ECC_MAKT_GDQ_ACTIVE vw;
How to write the stored procedure so that it can be a loop and read from the last column and return the output?
I have tried writing a stored procedure but it is not able to select only last column and insert the value into main table.
I have tried below code:
DECLARE #item CHAR(2)
DECLARE item_cursor CURSOR FAST_FORWARD READ_ONLY FOR
SELECT [text] from GDQ_RULE_MSTR_copy
OPEN item_cursor
FETCH NEXT FROM item_cursor INTO #item
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Query nvarchar(max)
SET #Query = (N'Select [text] FROM GDQ_RULE_MSTR_copy')
EXECUTE sp_executesql #Query
FETCH NEXT FROM item_cursor INTO #item
END
CLOSE item_cursor
DEALLOCATE item_cursor
I have tried this code but here it's executing the text column not the inside value which is the original code in string format I have saved
You are putting Select [text] FROM GDQ_RULE_MSTR_copy in quotes N'' which will actually execute the Select [text] FROM GDQ_RULE_MSTR_copy statement itself when you execute the #Query.
Also, your cursor is putting the value of Text column in a char(2) #item field which will truncate the string to 2 characters.
Try this:
DECLARE #item NVARCHAR(MAX)
DECLARE item_cursor CURSOR FAST_FORWARD READ_ONLY
FOR SELECT [text] from GDQ_RULE_MSTR_copy
OPEN item_cursor
FETCH NEXT FROM item_cursor INTO #item
WHILE ##FETCH_STATUS = 0
BEGIN
EXECUTE sp_executesql #Item
FETCH NEXT FROM item_cursor INTO #item
END
CLOSE item_cursor DEALLOCATE item_cursor

reverse where query in mysql2

I want to find records using like query but in reverse mode
For exa: I have one string ts5e434
And now in databse I have one column called geohash and its contan comma seperated values
1) "ts5e4,ts5,ts5e434"
2) "ab,ye"
3) "ts,thh"
4) "t"
So here I want to get 1, 3 and 4 no records because its partially matching string
exa like clause
SELECT
*
FROM
service_geohashes
WHERE
'ts5e434' LIKE geohashes
Can anyone help me
Thanks in advance
I created function "LikeAny" in MSSQL which looks like:
CREATE FUNCTION [dbo].[LikeAny](#text nvarchar(MAX), #delimiter varchar(20), #comparestring nvarchar(MAX))
RETURNS BIT AS
BEGIN
DECLARE #LikeAny BIT = 0,
#TempString nvarchar(MAX)
DECLARE MY_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT value FROM STRING_SPLIT(#text, #delimiter)
OPEN MY_CURSOR
FETCH NEXT FROM MY_CURSOR INTO #TempString
WHILE ##FETCH_STATUS = 0
BEGIN
--Do something with Id here
IF (#TempString <> '' AND #comparestring LIKE N'%' + #TempString + '%')
BEGIN
SET #LikeAny = 1
BREAK;
END
ELSE
FETCH NEXT FROM MY_CURSOR INTO #TempString
END
CLOSE MY_CURSOR
DEALLOCATE MY_CURSOR
RETURN #LikeAny
END
If you use this in your example, it should look like:
SELECT
*
FROM
service_geohashes
WHERE
[dbo].[LikeAny](geohashes ,',', 'ts5e434') = 1
I tried also to convert the function above into MySQL but I had no option to test it on real environment
it looks like:
DROP FUNCTION IF EXISTS LikeAnyCommaDelimited;
DELIMITER $$
CREATE FUNCTION LikeAnyCommaDelimited(p_text longtext, p_comparestring longtext)
RETURNS TINYINT
DETERMINISTIC
BEGIN
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE v_LikeAny TINYINT DEFAULT 0;
DECLARE v_TempString longtext;
DECLARE v_SQL longtext;
drop temporary table if exists tempa;
drop temporary table if exists tempb;
CREATE TEMPORARY TABLE tempa( txt text );
CREATE TEMPORARY TABLE tempb( val char(255) );
insert into tempa values(p_text);
set v_SQL = concat("insert into tempb (val) values ('", replace(( select group_concat(distinct txt) as data from tempa), ',', "'),('"),"');");
prepare statement1 from #sql;
execute statement1;
DEClARE split_cursor CURSOR FOR
SELECT value FROM (select distinct(val) as value from tempb);
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN split_cursor;
get_string: LOOP
FETCH split_cursor INTO v_TempString;
IF v_finished = 1 THEN
LEAVE get_string;
END IF;
IF (v_TempString <> '' AND p_comparestring LIKE N'%' + CONCAT(v_TempString , '%') THEN
BEGIN
SET v_LikeAny = 1;
LEAVE get_string;
END
END LOOP get_string;
CLOSE split_cursor;
END$$
DELIMITER ;
Let me know if you have any issues.

How can I create a cursor from xml nodes in a stored procedure in SQL Server?

I'm passing an XML document to my stored procedure as an argument. Then I'm trying to fill a cursor in order to loop through the elements of the XML. My question is how can I select every element of this XML Document and fill my cursor with them?
XML Document
<Authors>
<Author_id>1</Author_id>
<Author_id>2</Author_id>
</Authors>
Stored Procedure
CREATE PROCEDURE Insert_Publication
#authors xml
AS
DECLARE #id int
DECLARE authors_cursor CURSOR FOR
SELECT #authors.query('(/Authors/Author_id)')
open authors_cursor
FETCH NEXT FROM authors_cursor INTO #id
You can use .nodes() and .value():
DECLARE #authors XML =
'<Authors>
<Author_id>1</Author_id>
<Author_id>2</Author_id>
</Authors>';
DECLARE #id INT;
DECLARE authors_cursor CURSOR FOR
SELECT n.c.value('.', 'INT') AS author_id
FROM #authors.nodes('/Authors/Author_id') AS n(c);
OPEN authors_cursor;
FETCH NEXT FROM authors_cursor INTO #id;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #id; -- do whatever you need with #id
FETCH NEXT FROM authors_cursor INTO #id;
END
CLOSE authors_cursor;
DEALLOCATE authors_cursor;
LiveDemo
How it works:
DECLARE authors_cursor CURSOR FOR
SELECT n.c.value('.', 'INT') AS author_id
FROM #authors.nodes('/Authors/Author_id') AS n(c);
#authors.nodes('/Authors/Author_id') get nodes based on XQuery and alias for derived table as n and c - for column
Use n.c.value('.', 'INT') AS author_id to get actual value of element

SQL use function returned string in select where in(..) query

I have a function that returns a string with id's connected by comma, something like that:
CREATE FUNCTION [dbo].[fGetChildIdByPId]
(
#ID INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #RET VARCHAR(1000)
SET #RET = ''
DECLARE CUR CURSOR LOCAL FORWARD_ONLY READ_ONLY FOR
SELECT ID FROM tControl WHERE parentID = #ID
OPEN CUR
FETCH FROM CUR INTO #ID
WHILE ##FETCH_STATUS = 0
BEGIN
SET #RET = #RET + CAST(#ID AS VARCHAR(10)) + ','
+ dbo.fGetChildIdByPId(#ID)
FETCH NEXT FROM CUR INTO #ID
END
CLOSE CUR
DEALLOCATE CUR
RETURN #RET
END
In another function I want to use the string returned from previous function in another select query like that:
CREATE FUNCTION ShouldOutput
(
#id int,
#gid int
)
returns int
as
begin
declare #out int
if exists(SELECT * FROM tControl
where id in (select [dbo].[fGetChildIdByPId](#id))
and summaryIndex=1 and goodId = #gid)
set #out = 1
else
set #out = 0
return #out
end
But of course, there is the problem because the value of select [dbo].fGetChildIdByPId can't be cast to integer and can't be used in a query. I thought of using EXEC or sp_executesql but I can't use them in a function, only in stored procedures but I need a function, because I need to use the returned 1 or 0 value in another sql query. What is the easiest way to make the mentioned query work?
You shouldn't.
Your second function should take a table valued parameter - where that parameter is a table of values that you wish to use in the function - rather than a concatenated string.
To that end, your first function should return a table of values, not a string.
While in an explicit IN function (where you define a set of results i.e. state IN (1, 2)) does use a comma separated list if you pass it a function that returns a comma separated list that wont work.
If you alter your first function to
CREATE FUNCTION [dbo].[fGetChildIdByPId]
(
#ID INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
SELECT ID, GoodId FROM tControl WHERE parentID = #ID
END
Then everything should work fine (not tested).

Loops within dynamic SQL

I have code that I'd like to apply to a number of tables but rather than simply copy and replace table names, I'd like to use some kind of loop or cursor to simplify things.
I envision setting up an array of my tables names and using an index to iterate over the list, retrieving each table name and using dynamic SQL to intersperse the table name where applicable in my code.
Since there's no 'array' construct, as far as I know, within SQL, I'm not sure how this would work.
Any ideas about how to go about this?
Here is one way of doing it:
--Declare a table variable to hold your table names (and column names in case needed)
declare #listOfTablesToUpdate table (tableName varchar(100), columnNameToUpdate varchar(50))
--insert the tables that you want to work with.
insert into #listOfTablesToUpdate values ('Table1', 'column2')
insert into #listOfTablesToUpdate values ('Table2', 'column3')
insert into #listOfTablesToUpdate values ('Table3', 'column4')
--Cursor for iterating
declare #tableCursor cursor,
#tableName varchar(100),
#columnName varchar(50)
set #tableCursor = cursor for select * from #listOfTablesToUpdate
open #tableCursor
fetch next from #tableCursor into #tableName, #columnName
while(##fetch_status = 0)
begin
--dynamic sql
declare #sql varchar(max)
--Your logic here...this is just an example
set #sql = 'update '+#tableName+' set '+#columnName+' = '+<value>+' where '+#columnName +' = '+<someothervalue>
exec #sql
fetch next from #tableCursor into #tableName, #columnName
end
close #tableCursor
deallocate #tableCursor
Another approach involves preparing a helper function and a procedure that allow one to apply different SQL statements to each object (table, database, et cetera) in a list. The helper function comes from a SSRS Parameter question and splits apart a comma delimited list into a table.
-- from https://stackoverflow.com/questions/512105/passing-multiple-values-for-a-single-parameter-in-reporting-services
CREATE FUNCTION [dbo].[fn_MVParam]
(#RepParam NVARCHAR(4000), #Delim CHAR(1)= ',')
RETURNS #Values TABLE (Param NVARCHAR(4000))AS
BEGIN
DECLARE #chrind INT
DECLARE #Piece NVARCHAR(100)
SELECT #chrind = 1
WHILE #chrind > 0
BEGIN
SELECT #chrind = CHARINDEX(#Delim,#RepParam)
IF #chrind > 0
SELECT #Piece = LEFT(#RepParam,#chrind - 1)
ELSE
SELECT #Piece = #RepParam
INSERT #Values(Param) VALUES(CAST(#Piece AS VARCHAR))
SELECT #RepParam = RIGHT(#RepParam,LEN(#RepParam) - #chrind)
IF LEN(#RepParam) = 0 BREAK
END
RETURN
END
GO
Below is the code for the ProcessListSQL procedure.
-- #SQL to execute shall include {RP} as the replacement expression that
-- will evaluate to all the items in the comma delimited list
-- Also, please include a double quote " rather than two single quotes ''
-- in the input statement.
CREATE PROCEDURE [dbo].[ProcessListSQL] (
#CommaDelimitedList AS NVARCHAR(MAX),
#SQLtoExecute AS NVARCHAR(MAX) )
AS BEGIN
DECLARE #Statements TABLE
( PK INT IDENTITY(1,1) PRIMARY KEY,
SQLObject NVARCHAR (MAX)
)
SET #SQLtoExecute = REPLACE (#SQLtoExecute, '"', '''')
INSERT INTO #Statements
SELECT PARAM FROM [dbo].[fn_MVParam](#CommaDelimitedList,',')
DECLARE #i INT
SELECT #i = MIN(PK) FROM #Statements
DECLARE #max INT
SELECT #max = MAX(PK) FROM #Statements
DECLARE #SQL AS NVARCHAR(MAX) = NULL
DECLARE #Object AS NVARCHAR(MAX) = NULL
WHILE #i <= #max
BEGIN
SELECT #Object = SQLObject FROM #Statements WHERE PK = #i
SET #SQL = REPLACE(#SQLtoExecute, '{RP}', #Object)
-- Uncommend below to check the SQL
-- PRINT #SQL
EXECUTE sp_executesql #SQL
SELECT #Object = NULL
SELECT #SQL = NULL
SET #i = #i + 1
END
END
GO
The ProcessListSQL procedure take two parameters. The first is a comma delimited string that contains the list of objects that will be cycled through. The second parameter is a string that contains the SQL that will be executed with each of the objects in the first parameter.
In the below example, four databases are created. Note that {rp} is replaced with each of the objects in the first parameter and double quotes are needed in each place where single quotes are needed in the SQL statement.
EXECUTE ProcessListSQL 'rice,apples,cheese,tomatos',
'CREATE DATABASE [{rp}] CONTAINMENT = NONE
ON PRIMARY ( NAME = N"{rp}",
FILENAME = N"D:\data\user\{rp}.mdf" ,
SIZE = 4096KB ,
FILEGROWTH = 1024KB )
LOG ON
( NAME = N"{rp}_log",
FILENAME = N"D:\DATA\USER\{rp}_log.ldf" ,
SIZE = 1024KB ,
FILEGROWTH = 10%)'