sql server datetime to float error - tsql

I trying to calculate work shift from as flollows by comparing the datetime::float value with 1/3 as follows:
select t,
cast( ( cast( cast(t as datetime) as float) - floor(cast( cast(t as datetime) as float ) ) ) * 3.0 as decimal(38,30) ) x,
floor(
( cast( cast(t as datetime) as float) - floor(cast( cast(t as datetime) as float ) ) ) * 3.0
) x1
from ( values ('2019-01-01 00:00:00.0') , ('2019-01-01 07:59:59.0') ,
('2019-01-01 08:00:00.0') , ('2019-01-01 15:59:59.0') ,
('2019-01-01 16:00:00.0') , ('2019-01-01 23:59:59.0') ) test_sample(t)
But the result is confusing:
2019-01-01 00:00:00.0 0E-30 0.0
2019-01-01 07:59:59.0 0.999965277776937010000000000000 0.0
2019-01-01 08:00:00.0 1.000000000007276000000000000000 1.0
2019-01-01 15:59:59.0 1.999965277784212900000000000000 1.0
2019-01-01 16:00:00.0 1.999999999992724100000000000000 1.0 <----- inconsistency, expected 2.0
2019-01-01 23:59:59.0 2.999965277769661100000000000000 2.0
Why is 16:00:00 different from other values ?
What can I do to correctly work around this error (without comparing dates with lot of case whens ) ?

Related

How does this Time Difference Calculation work?

I wanted to display the difference in HH:MM:SS between two datetime fields in SQL Server 2014.
I found a solution in this Stack Overflow post. And it works perfectly. But I want to understand the "why" of how this arrives at the correct answer.
T-SQL:
SELECT y.CustomerID ,
y.createDate ,
y.HarvestDate ,
y.DateDif ,
DATEDIFF ( DAY, 0, y.DateDif ) AS [Days] ,
DATEPART ( HOUR, y.DateDif ) AS [Hours] ,
DATEPART ( MINUTE, y.DateDif ) AS [Minutes]
FROM (
SELECT x.createDate - x.HarvestDate AS [DateDif] ,
x.createDate ,
x.HarvestDate ,
x.CustomerID
FROM (
SELECT CustomerID ,
HarvestDate ,
createDate
FROM dbo.CustomerHarvestReports
WHERE HarvestDate >= DATEADD ( MONTH, -6, GETDATE ())
) AS [x]
) AS [y]
ORDER BY DATEDIFF ( DAY, 0, y.DateDif ) DESC;
Results:
1239090 2017-11-07 08:51:03.870 2017-10-14 11:39:49.540 1900-01-24 21:11:14.330 23 21 11
1239090 2017-11-07 08:51:04.823 2017-10-19 11:17:48.320 1900-01-19 21:33:16.503 18 21 33
1843212 2017-10-27 19:14:02.070 2017-10-21 10:49:57.733 1900-01-07 08:24:04.337 6 8 24
1843212 2017-10-27 19:14:03.057 2017-10-21 10:49:57.733 1900-01-07 08:24:05.323 6 8 24
The first column in Customer ID - the second and third columns are the columns I wanted to calculate the time difference between. The third column is the difference between the two columns - and one of the points in the code in which I do not understand.
If you subtract two datetime fields like this create date - harvestdate, why does it default to the year 1900?
And regarding DATEDIFF ( DAY, 0 , y.DateDiff) - what does the 0 mean? Does the 0 set the date as '01-01-1900'?
It works - for that I am grateful. I was hoping I could get an explanation as to why this behavior works?
I've added some comments that should explain it:
SELECT y.CustomerID ,
y.createDate ,
y.HarvestDate ,
y.DateDif ,
DATEDIFF ( DAY, 0, y.DateDif ) AS [Days] , -- calculates the number of whole days between 0 and the difference
DATEPART ( HOUR, y.DateDif ) AS [Hours] , -- the number of hours between the two dates has already been cleverly
-- calculated in [DateDif], therefore, all that is required is to extract
-- that figure using DATEPART
DATEPART ( MINUTE, y.DateDif ) AS [Minutes] -- same explanation as [Hours]
FROM (
SELECT x.createDate - x.HarvestDate AS [DateDif] , -- calculates the difference expressed as a datetime;
-- 0 is '1900-01-01 00:00:00.000' as a datetime, so the
-- resulting datetime will be that plus the difference
x.createDate ,
x.HarvestDate ,
x.CustomerID
FROM (
SELECT CustomerID ,
HarvestDate ,
createDate
FROM dbo.CustomerHarvestReports
WHERE HarvestDate >= DATEADD ( MONTH, -6, GETDATE ())
) AS [x]
) AS [y]
ORDER BY DATEDIFF ( DAY, 0, y.DateDif ) DESC;

Find date sequence in PostgreSQL

I'm trying to find the maximum sequence of days by customer in my data. I want to understand what is the max sequence of days that specific customer made. If someone enter to my app in the 25/8/16 AND 26/08/16 AND 27/08/16 AND 01/09/16 AND 02/09/16 - The max sequence will be 3 days (25,26,27).
In the end (The output) I want to get two fields: custid | MaxDaySequence
I have the following fields in my data table: custid | orderdate(timestemp)
For exmple:
custid orderdate
1 25/08/2007
1 03/10/2007
1 13/10/2007
1 15/01/2008
1 16/03/2008
1 09/04/2008
2 18/09/2006
2 08/08/2007
2 28/11/2007
2 04/03/2008
3 27/11/2006
3 15/04/2007
3 13/05/2007
3 19/06/2007
3 22/09/2007
3 25/09/2007
3 28/01/2008
I'm using PostgreSQL 2014.
Thanks
Trying:
select custid, max(num_days) as longest
from (
select custid,rn, count (*) as num_days
from (
select custid, date(orderdate),
cast (row_number() over (partition by custid order by date(orderdate)) as varchar(5)) as rn
from table_
) x group by custid, CURRENT_DATE - INTERVAL rn|| ' day'
) y group by custid
Try:
SELECT custid, max( abc ) as max_sequence_of_days
FROM (
SELECT custid, yy, count(*) abc
FROM (
SELECT * ,
SUM( xx ) OVER (partition by custid order by orderdate ) yy
FROM (
select * ,
CASE WHEN
orderdate - lag( orderdate ) over (partition by custid order by orderdate )
<= 1
THEN 0 ELSE 1 END xx
from mytable
) x
) z
GROUP BY custid, yy
) q
GROUP BY custid
Demo: http://sqlfiddle.com/#!15/00422/11
===== EDIT ===========
Got "operator does not exist: interval <= integer"
This means that orderdate column is of type timestamp, not date.
In this case you need to use <= interval '1' day condition instead of <= 1:
Please see this link: https://www.postgresql.org/docs/9.0/static/functions-datetime.html to learn more about date arithmetic in PostgreSQL
Please see this demo:
http://sqlfiddle.com/#!15/7c2200/2
SELECT custid, max( abc ) as max_sequence_of_days
FROM (
SELECT custid, yy, count(*) abc
FROM (
SELECT * ,
SUM( xx ) OVER (partition by custid order by orderdate ) yy
FROM (
select * ,
CASE WHEN
orderdate - lag( orderdate ) over (partition by custid order by orderdate )
<= interval '1' day
THEN 0 ELSE 1 END xx
from mytable
) x
) z
GROUP BY custid, yy
) q
GROUP BY custid

3 months rolling average for 3 columns

Need help
Input
Date A B C
2015-10-31 1.49 3.7 7.8
2015-11-30 1.45 3.6 7.6
2015-12-31 1.41 3.7 8.0
2016-01-31 1.33 3.7 8.3
2016-02-29 1.29 4.1 8.6
2016-03-31 1.46 4.4 9.7
CREATE TABLE dbo.ThreeMonth(RDate DATE,A FLOAT,b FLOAT,C FLOAT)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2015-10-31' , 1.49, 3.7,7.8)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2015-11-30' , 1.45, 3.6,7.6)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2015-12-31' , 1.41, 3.7,8.0)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2016-01-31' , 1.33, 3.7,8.3)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2016-02-29' , 1.9, 4.1,8.6)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2016-03-31' , 1.46, 4.4,9.7)
INSERT into dbo.threemonth
( RDate , a , b,c)
VALUES ( '2016-04-30' , 1.35, 4.3,9.4)
SELECT * FROM threemonth
--Tried the Following query
select rdate, avg(A)
OVER (
ORDER BY Rdate
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
)
FROM threemonth
OutPut
I need TO display rolling 3 months average FOR 3 COLUMNS a,b,c .WHEN I ADD AVG(b) AND AVG(c) it gives ERROR
"Column 'threemonth.RDate' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
--Also I want the OUTPUT IN following format
2015-10-31 NULL -- Because 3 months are not available avg cannot be calculated
2015-11-30 NULL -- Because 3 months are not available avg cannot be calculated
2015-12-31 1.45
2016-01-31 1.39666666666667
2016-02-29 1.54666666666667
2016-03-31 1.56333333333333
2016-04-30 1.57 code here
Can somebody advise TO solve the above problem WHERE I need TO TAKE AVG FOR more THEN 1 COLUMN AND display OUTPUT IN the FORMAT I have shown above FOR ALL the 3 COLUMNS?
Somthing Like this
;with cteBase as (SELECT *,RowNr = Row_Number() Over (Order by RDate) FROM threemonth )
Select RDate,Avg=(select Avg(A) from cteBase where RowNr between A.RowNr-2 and A.RowNr and A.RowNr>2)
From cteBase A
Returns
RDate Avg
2015-10-31 NULL
2015-11-30 NULL
2015-12-31 1.45
2016-01-31 1.39666666666667
2016-02-29 1.54666666666667
2016-03-31 1.56333333333333
2016-04-30 1.57
For Cols A,B, and C
;with cteBase as (SELECT *,RowNr = Row_Number() Over (Order by RDate) FROM threemonth )
Select RDate
,AvgA=(select Avg(A) from cteBase where RowNr between A.RowNr-2 and A.RowNr and A.RowNr>2)
,AvgB=(select Avg(B) from cteBase where RowNr between A.RowNr-2 and A.RowNr and A.RowNr>2)
,AvgC=(select Avg(C) from cteBase where RowNr between A.RowNr-2 and A.RowNr and A.RowNr>2)
From cteBase A

TSQL SELECT data within range of year and month

I'm trying to query data within a range of start year and month and end year and month. But SQL returnes onty the year and the month chosen. Can anyone identify the problem with my approach.
Thanks!
ALTER PROCEDURE xxx
(#JaarBegin AS int
, #JaarEind AS int
, #MaandBegin AS int
, #MaandEind AS int)
AS
BEGIN
WITH
CTE AS
(
SELECT [D_Medewerker_ID]
,[Gebruikersnaam]
,[Naam]
,[Afdelingscode]
,CONVERT(date, [Datum_uit_dienst]) AS DatumIn
,CONVERT(date, [Datum_in_dienst]) AS DatumUit
FROM [DM].[dm].[D_Medewerker] AS M
),
CTE2 AS(
SELECT F.[D_Functie_ID]
,[Generieke_Functie]
,[Specifieke_Functie]
,Fo.[D_Medewerker_ID]
FROM [DM].[dm].[D_Functie] AS F
JOIN dm.dm.F_FormatieBezetting AS Fo
ON F.D_Functie_ID = Fo.D_Functie_ID
)
SELECT DISTINCT CTE.[Gebruikersnaam]
, CTE.Naam
, CTE.Afdelingscode
, CTE.DatumIn
, CTE.DatumUit
, CTE2.Generieke_Functie
, CTE2.Specifieke_Functie
FROM CTE
JOIN CTE2
ON CTE.D_Medewerker_ID = CTE2.D_Medewerker_ID
WHERE DATEPART(year,CTE.DatumUit) BETWEEN #JaarBegin AND #JaarEind
AND DATEPART(MONTH, CTE.DatumUit) >= #MaandBegin AND DATEPART(MONTH, CTE.DatumUit) <= #MaandEind
ORDER BY CTE.DatumUit DESC;
END
You need to convert the int values you get to a date value.
In Sql server 2012 or later, you can use the built-in function DATEFROMPARTS to do this:
WHERE CTE.DatumUit >= DATEFROMPARTS ( #JaarBegin , #MaandBegin , 1 )
AND CTE.DatumUit < DATEADD(MONTH, 1, DATEFROMPARTS ( #JaarEind , #MaandBegin , 1 ))
If you are working with an earlier version of sql server, you need to build a string that represents the date (using iso format yyyy-mm-dd) and then cast it to date:
WHERE CTE.DatumUit >= CAST(RIGHT('0000' + CAST(#JaarBegin as varchar(4)), 4) + '-' + RIGHT('00' + CAST(#MaandBegin as varchar(2)), 2) +'-01' as datetime)
AND CTE.DatumUit < DATEADD(MONTH, 1, CAST(RIGHT('0000' +CAST(#JaarEind as varchar(4)), 4) + '-' + RIGHT('00' + CAST(#MaandBegin as varchar(2)), 2) +'-01' as datetime))

Query Only Specific Days of Month

I'm not sure where to begin on solving this problem. I need to update a record that is only every 3rd Monday of the month. In Postgres can I query every 2nd or 3rd Monday, or to be a little more abstract every nth day of nth week?
I'm looking for an elegant answer with Postgresql. Right now I have something crude like this:
select d from generate_series(date_trunc('week',timestamp '2015-02-01' + interval '13 days'), timestamp '2015-02-01' + interval '1 month -1 day', interval '14 days') d;
I like to use a calendar table for queries like this one.
To select the third Monday of every month in 2015, I can query a calendar table like this.
select cal_date
from calendar
where year_of_date = 2015
and day_of_week = 'Mon'
and day_of_week_ordinal = 3
order by cal_date;
cal_date
--
2015-01-19
2015-02-16
2015-03-16
2015-04-20
2015-05-18
2015-06-15
2015-07-20
2015-08-17
2015-09-21
2015-10-19
2015-11-16
2015-12-21
Code to create a calendar table. (This is how pgAdminIII presents it through its CREATE SCRIPT menu selection.)
CREATE TABLE calendar
(
cal_date date NOT NULL,
year_of_date integer NOT NULL,
month_of_year integer NOT NULL,
day_of_month integer NOT NULL,
day_of_week character(3) NOT NULL,
day_of_week_ordinal integer NOT NULL,
iso_year integer NOT NULL,
iso_week integer NOT NULL,
cal_quarter integer,
CONSTRAINT calendar_pkey PRIMARY KEY (cal_date),
CONSTRAINT cal_quarter_check CHECK (cal_quarter =
CASE
WHEN date_part('month'::text, cal_date) >= 1::double precision AND date_part('month'::text, cal_date) <= 3::double precision THEN 1
WHEN date_part('month'::text, cal_date) >= 4::double precision AND date_part('month'::text, cal_date) <= 6::double precision THEN 2
WHEN date_part('month'::text, cal_date) >= 7::double precision AND date_part('month'::text, cal_date) <= 9::double precision THEN 3
WHEN date_part('month'::text, cal_date) >= 10::double precision AND date_part('month'::text, cal_date) <= 12::double precision THEN 4
ELSE NULL::integer
END),
CONSTRAINT cal_quarter_range CHECK (cal_quarter >= 1 AND cal_quarter <= 4),
CONSTRAINT calendar_check CHECK (year_of_date::double precision = date_part('year'::text, cal_date)),
CONSTRAINT calendar_check1 CHECK (month_of_year::double precision = date_part('month'::text, cal_date)),
CONSTRAINT calendar_check2 CHECK (day_of_month::double precision = date_part('day'::text, cal_date)),
CONSTRAINT calendar_check3 CHECK (day_of_week::text =
CASE
WHEN date_part('dow'::text, cal_date) = 0::double precision THEN 'Sun'::text
WHEN date_part('dow'::text, cal_date) = 1::double precision THEN 'Mon'::text
WHEN date_part('dow'::text, cal_date) = 2::double precision THEN 'Tue'::text
WHEN date_part('dow'::text, cal_date) = 3::double precision THEN 'Wed'::text
WHEN date_part('dow'::text, cal_date) = 4::double precision THEN 'Thu'::text
WHEN date_part('dow'::text, cal_date) = 5::double precision THEN 'Fri'::text
WHEN date_part('dow'::text, cal_date) = 6::double precision THEN 'Sat'::text
ELSE NULL::text
END),
CONSTRAINT calendar_check4 CHECK (day_of_week_ordinal =
CASE
WHEN day_of_month >= 1 AND day_of_month <= 7 THEN 1
WHEN day_of_month >= 8 AND day_of_month <= 14 THEN 2
WHEN day_of_month >= 15 AND day_of_month <= 21 THEN 3
WHEN day_of_month >= 22 AND day_of_month <= 28 THEN 4
ELSE 5
END),
CONSTRAINT calendar_check5 CHECK (iso_year::double precision = date_part('isoyear'::text, cal_date)),
CONSTRAINT calendar_check6 CHECK (iso_week::double precision = date_part('week'::text, cal_date))
)
WITH (
OIDS=FALSE
);
You also need
GRANT and REVOKE statments--very few people should be allowed to change the content of this kind of table, and
suitable CREATE INDEX statements.
Maybe try this:
SELECT *
, EXTRACT(DAY FROM gen)::int as dom -- DayOfMonth
, CEIL(EXTRACT(DAY FROM gen) / 7)::int as mow -- MonthOfWeek
from (
select generate_series(date_trunc('year', now()), date_trunc('year', now() + interval '1 year'), interval '1 day' )::date as gen
) as src
WHERE extract ('dow' from gen) = 1
AND CEIL(EXTRACT(DAY FROM gen) / 7)::int in (2,3)