SQL Server : datalength conversion - sql-server

I have a table dbo.files with 9 columns that include file_size and created_time and filepath.
Sample values:
file_size = 528300
created_time = 2012-06-28 09:31:17.610
I have the following query where I'm trying to show the total # of MB have been written to the filesystem 'today' by these files.
select
sum(datalength(f.file_size)) / 1048576.0 AS 'Storage Used Today"
from
dbo.files AS f
where
f.created_time >= CAST(getdate() as DATE)
and f.created_time < CAST(DATEADD(day, 1, getdate()) as DATE)
The result is '0.173525810'. Is there a way to move that decimal over to show the proper value?
Thanks

SUM(DATALENGTH(x)) tells you the size in bytes of the numeric representation.
Which isn't what you need.
For example if the datatype was integer (4 bytes) and you had three rows with none null values in the column it would evaluate to 12 irrespective of the actual numeric contents.
Just remove the function call.
sum(f.file_size) / (1024.0 * 1024)
Will work fine

Related

Convert ISO8061 Duration to a datetime or time value

I have some ISO8601 Durations (not to be confused with ISO601 datetime)
Here are some example valid values:
P1D
PT0H
PT11M
P1DT2H15M
PT10H11M
PT2H46M12S
the specification is here:
https://en.wikipedia.org/wiki/ISO_8601#Durations
Ideally, I would like to take these values and parse them as either time or datetime2 values
to make it easier to work with
I was able to brute force parse these using string functions but the code is complex and seems error prone hoping there was a better way?
with anchors as
(
SELECT
dt[duration]
, NULLIF(CHARINDEX('D', dt.duration), 0) As DLocation
, NULLIF(CHARINDEX('T', dt.duration), 0) As TLocation
, NULLIF(CHARINDEX('H', dt.duration), 0) As HLocation
, NULLIF(CHARINDEX('M', dt.duration), 0) As MLocation
, NULLIF(CHARINDEX('S', dt.duration), 0) As SLocation
, LEN(dt.duration) as TotalLength
FROM dbo.DurationTest dt
)
SELECT
duration
,DaysValue = CAST(ISNULL(SUBSTRING(duration, 2, (DLocation - 2)), 0) as tinyint)
,HoursValue = CAST(ISNULL(SUBSTRING(duration, TLocation + 1, (HLocation - TLocation) - 1 ), 0) as tinyint)
,MinutesValue = CAST(ISNULL(SUBSTRING(duration, COALESCE(HLocation, TLocation) + 1, MLocation - COALESCE(HLocation, TLocation) - 1), 0) as tinyint)
,SecondsValue = CAST(ISNULL(SUBSTRING(duration, COALESCE(MLocation, TLocation) + 1, SLocation - COALESCE(MLocation, TLocation) - 1 ), 0) as tinyint)
FROM anchors
this code gets the values into days, hours, minutes and seconds. converting to either a int value to seconds or a datetime2 is pretty well documented from that. As pointed out in the comments a time data type will only work for values <24 so I've kind of given up on that.
for further context this data is coming from ADP payroll webservice and this field tracks the daily time entry you would think it would be less than 24hrs but I have some outliers in my dataset.
I've created the following enhancement request here (not sure if it will get traction or not):
https://feedback.azure.com/d365community/idea/557e5b51-1824-ed11-a81b-6045bd853198
Here's one working example. Read the header for what it does but this one returns the ADP "ISO-Like" durations as Decimal Hours to 6 Decimal Places. If you need something else instead, the changes will be quite simple to make. Let me know.
Here's the iTVF (inline Table Valued Function). Details are in the comments. As so often happens, the comments are bigger than the code itself.
CREATE FUNCTION dbo.AdpDurToDecHours
/*********************************************************************
Purpose:
Convert ADP payroll webservice daily time entries (durations) from a
subset of the ISO Duration notation to Decimal Hours.
*** DO NOT USE THIS FUNCTION IF YEAR AND/OR MONTH CAN BE PRESENT! ***
----------------------------------------------------------------------
Usage Examples:
--===== Convert a set of ADP time entries from a table.
SELECT st.SomeOtherColumns
,adp.DecimalHours
FROM dbo.SomeTable st
CROSS APPLY dbo.AdpDurToDecHours(st.AdpDur) adp
;
--===== Convert a single value from a variable.
SELECT adp.DecimalHours FROM dbo.AdpDurToDecHours(#AdpDur) adp
;
----------------------------------------------------------------------
Dependencies:
1. None
----------------------------------------------------------------------
Programmer Notes:
1. DO NOT USE THIS FUNCTION IF YEAR AND/OR MONTH CAN BE PRESENT!!!
2. This code will fail with the following error if you pass it a
NULL, Empty String, or BLANK String. Other "non-ISO" compliant
values may also fail or create improper output but such other
things have not been tested. Ostensibly, the values passed should
be compliant for proper returns.
Msg 1014, Level 15, State 1, Line 30
A TOP or FETCH clause contains an invalid value.
3. This code follows the ISO specification for durations except for
"Year" and "Month", which have been intentionally excluded because
they're "indeterminate" for durations unless combined with a
starting date.
----------------------------------------------------------------------
References:
1. https://en.wikipedia.org/wiki/ISO_8601#Durations
2. https://stackoverflow.com/questions/73479091/convert-iso8061-duration-to-a-datetime-or-time-value
----------------------------------------------------------------------
Revision History:
Rev 00 - 25 Aug 2022 - Jeff Moden
- Create and test PoP code.
Rev 01 - 26 Aug 2022 - Jeff Moden
- Exclude "Year" and "Month".
- Convert output to Decimal Hours.
Rev 02 - 27 Aug 2022 - Jeff Moden
- Convert to documented function (iTVF).
*********************************************************************/
(#AdpDur VARCHAR(36))
RETURNS TABLE WITH SCHEMABINDING
RETURN WITH
--==== Creates an inline sequence generator
S1(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1))S0(N))
,cteTally(N) AS (SELECT TOP(LEN(#AdpDur))
N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM S1 a,S1) --1 to 36 rows max
,cteLocateDurTypes AS
(--==== Find positions/lagging positions of the duration type tokens
SELECT DurType = SUBSTRING(#AdpDur,t.N,1)
,LN = LAG(t.N,1,0) OVER (ORDER BY t.N)
,t.N
FROM cteTally t
WHERE SUBSTRING(#AdpDur,N,1) IN ('P','W','D','T','H','M','S')
)
,cteIsolateValue AS
(--==== Determine the value for each duration token present
SELECT DurType
,Value = CONVERT(INT,SUBSTRING(#AdpDur,LN+1,N-1-LN))
FROM cteLocateDurTypes
)--==== Convert DurType Values to seconds and sum as Decimal Hours
SELECT DecimalHours = SUM(
CASE DurType
WHEN 'S' THEN Value
WHEN 'M' THEN Value*60
WHEN 'H' THEN Value*3600
WHEN 'D' THEN Value*86400
WHEN 'W' THEN Value*604800
ELSE 0
END
)/3600.0
FROM cteIsolateValue
;
GO
Here's a short test table based on what #JasonHorner posted with a few additions.
--===== Drop the test table if it exists to make reruns easier in SSMS
DROP TABLE IF EXISTS #DurationTest;
GO
--===== Create and populate the test table on-the-fly.
SELECT Duration = CONVERT(VARCHAR(36),v.Duration)
INTO #DurationTest
FROM (VALUES
--(NULL) --Causes Failure
--,('') --Causes Failure
--,(' ') --Causes Failure
('PT') --It could happen
,('P1W') --Added this entry
,('P1D')
,('PT1H') --Added this entry
,('PT1M') --Added this entry
,('PT1S') --Added this entry
,('PT0H')
,('PT10H') --Added this entry
,('PT11M')
,('P1DT2H15M')
,('PT10H11M')
,('PT2H46M12S')
,('P1W2DT12H46M12S') --Added this entry
)v(Duration)
;
GO
Here's a test of the function using the test table as the source.
--===== Simple test
SELECT *
FROM #DurationTest dt
CROSS APPLY dbo.AdpDurToDecHours(dt.Duration) adp
;
GO
And, finally, here are the results from the test.

Microsoft SQL Server 2016 - Convert a varchar to money with a $ sign & 2 decimals

A table of financials has been provided to me with the following datatypes:
billed varchar 9
allowed varchar 9
paid varchar 7
with the following columns and values:
billed = 2555 allowed = 1051 paid = 951
I want to convert the varchar values (the whole column) to money or to some format where I'll have a $ sign and the number will have 2 decimal points instead of rounding up. I need the SUM to remain because I'm adding up values throughout the columns based on the date.
My Expected Results are:
BILLED
$2,554.67
ALLOWED
$1,050.75
PAID
$950.75
I have code that I've used, but I can't seem to format it correctly to be viewable in the post.`
Cast the values as numeric, do math with a function like sum, format as money and concatenate the $ symbol at the beginning.
Simplified example doing conversion:
select ('$' + FORMAT(CONVERT(MONEY, cast([allowed] as numeric(38,2))), '###,###.####')) as AllowedConversionExample
from dbo.payments
Simplified example with math using sum()
select
'$' + FORMAT(CONVERT(MONEY, sum(cast(V.Val as numeric(38,2)))), '###,###.####')
from (
select cast('1050.75' as varchar(9)) Val
union select cast('950.75' as varchar(9))
) V

Convert from 4 digit Military time to Standard time

I am using SQL Server 2016 and I'm trying to convert military time to standard time. The military time is a 4 digit integer and I'm trying to get the standard time format (00:00 am/pm).
I ran a simple CASE statement that made it standard but without ':' and 'am/pm'.
CASE
WHEN SA_BEGTIME between 0 and 1259 then SA_BEGTIME
WHEN SA_BEGTIME between 1300 and 2400 then SA_BEGTIME - 1200
ELSE ''
END as Time
Results
How do I convert it so that it is in the right format: '00:00 am/pm'
Thank you!
You can split it into parts with integer division and modulo, cast it to a VARCHAR and then you can convert it to a TIME:
declare #x int = 109
select cast(cast(#x / 100 as varchar(2)) + ':' + cast(#x % 100 as varchar(2)) as time)
Or you can use the new TIMEFROMPARTS() function (SQL Server 2012+):
declare #x int = 109
select TIMEFROMPARTS(#x / 100,#x % 100, 0, 0, 0)
You can then format it however you'd like.
Assuming your data is stored as an integer, and also assuming there is not invalid time stored (i.e. values above 2400 or below 0) you can use the following:
declare #TheTime int = 900
select right(convert(varchar(20),
cast(stuff(right('0000' + convert(varchar(4),#TheTime),4),3,0,':')
as datetime),100),7)
-------
9:00AM
Sorry for the density of the solution. This is what I did:
Convert #TheTime to varchar(4)
Add a string of zeros at the front
Take the rightmost 4 characters from this new string
Stuff a colon sign in the middle of this new string
Cast the string as datetime
Convert back to string using 100 as the style indicator to get AM/PM
Get the right most 7 characters of the string.
I am sure there are more elegant ways, but this one works for me quite well.
I'm using the solution that #BaconBits provided but I had to make a tweak because 2400 was a valid representation but it failed with that code. Here's the tweaked solution:
declare #x int = 2400
select TIMEFROMPARTS((#x / 100) % 24,#x % 100, 0, 0, 0)
I needed to convert a datetime field where the time was military to just the time in AM/PM format. This worked beautifully.
Left(convert(varchar(20), cast(MyDateField as time),100),7)
You can use convert to get n appropriate presentation of time.
declare #mt varchar(4) = '1500'
select convert(varchar, cast(left(#mt, 2) + ':' + right(#mt, 2) as time), 0)

How to get the count of digits after the decimal point in a float column in ms sql?

I have to count the digits after the decimal point in a database hosted by a MS Sql Server (2005 or 2008 does not matter), in order to correct some errors made by users.
I have the same problem on an Oracle database, but there things are less complicated.
Bottom line is on Oracle the select is:
select length( substr(to_char(MY_FIELD), instr(to_char(MY_FILED),'.',1,1)+1, length(to_char(MY_FILED)))) as digits_length
from MY_TABLE
where the filed My_filed is float(38).
On Ms Sql server I try to use:
select LEN(SUBSTRING(CAST(MY_FIELD AS VARCHAR), CHARINDEX('.',CAST(MY_FILED AS VARCHAR),1)+1, LEN(CAST(MY_FIELD AS VARCHAR)))) as digits_length
from MY_TABLE
The problem is that on MS Sql Server, when i cast MY_FIELD as varchar the float number is truncated by only 2 decimals and the count of the digits is wrong.
Can someone give me any hints?
Best regards.
SELECT
LEN(CAST(REVERSE(SUBSTRING(STR(MY_FIELD, 13, 11), CHARINDEX('.', STR(MY_FIELD, 13, 11)) + 1, 20)) AS decimal))
from TABLE
I have received from my friend a very simple solution which is just great. So I will post the workaround in order to help others in the same position as me.
First, make function:
create FUNCTION dbo.countDigits(#A float) RETURNS tinyint AS
BEGIN
declare #R tinyint
IF #A IS NULL
RETURN NULL
set #R = 0
while #A - str(#A, 18 + #R, #r) <> 0
begin
SET #R = #R + 1
end
RETURN #R
END
GO
Second:
select MY_FIELD,
dbo.countDigits(MY_FIELD)
from MY_TABLE
Using the function will get you the exact number of digits after the decimal point.
The first thing is to switch to using CONVERT rather than CAST. The difference is, with CONVERT, you can specify a format code. CAST uses whatever the default format code is:
When expression is float or real, style can be one of the values shown in the following table. Other values are processed as 0.
None of the formats are particularly appealing, but I think the best for you to use would be 2. So it would be:
CONVERT(varchar(25),MY_FIELD,2)
This will, unfortunately, give you the value in scientific notation and always with 16 digits e.g. 1.234567890123456e+000. To get the number of "real" digits, you need to split this number apart, work out the number of digits in the decimal portion, and offset it by the number provided in the exponent.
And, of course, insert usual caveats/warnings about trying to talk about digits when dealing with a number which has a defined binary representation. The number of "digits" of a particular float may vary depending on how it was calculated.
I'm not sure about speed. etc or the elegance of this code. it was for some ad-hoc testing to find the first decimal value . but this code could be changed to loop through all the decimals and find the last time a value was greater than zero easily.
DECLARE #NoOfDecimals int = 0
Declare #ROUNDINGPRECISION numeric(32,16) = -.00001000
select #ROUNDINGPRECISION = ABS(#ROUNDINGPRECISION)
select #ROUNDINGPRECISION = #ROUNDINGPRECISION - floor(#ROUNDINGPRECISION)
while #ROUNDINGPRECISION < 1
Begin
select #NoOfDecimals = #NoOfDecimals +1
select #ROUNDINGPRECISION = #ROUNDINGPRECISION * 10
end;
select #NoOfDecimals

Conversion failed when converting the nvarchar to int

I have a field which is varchar and contains numbers and dates as strings. I want to update all numbers in this field that is greater than 720. I have attempted firstly to do a select but I get this error:
Conversion failed when converting the nvarchar value '16:00' to data type int.
This is my query:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER where sourcecode like 'ANE%' --and other08 > 720
It fails when I uncomment the last part.
I am trying to get all numerics greater than 720, but I can't do the comaprison. It also fails when casting and converting.
Thanks all for any help
You also need to perform the checks and conversion in the WHERE clause:
SELECT
id,
CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END
FROM CER
WHERE sourcecode LIKE 'ANE%'
AND CASE WHEN isnumeric([other08]) = 1 THEN CAST([other08] AS INT) ELSE 0 END > 720
You need to use IsNumeric in your where clause, to avoid trying to compare strings to the number 720. Eg:
select id, case(isnumeric([other08])) when 1 then [other08] else 0 end
from CER
where sourcecode like 'ANE%' and ISNUMERIC(other08) = 1 and other08 > 720
EDIT
As #Abs pointed out, the above approach won't work. We can use a CTE to compute a reliable field to filter on, however:
WITH Data AS (
select id
, case WHEN isnumeric([other08]) THEN CAST([other08] AS int) else 0 end AS FilteredOther08
, CER.*
from CER
where sourcecode like 'ANE%'
)
SELECT *
FROM Data
WHERE [FilteredOther08] > 720

Resources