Convert decimal hours to 'hh:mm' format - SQL Server - sql

This is more of a solution than question.
I've looked at many threads to find a good solution for converting decimal hours into 'hh:mm' format but only got bits and pieces here and there. So thought, I'd share my solution if it's useful for anyone.
Mods - please feel free to optimize/refine my function.

--this will convert 45.50 to '45:30' or -45.50 to '-45:30'
CREATE FUNCTION [dbo].[GetTimeFromNumericValue](#numerichours numeric(24,6))
RETURNS varchar(6) --Only string takes negative
AS
BEGIN
Declare #returnvalue numeric(24,6) ;
Declare #left varchar(10);
Declare #right varchar(10);
If(#numerichours is NULL OR #numerichours = 0.0 )
Begin
Return '';
End
Else if (#numerichours<0)
Begin
set #numerichours = ABS(#numerichours)
Return '-' + RIGHT('0'+Convert(varchar, FLOOR(#numerichours)),2) +':' + RIGHT('0'+Convert(varchar,Convert(int,((#numerichours - FLOOR(#numerichours)) * 60))),2)
End
Else
Begin
Return RIGHT('0'+Convert(varchar, FLOOR(#numerichours)),2) +':' + RIGHT('0'+Convert(varchar,Convert(int,((#numerichours - FLOOR(#numerichours)) * 60))),2)
End
Return '';
End

Related

sql server concating or replacing, which one is better (faster)

I have to generate a very long procedure every time for a reporting system, so i created a template for my procedure and replacing the parts are needed to, but i could do it with Concat or +(&)
for example:
set #query = '... and (
--#InnerQueries
)'
set #query = replace(#query,'--#InnerQueries',#otherValues)
vs
set #query += ' and exists (...)'
if(#xxx is not null)
set #query += 'and not exists (...)'
with replace approach it's more readable and maintainable for me, but for sake of optimization, what about Concat and attaching string together?
with replace: there are a lot of searching but less string creation
and with concat: lot's of string creation but no searching
so any idea?
I assume you're talking about using CONCAT or REPLACE to build an SQL then run it. If ultimately you'll process fewer than 100 REPLACEments, I'd go with that approach rather than CONCAT because it's more readable.
If however, you're talking about using concat/replace to create report output data and you will e.g. be carrying out 100 REPLACE operations per row on a million rows, I'd do the CONCAT route
update 2:
there could be something missing here:
if i change first variable :#sourceText_Replace
to a max value of 8000 character, and continue to add to it:
set #sourceText_Replace += '8000 character length'
set #sourceText_Replace +=#sourceText_Replace
set #sourceText_Replace +=#sourceText_Replace
set #sourceText_Replace +=#sourceText_Replace
set #sourceText_Replace +=#sourceText_Replace
set #sourceText_Replace +=#sourceText_Replace
set #sourceText_Replace +=#sourceText_Replace
it works fine, even if go up until: 16384017 character length
so any idea here is as good as mine
orginal answer:
to summarize (and if i didnt make any mistakes):
if you are searching in a long text, dont even think about using replace, it took seconds not milliseconds, but for concat obviously does not make any difference
in the blew code, in first try(small text), i just used variables default values and did not append to them,
but for second try(long Text) , i just append result from previous loop run
for long text, i did not bothered to run the loop more than 20 time, because it took over minutes.
smallText: set #destSmallText_Replace =
longText: set #destSmallText_Replace +=
here is the code for test:
SET NOCOUNT ON
drop table if exists #tempReplace
drop table if exists #tempConcat
create table #tempReplace
(
[txt] nvarchar(max) not null
)
create table #tempConcat
(
[txt] nvarchar(max) not null
)
declare #sourceText_Replace nvarchar(max) = 'small1 text to replace #textToBeReplaced after param text'
declare #text_Replace nvarchar(max) = #sourceText_Replace
declare #textToSearch nvarchar(max) = '#textToBeReplaced'
declare #textToReplace nvarchar(max) = 'textToBeReplaced'
declare #concat_Start nvarchar(max) = 'small1 text to replace'
declare #concat_End nvarchar(max) = 'after param text'
declare #text_Concat nvarchar(max) = #concat_Start
declare #whileCounter int =0
declare #maxCounter int = 5
declare #startTime datetime = getdate();
declare #endTime datetime = getdate();
begin
set #startTime = getDate();
while(#whileCounter <=#maxCounter)
begin
--long text
set #text_Replace += replace(#sourceText_Replace,#textToSearch,#textToReplace + convert(nvarchar(10), #whileCounter)) + #textToSearch
--small text
--set #text_Replace = replace(#sourceText_Replace,#textToSearch,#textToReplace + convert(nvarchar(10), #whileCounter)) + #textToSearch
--print #destSmallText_Replace
insert into #tempReplace values(#text_Replace)
set #whileCounter+=1
end
set #endTime = getDate();
print 'passedTime ' + Convert(nvarchar(20), DATEPART(millisecond, #endTime) - DATEPART(millisecond, #startTime))
end
begin
set #whileCounter = 0;
set #startTime = getDate();
while(#whileCounter <=#maxCounter)
begin
set #text_Concat += concat(#concat_Start,#textToReplace + convert(nvarchar(10), #whileCounter),#concat_End) + #textToSearch
--print #sourceSmallText_Concat
insert into #tempConcat values(#text_Concat)
set #whileCounter+=1
end
set #endTime = getDate();
print 'passedTime ' + Convert(nvarchar(20), DATEPART(millisecond, #endTime) - DATEPART(millisecond, #startTime))
end

Algorithm for auto generated series number in sql

I want to make an algorithm for generate next series number by specified last series number in sql like below:
Last Number Next Number
> AAAA095 AAAA096
> AAAA999 AAAB001
> AAAB001 AAAB002
> AAAZ999 AABA001
After some try, Finally i got an algorithm & create it to SQL Scalar-valued function for this question as below
CREATE FUNCTION GetNextSeries ( #lastSeriesNo VARCHAR(8))
RETURNS VARCHAR(8)
AS
BEGIN
DECLARE #nextSeriesNo VARCHAR(8)
DECLARE #CHAR1 CHAR=SUBSTRING(#lastSeriesNo,1,1)
DECLARE #CHAR2 CHAR=SUBSTRING(#lastSeriesNo,2,1)
DECLARE #CHAR3 CHAR=SUBSTRING(#lastSeriesNo,3,1)
DECLARE #CHAR4 CHAR=SUBSTRING(#lastSeriesNo,4,1)
DECLARE #n INT=SUBSTRING(#lastSeriesNo,5,3)
SET #n = #n + 1
IF(#n>999)
BEGIN
SET #n=1
IF(#CHAR4<>'Z')
BEGIN
SET #CHAR4=CHAR(UNICODE(#CHAR4)+1)
END
ELSE IF(#CHAR3<>'Z')
BEGIN
SET #CHAR4='A'
SET #CHAR3=CHAR(UNICODE(#CHAR3)+1)
END
ELSE IF(#CHAR2<>'Z')
BEGIN
SET #CHAR4='A'
SET #CHAR3='A'
SET #CHAR2=CHAR(UNICODE(#CHAR2)+1)
END
ELSE IF(#CHAR1<>'Z')
BEGIN
SET #CHAR4='A'
SET #CHAR3='A'
SET #CHAR2='A'
SET #CHAR1=CHAR(UNICODE(#CHAR1)+1)
END
END
SET #nextSeriesNo=#CHAR1+#CHAR2+#CHAR3+#CHAR4+(CASE LEN(#n) WHEN 1 THEN '00' WHEN 2 THEN '0' ELSE '' END)+convert(VARCHAR(3),#n)
RETURN #nextSeriesNo
END

Create automatic code

I want to create automatic code example:
B001, B002, B003, B004 .....
I have create the function for that:
CREATE FUNCTION AUTO_CODE()
RETURNS CHAR (4)
AS
BEGIN
DECLARE #KODE CHAR(4)
SELECT #KODE = COUNT (KODE_BARANG)FROM BARANG
IF #KODE>0
BEGIN
SELECT #KODE = RIGHT(KODE_BARANG,4) FROM BARANG
SET #KODE = #KODE+1
END
ELSE SET #KODE=1
RETURN 'B' + LEFT('00',3-LEN(#KODE))+(#KODE)
END
The function above only works for B001 through B010, beyond that it was back to B001. It won't work for B011, B012 or B120.
After that I have try to do with if else:
...
DECLARE KODENYA CHAR (5)
IF #KODE >= 0 AND #KODE <=9
BEGIN
SET #KODENYA = 'B' + LEFT('00',4-LEN(#KODE))+(#KODE)
END
ELSE IF #KODE >= 10 AND #KODE <=99
BEGIN
SET #KODENYA = 'B' + LEFT('0',4-LEN(#KODE))+(#KODE)
END
ELSE IF #KODE >= 100 AND #KODE <=999
BEGIN
SET #KODENYA = 'B' + LEFT('',4-LEN(#KODE))+(#KODE)
END
RETURN #KODENYA
The result is still the same and somehow I get #KODE if it was beyond 9 it return to null and SQL SERVER read it as 0.
Is there another way to create this kind of code in SQL SERVER?
There is no need for multiple if/case just simple FORMAT:
CREATE TABLE #tab(KODE INT);
INSERT INTO #tab(KODE) VALUES (1),(2),(10),(99),(101),(100),(999);
SELECT FORMAT(KODE, 'B00#')
FROM #tab;
LiveDemo
You can easily tweak it for longer codes by changing format string 'B000#'
And in your case:
CREATE FUNCTION [dbo].[AUTO_CODE]()
RETURNS CHAR(4)
AS
BEGIN
DECLARE #KODE INT = (SELECT COUNT(KODE_BARANG)FROM BARANG);
RETURN FORMAT(#KODE, 'B00#');
END
Warning:
You function may return duplicates/create gaps when many concurrent calls occur.
SqlFiddleDemo
Depending on your needs you may consider adding calculated column to your BARANG table:
CREATE TABLE BARANG(ID INT IDENTITY(1,1) PRIMARY KEY,
col2 VARCHAR(120) NOT NULL,
...
KODE AS (FORMAT(ID, 'B00#'))
);

T-SQL Procedure not working, issue with varchar to date conversion

When I run this SP, I get:
Msg 241, Level 16, State 1, Procedure PED_SP_PED_Updates, Line 22
Conversion failed when converting date and/or time from character string.
Here is the execution:
exec dbo.ped_sp_ped_updates
#CURRENTHICN='111111111A',
#DATERECEIVED = '20140904',
#FIELDTOBECHANGED='FIRST_NAME_MEMBER',
#CURRENTFIELDVALUE = 'MARY',
#NEWFIELDVALUE = 'MARYTEST',
#REQUESTEDBY = 'IPISORS',
#ID=156
I am not sure why, I'm casting the varchar back to a date for the comparison.
Please note, I have no problem being told a better way to do it, but it would be (I think) more helpful to my learning if I could, at least 'also', get a direct answer as to why my current proc isn't working. In addition to any helpful ideas as to why it should be done different, better, etc, etc. etc.
ALTER PROCEDURE [dbo].[PED_SP_PED_Updates]
#CurrentHicn VARCHAR(500),
#DateReceived VARCHAR(20),
#FieldToBeChanged VARCHAR(500),
#CurrentFieldValue VARCHAR(500),
#NewFieldValue VARCHAR (500),
#RequestedBy VARCHAR(10),
#ID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #CurrentDBNote VARCHAR(MAX)
DECLARE #NewNote VARCHAR(MAX)
DECLARE #CountofHicn INT
SET #NEWNOTE = 'Isaac Pisors | ' + GetDate() + ' | '
+ 'Changing field: ' + #FieldToBeChanged + ' from ' + #CurrentFieldValue + ' to ' + #NewFieldValue
+ ', per ' + #RequestedBy + ' request. Also changing any related DOCS/FAXES records to correspond'
SET #CurrentDBNote=
(SELECT NOTES_GENERAL FROM PED_APPLICATIONS WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (Cast(ISNULL(DATE_RECEIVED,'1900-01-01') as DATE)=CAST(#DateReceived AS DATE)))
--NOW ADD THE TWO:
SET #NewNote = #CurrentDBNote + CHAR(13) + #CurrentDBNote
--SEE IF THERE IS STILL A MATCHING RECORD
SET #CountofHicn=
(SELECT COUNT(*) FROM PED_APPLICATIONS WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE)))
IF #CountofHicn=0 --THERE IS NO LONGER A MATCHING RECORD - INSERT THAT NOTE AND CALL IT A DAY
BEGIN
UPDATE PED_PEDUPDATES SET COMPLETEDON=GetDate(), COMPLETEDBY='SSIS',
EXCEPTIONNOTE='Could not locate any records where HICN is ' + #CurrentHicn + ' and Date Received is ' + CAST(#DateReceived AS VARCHAR)
WHERE [ID]=#ID
END
ELSE --GO AHEAD AND DO THE UPDATE
BEGIN
UPDATE PED_APPLICATIONS SET #FieldToBeChanged = #NewFieldValue
WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
IF #FieldToBeChanged='HICN_MEDICARE_NUMBER' --THEN WE HAVE TO UPDATE DOCS TABLE, TOO
BEGIN
UPDATE PED_DOCS SET HICN_MEDICARE_NUMBER=#NewFieldValue
WHERE
(HICN_MEDICARE_NUMBER=#CurrentFieldValue AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=#DateReceived)) or
(HICN_MEDICARE_NUMBER=#CurrentFieldValue AND DATE_RECEIVED IS NULL)
END
IF #FieldToBeChanged='HICN_MEDICARE_NUMBER' --THEN OUR WHERE CLAUSE-HICN IS THE *NEW* HICN
BEGIN
UPDATE PED_APPLICATIONS SET NOTES_GENERAL=#NewNote
WHERE HICN_MEDICARE_NUMBER=#NewFieldValue AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
ELSE --ELSE OUR WHERE CLAUSE-HICN IS THE *OLD* HICN
BEGIN
UPDATE PED_APPLICATIONS SET NOTES_GENERAL=#NewNote
WHERE HICN_MEDICARE_NUMBER=#CurrentHicn AND (CAST(ISNULL(DATE_RECEIVED,'1900-01-01') AS DATE)=CAST(#DateReceived AS DATE))
END
--FINALLY, UPDATE RECORD AS COMPLETE:
UPDATE PED_PEDUPDATES SET COMPLETEDON=GetDate(),COMPLETEDBY='SSIS' WHERE [ID]=#ID
END
GO
Short Term Fix
Instead of CAST(#DateReceived AS DATE), use CONVERT(date, #DateReceived, 112)
The value 112 is the style code for the yyyymmdd formated varchar you're using. See the cast and convert documentation for more details.
Also, you should verify that all values in the DATE_RECEIVED column of your table are in the correct format. Even one value that is not convertible will cause this error.
Proper Fix
#DateReceived should be passed in to the procedure as a date instead of a varchar.
The DATE_RECEIVED field in your table should be declared as a date instead of a varchar.
In general, avoid treating dates or times as strings in a database when there are native types for that purpose.

SQL Server 2008 math fail

After hunting around on various forums for almost an hour, I've come to the conclusion that SQL server is slightly stupid about simple arithmetic.
I am attempting to utilize a function which, until recently seemed to work just fine. Upon changing out some of the values for a different set of information on the form in use, I get the odd behavior ahead.
The problem is that it is giving me the incorrect result as based on an excel spreadsheet formula.
The formula looks like this:
=IF(D8=0,0,(((D8*C12-C16)*(100-C13)/100+C16)/D8)+(C18*D8))
My SQL looks like this:
(((#DaysBilled * #ContractRate - #ActualPlanDed) * (100 - #InsCover) / 100 + #ActualPlanDed) / #DaysBilled) + (#CoPay * #DaysBilled)
Filling the variables with the given data looks like this:
(((11 * 433 - 15) * (100 - 344) / 100 + 15) / 11) + (15 * 11)
Even stranger, if I use the numbers above (adding .00 to the end of each value) manually in the server environment, it gives me -11405.1200000000
With the values I am giving, it should come out 166.36. Unfortunately, I am getting -886.83
Here is the entire function and how it is called:
ALTER FUNCTION Liability
(
#ClientGUID CHAR(32),
#RecordGUID CHAR(32),
#Type CHAR(3)
)
RETURNS DECIMAL(18,2) AS
BEGIN
DECLARE #ReturnValue decimal(18,2);
DECLARE #DaysBilled int;
DECLARE #ContractRate decimal(18,2);
DECLARE #ActualPlanDed decimal(18,2);
DECLARE #InsCover decimal(18,2);
DECLARE #CoPay decimal(18,2);
IF (#Type = 'RTC')
BEGIN
SELECT #DaysBilled = RTCDaysBilled,
#ContractRate = CAST(REPLACE(REPLACE(ContractRateRTC, ' ',''),'$', '') AS DECIMAL(6,2)),
#ActualPlanDed = RTCActualPlanDed,
#InsCover = InsRTCCover,
#CoPay = RTCCoPay
FROM AccountReconciliation1
WHERE #ClientGUID = tr_42b478f615484162b2391ef0b2c35ddc
AND #RecordGUID = tr_abb4effa0d9c4fe98c78cb4d2e21ba5d
END
IF (#Type = 'PHP')
BEGIN
SELECT #DaysBilled = PHPDaysBilled,
#ContractRate = CAST(REPLACE(REPLACE(ContractRatePHP, ' ',''),'$', '') AS DECIMAL(6,2)),
#ActualPlanDed = PHPActualPlanDed,
#InsCover = InsPHPCover,
#CoPay = PHPCoPay
FROM AccountReconciliation1
WHERE #ClientGUID = tr_42b478f615484162b2391ef0b2c35ddc
AND #RecordGUID = tr_abb4effa0d9c4fe98c78cb4d2e21ba5d
END
IF (#Type = 'IOP')
BEGIN
SELECT #DaysBilled = IOPDaysBilled,
#ContractRate = CAST(REPLACE(REPLACE(ContractRateIOP, ' ',''),'$', '') AS DECIMAL(6,2)),
#ActualPlanDed = IOPActualPlanDed,
#InsCover = InsIOPCover,
#CoPay = IOPCoPay
FROM AccountReconciliation1
WHERE #ClientGUID = tr_42b478f615484162b2391ef0b2c35ddc
AND #RecordGUID = tr_abb4effa0d9c4fe98c78cb4d2e21ba5d
END
IF (#DaysBilled <> 0)
BEGIN
SET #ReturnValue = (((#DaysBilled * #ContractRate - #ActualPlanDed)
*
(100 - #InsCover) / 100 + #ActualPlanDed)
/
#DaysBilled
)
+
(#CoPay * #DaysBilled)
END
ELSE
BEGIN
SET #ReturnValue = 0;
END
RETURN #ReturnValue;
END
It is called by running a select statement from our front-end, but the result is the same as calling the function from within management studio:
SELECT dbo.Liability('ClientID','RecordID','PHP') AS Liability
I have been reading about how a unary minus tends to break SQL's math handling, but I'm not entirely sure how to counteract it.
One last stupid trick with this function: It must remain a function. I cannot convert it into a stored procedure because it must be used with our front-end, which cannot utilize stored procedures.
Does SQL server even care about the parentheses? Or is it just ignoring them?
The calculation is correct, it differes of course if you are using float values
instead of integers.
For (((11 * 433 - 15) * (100 - 344) / 100 + 15) / 11) + (15 * 11)
a value around -886.xx depending in which places integers/floats are used is correct,
What makes you believe it should be 166.36?