MS SQL自定义字符串拆分函数的惨痛经历

我想大家都会有一个自己的函数来处理这样的需求,在SQL中按给定的分隔符来拆分字符串,产生一个表。

以下是我的经历,我以流水账式请有更高明的大侠指教。

 

话说有这么一个需求谈话:

Jeff:

“我们工厂生产的产品,有机会会从客户那里退回到工厂返工,返工后再发给客户,不过会更换新的条码。客户需要知道退回来的条码是什么,换成了新的条码是什么,返工过程中,有多少数量是更换了的。”

Darren Xie:

“你提供一个条码ID,系统找出这个条码在更换前后的信息以及过程中的信息,如旧条码和新条码、不良以及更换品信息,产生报表。这些信息都可以从我们的MES系统中得到,但是需要在后台经过计算,会需要一些时间。”

“有些问题需要您把答案补充到你的需求中。”

“你一次会提交多少个ID进行查询?”

“你以什么方式输入到系统中?比如一个条码一个条码进行扫描、手工一个一个输入、复制粘贴、上传固定格式的文件还是其它方式?”

“你想要的报表是什么样的?”

Jeff:

“需要一个功能,可以通过手工输入或复制粘贴的方式输入一些条码号(数据不确定),一次可以提交多个条码。系统需要产生这些条码的报表,报表的内容是MES中这些条码的反工信息:旧条码是什么,换成了新的条码是什么,返工过程中,有多少数量是更换了的。报表要可以在线查询和导出Excel文件。”

Darren Xie:

“我想复述一下,看我的理解是否正确,同时加上我的想法。”

“我将配合你的需求内容,做一个功能,用WEB的方式实现(因为你在美国,我在中国,中国和美国的同事都可能要用到这个功能),有一个网页提供给你输入要查询的条码,你输入条码(可以输入一个再输入一个的方式,也可以复制有格式的文本,如用逗号、空格、换行分隔的文本。),输入的条码系统会自动格式化系统认识的格式,你输入你的邮件地址后再提交,之后系统会发送报表的连接地址给你,你通过连接地址查看报表。因为你提交的条码可能会很多,系统处理的时间较长,你提交后,系统会在后台计算出你要的数据,完成后就发送一个连接地址给你,我不使用附件的方式,是因为不知道附件会有多大,同是你也要求在线查询,有了连接地址你就可以与同事分享你的报表。下面给你一些设计预想,你看看是否同意或有更好的主意!”

提供一个网页,接收你提供的条码信息:

image

 

你提交数据后,你会收到邮件告诉你,报表已产生以及报表的连接地址:

image

 

你打开连接地址就是你要的报表,可以导出包括Excel在内的多种格式文件:

image

Jeff:

“这就是我想要的,这样我得到报表的同时,也可以分享给需要的同事。请帮我做出来吧!”

这就是背景,我的设计是这样来处理的,用户在网面上输入信息,保存到数据库中,在数据库中建立Job或Trigger等去开始计算,计算好的结果保存好,再由数据库通过DBMail发邮件给用户,邮件中包括一个报表的连接地址,是一个网页,地址中带参数就可以了。

这中间有数据库的设计和使用(表、存储过程、函数、Jobs)、C#的功能代码、ASP.NET等等内容,但是我只是想说说这里要用到的SQL函数,其它的不会在本文中。

正题吧!这里提交的多个条码都是用逗号分的一个字符串,我就需要有一个功能在SQL中完成拆分成一个一列表,用于关联其它的表取数据计算。我的初始版本如下:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:        <Author,,Darren>
-- Create date: <Create Date,,20110823>
-- Description:    <Description,,傳入帶指定分隔符的字符串,返回分割後的單列表>
-- =============================================
ALTER FUNCTION [dbo].[f_String2Table]
(    
    @S varchar(max)
    ,@CHAR CHAR(1)
)
returns @r_table table    
 (COL NVARCHAR(MAX) )   
AS
BEGIN
    IF(CHARINDEX(@CHAR,@S)>0)
    BEGIN
        insert @r_table 
            select LTRIM(RTRIM(LEFT(@S,CHARINDEX(@CHAR,@S)-1))) WHERE LEFT(@S,CHARINDEX(@CHAR,@S)-1)<>'';
        select @S=RTRIM(LTRIM(RIGHT(@S,len(@S)-CHARINDEX(@CHAR,@S))));
    END
    IF(CHARINDEX(@CHAR,@S)>0)
    BEGIN
        insert @r_table
            SELECT * FROM [f_String2Table](@S,@CHAR);
    END
    ELSE
    BEGIN
    insert @r_table 
            select @S WHERE @S<>'';
    END
RETURN 
END

代码执行如下:

image

 

在应用上线后,有一天,美国佬发来了一封邮件,如下:

I tried using the system again today and received the following error message (See screenshot below). 

I completely understand the importance of creating a system that I should be able to use myself, but I’m sure you agree we have had our fair share of problems on this.  Due to all the problems we have

been having with this, I believe it would be much faster if you could run the data in HZ for the 184 lots

that I am looking to get the new id’s for.  The list of these 184 are attached via the excel file. 

Please approve for Xie TangWen to run these for me so I can move forward with my project.

image

这次,才真的认真看了看我的代码,后来才发现问题出在这个字符串处理的函数上,怪就怪当时认为是小功能,没有

TDD用去做。也才发现,对MS SQL中的递归调用函数了解得不够,这个问题在7.0的时候就有的了,一直到现在的SQL 2008R2都是存在的,因为错误信息“No more the lock and the classes available from transaction.”告诉我问题所在,原来它只有24,我给提交的是184个条码,从我的代码就就是调用182次,就出错了。就算是

SQL 2008R2也只有32次。

只好改代码逻辑了。

用以下的版本代替才得到解决:

alter FUNCTION [dbo].[f_String2Table]
(    
    @S varchar(max)
    ,@CHAR CHAR(1)
)
returns @r_table table    
 (COL NVARCHAR(MAX) )   
AS
BEGIN
 
    IF(CHARINDEX(@CHAR,@S)>0)
    BEGIN
        insert @r_table 
            select LTRIM(RTRIM(LEFT(@S,CHARINDEX(@CHAR,@S)-1))) WHERE LEFT(@S,CHARINDEX(@CHAR,@S)-1)<>'';
        select @S=RTRIM(LTRIM(RIGHT(@S,len(@S)-CHARINDEX(@CHAR,@S))));
    END
    while(CHARINDEX(@CHAR,@S)>0)
    BEGIN
        insert @r_table 
            select LTRIM(RTRIM(LEFT(@S,CHARINDEX(@CHAR,@S)-1))) WHERE LEFT(@S,CHARINDEX(@CHAR,@S)-1)<>'';
        select @S=RTRIM(LTRIM(RIGHT(@S,len(@S)-CHARINDEX(@CHAR,@S))));
    END
    insert @r_table 
            select @S WHERE @S<>'';
RETURN 
END

 

在SQL中函数调用函数自身需要注意了。

posted @ 2012-02-03 15:57  谢堂文(Darren Xie)  阅读(2140)  评论(3编辑  收藏  举报