November 19, 2003 at 8:50 am
I'm sure they aren't perfect by a longshot (I may even have functions that have other internal functions that do the same thing), but here are a couple of my non-application-specific functions.
drop function dbo.fnAlphabetOnly
go
create function dbo.fnAlphabetOnly (@Input nvarchar(400))
returns nvarchar(400)
as
begin
-- Author: Norman Seymore
-- 11/19/2003
-- Removes everything but spaces and letters (english) from a string
-- DB case insensitive
set @Input = ltrim(rtrim(@Input))
declare @StartLen int, @Output nvarchar(400), @ThisChar nchar(1), @CharCount int
set @Output = ''
set @StartLen = len(@Input)
set @CharCount = 0
while @StartLen > @CharCount
begin
set @CharCount = @CharCount + 1
set @ThisChar = right(@Input,1)
set @Input = left(@Input,@StartLen-@CharCount)
if @ThisChar in (' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z')
set @OutPut = @ThisChar + @Output
end
return @Output
end
drop function dbo.fnPaddedMSDateDiffStr
go
CREATE FUNCTION dbo.fnPaddedMSDateDiffStr (
@Split datetime, @Now datetime, @MaxLen tinyint = 20)
RETURNS varchar(20)
AS
BEGIN
-- Author: Norman Seymore
-- 11/19/2003
-- Given two datetime values and how long of a string you want, returns a string of exactly that length
-- that contains the datediff in milliseconds of the two datetime values
-- I use this when I include formatted debug output code in a procedure or script.
-- It allows me to use indentation to clearly show what is nested in what
declare @MSStr varchar(20)
if @MaxLen > 20
set @MSStr = 'MAXLENEXCEEDED'
else
begin
set @MSStr = ltrim(rtrim(str(datediff(ms,@Split,@Now))))
set @MSStr = replicate(' ',@MaxLen-len(@MSStr))+@MSStr
end
RETURN @MSStr
END
go
drop function dbo.fnPadl
go
create function dbo.fnPadl (@Str nvarchar(200), @PadWith nvarchar(10), @PadLength int)
returns nvarchar(200)
as
begin
-- Author: Norman Seymore
-- 11/19/2003
declare @RtrnVal nvarchar(200), @Len int
set @Len = len(@Str)
set @RtrnVal = replicate(@PadWith,@PadLength-@Len)+@Str
return @RtrnVal
end
go
select dbo.fnPadl('1','0',4)
drop function dbo.fnPadr
go
create function dbo.fnPadr (@Str nvarchar(200), @PadWith nvarchar(10), @PadLength int)
returns nvarchar(200)
as
begin
-- Author: Norman Seymore
-- 11/19/2003
declare @RtrnVal nvarchar(200), @Len int
set @Len = len(ltrim(rtrim(@Str)))
set @RtrnVal = ltrim(rtrim(@Str))+replicate(@PadWith,@PadLength-@Len)
return @RtrnVal
end
go
select dbo.fnPadr('1','0',4)
November 19, 2003 at 8:51 am
drop function dbo.fnRemoveNChar
go
CREATE FUNCTION dbo.fnRemoveNChar
(@String nvarchar(400))
RETURNS nvarchar(32)
AS
BEGIN
-- Author: Norman Seymore
-- 11/19/2003
-- removes non-numeric data from an nchar string
-- essentially extracting numeric data from junky fields
-- One use of this is to extract numeric data from system stored procedure result output, such as the space used stuff.
-- declare @String varchar(400)
-- declare @x char(11)
-- set @x = '123131 kb'
-- set @String = @x
declare @Number nvarchar(128)
set @Number = N''
while len(@String) > 0
begin
if isnumeric(left(@string,1)) = 1 and left(@String,1) not in ('.','+','-','=')
begin
set @Number = @Number + ltrim(rtrim(left(@string,1)))
end
set @String = right(ltrim(rtrim(@String)),len(ltrim(rtrim(@String)))-1)
--print @String+'::'+ @Number
end
RETURN @Number
END
go
select convert(int,dbo.fnRemoveNChar('12kjliy1241240-'))
select abs(dbo.fnRemoveNChar('12kjliy1241240-')) as x
November 19, 2003 at 8:52 am
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
CREATE FUNCTION dbo.fnIndexableDateTimeString
(@SeedDate datetime)
RETURNS char(17)
AS
BEGIN
-- Author: Norman Seymore
-- 11/19/2003
-- does what it says. I use this a ton, lately
-- it's usually to just give me a formatted string of
-- CCYYMM or CCYYMMDD for a pre-extrapolated time sensitive
-- field for querying against.
declare
@DTStr char(17),
@CCYY varchar(4),
@m varchar(2),
@D varchar(2),
@h varchar(2),
@mn varchar(2),
@s-2 varchar(2),
@ms varchar(3)
set @CCYY = convert(varchar(4),datepart(year,@SeedDate))
set @m = convert(varchar(2),datepart(month,@SeedDate))
set @D = convert(varchar(2),datepart(day,@SeedDate))
set @h = convert(varchar(2),datepart(hour,@SeedDate))
set @mn = convert(varchar(2),datepart(minute,@SeedDate))
set @s-2 = convert(varchar(2),datepart(second,@SeedDate))
set @ms = convert(varchar(3),datepart(ms,@SeedDate))
/*
print @CCYY
print @m
Print @D
print @h
print @mn
print @s-2
print @ms
*/
set @m = replicate('0',2-len(@M))+@M
set @D = replicate('0',2-len(@D))+@D
set @h = replicate('0',2-len(@H))+@H
set @mn = replicate('0',2-len(@MN))+@MN
set @s-2 = replicate('0',2-len(@S))+@S
set @ms = replicate('0',3-len(@MS))+@MS
/*
print @CCYY
print @m
Print @D
print @h
print @mn
print @s-2
print @ms
*/
set @DTStr = @CCYY+@M+@D+@H+@MN+@S+@MS
RETURN @DTStr
END
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
/*
--exec CCR_CheckForSnapshotRoom
exec sp_spaceused
dbcc sqlperf(logspace)
*/
-- if @Available-@Used < 2000
-- raiserror('May not be enough space for snapshot. Expand the db size or delete some snapshots.', 16, 1)
drop procedure UTIL_DataSpaceAvailable
go
create procedure UTIL_DataSpaceAvailable (@SupressOutput bit = 0) as
declare @dbsize dec(15,0)
declare @logsize dec(15)
declare @bytesperpagedec(15,0)
declare @pagesperMBdec(15,0)
declare @DataTotal dec(15,0)
declare @LogTotal dec(15,0)
declare @DataReserved dec(15,0)
--sp_spaceused
set nocount on
-- get the data file size
select @dbsize = sum(convert(dec(15),size))
from dbo.sysfiles
where (status & 64 = 0)
-- get the log file size
select @logsize = sum(convert(dec(15),size))
from dbo.sysfiles
where (status & 64 <> 0)
select @bytesperpage = low
from master.dbo.spt_values
where number = 1
and type = 'E'
select @pagesperMB = 1048576 / @bytesperpage
select @DataTotal = ((@dbsize) / @pagesperMB)
select @LogTotal = ((@logsize) / @pagesperMB)
-- get the data space used
select @DataReserved = reserved * d.low / 1024. /1024.
from (
select sum(convert(dec(15),reserved)) as reserved
from sysindexes
where indid in (0, 1, 255)) as a,
master.dbo.spt_values d
where d.number = 1
and d.type = 'E'
-- get the log space used
-- nix. this can be monitored via perf mon and or alerts
select
@DataTotal as DataTotal
,@DataReserved as DataReserved
,@DataTotal-@DataReserved as DataAvailable
,@LogTotal as LogTotal
-- can't seem to figure out a way to get logused... maybe sysindex indid other than the ones being queried?
-- or can just use the dbcc sqlperf(logspace), but will have to use temp table to store output
return @DataTotal-@DataReserved
November 19, 2003 at 10:28 am
CREATE FUNCTION dbo.fnAlphabetOnly(@Input nvarchar(400))
RETURNS nvarchar(400) AS
BEGIN
DECLARE @pos smallint
SET @Pos = PATINDEX('%[^A-Za-z ]%',@Input)
WHILE @Pos > 0 BEGIN
SET @Input = STUFF(@Input,@pos,1,'')
SET @Pos = PATINDEX('%[^A-Za-z ]%',@Input)
END
RETURN @Input END
SELECT STR(DATEDIFF(ms,@split,GETDATE()),20)
SELECT RIGHT(REPLICATE('0',4)+'1',4)
SELECT LEFT('1'+REPLICATE('0',4),4)
CREATE FUNCTION dbo.fnRemoveNChar(@Input nvarchar(400))
RETURNS nvarchar(400) AS
BEGIN
DECLARE @pos smallint
SET @Pos = PATINDEX('%[^0-9]%',@Input)
WHILE @Pos > 0 BEGIN
SET @Input = STUFF(@Input,@pos,1,'')
SET @Pos = PATINDEX('%[^0-9]%',@Input)
END
RETURN @Input END
SELECT REPLACE(REPLACE(REPLACE(REPLACE(CONVERT(char(23),@SeedDate,121),'-',''),' ',''),':',''),'.','')
Why not just use the datetime value itself "for querying against"?
--Jonathan
--Jonathan
November 19, 2003 at 3:58 pm
Neato thanks for the improvements. I'll try them out in production soon as I burn em in.
"Why not just use the datetime value itself "for querying against"?"
If you mean the indexabledatetimestring? The name is a bit misleading, as my note may be also. In reality I use it when taking sales transactions and storing them for commission processing. Because they also had me provide MTD qualification info a process, including a 'compare' process, runs against history, so I frequently have to work with something that is no more and no less specific down to a month. Best Practice, i don't know. I do know that I use datetime far more frequently- any time I want to allow data more specific than CCYYMM, or the data is for transaction capture instead of for querying.
November 19, 2003 at 5:42 pm
quote:
Neato thanks for the improvements. I'll try them out in production soon as I burn em in."Why not just use the datetime value itself "for querying against"?"
If you mean the indexabledatetimestring? The name is a bit misleading, as my note may be also. In reality I use it when taking sales transactions and storing them for commission processing. Because they also had me provide MTD qualification info a process, including a 'compare' process, runs against history, so I frequently have to work with something that is no more and no less specific down to a month. Best Practice, i don't know. I do know that I use datetime far more frequently- any time I want to allow data more specific than CCYYMM, or the data is for transaction capture instead of for querying.
A datetime column is eight bytes and you are expanding it to 17 bytes and storing that so you can compare it with char values by using something like "LIKE '200310%'"?
Let's say your stored procedure now looks something like this, so you can return the value for a year, month, or day:
CREATE PROC p_SalesByPd
@RepID smallint,
@Period varchar(8),
@PdSales money OUTPUT
AS
SET NOCOUNT ON
SELECT @PdSales = SUM(OrdTot) - SUM(TaxAmt)
FROM Sales
WHERE SalesRepID = @RepID
AND OrdDate LIKE @Period + '%'
If we instead keep the temporal data in a temporal data type, it is tempting to just use something like:
AND CONVERT(char(8),OrdDate,112) LIKE @Period + '&'
But, based on your udf name, you have found that such a predicate is not sargable and therefore will not use an index on the column. But something like this will work well with an indexed datetime column:
AND OrdDate >= LEFT(@Period + '0101',8)
AND OrdDate < CASE LEN(@Period)
WHEN 4 THEN DATEADD(yy,1,@Period + '0101')
WHEN 6 THEN DATEADD(m,1,@Period + '01')
ELSE DATEADD(d,1,@Period) END
--Jonathan
--Jonathan
November 20, 2003 at 8:29 am
I'm confused why you think the size is large - I use int to store the CCYYMM period. That's 4 bytes not 8 (datetime).
It's rare, if at all, that I use text fields for comparison- I usually only use them for descriptions in small type reference tables, which I typically don't access when saving transactions to the DB or when performing complex queries. Just when a report or GUI needs to see something in plain english rather than XXXTypeID.
Edited by - cmore on 11/20/2003 08:43:11 AM
November 20, 2003 at 8:45 am
quote:
I'm confused why you think the size is large - I use int to store the CCYYMM period.
We were discussing your fnIndexableDateTimeString udf, which returns a char(17) value.
--Jonathan
--Jonathan
November 20, 2003 at 11:57 am
I wanted something to call to set memory variables, instead of retyping the same code every time.
I set it up to return a string because at the time:
set @Period = convert(int,left(dbo.fn(@Date),6))
instead of
set @Period = convert(int,left(ltrim(str(dbo.fn(@Date))),6))
seemed a better option.
I should add to this that in my head when I was thinking 'indexable' I was not thinking of using as you did in your example. I was thinking of it as an int. If querying data without a period in a where clause, such as filtering sales transactions, I'll use @BeginDate datetime and @EndDate datetime, and sometimes datepart(month) and datepart(year), to match against the date of the sale. - I haven't checked to see which is faster or whatnot because it's never seemed to be boggy as long as the indexes are ok.
If I want a period field in the data that I am creating, I use the function to grab the first six characters and convert it into an int. The conversion of the time dimension into a specifiable scope is what was desirable.
Edited by - cmore on 11/20/2003 12:18:05 PM
November 20, 2003 at 1:31 pm
quote:
I wanted something to call to set memory variables, instead of retyping the same code every time.I set it up to return a string because at the time:
set @Period = convert(int,left(dbo.fn(@Date),6))
instead of
set @Period = convert(int,left(ltrim(str(dbo.fn(@Date))),6))
seemed a better option.
I should add to this that in my head when I was thinking 'indexable' I was not thinking of using as you did in your example. I was thinking of it as an int. If querying data without a period in a where clause, such as filtering sales transactions, I'll use @BeginDate datetime and @EndDate datetime, and sometimes datepart(month) and datepart(year), to match against the date of the sale. - I haven't checked to see which is faster or whatnot because it's never seemed to be boggy as long as the indexes are ok.
If I want a period field in the data that I am creating, I use the function to grab the first six characters and convert it into an int. The conversion of the time dimension into a specifiable scope is what was desirable.
Edited by - cmore on 11/20/2003 12:18:05 PM
This reminds me I've got to write that article on using dates in SQL Server...
DECLARE @Period int
SET @Period = CONVERT(char(6),@Date,112)
--Jonathan
--Jonathan
November 20, 2003 at 1:42 pm
Yes, that does the same thing. Mine has granularity out to the MS, but I haven't needed it. Yours is certainly more SQL guruish.
I bet yours is faster too, if one needed to convert a datetime field to an int with CCYYMM when copying a bunch of data.
November 20, 2003 at 2:01 pm
quote:
Yes, that does the same thing. Mine has granularity out to the MS, but I haven't needed it. Yours is certainly more SQL guruish.I bet yours is faster too, if one needed to convert a datetime field to an int with CCYYMM when copying a bunch of data.
I've never seen an example where a UDF performs better than internal functions. This can make a big difference when applying them to large result sets. Note that I suggested internal function alternatives for two-thirds of the UDFs you posted.
--Jonathan
--Jonathan
November 20, 2003 at 2:33 pm
quote:
I've never seen an example where a UDF performs better than internal functions. This can make a big difference when applying them to large result sets.
Yes, which is why I said I bet it would be faster.
quote:
Note that I suggested internal function alternatives for two-thirds of the UDFs you posted.
Yes, it's actually what I hoped for. If you will look at the header of the original post you will note that I even specifically stated that there was likely pre-made utilities to do what the udfs do. It's also why I thanked you for the improvements.
I did not post these to compete, merely to carry on a conversation and perhaps glean something useful from it. I work in a vacuum of SQL talent, which means there's nobody here to collaborate or learn with. Unfortunately the only bad habits I end up breaking on my own are ones where they smack me in the face.
I apologize if I stepped on your toes in any way. I try to not have an ego. The day I assume I know more as a whole than someone else is the day I quit learning.
November 20, 2003 at 3:19 pm
quote:
quote:
I've never seen an example where a UDF performs better than internal functions. This can make a big difference when applying them to large result sets.Yes, which is why I said I bet it would be faster.
Yes, I was trying to reinforce that impression.
quote:
quote:
Note that I suggested internal function alternatives for two-thirds of the UDFs you posted.Yes, it's actually what I hoped for. If you will look at the header of the original post you will note that I even specifically stated that there was likely pre-made utilities to do what the udfs do. It's also why I thanked you for the improvements.
I did not post these to compete, merely to carry on a conversation and perhaps glean something useful from it. I work in a vacuum of SQL talent, which means there's nobody here to collaborate or learn with. Unfortunately the only bad habits I end up breaking on my own are ones where they smack me in the face.
I apologize if I stepped on your toes in any way. I try to not have an ego. The day I assume I know more as a whole than someone else is the day I quit learning.
Don't know how I gave that impression; do I need to apologize? I have trouble understanding your writing (still don't know what you're trying to do with the code fragments at the end of your third post), but I assumed you posted your scripts here in a forum rather than in the Scripts section because you wanted to know if there were better ways. A lot of beginners read these posts, and I also did not want them to assume your methods were the only way.
--Jonathan
--Jonathan
November 20, 2003 at 3:48 pm
Sounds like we are on the same sheet then. The fragments were junk- I should have removed them before posting.
Viewing 15 posts - 1 through 14 (of 14 total)
You must be logged in to reply to this topic. Login to reply