I'm migrating from SqlServer 2008 to MySql and I have a lot of stored procedure that use a generic table-value function that split a varchar.
The sqlserver function declaration is:
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item varchar(50))
/* SAMPLE
select * from fnSplit('12 345 67',' ')
select * from fnSplit('12##34#5##67','##')
*/
BEGIN
DECLARE #sItem VARCHAR(8000)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT #sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
It is not possible to write Table-value stored function in my SQL server. MySQL stored function returns single values only. However, if the situation demands a function to return a set of data, you have only two choices. You may create a string using some separators with your result set and return it to the caller.
Related
I have a stored procedure that accepts a string such as A, B, C...etc. I want to split the string and insert each letter as one record into a table. The result should be:
col1 col2
1 A
2 B
3 C
I could use cursor, but cursor is kind of slow if I call this stored procedure from my web page. Is there any better solution?
Instead of passing a comma-separated string, pass a table-valued parameter. First, create a table type in your database:
CREATE TYPE dbo.Strings AS TABLE(String NVARCHAR(32));
Then your stored procedure:
CREATE PROCEDURE dbo.InsertStrings
#Strings dbo.Strings READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Table(Col2) -- assuming col1 is an IDENTITY column?
SELECT String FROM #Strings;
END
GO
Then in your C# code or whatever, you just pass a DataTable as a Structured parameter. Example here and background here.
If you really don't want to do this, because it's too hard or whatever, then you can use a much less efficient string splitting function, e.g.
CREATE FUNCTION [dbo].[SplitString]
(
#List NVARCHAR(MAX),
#Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT
[Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim, #List + #Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim + #List, [Number], LEN(#Delim)) = #Delim
) AS y
);
Then your procedure is:
CREATE PROCEDURE dbo.InsertStrings
#Strings NVARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Table(Col2) -- assuming col1 is an IDENTITY column?
SELECT [Value] FROM dbo.SplitString(#Strings, ',');
END
GO
how to create udf in mssql which performs group_concat() operation in mysql for generic table not for specific table...
I tried it in this way but its not working for generic table(any table column)
CREATE FUNCTION [dbo].[GROUP_CONCAT](
#tableColumn varchar(max),
#separator varchar(max)
)
returns varchar(max)
as
begin
declare #concatResult varchar(max)
select #concatResult =STUFF((SELECT DISTINCT #separator + CAST( #tableColumn AS varchar)
From ?
FOR XML PATH('')), 1, 1, '')
return #concatResult
end;
I have a table called webqueries with a column named qQuestion of data type text(sql server 2008). I want to create a count on words used in qQuestion (excluding 'and', 'is' etc). My goal is to see how many times a person has asked a question about a specific product.
You could create a table-valued function to parse words and join it to your query against qQuestion. In your schema, I recommend using varchar(8000) or varchar(max) instead of text. Meanwhile, the following should get you started:
create function [dbo].[fnParseWords](#str varchar(max), #delimiter varchar(30)='%[^a-zA-Z0-9\_]%')
returns #result table(word varchar(max))
begin
if left(#delimiter,1)<>'%' set #delimiter='%'+#delimiter;
if right(#delimiter,1)<>'%' set #delimiter+='%';
set #str=rtrim(#str);
declare #pi int=PATINDEX(#delimiter,#str);
while #pi>0 begin
insert into #result select LEFT(#str,#pi-1) where #pi>1;
set #str=RIGHT(#str,len(#str)-#pi);
set #pi=PATINDEX(#delimiter,#str);
end
insert into #result select #str where LEN(#str)>0;
return;
end
go
select COUNT(*)
from webqueries q
cross apply dbo.fnParseWords(cast(q.qQuestion as varchar(max)),default) pw
where pw.word not in ('and','is','a','the'/* plus whatever else you need to exclude */)
I'm writing a function that I need to use either a TABLE variable for (I hear they don't exist in MySQL) or a temporary table.
However, it seems that temporary tables only seem to work in stored procedures, not functions. I keep getting this error:
Explicit or implicit commit is not allowed in stored function or
trigger.
What I'm trying to build is a solution to an earlier question of mine. It's a function that receives a start date, an end date, and a comma-deliminated string. It first finds all the months between the start and end date and saves them as individual records in the first temporary table. It then parses out the comma-deliminated string and saves those into a second temporary table. Then it does a select join on the two, and if records are present, it returns true, otherwise false.
My intention is to use this as part of another queries WHERE clause, so it needs to be a function and not a stored procedure.
How can I use temporary tables in stored functions? And if I can't, what can I do instead?
Here's my (currently broken) function (or as a gist):
-- need to parse out a string like '4,2,1' and insert values into temporary table
-- MySQL doesn't have a native string split function, so we make our own
-- taken from: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
DROP FUNCTION IF EXISTS SPLIT_STR;
CREATE FUNCTION SPLIT_STR(x VARCHAR(255), delim VARCHAR(12), pos INT) RETURNS VARCHAR(255)
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos), LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1), delim, '');
-- need to find all months between the start and end date and insert each into a temporary table
DROP FUNCTION IF EXISTS months_within_range;
DELIMITER //
CREATE FUNCTION months_within_range(starts_at DATE, ends_at DATE, filter_range VARCHAR(255)) RETURNS TINYINT
BEGIN
DROP TABLE IF EXISTS months_between_dates;
DROP TABLE IF EXISTS filter_months;
CREATE TEMPORARY TABLE months_between_dates (month_stuff VARCHAR(7));
CREATE TEMPORARY TABLE filter_months (filter_month VARCHAR(7));
SET #month_count = (SELECT PERIOD_DIFF(DATE_FORMAT(ends_at, "%Y%m"), DATE_FORMAT(starts_at, "%Y%m")));
-- PERIOD_DIFF only gives us the one month, but we want to compare to, so add one
-- as in, the range between 2011-01-31 and 2011-12-01 should be 12, not 11
INSERT INTO months_between_dates (month_stuff) VALUES (DATE_FORMAT(starts_at, "%Y-%m"));
SET #month_count = #month_count + 1;
-- start he counter at 1, since we've already included the first month above
SET #counter = 1;
WHILE #counter < #month_count DO
INSERT INTO months_between_dates (month_stuff) VALUES (DATE_FORMAT(starts_at + INTERVAL #counter MONTH, "%Y-%m"));
SET #counter = #counter + 1;
END WHILE;
-- break up the filtered string
SET #counter = 1;
-- an infinite loop, since we don't know how many parameters are in the filtered string
filters: LOOP
SET #filter_month = SPLIT_STR(filter_range, ',', #counter);
IF #filter_month = '' THEN LEAVE filters;
ELSE
INSERT INTO filter_months (filter_month) VALUES (#filter_month);
SET #counter = #counter + 1;
END IF;
END LOOP;
SELECT COUNT(*) INTO #matches FROM months_between_dates INNER JOIN filter_months ON months_between_dates.month_stuff = filter_months.filter_month;
IF #matches >= 1 THEN RETURN 1;
ELSE RETURN 0;
END//
DELIMITER ;
drop table statements cause an implicit commit, which is not allowed in a mysql function. drop temporary table doesn't cause the commit though. if you're not worried about regular (non-temporary) tables named months_between_dates or filter_months existing you should be able to change
DROP TABLE IF EXISTS months_between_dates;
DROP TABLE IF EXISTS filter_months;
to
DROP TEMPORARY TABLE IF EXISTS months_between_dates;
DROP TEMPORARY TABLE IF EXISTS filter_months;
I am working on a SSRS report that uses a stored procedure containing a few parameters. I am having problems with two of the parameters because I want to have the option of selecting more than one item.
Here's a condensed version of what I have:
CREATE PROCEDURE [dbo].[uspMyStoredProcedure]
(#ReportProductSalesGroupID AS VARCHAR(MAX)
,#ReportProductFamilyID AS VARCHAR(MAX)
,#ReportStartDate AS DATETIME
,#ReportEndDate AS DATETIME)
--THE REST OF MY QUERY HERE WHICH PULLS ALL OF THE NEEDED COLUMNS
WHERE DateInvoicedID BETWEEN #ReportStartDate AND #ReportEndDate
AND ProductSalesGroupID IN (#ReportProductSalesGroupID)
AND ProductFamilyID IN (#ReportProductFamilyID)
When I try to just run the stored procedure I only return values if I enter only 1 value for #ReportProductSalesGroupID and 1 value #ReportProductFamilyID. If I try to enter two SalesGroupID and/or 2 ProductFamilyID it doesn't error, but I return nothing.
-- Returns data
EXEC uspMyStoredProcedure 'G23', 'NOF', '7/1/2009', '7/31/2009'
-- Doesn't return data
EXEC uspMyStoredProcedure 'G23,G22', 'NOF,ALT', '7/1/2009', '7/31/2009'
In SSRS I get an error that says:
Incorrect syntax near ','
It appears that the , separator is being included in the string instead of a delimiter
You need three things:
In the SSRS dataset properties, pass the multi-value param to the stored procedure as a comma-delimited string
=Join(Parameters!TerritoryMulti.Value, ",")
In Sql Server, you need a table-value function that can split a comma-delimited string back out into a mini table (eg see here). edit: Since SQL Server 2016 you can use the built-in function STRING_SPLIT for this
In the stored procedure, have a where clause something like this:
WHERE sometable.TerritoryID in (select Item from dbo.ufnSplit(#TerritoryMulti,','))
... where ufnSplit is your splitting function from step 2.
(Full steps and code in my blog post 'SSRS multi-value parameters with less fail'):
Let us assume that you have a multi value list #param1
Create another Internal Parameter on your SSRS report called #param2 and set the default value to:
=Join(Parameters!param1.value, 'XXX')
XXX can be any delimiter that you want, EXCEPT a comma (see below)
Then, you can pass #param2 to your query or stored procedure.
If you try to do it any other way, it will cause any string function that uses commas to separate arguments, to fail. (e.g. CHARINDEX, REPLACE).
For example Replace(#param2, ',', 'replacement') will not work. You will end up with errors like "Replace function requires 3 arguments".
Finally I was able to get a simple solution for this problem. Below I have provided all (3) steps that I followed.
I hope you guys will like it :)
Step 1 - I have created a Global Temp Table with one column.
CREATE GLOBAL TEMPORARY TABLE TEMP_PARAM_TABLE(
COL_NAME VARCHAR2(255 BYTE)
) ON COMMIT PRESERVE ROWS NOCACHE;
Step 2 - In the split Procedure, I didn't use any array or datatable, I have directly loaded the split values into my global temp table.
CREATE OR REPLACE PROCEDURE split_param(p_string IN VARCHAR2 ,p_separator IN VARCHAR2
)
IS
v_string VARCHAR2(4000);
v_initial_pos NUMBER(9) := 1;
v_position NUMBER(9) := 1;
BEGIN
v_string := p_string || p_separator;
delete from temp_param_policy;
LOOP
v_position :=
INSTR(v_string, p_separator, v_initial_pos, 1);
EXIT WHEN(NVL(v_position, 0) = 0);
INSERT INTO temp_param_table
VALUES (SUBSTR(v_string, v_initial_pos
, v_position - v_initial_pos));
v_initial_pos := v_position + 1;
END LOOP;
commit;
END split_param;
/
Step 3 - In the SSRS dataset parameters, I have used
=Join(Parameters!A_COUNTRY.Value, ",")
Step 4: In the start of your stored procedure executes the Procedure
Exec split_param(A_Country, ‘,’);
Step 5: In your stored procedure sql use the condition like below.
Where country_name in (select * from TEMP_PARAM_TABLE)
When SSRS passes the parameter it is in the form: Param1,Param2,Param3.
In the procedure, you just need to put identifiers around each parameter. And also identifiers around the value that is returned by the dataset. In my case, I used semicolons.
CREATE OR REPLACE PROCEDURE user.parameter_name (
i_multivalue_parameter
)
AS
l_multivalue_parameter varchar2(25555) := ';' || replace(i_multivalue_parameter,',',';') || ';';
BEGIN
select something
from dual
where (
instr(l_multivalue_parameter, ';' || database_value_that_is_singular || ';') > 0
)
END;
i_multivalue_parameter is passed in via SSRS.
l_multivalue_parameter reads the parameter passed in via SSRS and puts identifiers around each value.
database_value_that_is_singular is the value returned for each record.
So if 'Type1,Type2,Type3'is passed in via SSRS:
i_multivalue_parameter is: Type1,Type2,Type3
l_multivalue_parameter is: ;Type1;Type2;Type3;
database_value_that_is_singular is: ;Type1; or ;Type2; or ;Type3;
Instr will return a value over 0 if the parameter matches.
This works even if each parameters are similar. EG: "Type A" and "Type AA". That is "Type A" will not match "Type AA".
I found a simple way for my solution. Define the parameter value in the report as an expression like this
="'" + Join(Parameters!parm.Value,"','") + "'"
(in case you can't read it the first and last literals are double quote, single quote, double quote. The join literal is double quote, single quote, comma, single quote, double quote)
Then in the stored procedure you can use dynamic sql to create your statement. I did this to create a temp table of values to join to in a later query, like this:
CREATE #nametable (name nvarchar(64))
SET #sql = N'SELECT Name from realtable where name in (' + #namelist + ')'
INSERT INTO #nametable exec sp_executesql #sql
#namelist would be the name of the stored procedure parameter.