Datepart for time between (instead of Convert date) - sql-server

Is there a way to use Datepart to select rows which have time between like 12:20 and 15:50 using datepart, because Convert date to time is unusably slow for me?

Just for example you can use this
DECLARE
#min FLOAT = CAST(CAST('19000101 12:20' AS DATETIME) AS FLOAT),
#max FLOAT = CAST(CAST('19000101 15:50' AS DATETIME) AS FLOAT)
SELECT
*
FROM table
WHERE CAST(DateField AS FLOAT) - FLOOR(CAST(DATEFIELD AS FLOAT)) BETWEEN #min AND #max
But this is actually not a solution!!!
The best way is to introduce 1 more calculated column as
NewColumn AS DATEPART(HOUR, DateColumn)*100+DATEPART(minute, DateColumn)
Create index on it and use in where clause
WHERE NewColumn BETWEEN 1220 AND 1550

Assuming time periods on the same day;
...
where cast(fld as time) between '12:20' and '15:50'

The only alternative is t use a CTE. Tested it and it works.
LH = Low hour
HH - High Hour
LM - Low minute
HM = High minute
;WITH CTE_LH AS
(
SELECT *
FROM DateTable
WHERE (DATEPART(HOUR, DateCol) >= 12)
)
, CTE_HH AS
(
SELECT *
FROM CTE_LH
WHERE (DATEPART(HOUR,DateCol) <= 15 )
)
,CTE_LM AS
(
SELECT *
FROM CTE_HH
WHERE (DATEPART(MINUTE,DateCol) >= 20 )
)
,CTE_HM AS
(
SELECT *
FROM CTE_LM
WHERE (DATEPART(MINUTE,DateCol) <= 50 )
)
SELECT * FROM CTE_HM;

Related

Grouping by shifted DATETIME field and PIVOT by ID

I'm having table with schema (simplified):
CREATE TABLE [Test]
(
CaptureTime DATETIME,
SnapShotValue INT,
Id INT
);
With following 30 minute data:
And I want calculate average value for every HH:00 hour data take values HH:30 and HH+1:00 values and PIVOT them. For test data above:
I'm starting here and how to group values HH:00 hour data take values HH:30 and HH+1:00 values and Pivot? Thank you!
If I follow you correctly, you can offset the capture time by 30 minutes, then remove the minutes, and finally do conditional aggregation:
select dateadd(minute, - datepart(minute, v.capturetime), v.capturetime) capture_time,
avg(case when id = 1 then 1.0 * snapshotvalue end) avg1,
avg(case when id = 2 then 1.0 * snapshotvalue end) avg2
from test t
cross apply (values (dateadd(minute, - 30, capturetime))) v(capturetime)
group by dateadd(minute, - datepart(minute, v.capturetime), v.capturetime)
Demo on DB Fiddle

Querying based on date range by using month and year numbers in SQL

DECLARE #startMonth int
DECLARE #endMonth int
DECLARE #startYear int
DECLARE #endYear int
SET #startMonth = 1
SET #endMonth =5
SET #startYear =2014
SET #endYear =2015
SELECT * FROM Table
WHERE (YEAR(Date)>=#startYear AND MONTH(Date) >= #startMonth)
AND (YEAR(Date)<=#endYear AND MONTH(Date) <= #endMonth)
This is apparently returning results of any date between 2014-01-01 to 2014-05-31
and 2014-01-01 to 2015-05-31
but I would like to get any date from 2014-01-01 to 2015-05-31 instead.
How should I change the query? I should write the following?
SELECT * FROM Table
WHERE Date>=DATEFROMPARTS ( #startYear, #startMonth, 1 )
AND Date <= DATEFROMPARTS ( #endYear, #endMonth, 31 ))
Then I end up with the problem that if the #endMonth does not contain 31 days. Then I would have to create another checking to ensure the correct number of end date.
I am sure there must be a better way of writing this. I would appreciate your help.
SELECT *
FROM your_table
WHERE Date >= DATEFROMPARTS(#startYear, #startMonth, 1)
AND Date < DATEFROMPARTS(#endYear, #endMonth + 1, 1)
SELECT *
FROM
<table_name>
WHERE
date BETWEEN DATEFROMPARTS(#startYear,#startMonth,1) AND EOMONTH(DATEFROMPARTS(#endYear,#endMonth,1));

How to get the difference between two times with SQL Server?

I have the following two column with Time values
;WITH cte AS (
SELECT *
FROM (VALUES
('08:00:00','18:30:00'),
('20:00:00','08:00:00'),
('18:30:00','06:00:00'),
('08:00:00','18:30:00')) as t(time1, time2)
)
How can I get the following difference values:
result
10.5
12
11.5
10.5
I tried with DATEDIFF but I am never able to get a true values especially for the case from 18:30 to 6:00
I tried like this, but does not work for this case(8:30 to 6:00) ...
CASE
WHEN GirisSaati > CikisSaati THEN cast( DATEDIFF(MINUTE, cast(CikisSaati as time ), cast(GirisSaati as time ))as float) / 60
WHEN GirisSaati <= CikisSaati THEN cast( DATEDIFF(MINUTE,cast( GirisSaati as time ), cast(CikisSaati as time ) )as float) /60
END
Hope to find help, thanks ...
This is because you are dividing by 60, an integer. This will return an integer result. Instead try dividing by a numeric:
Example
DECLARE #Time1 TIME(0) = '08:00:00';
DECLARE #Time2 TIME(0) = '18:30:00';
SELECT
DATEDIFF(MINUTE, #Time1, #Time2) / 60 AS Incorrect,
DATEDIFF(MINUTE, #Time1, #Time2) / 60.0 AS Correct
;
Result
Incorrect Correct
10 10.500000
This is a consequence of data type precedence. From MSDN:
When an operator combines two expressions of different data types, the
rules for data type precedence specify that the data type with the
lower precedence is converted to the data type with the higher
precedence. If the conversion is not a supported implicit conversion,
an error is returned. When both operand expressions have the same data
type, the result of the operation has that data type.
In this case INT has the highest precedence.
Alternatively you could use CAST to explicitly set the data type: CAST(60 AS DECIMAL(18,2)).
Here is how you can do it:
DECLARE #t table(time1 time, time2 time)
INSERT #t values
('08:00','18:30'),
('20:00','08:00'),
('18:30','06:00'),
('08:00','18:30')
;WITH CTE as
(
SELECT
time1, time2,
CASE WHEN time2 < time1 THEN 1 ELSE 0 END
+ CAST(time2 as datetime) - CAST(time1 as datetime) diff
FROM #t
)
SELECT
time1,
time2,
cast(diff as time) timediff,
cast(diff as float) * 24 decimaldiff
FROM CTE
Result:
time1 time2 timediff decimaldiff
08:00 18:30 10:30 10.5
20:00 08:00 12:00 12
18:30 06:00 11:30 11.5
08:00 18:30 10:30 10.5
I found another option...
Step 1 -> Create this SQL function
CREATE FUNCTION [dbo].[get_TimeDefferance_hh_mm_ss]
(
-- Add the parameters for the function here
#FromTime time,#ToTime time
)
RETURNS time
AS
BEGIN
declare #totalsec decimal(10,2)= datediff(SECOND,#FromTime,#ToTime);
declare #secondPart int= #totalsec%60
--select #secondPart As secondpart
declare #totalminsOnly int= (#totalsec-#secondPart)/60
declare #MinsPart int=#totalminsOnly%60
--select #MinsPart AS minsPart
declare #totalHoursOnly int= (#totalminsOnly-#MinsPart)/60
--select #totalHoursOnly AS totalhoursonly
-- Return the result of the function
RETURN cast(#totalHoursOnly AS varchar(50))+':'+cast(#MinsPart as varchar(50))+':'+cast(#secondPart as varchar(50))
END
Step 2->Go to new query and check is it working..
declare #FromTime time='2:30:41';
declare #ToTime time='4:20:30';
declare #timeDiff time= **dbo.get_TimeDefferance_hh_mm_ss(#FromTime,#ToTime)**
select #timeDiff
output---
01:49:49.0000000
Maybe it is a 'bit' later, but I will post my solution in order to help someone else.
set #from = '10:15';
set #to = '12:30';
select (time_to_sec(timediff(#to, #from))/3600);
Output is 2.2500

How do I use a UDF with a select statement?

I created a scalar user defined function that will accept a date of birth and return the age a person will be in 15 years from now.
CREATE FUNCTION AgeIn15 (#DateOfBirth DATE)
RETURNS INT
AS
BEGIN
RETURN Convert(INT, DateAdd(year, 15, DateDiff(year, #DateOfBirth, GetDate())))
END
Now I want to Use the UDF created to show the age of all persons in my table marketing_list along with their original information. How do I do this? I tried this but I got an error
SELECT *
FROM marketing_list AgeIn15(marketing_list.DateOfBirth)
SELECT *
,AgeIn15(marketing_list.Date_of_Birth)
FROM marketing_list
WHERE AgeIn15(Date_of_Birth) > 45
Add schema name:
SELECT *, dbo.AgeIn15(m.Date_of_Birth) AS col_name
FROM marketing_list AS m;
SELECT *
,dbo.AgeIn15(m.Date_of_Birth) AS col_name
FROM marketing_list m
WHERE dbo.AgeIn15(m.Date_of_Birth) > 45
Optionally you can use CROSS APPLY:
Select *
from marketing_list AS m
CROSS APPLY (SELECT dbo.AgeIn15(m.Date_of_Birth)) AS c(col_name)
Demo
SELECT *, dbo.AgeIn15(t.c) AS col_name
FROM (SELECT '2000-01-01') AS t(c);
SELECT *
FROM (SELECT '2000-01-01') AS t(c)
CROSS APPLY (SELECT dbo.AgeIn15(t.c)) AS c(col_name)
Your function should looks like:
CREATE FUNCTION dbo.AgeIn15 (#DateOfBirth DATE)
RETURNS INTEGER
AS
BEGIN
RETURN DATEDIFF(year,#DateOfBirth,GETDATE()) + 15
END
What you did doesn't make any sense:
Convert(integer,DateAdd(year,15,DateDiff(year,#DateOfBirth,GetDate())))
Calculate DATEDIFF - it returns number years OK
DATEADD(year, 15, number) - it returns date 15 years + of 1900-01-number FAIL
CONVERT(INTEGER, DATE) - it returns number of days starting at 1900-01-01 FAIL
CREATE FUNCTION AgeIn15 (#DateOfBirth DATE)
RETURNS INT
AS
BEGIN
RETURN
-- get the year today + 15 years
(
(DATEPART(YEAR, GETDATE() ) + 15) -
-- minus the year of date of birth
DATEPART(YEAR,#DateOfBirth)
)
-- its like saying (2015 + 15)-09-29 - (1990)-09-19 = 40
END
SELECT *
, dbo.AgeIn15(mlist.Date_of_Birth) AS [AgeAfterFifteenYears]
FROM marketing_list AS mlist
hopefully this helps

Set based solution for processing rows in a SQL table

Can someone steer me in the right direction for solving this issue with a set-based solution versus cursor-based?
Given a table with the following rows:
Date Value
2013-11-01 12
2013-11-12 15
2013-11-21 13
2013-12-01 0
I need a query that will give me a row for each date between 2013-11-1 and 2013-12-1, as follows:
2013-11-01 12
2013-11-02 12
2013-11-03 12
...
2013-11-12 15
2013-11-13 15
2013-11-14 15
...
2013-11-21 13
2013-11-21 13
...
2013-11-30 13
2013-11-31 13
Any advice and/or direction will be appreciated.
The first thing that came to my mind was to fill in the missing dates by looking at the day of the year. You can do this by joining to the spt_values table in the master DB and adding the number to the first day of the year.
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
SELECT
DateAdd(D, v.number, MinDate) Date
FROM (SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
Next I would wrap that to make a derived table, and add a subquery to get the most recent number. Your end result may look something like:
DECLARE #Table AS TABLE(ADate Date, ANumber Int);
INSERT INTO #Table
VALUES
('2013-11-01',12),
('2013-11-12',15),
('2013-11-21',13),
('2013-12-01',0);
-- Uncomment the following line to see how it behaves when the date range spans a year end
--UPDATE #Table SET ADate = DateAdd(d, 45, ADate)
SELECT
AllDates.Date
,(SELECT TOP 1 ANumber FROM #Table t WHERE t.ADate <= AllDates.Date ORDER BY ADate DESC)
FROM (
SELECT
DateAdd(D, v.number, MinDate) Date
FROM
(SELECT number FROM master.dbo.spt_values WHERE name IS NULL) v
INNER JOIN (
SELECT
Min(ADate) MinDate
,DateDiff(D, Min(ADate), Max(ADate)) DaysInSpan
,Year(Min(ADate)) StartYear
FROM #Table
) dates ON v.number BETWEEN 0 AND DaysInSpan - 1
) AllDates
Another solution, not sure how it compares to the two already posted performance wise but it's a bit more concise:
Uses a numbers table:
Linky
Query:
DECLARE #SDATE DATETIME
DECLARE #EDATE DATETIME
DECLARE #DAYS INT
SET #SDATE = '2013-11-01'
SET #EDATE = '2013-11-29'
SET #DAYS = DATEDIFF(DAY,#SDATE, #EDATE)
SELECT Num, DATEADD(DAY,N.Num,#SDATE), SUB.[Value]
FROM Numbers N
LEFT JOIN MyTable M ON DATEADD(DAY,N.Num,#SDATE) = M.[Date]
CROSS APPLY (SELECT TOP 1 [Value]
FROM MyTable M2
WHERE [Date] <= DATEADD(DAY,N.Num,#SDATE)
ORDER BY [Date] DESC) SUB
WHERE N.Num <= #DAYS
--
SQL Fiddle
It's possible, but neither pretty nor very performant at scale:
In addition to your_table, you'll need to create a second table/view dates containing every date you'd ever like to appear in the output of this query. For your example it would need to contain at least 2013-11-01 through 2013-12-01.
SELECT m.date, y.value
FROM your_table y
INNER JOIN (
SELECT md.date, MAX(my.date) AS max_date
FROM dates md
INNER JOIN your_table my ON md.date >= my.date
GROUP BY md.date
) m
ON y.date = m.max_date

Resources