SQL Server CL奥德赛 使用 C# 自定义存款和储蓄进度和触发器

发布时间:2019-05-04  栏目:NoSQL  评论:0 Comments

这一篇博客接着上一篇博客继续介绍 SQL CL路虎极光 Stored Procedure 和 CLR
Trigger,

上1篇博客介绍了 SQL CLMurano Function 的行使,以及 CL昂科威 程序集的挂号和 CLCRUISERFunction 的登记。

自家的上1篇博客:SQL Server CLR 使用 C#
自定义函数

 

四、CLR Stored Procedure

接下去在后面包车型地铁类别选取增添新项,接纳 SQL CL奥德赛 C# 存款和储蓄进度。

public partial class StoredProcedures
{
    /// <summary>
    /// 无输入参数,无输出参数,无输出结果,有输出消息,无返回值的存储过程
    /// </summary>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "HelloWorld")]
    public static void HelloWorld()
    {
        SqlContext.Pipe.Send("Hello World");
    }

    /// <summary>
    /// 有输入参数,无输出参数,无输出结果,无输出消息,有返回值的存储过程
    /// </summary>
    /// <param name="name"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStrLength")]
    public static SqlInt32 GetStrLength(SqlString str)
    {
        return str.ToString().Length;
    }

    /// <summary>
    /// 有输入参数,有输出参数,无输出结果,无输出消息,无返回值的存储过程
    /// </summary>
    /// <param name="name"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "SayHello")]
    public static void SayHello(SqlString name,out SqlString sayHello)
    {
        sayHello = "Hello " + name.ToString();
    }
}

挂号程序集和挂号存款和储蓄进度的 SQL 前面再贴出来,这里大家先看看结果。

PS:若是您用的是 Visual Studio
201五,那么你能够在【项目路径>obj>Debug】文件夹下边找到自动生成的注册程序集和仓库储存进程的
SQL 语句。至于其它版本我们能够尝试。

实施存款和储蓄进度 HelloWorld:

--执行存储过程 HelloWorld
exec [dbo].[HelloWorld]

结果:

图片 1

那便是出口新闻,输出新闻和输出结果是不壹致的,输出消息是不能够得到的(笔者无法),而输出结果就一定于用
Select 语句询问出来的结果1律,是能够收获的。

实施存款和储蓄进程 GetStrLength:

--执行存储过程 GetStrLength
declare @res int 
exec @res=[dbo].[GetStrLength] '123456' 
select @res 

结果:

图片 2

这个 C#
代码里面是有重临值的,也得以经过那种方法取获得重临值,可是那种重返值的不二诀要只可以回去
int 类型的重回值。

万一急需四个再次来到值呢?看上面的积累进程,能够透过设置多个出口参数来达成。

推行存款和储蓄进程 SayHello:

--执行存储过程 SayHello
declare @SayHello nvarchar(32)
exec [dbo].[SayHello] 'Brambling',@SayHello output 

select @SayHello

结果:

图片 3

其实弄精通输入参数、输出参数、输出音信、输出结果和重回值那多少个难题,CL宝马X3存款和储蓄进程的牵线就足以完了。

唯独存款和储蓄进度里面总是免不了要操作数据的,那么上面就看看对于数据库数据的操作和输出结果集的措施呢。

   /// <summary>
    /// 根据学生学号获取学生姓名
    /// </summary>
    /// <param name="Id"></param>
    /// <returns></returns>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentNameByStuNo")]
    public static void GetStudentNameByStuNo(SqlString stuNo,out SqlString stoName)
    {
        stoName = string.Empty;

        //因为程序是在SQL Server内执行,所以连接字符串写成"context connection=true"即可
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select StuName from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();
            if (dataReader.Read())
            {
                stoName = dataReader.GetString(0);
            }
            dataReader.Close();
        }
    }

推行存款和储蓄进度 GetStudentNameByStuNo:

declare @StuName nvarchar(32)
exec [GetStudentNameByStuNo] 'A001',@StuName output 
select @StuName 
exec [GetStudentNameByStuNo] 'A003',@StuName output 
select @StuName 

结果:

图片 4

能够见见大家透过输出参数获取到了重临值。如若昨日自家索要获得整个学生的有所音讯吗?

虽然如此能够通过安装八个出口参数获得,然而学生新闻的字段过多呢?上面看看输出结果集的不二等秘书技。

   /// <summary>
    /// 根据学生的学号获取该学生的所有信息
    /// 返回的是一个结果集,即有多少条数据就返回多少条数据
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_First")]
    public static void GetStudentInfoByStuNo_First(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();
            SqlContext.Pipe.Send(dataReader);
            dataReader.Close();
        }
    }

    /// <summary>
    /// 根据学生的学号获取该学生的所有信息
    /// 这种方式效率比较高,是通过直接执行 SqlCommand 指令,然后把数据发送到客户端,不需要经过托管内存
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_Second")]
    public static void GetStudentInfoByStuNo_Second(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlContext.Pipe.ExecuteAndSend(comm);
        }
    }

    /// <summary>
    /// 根据学生的学号获取该学生的所有信息
    /// </summary>
    /// <param name="stuNo"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentInfoByStuNo_Third")]
    public static void GetStudentInfoByStuNo_Third(SqlString stuNo)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuNo=@StuNo;";

            SqlParameter param = new SqlParameter("@StuNo", SqlDbType.NVarChar, 4000);
            param.SqlValue = stuNo;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            if(dataReader.Read())
            {
                dataRecord.SetInt32(0,(int)dataReader["ID"]);
                dataRecord.SetString(1,(string)dataReader["StuNo"]);
                dataRecord.SetString(2,(string)dataReader["StuName"]);
                dataRecord.SetInt32(3,(int)dataReader["StuAge"]);
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

试行存款和储蓄进程:

--执行存储过程 GetStudentInfoByStuNo_First
exec [GetStudentInfoByStuNo_First] 'A003'

--执行存储过程 GetStudentInfoByStuNo_Second
exec [GetStudentInfoByStuNo_Second] 'A003'

--执行存储过程 GetStudentInfoByStuNo_Third
exec [GetStudentInfoByStuNo_Third] 'A003'

结果:

图片 5

上面多个方式中,第一个格局和第三个方法都以一贯回到查询结果的,不过在骨子里存款和储蓄进度当中是不会如此写的,里面应该包罗有逻辑操作等等,所以就有了第十二个主意。

那就是说以后是回来的一条数据,如若是回去多条数据吧?第一种格局和第3种格局就背着了,因为那二种方法都以重回结果集的。

   /// <summary>
    /// 根据年龄查询学生的信息
    /// 这种方式是一条数据返回一个结果集
    /// </summary>
    /// <param name="stuAge"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentsInfoByStuAge_Single")]
    public static void GetStudentsInfoByStuAge_Single(SqlInt32 stuAge)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuAge=@StuAge;";

            SqlParameter param = new SqlParameter("@StuAge", SqlDbType.Int);
            param.SqlValue = stuAge;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            while (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //发送结果集到客户端
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

    /// <summary>
    /// 根据年龄查询学生的信息
    /// 这种方式是所有的数据返回一个结果集
    /// </summary>
    /// <param name="stuAge"></param>
    [Microsoft.SqlServer.Server.SqlProcedure(Name = "GetStudentsInfoByStuAge_Multiple")]
    public static void GetStudentsInfoByStuAge_Multiple(SqlInt32 stuAge)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from StudentInfo where StuAge=@StuAge;";

            SqlParameter param = new SqlParameter("@StuAge", SqlDbType.Int);
            param.SqlValue = stuAge;
            comm.Parameters.Add(param);

            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            //标记结果集的开始
            SqlContext.Pipe.SendResultsStart(dataRecord);
            while (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //填充数据到结果集
                SqlContext.Pipe.SendResultsRow(dataRecord);
            }
            //标记结果集的结束
            SqlContext.Pipe.SendResultsEnd();
            dataReader.Close();
        }
    }

举行存款和储蓄进程:

--执行存储过程 GetStudentsInfoByStuAge_Single
exec [dbo].[GetStudentsInfoByStuAge_Single] '18'

--执行存储过程 GetStudentsInfoByStuAge_Multiple
exec [dbo].[GetStudentsInfoByStuAge_Multiple] '18'

结果:

图片 6

能够很精通的看看,方法一是一条数据重临1个结果集,方法2是兼具数据重返2个结实集。

下边贴出注册存款和储蓄进度的 SQL
语句,注册程序集的就不贴了,笔者的上壹篇博客有过介绍。

--注册存储过程 HelloWorld
CREATE PROCEDURE [dbo].[HelloWorld] 
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[HelloWorld];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStrLength
CREATE PROCEDURE [dbo].[GetStrLength] 
    @str [nvarchar](MAX)
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStrLength];        --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 SayHello
CREATE PROCEDURE [dbo].[SayHello] 
    @name [nvarchar](MAX), 
    @sayHello [nvarchar](MAX) OUTPUT
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[SayHello];        --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentNameByStuNo
CREATE PROCEDURE [dbo].[GetStudentNameByStuNo] 
    @stuNo [nvarchar](MAX), 
    @stoName [nvarchar](MAX) OUTPUT
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentNameByStuNo];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentInfoByStuNo_First
CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_First] 
    @stuNo [nvarchar](MAX)
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_First];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentInfoByStuNo_Second
CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_Second] 
    @stuNo [nvarchar](MAX)
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_Second];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentInfoByStuNo_Third
CREATE PROCEDURE [dbo].[GetStudentInfoByStuNo_Third] 
    @stuNo [nvarchar](MAX)
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentInfoByStuNo_Third];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentsInfoByStuAge_Single
CREATE PROCEDURE [dbo].[GetStudentsInfoByStuAge_Single] 
    @stuAge [int]
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentsInfoByStuAge_Single];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册存储过程 GetStudentsInfoByStuAge_Multiple
CREATE PROCEDURE [dbo].[GetStudentsInfoByStuAge_Multiple] 
    @stuAge [int]
WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [UserDefinedSqlClr].[StoredProcedures].[GetStudentsInfoByStuAge_Multiple];    --EXTERNAL NAME 程序集名.类名.方法名
GO

 

五、CLR Trigger

接下去采纳增加新项,选用 SQL CLKoleos C# 触发器。

1、DML 触发器

(1) after trigger

public partial class Triggers
{
    /// <summary>
    /// 输出操作的数据
    /// </summary>
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "FirstSqlTrigger", Target = "StudentInfo", Event = "FOR INSERT,UPDATE,DELETE")]
    public static void FirstSqlTrigger()
    {
        switch (SqlContext.TriggerContext.TriggerAction)
        {
            case TriggerAction.Insert:
                GetInsertedOrDeleted(InsOrDel.Inserted);
                break;
            case TriggerAction.Update:
                GetInsertedOrDeleted(InsOrDel.Inserted);
                GetInsertedOrDeleted(InsOrDel.Deleted);
                break;
            case TriggerAction.Delete:
                GetInsertedOrDeleted(InsOrDel.Deleted);
                break;
            default:
                break;
        }
    }

    /// <summary>
    /// 获取操作的数据或之后的数据
    /// </summary>
    /// <param name="insOrDel"></param>
    /// <returns></returns>
    private static void GetInsertedOrDeleted(InsOrDel insOrDel)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            SqlCommand comm = new SqlCommand();
            comm.CommandText = "select ID,StuNo,StuName,StuAge from " + insOrDel.ToString() + ";";
            comm.Connection = conn;
            conn.Open();
            SqlDataReader dataReader = comm.ExecuteReader();

            SqlDataRecord dataRecord = new SqlDataRecord(
                new SqlMetaData[]
                {
                    new SqlMetaData("ID",SqlDbType.Int),
                    new SqlMetaData("StuNo",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuName",SqlDbType.NVarChar,128),
                    new SqlMetaData("StuAge",SqlDbType.Int)
                }
            );

            if (dataReader.Read())
            {
                dataRecord.SetInt32(0, (int)dataReader["ID"]);
                dataRecord.SetString(1, (string)dataReader["StuNo"]);
                dataRecord.SetString(2, (string)dataReader["StuName"]);
                dataRecord.SetInt32(3, (int)dataReader["StuAge"]);
                //发送结果集到客户端
                SqlContext.Pipe.Send(dataRecord);
            }
            dataReader.Close();
        }
    }

    private enum InsOrDel
    {
        Inserted,
        Deleted
    }
}

测试 SQL 语句:

  -- Insert 操作
  insert into StudentInfo(StuNo,StuName,StuAge)
  values('A006','小飞',20)

  -- Update 操作
  update StudentInfo set StuName='小飞飞' where StuNo='A006' 

  -- Delete 操作
  delete from StudentInfo where StuNo='A006'

结果:

图片 7

此处说多美滋(Dumex)下,Microsoft.SqlServer.Server.SqlTrigger 有三个属性。

Name:表示触发器的称谓。

Target:表示触发器的目标表的名目。

伊芙nt:表示触发施行触发器的动作。

 

(2) instead of trigger

public partial class Triggers
{
    /// <summary>
    /// 输出操作类型
    /// </summary>
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "InsteadOfTrigger",Target = "StudentInfo",Event = "INSTEAD OF INSERT,UPDATE,DELETE")]
    public static void InsteadOfTrigger()
    {
        SqlDataRecord dataRecord = new SqlDataRecord(
            new SqlMetaData[]
            {
                new SqlMetaData("Message",SqlDbType.NVarChar,128)
            }
        );

        switch (SqlContext.TriggerContext.TriggerAction)
        {
            case TriggerAction.Insert:
                dataRecord.SetString(0, "Insert操作");
                break;
            case TriggerAction.Update:
                dataRecord.SetString(0, "Update操作");
                break;
            case TriggerAction.Delete:
                dataRecord.SetString(0, "Delete操作");
                break;
            default:
                dataRecord.SetString(0, "Nothing");
                break;
        }
        SqlContext.Pipe.Send(dataRecord);
    }
}

测试 SQL 语句:

-- Insert 操作
insert into StudentInfo(StuNo,StuName,StuAge)
values('A006','小飞',20)

-- Update 操作
update StudentInfo set StuName='小飞飞' where StuNo='A006' 

-- Delete 操作
delete from StudentInfo where StuNo='A006'

结果:

图片 8

Instead of
是1种奇特的触发器,它只进行触发器自个儿,也便是触发器里面包车型地铁操作,

从而 Insert、Update、Delete 操作是不实践的,只是用于触发该触发器,而且
Instead of 触发器会覆盖掉 after 触发器。

 

2、DDL 触发器

DDL
触发器又分为数据库品级的触发器和服务器等第的触发器,这里只介绍数据库级其他触发器。

public partial class Triggers
{
    /// <summary>
    /// 禁止删除表和删除存储过程的 DDL 触发器
    /// </summary>
    [Microsoft.SqlServer.Server.SqlTrigger(Name = "SecondSqlTrigger")]
    public static void SecondSqlTrigger()
    {
        switch (SqlContext.TriggerContext.TriggerAction)
        {
            case TriggerAction.DropTable:
                try
                {
                    Transaction tran = Transaction.Current;
                    tran.Rollback();
                }
                catch
                {
                }
                SqlContext.Pipe.Send("You have no authority");
                break;
            case TriggerAction.DropProcedure:
                try
                {
                    Transaction tran = Transaction.Current;
                    tran.Rollback();
                }
                catch
                {
                }
                SqlContext.Pipe.Send("You have no authority");
                break;
            default:
                break;
        }
    }
}

此地 DDL 的触发器,只须要钦定触发器名称的质量就足以了。

测试 SQL 语句:

--删除表 StudentInfo
drop table StudentInfo

结果:

图片 9

上边贴出注册触发器的 SQL 语句。

--注册触发器 FirstSqlTrigger
CREATE TRIGGER [FirstSqlTrigger] 
ON StudentInfo    --目标表
FOR INSERT,UPDATE,DELETE        --指定触发的操作
AS 
EXTERNAL NAME [UserDefinedSqlClr].[Triggers].[FirstSqlTrigger];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册触发器 InsteadOfTrigger
CREATE TRIGGER [InsteadOfTrigger] 
ON StudentInfo    --目标表
INSTEAD OF INSERT,UPDATE,DELETE        --指定触发的操作
AS 
EXTERNAL NAME [UserDefinedSqlClr].[Triggers].[InsteadOfTrigger];    --EXTERNAL NAME 程序集名.类名.方法名
GO

--注册触发器 SecondSqlTrigger
CREATE TRIGGER [SecondSqlTrigger] 
ON database  --数据库级别触发器
for drop_table,drop_procedure        --指定触发的操作
AS 
EXTERNAL NAME [UserDefinedSqlClr].[Triggers].[SecondSqlTrigger];    --EXTERNAL NAME 程序集名.类名.方法名
GO

删去存款和储蓄进度和删除触发器的 SQL
语句看似,唯一要求小心的便是剔除数据库等第的触发器时,需求在后头加上 on
database,比方:

--删除数据库级别触发器 SecondSqlTrigger
drop trigger [SecondSqlTrigger] on database

骨子里触发器自己就很少用到,因为对此数据量大的时候,越发影响属性,所以这里不多做牵线。

能够参照这里:CLR
触发器
.aspx)

 

六、总结

到底写完了。。。

事实上 CLEnclave 自定义函数、存款和储蓄进度和触发器等,不必然比 T-SQL
好用,准确来讲质量稍微差了一点。

但是那只是提供一种方法,遭遇 T-SQL 不能够一下子就解决了时方可设想的壹种艺术。

总归了然的越来越多,会的越来越多,际遇标题管理的办法就越来越多。

 

留下评论

网站地图xml地图