|
database
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
UDF that take a UDT as a parameter (CLR)to work around this. I have a UDT type, called interval, that I want to be a parameter to a UDF. Let's say interval 1, i1 is [2:4] and interval 2, i2 is [3:5]. I would like to do something like i1 intersect i2 = [3:4] So, it would look something like: intersect (i1, i2) and return me an interval type. I have read in the VS documentation: ..NET Framework SVFs are implemented as methods on a class in a .NET Framework assembly. The input parameters and the type returned from a SVF can be any of the scalar data types supported by SQL Server, except rowversion, text, ntext, image, timestamp, table, or cursor. SVFs must ensure a match between the SQL Server data type and the return data type of the implementation method. So, I don't have much hope, but I thought I might see what someone has to say. Hello Andy in S. Jersey Andy in S.,
> I have a feeling that I can't do this, but thought someone might have This seems to work. Consider:> a way to work around this. using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.IO; using System.Text.RegularExpressions; [Serializable] [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLength=false,MaxByteSize=8000)] public class IntervalUDT : INullable, IBinarySerialize { static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); int? Start = null; int? End = null; public override string ToString() { return string.Format("{0}:{1}",Start,End); } public bool IsNull { get { return (Start == null? true : false) ; } } public static IntervalUDT Null { get { IntervalUDT h = new IntervalUDT(); return h; } } public static IntervalUDT Parse(SqlString s) { if (s.IsNull) return Null; IntervalUDT u = new IntervalUDT(); string[] v = PARSER.Split(s.Value); u.Start = int.Parse(v[1]); u.End = int.Parse(v[2]); return u; } public void Read(BinaryReader r) { this.Start = r.ReadInt32(); this.End = r.ReadInt32(); } public void Write(BinaryWriter w) { w.Write((int)this.Start); w.Write((int)this.End); } public SqlInt32 GetStart() { if(Start == null) return SqlInt32.Null; else return new SqlInt32(this.Start.Value); } public SqlInt32 GetEnd() { if(End == null) return SqlInt32.Null; else return new SqlInt32(this.End.Value); } } and using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.Collections.Generic; using System.Collections; public partial class IntervalFunctions { [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT B) { List<int?> alist = new List<int?>(); List<int?> ilist = new List<int?>(); for(int i = A.GetStart().Value;i <= A.GetEnd();i++) alist.Add(i); for(int i = B.GetStart().Value;i <= B.GetEnd();i++) if(alist.Contains(i)) ilist.Add(i); return(ilist); } public static void GetRows(object o, out SqlInt32 v) { v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); } }; This query seems to work: declare @u1 IntervalUDT declare @u2 IntervalUDT set @u1 = '2:4' set @u2 = '3:5' select * from dbo.IntervalIntersect(@u1,@u2) Cheers, Kent Tegels, DevelopMentor http://staff.develop.com/ktegels/ Thanks for you help Kent.
This code looks pretty good, but let's say the test.sql is declare @u1 IntervalUDT declare @u2 IntervalUDT set @u1 = '2:5' set @u2 = '3:7' select * from dbo.IntervalIntersect(@u1,@u2) This would return v ----------- 3 4 5 I would want only v ----------- 3 5 and then convert that into my interval type again for storage in a result table in the format 3:5 Show quote "Kent Tegels" wrote: > Hello Andy in S. Jersey Andy in S., > > > I have a feeling that I can't do this, but thought someone might have > > a way to work around this. > > This seems to work. Consider: > > using System; > using System.Data; > using System.Data.SqlClient; > using System.Data.SqlTypes; > using Microsoft.SqlServer.Server; > using System.IO; > using System.Text.RegularExpressions; > [Serializable] > [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLength=false,MaxByteSize=8000)] > public class IntervalUDT : INullable, IBinarySerialize > { > static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); > int? Start = null; > int? End = null; > public override string ToString() { > return string.Format("{0}:{1}",Start,End); > } > public bool IsNull { > get { > return (Start == null? true : false) ; > } > } > public static IntervalUDT Null { > get { > IntervalUDT h = new IntervalUDT(); > return h; > } > } > public static IntervalUDT Parse(SqlString s) > { > if (s.IsNull) > return Null; > IntervalUDT u = new IntervalUDT(); > string[] v = PARSER.Split(s.Value); > u.Start = int.Parse(v[1]); > u.End = int.Parse(v[2]); > return u; > } > public void Read(BinaryReader r) { > this.Start = r.ReadInt32(); > this.End = r.ReadInt32(); > } > public void Write(BinaryWriter w) { > w.Write((int)this.Start); > w.Write((int)this.End); > } > public SqlInt32 GetStart() { > if(Start == null) > return SqlInt32.Null; > else > return new SqlInt32(this.Start.Value); > } > public SqlInt32 GetEnd() { > if(End == null) > return SqlInt32.Null; > else > return new SqlInt32(this.End.Value); > } > } > > and > > using System; > using System.Data; > using System.Data.SqlClient; > using System.Data.SqlTypes; > using Microsoft.SqlServer.Server; > using System.Collections.Generic; > using System.Collections; > public partial class IntervalFunctions > { > [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] > public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT B) > { > List<int?> alist = new List<int?>(); > List<int?> ilist = new List<int?>(); > for(int i = A.GetStart().Value;i <= A.GetEnd();i++) > alist.Add(i); > for(int i = B.GetStart().Value;i <= B.GetEnd();i++) > if(alist.Contains(i)) > ilist.Add(i); > return(ilist); > } > public static void GetRows(object o, out SqlInt32 v) { > v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); > } > }; > > This query seems to work: > > declare @u1 IntervalUDT > declare @u2 IntervalUDT > set @u1 = '2:4' > set @u2 = '3:5' > select * from dbo.IntervalIntersect(@u1,@u2) > > Cheers, > Kent Tegels, DevelopMentor > http://staff.develop.com/ktegels/ > > > Hello Andy in S. Jersey,
Well, you can either return an instance of the type OR a table of variables. I'll leave the table min and max values as an excercise for you, but here's modified code to give you back a type instance. using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.Collections.Generic; using System.Collections; public partial class IntervalFunctions { [SqlFunction()] public static IntervalUDT IntervalIntersect2(IntervalUDT A,IntervalUDT B) { IntervalUDT c = new IntervalUDT(); c.Start = Math.Max(A.Start.Value,B.Start.Value); c.End = Math.Min(A.End.Value,B.End.Value); return c; } [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT B) { List<int?> alist = new List<int?>(); List<int?> ilist = new List<int?>(); for(int i = A.Start.Value;i <= A.End.Value;i++) alist.Add(i); for(int i = B.Start.Value;i <= B.End.Value;i++) if(alist.Contains(i)) ilist.Add(i); return(ilist); } public static void GetRows(object o, out SqlInt32 v) { v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); } }; using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.IO; using System.Text.RegularExpressions; [Serializable] [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLength=false,MaxByteSize=8000)] public class IntervalUDT : INullable, IBinarySerialize { static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); int? _Start = null; int? _End = null; public override string ToString() { return string.Format("{0}:{1}",_Start,_End); } public bool IsNull { get { return (_Start == null? true : false) ; } } public static IntervalUDT Null { get { IntervalUDT h = new IntervalUDT(); return h; } } public static IntervalUDT Parse(SqlString s) { if (s.IsNull) return Null; IntervalUDT u = new IntervalUDT(); string[] v = PARSER.Split(s.Value); u._Start = int.Parse(v[1]); u._End = int.Parse(v[2]); return u; } public void Read(BinaryReader r) { this._Start = r.ReadInt32(); this._End = r.ReadInt32(); } public void Write(BinaryWriter w) { w.Write((int)this._Start); w.Write((int)this._End); } public SqlInt32 Start { get { if(this._Start == null) return SqlInt32.Null; else return new SqlInt32(this._Start.Value); } set { this._Start = value.Value; } } public SqlInt32 End { get { if(this._End == null) return SqlInt32.Null; else return new SqlInt32(this._End.Value); } set { this._End = value.Value; } } } Test with: declare @u1 IntervalUDT declare @u2 IntervalUDT set @u1 = '2:5' set @u2 = '3:7' /* select * from dbo.IntervalIntersect(@u1,@u2) */ select dbo.IntervalIntersect2(@u1,@u2).ToString() Show quote > Thanks for you help Kent. Kent Tegels, DevelopMentor> > This code looks pretty good, but let's say the test.sql is > > declare @u1 IntervalUDT > declare @u2 IntervalUDT > set @u1 = '2:5' > set @u2 = '3:7' > select * from dbo.IntervalIntersect(@u1,@u2) > This would return > > v ----------- 3 4 5 > > I would want only > > v ----------- 3 5 > > and then convert that into my interval type again for storage in a > result table in the format > > 3:5 > > "Kent Tegels" wrote: > >> Hello Andy in S. Jersey Andy in S., >> >>> I have a feeling that I can't do this, but thought someone might >>> have a way to work around this. >>> >> This seems to work. Consider: >> >> using System; >> using System.Data; >> using System.Data.SqlClient; >> using System.Data.SqlTypes; >> using Microsoft.SqlServer.Server; >> using System.IO; >> using System.Text.RegularExpressions; >> [Serializable] >> [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLeng >> th=false,MaxByteSize=8000)] >> public class IntervalUDT : INullable, IBinarySerialize >> { >> static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); >> int? Start = null; >> int? End = null; >> public override string ToString() { >> return string.Format("{0}:{1}",Start,End); >> } >> public bool IsNull { >> get { >> return (Start == null? true : false) ; >> } >> } >> public static IntervalUDT Null { >> get { >> IntervalUDT h = new IntervalUDT(); >> return h; >> } >> } >> public static IntervalUDT Parse(SqlString s) >> { >> if (s.IsNull) >> return Null; >> IntervalUDT u = new IntervalUDT(); >> string[] v = PARSER.Split(s.Value); >> u.Start = int.Parse(v[1]); >> u.End = int.Parse(v[2]); >> return u; >> } >> public void Read(BinaryReader r) { >> this.Start = r.ReadInt32(); >> this.End = r.ReadInt32(); >> } >> public void Write(BinaryWriter w) { >> w.Write((int)this.Start); >> w.Write((int)this.End); >> } >> public SqlInt32 GetStart() { >> if(Start == null) >> return SqlInt32.Null; >> else >> return new SqlInt32(this.Start.Value); >> } >> public SqlInt32 GetEnd() { >> if(End == null) >> return SqlInt32.Null; >> else >> return new SqlInt32(this.End.Value); >> } >> } >> and >> >> using System; >> using System.Data; >> using System.Data.SqlClient; >> using System.Data.SqlTypes; >> using Microsoft.SqlServer.Server; >> using System.Collections.Generic; >> using System.Collections; >> public partial class IntervalFunctions >> { >> [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] >> public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT >> B) >> { >> List<int?> alist = new List<int?>(); >> List<int?> ilist = new List<int?>(); >> for(int i = A.GetStart().Value;i <= A.GetEnd();i++) >> alist.Add(i); >> for(int i = B.GetStart().Value;i <= B.GetEnd();i++) >> if(alist.Contains(i)) >> ilist.Add(i); >> return(ilist); >> } >> public static void GetRows(object o, out SqlInt32 v) { >> v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); >> } >> }; >> This query seems to work: >> >> declare @u1 IntervalUDT >> declare @u2 IntervalUDT >> set @u1 = '2:4' >> set @u2 = '3:5' >> select * from dbo.IntervalIntersect(@u1,@u2) >> Cheers, >> Kent Tegels, DevelopMentor >> http://staff.develop.com/ktegels/ --- Thanks, http://staff.develop.com/ktegels/ Thanks again Kent.
I am a pretty good T-SQL programmer, but C# is not my best language, please bear with me. The only thing I added was I changed the IntervalIntersect2 function to accomodate the fact that the two intervals may not intersect: public static IntervalUDT IntervalIntersect2(IntervalUDT A, IntervalUDT B) { IntervalUDT c = new IntervalUDT(); if ((A.End.Value < B.Start.Value) || (B.End.Value < A.Start.Value)) { return null; } else { c.Start = Math.Max(A.Start.Value, B.Start.Value); c.End = Math.Min(A.End.Value, B.End.Value); return c; } } I think this is the best way ie to check for intersection before you do the math funcs. So, now, if you have intervals that don't intersect declare @u1 IntervalUDT declare @u2 IntervalUDT set @u1 = '2:5' set @u2 = '6:8' select dbo.IntervalIntersect2(@u1,@u2).ToString() You will get null back. Show quote "Kent Tegels" wrote: > Hello Andy in S. Jersey, > > Well, you can either return an instance of the type OR a table of variables. > I'll leave the table min and max values as an excercise for you, but here's > modified code to give you back a type instance. > > using System; > using System.Data; > using System.Data.SqlClient; > using System.Data.SqlTypes; > using Microsoft.SqlServer.Server; > using System.Collections.Generic; > using System.Collections; > public partial class IntervalFunctions { > [SqlFunction()] > public static IntervalUDT IntervalIntersect2(IntervalUDT A,IntervalUDT > B) { > IntervalUDT c = new IntervalUDT(); > c.Start = Math.Max(A.Start.Value,B.Start.Value); > c.End = Math.Min(A.End.Value,B.End.Value); > return c; > } > [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] > public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT > B) { > List<int?> alist = new List<int?>(); > List<int?> ilist = new List<int?>(); > for(int i = A.Start.Value;i <= A.End.Value;i++) > alist.Add(i); > for(int i = B.Start.Value;i <= B.End.Value;i++) > if(alist.Contains(i)) > ilist.Add(i); > return(ilist); > } > public static void GetRows(object o, out SqlInt32 v) { > v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); > } > }; > > using System; > using System.Data; > using System.Data.SqlClient; > using System.Data.SqlTypes; > using Microsoft.SqlServer.Server; > using System.IO; > using System.Text.RegularExpressions; > [Serializable] > [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLength=false,MaxByteSize=8000)] > public class IntervalUDT : INullable, IBinarySerialize { > static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); > int? _Start = null; > int? _End = null; > public override string ToString() { > return string.Format("{0}:{1}",_Start,_End); > } > public bool IsNull { > get { > return (_Start == null? true : false) ; > } > } > public static IntervalUDT Null { > get { > IntervalUDT h = new IntervalUDT(); > return h; > } > } > public static IntervalUDT Parse(SqlString s) > { > if (s.IsNull) > return Null; > IntervalUDT u = new IntervalUDT(); > string[] v = PARSER.Split(s.Value); > u._Start = int.Parse(v[1]); > u._End = int.Parse(v[2]); > return u; > } > public void Read(BinaryReader r) { > this._Start = r.ReadInt32(); > this._End = r.ReadInt32(); > } > public void Write(BinaryWriter w) { > w.Write((int)this._Start); > w.Write((int)this._End); > } > public SqlInt32 Start { > get { if(this._Start == null) > return SqlInt32.Null; > else > return new SqlInt32(this._Start.Value); } > set { this._Start = value.Value; } > } > public SqlInt32 End { > get { if(this._End == null) > return SqlInt32.Null; > else > return new SqlInt32(this._End.Value); } > set { this._End = value.Value; } > } > } > Test with: > > declare @u1 IntervalUDT > declare @u2 IntervalUDT > set @u1 = '2:5' > set @u2 = '3:7' > /* select * from dbo.IntervalIntersect(@u1,@u2) */ > select dbo.IntervalIntersect2(@u1,@u2).ToString() > > > > > > Thanks for you help Kent. > > > > This code looks pretty good, but let's say the test.sql is > > > > declare @u1 IntervalUDT > > declare @u2 IntervalUDT > > set @u1 = '2:5' > > set @u2 = '3:7' > > select * from dbo.IntervalIntersect(@u1,@u2) > > This would return > > > > v ----------- 3 4 5 > > > > I would want only > > > > v ----------- 3 5 > > > > and then convert that into my interval type again for storage in a > > result table in the format > > > > 3:5 > > > > "Kent Tegels" wrote: > > > >> Hello Andy in S. Jersey Andy in S., > >> > >>> I have a feeling that I can't do this, but thought someone might > >>> have a way to work around this. > >>> > >> This seems to work. Consider: > >> > >> using System; > >> using System.Data; > >> using System.Data.SqlClient; > >> using System.Data.SqlTypes; > >> using Microsoft.SqlServer.Server; > >> using System.IO; > >> using System.Text.RegularExpressions; > >> [Serializable] > >> [SqlUserDefinedType(Format.UserDefined,IsByteOrdered=true,IsFixedLeng > >> th=false,MaxByteSize=8000)] > >> public class IntervalUDT : INullable, IBinarySerialize > >> { > >> static readonly Regex PARSER = new Regex(@"(\d*)\:(\d*)"); > >> int? Start = null; > >> int? End = null; > >> public override string ToString() { > >> return string.Format("{0}:{1}",Start,End); > >> } > >> public bool IsNull { > >> get { > >> return (Start == null? true : false) ; > >> } > >> } > >> public static IntervalUDT Null { > >> get { > >> IntervalUDT h = new IntervalUDT(); > >> return h; > >> } > >> } > >> public static IntervalUDT Parse(SqlString s) > >> { > >> if (s.IsNull) > >> return Null; > >> IntervalUDT u = new IntervalUDT(); > >> string[] v = PARSER.Split(s.Value); > >> u.Start = int.Parse(v[1]); > >> u.End = int.Parse(v[2]); > >> return u; > >> } > >> public void Read(BinaryReader r) { > >> this.Start = r.ReadInt32(); > >> this.End = r.ReadInt32(); > >> } > >> public void Write(BinaryWriter w) { > >> w.Write((int)this.Start); > >> w.Write((int)this.End); > >> } > >> public SqlInt32 GetStart() { > >> if(Start == null) > >> return SqlInt32.Null; > >> else > >> return new SqlInt32(this.Start.Value); > >> } > >> public SqlInt32 GetEnd() { > >> if(End == null) > >> return SqlInt32.Null; > >> else > >> return new SqlInt32(this.End.Value); > >> } > >> } > >> and > >> > >> using System; > >> using System.Data; > >> using System.Data.SqlClient; > >> using System.Data.SqlTypes; > >> using Microsoft.SqlServer.Server; > >> using System.Collections.Generic; > >> using System.Collections; > >> public partial class IntervalFunctions > >> { > >> [SqlFunction(FillRowMethodName="GetRows",TableDefinition="v int")] > >> public static IEnumerable IntervalIntersect(IntervalUDT A,IntervalUDT > >> B) > >> { > >> List<int?> alist = new List<int?>(); > >> List<int?> ilist = new List<int?>(); > >> for(int i = A.GetStart().Value;i <= A.GetEnd();i++) > >> alist.Add(i); > >> for(int i = B.GetStart().Value;i <= B.GetEnd();i++) > >> if(alist.Contains(i)) > >> ilist.Add(i); > >> return(ilist); > >> } > >> public static void GetRows(object o, out SqlInt32 v) { > >> v = (o == null ? SqlInt32.Null : new SqlInt32((int)(o)) ); > >> } > >> }; > >> This query seems to work: > >> > >> declare @u1 IntervalUDT > >> declare @u2 IntervalUDT > >> set @u1 = '2:4' > >> set @u2 = '3:5' > >> select * from dbo.IntervalIntersect(@u1,@u2) > >> Cheers, > >> Kent Tegels, DevelopMentor > >> http://staff.develop.com/ktegels/ > --- > Thanks, > Kent Tegels, DevelopMentor > http://staff.develop.com/ktegels/ > > > Hello Andy in S. Jersey,
There's a huge "oh duh" look on my face. Naturally, my shortcut didn't consider that. Unit Testing is your friend... Thanks, Kent Tegels, DevelopMentor http://staff.develop.com/ktegels/ |
|||||||||||||||||||||||