Between clause in sql server - sql-server

I am trying to find submitted data based on the start and end range entered.
Suppose i have submitted the data today, 30th june
When I give the range as start date, 1 june and end date, 30th june I dont get today's submitted data. When i give start date, 1 june and end date, 1st july I get today's submitted data. How to include start and end date too in the where clause?
AS
BEGIN
#Year navchar(200) = null
#Rtype navchar(200) = = null
SELECT *
FROM ProjectDetails
where SubmittedDate Between #Year and #Rtype
END

This is because you're misunderstanding how dates work. This is further evidenced by the fact that you're passing and using your dates as strings
First off, dates internally to sql server are expressed as numbers of days since a certain date (midnight on 1/1/1900)
This means a date of 2 jan 1900 is internally expressed by sqlserver as 1.0. A date of 1800 hours on 2 jan 1900 is expressed internally as 1.75 - because there have been 1.75 days since midnight on the first. 6pm is three quarters the way through a 24 hour day hence the .75 part in the internal representation
With me so far?
it is the decimal point part that is defeating your logic
Let's see an example
Lets say you want records between 1 jan 1900 and 10 jan 1900, so really, you want records that have a time anything up to 23:59:59 (and 999999... milliseconds) on 10 jan 1900
This means you want records that are between 0.0 and 9.999999999 days after midnight on 1 jan....
But when you're running your query, you're just asking for:
BETWEEN #1 jan 1900# and #10 jan 1900#
In SQL terms this is
BETWEEN 0.0 and 9.0
And not what you want:
BETWEEN 0.0 and 9.9999999999999999999999999....
Ultimately, midnight is the very first thing that happens on a day. You won't get any records for 6am on the 10th jan because that is WELL AFTER midnight, it is a decimal number like 9.25
I'm sure you can appreciate that 9.25 is NOT BETWEEN 0.0 and 9.0
You will however get records that occurred exactly bang on midnight on the 10th, because they would internally be represented as 9.0, and 9.0 is between 0.0 and 9.0
So you need to alter the way you are doing your query:
date >= startdate AND date < enddate_plus_one_day
i.e. in internal date representation tersm if you want yo get the dates that are 6am, i.e. the 9.25 days after 1 jan 1900, then you need to be querying date >= 0.0 and date < 10.0 - this will return the records all the way up to and including 23:59:59
I also complained at your querying style - youre passing dates as strings and hoping that sqlserver will figure you out. Don't do this. Be explicit:
SELECT *
FROM ProjectDetails
where SubmittedDate >= CONVERT(datetime, #Year, 112)--pass date as string in yyyymmdd
AND SubmittedDate < (CONVERT(datetime, #Rtype, 112)+1.0) --pass date as a string in yyyymmdd

- --Convert The Date And check The Result Output
DECLARE
#Year NVARCHAR(200) = null,
#Rtype NVARCHAR(200) = null
SELECT *
FROM ProjectDetails
WHERE CONVERT(NVARCHAR, SubmittedDate,111) >= CONVERT(NVARCHAR,#Year,111) AND CONVERT(NVARCHAR,
SubmittedDate,111)<= CONVERT(NVARCHAR,#Rtype,111)

Related

How to convert string to date format when string has only two digits for a year (Oracle, Netezza)

I have this issue databases: on Oracle and Netezza.
ISSUE: I have a string: 16101211213. This string means: YYMMDDHH24MMSS. I need to convert it to date format YYYY-MM-DD HH24:MM:SS. So on the way I need to add the two digits (in front of the string). I know that dates are for XXI century. So I need to 20 at the begining.So in result I should get 2016-10-12 21:12:13
Can anybody help me with it? I have tried many options (mainly on Netezza) but could not figure it out.
Thanks a lot in advance!
RR might be your savior.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> with test as (select '16101211213' datum from dual)
2 select to_date(datum, 'rrmmddhh24miss') result
3 from test;
RESULT
-------------------
12.10.2016 11:21:03
SQL>
Oh, and - be careful! You used wrong format mask for minutes; it is "MI", not "MM" (which is a month).
[EDIT: displaying result using a different format mask]
SQL> with test as (select '16101211213' datum from dual)
2 select
3 to_date(datum, 'rrmmddhh24miss') result_1,
4 to_char(to_date(datum, 'rrmmddhh24miss'), 'mm/dd/yyyy hh:mi:ss am') result_2
5 from test;
RESULT_1 RESULT_2
------------------- ----------------------
12.10.2016 11:21:03 10/12/2016 11:21:03 AM
SQL>
You can either reply on Oracle inferring the year by converting with the YY or RR format model elements, or concatenate the century value and use YYYY.
If you are really sure that the dates are all in the 21st century then using concatenation and YYYY:
to_date('20' || tistamp, 'yyyymmddhh24miss')
will behave the same as using YY (which uses the current date to decide the century):
to_date(tistamp, 'yymmddhh24miss')
and if all the years are below 50 then RR (which uses the current date's century or the last century depending on the supplied 2-digit year) will also get the same result:
to_date(tistamp, 'rrmmddhh24miss')
But if any of the values are 50 or above RR and YY/YYYY behave differently. As these seem to be event timestamps it's unlikely they will be in the future, but the difference may still matter one day. (But then, eventually, assuming 21st century might not be valid either...)
Quick demo of the difference, using your sample value and a couple of others, supplied via a CTE:
alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
with your_table(tistamp) as (
select '16101211213' from dual
union all select '491231235959' from dual
union all select '500101000000' from dual
)
select to_date('20' || tistamp, 'yyyymmddhh24miss') as yyyy,
to_date(tistamp, 'yymmddhh24miss') as yy,
to_date(tistamp, 'rrmmddhh24miss') as rr
from your_table;
YYYY YY RR
------------------- ------------------- -------------------
2016-10-12 11:21:03 2016-10-12 11:21:03 2016-10-12 11:21:03
2049-12-31 23:59:59 2049-12-31 23:59:59 2049-12-31 23:59:59
2050-01-01 00:00:00 2050-01-01 00:00:00 1950-01-01 00:00:00
All of these would also work with to_timestamp() of course; as you don't have fractional seconds or time zone info using dates should be fine as long as your client knows that Oracle dates have a time component.

Return dates from last/this week depending on the current time and when "production" started

Sorry if the Title is confusing but it's hard to explain what I'm after in one phrase.
I'm currently producing a report based on the production for the week. I start off my CTE construction with the following to get the days Monday to Friday of the current week:
WITH
cte_Date AS
(
SELECT
CAST(DateTime AS date) AS Date
FROM
( VALUES
(GETDATE()
)
, (DATEADD(day,-1,GETDATE()))
, (DATEADD(day,-2,GETDATE()))
, (DATEADD(day,-3,GETDATE()))
, (DATEADD(day,-4,GETDATE()))
, (DATEADD(day,-5,GETDATE()))
, (DATEADD(day,-6,GETDATE())) ) AS LastSevenDays(DateTime)
WHERE
DATENAME(weekday, DateTime) = 'Monday'
UNION ALL
SELECT
DATEADD(day,1,Date)
FROM
cte_Date
WHERE
DATENAME(weekday,Date) <> 'Friday'
)
This is working fine. I have made the report available to users so they can run it anytime however sometimes nobody is available to run it last thing Friday. This means they don't get to see the full production for Friday and then the following week the CTE days change.
I'm trying to keep this a one-click affair so rather than introduce date parameters I proposed to the users that we adjust the query such that if they run the report before midday on "Monday" then it will show them last week's figures and they were happy with this (me and my big mouth). I put Monday in quotes because what we really mean of course is the first production day of the week.
My primary data table (which we'll call MyData) has a datetime field named DateTime (really!) that I can reference to determine the first day of production for the week.
One final caveat: Due to the layout of the report the users insisted that they always want to see the five days Monday to Friday, even if there is no production on a given day. (Consequently I do a LEFT JOIN from cte_Date to all other tables required.) So to be clear, right now as I'm typing this it's 11:45am local time on Tuesday and yesterday happened to be a public holiday here so running the report now should return Monday to Friday last week, but running it in 20 minutes time should return Monday to Friday this week.
Please help, my poor brain is getting twisted trying to figure it out.
There are a few different ways you can tackle this, but they all boil down to the same thing: you need a way of figuring out whether it's before or after 12pm on the first working day of the current week, then you need to get the Monday of the current "production week".
Let's just say, for simplicity's sake, you have some sort of table that contains public holidays (or non-production days). To find out whether it's the first day of the current production week, you basically just have to add the number of days in a row since the start of the week that have been public holidays.
Then you need to figure out whether it's before or after 12pm of that day.
If it's before you want last week's Monday-Friday. If it's after, you want this week's Monday-Friday.
Here's one way you might do this:
DECLARE #NonProductionDays TABLE (NPD DATE UNIQUE NOT NULL); -- Public holiday table.
INSERT #NonProductionDays (NPD) VALUES ('2017-09-25');
DECLARE #i INT = -- You don't need a variable for this, but just to keep things simple...
(
SELECT COUNT(*) -- Extract number of public holidays in a row this week before current date.
FROM #NonProductionDays AS N
WHERE DATEDIFF(WEEK, 0, N.NPD) = DATEDIFF(WEEK, 0, GETDATE())
AND N.NPD <= GETDATE()
AND (DATENAME(WEEKDAY, N.NPD) = 'Monday' OR EXISTS (SELECT 1 FROM #NonProductionDays AS N2 WHERE N2.NPD = DATEADD(DAY, -1, N.NPD)))
);
SELECT D = CAST(DATEADD(DAY, T.N, DATEADD(WEEK, DATEDIFF(HOUR, DATEADD(DAY, #i, '1900-01-01 12:00:00'), GETDATE()) / 24 / 7, '1900-01-01')) AS DATE)
FROM (VALUES (0), (1), (2), (3), (4)) AS T(N);
/*
Breaking this down:
X = DATEADD(DAY, #i, '1900-01-01 12:00:00')
-- Adds the number of NPD days this week to '1900-01-01 12:00:00'
-- So, for example, X would be '1900-01-02 12:00:00' this week
Y = DATEDIFF(HOUR, X, GETDATE()) / 24 / 7
-- The number of weeks between X and now, by taking the number of hours and dividing by 24 then by 7
-- The division is necessary to compare the hour.
-- So, for example, as of 11am on the September 26 2017, you'd get 6142.
-- As of 12pm on September 26 2017, you'd get 6143.
Z = DATEADD(WEEK, Y, '1900-01-01')
-- Just adds Y weeks to 1900-01-01, which was a Monday. This tells you the Monday of the current "production week".
-- So, for example, as of 11am on September 26 2017, you'd get '2017-09-18 00:00:00.000'.
-- As of 12pm on September 26 2017, you'd get '2017-09-25 00:00:00.000'.
Then we cast this as a date and add 0/1/2/3/4 days to it to get Monday, Tuesday, Wednesday, Thursday and Friday of the current "production week".
*/
I'm not sure I came up with the most efficient approach, but after a week of tossing it about in my brain this is what I came up with. I approached the problem from the opposite direction of that suggested by #ZLK.
My existing logic was already giving me the Monday of this week so in a subquery I looked for the first production record after Monday, stripped off the time with a DATEDIFF and made it midday with a DATEADD. I was then able to compare the current Date/Time with midday of the first production day to determine whether to reduce the date by one week.
I replaced this SELECT clause:
SELECT
CAST(DateTime AS date) AS Date
with this one:
SELECT -- Monday this week if it's after midday on the first production day otherwise Monday last week
DATEADD(week,IIF(GETDATE()>=DATEADD(hour,12,(
SELECT DATEDIFF(day,0,MIN(DateTime))
FROM MyData
WHERE CAST(MyData.DateTime AS date) >= CAST(LastSevenDays.DateTime AS date)
)),0,-1),CAST(LastSevenDays.DateTime AS date)) AS Date
To cater for the case where a new week has commenced but the operator runs the report before production starts I carefully arranged the boolean condition inside my IIF clause so that the empty result set from the subquery would mean the test returned FALSE and the operator would still see last week's figures.
(#ZLK, Thanks for your input - you did help my thinking a bit but I don't think your answer should be marked as correct. What I've come up with here is what I was originally requesting and didn't require the use of a static table.)

Storing a trial duration in years, months, days, hours in SQL Server

UPDATE: Just to be clear for future viewers, the accepted answer does not propose a different or better approach to those given in this question (it uses a "total number of days" (or hours) column). From the discussion in the comments, after the initial confusion, it resulted that there's likely no way to cover both ease of use and precision, so using a single but inexact column in "days" was the approach we went with, and the accepted answer represents that. Cheers!
Our products have licenses, and those licenses might be trials, having an end time starting from the point when they are first used. The trial period consists of a certain number of years, months, days and, ideally, hours as well.
We wanted to store this period in an SQL Server database, but are struggling. There doesn't seem to be a data type for this, based on this documentation.
DateTimeOffset is not a real "offset" but a date AND its offset from UTC (please correct me if I'm wrong), so it doesn't seem to support concepts like "0 months".
Storing a "total number of seconds" or "total number of hours" is susceptible to time changes around the globe. It is also hard to look at a value (as a human) and have a notion of how long the trial is.
Other alternatives, such as storing a string "1y2m15d5h" (1 year, 2 months, 15 days and 5 hours. Extreme example) would render queries impossible, such as searching for all trials with duration longer than 1 year (there might even be some specified as 14m, which is also more than a year).
Possible solutions are:
Adding 4 columns just for this (years, months, days, hours). It would make queries much more complex than we wanted.
Forgetting support for durations in hours (probably a very uncommon case) and using "total number of days" instead. Unfortunately, this can't accurately represent a year, because of leap years (365 days might not be a full year).
Do you have other possible solutions for this? A readable, easily queryable format for a time/date offset? We couldn't find a concrete answer after searching the web.
As stated by myself and others in the comments, simplify the duration values so that you store it either in hours or days, ignore the longer months and leap years for the sake of simplicity.
This solution stores duration in days, to keep it simple for your queries for the UI and has a computed value for hours to be used in the calculation of the end date, although you may not need both columns, I'm just presenting them as an option:
CREATE TABLE #trials
(
Customer NVARCHAR(20),
StartTime DATETIME,
TrialDays INT,
TrialHours AS(TrialDays*24),
EndTime DATETIME
)
INSERT INTO #trials
(
Customer,
StartTime,
TrialDays,
EndTime
)
VALUES
( N'Bob', GETDATE(), 1, NULL), -- 1 day
( N'Dave', GETDATE(), 7, NULL), -- 7 days
( N'Jon', GETDATE(), 30, NULL), -- 30 days
( N'Tom', GETDATE(), 90, NULL), -- 90 days
( N'Jim', GETDATE(), 365, NULL) -- 365 days (approximation of a year)
SELECT * FROM #trials AS t
UPDATE #trials
SET EndTime = DATEADD(HOUR, TrialHours, StartTime)
SELECT * FROM #trials AS t
DROP TABLE #trials
Produces:
Customer StartTime TrialDays TrialHours EndTime
Bob 2017-09-11 10:50:30.940 1 24 2017-09-12 10:50:30.940
Dave 2017-09-11 10:50:30.940 7 168 2017-09-18 10:50:30.940
Jon 2017-09-11 10:50:30.940 30 720 2017-10-11 10:50:30.940
Tom 2017-09-11 10:50:30.940 90 2160 2017-12-10 10:50:30.940
Jim 2017-09-11 10:50:30.940 365 8760 2018-09-11 10:50:30.940
I am saving year, month , day and hrs in xml columns and calculating expire date by retrieving data from this xml column-
create table temp (
ID int , Duration xml , StartDate datetime
)
go
insert into temp
select 1 ,'<Time><Yr>1</Yr><Mo>2</Mo><Dy>3</Dy><Hr>4</Hr></Time>' , getdate()
union all select 2 ,'<Time><Yr>2</Yr><Mo>2</Mo><Dy>3</Dy><Hr>3</Hr></Time>', getdate()
union all select 3 , '<Time><Yr>1</Yr><Mo>7</Mo><Dy>6</Dy><Hr>9</Hr></Time>', getdate()
union all select 3 , '<Time><Yr>0</Yr><Mo>11</Mo><Dy>22</Dy><Hr>15</Hr></Time>' , getdate()
SELECT ID , StartDate ,
t1.Duration.value('(/Time/Yr)[1]','int')YR ,
t1.Duration.value('(/Time/Mo)[1]','int')MO,
t1.Duration.value('(/Time/Dy)[1]','int')DY ,
t1.Duration.value('(/Time/Hr)[1]','int')HR ,
dateadd( HOUR , t1.Duration.value('(/Time/Hr)[1]','int'), dateadd( day ,t1.Duration.value('(/Time/Dy)[1]','int') ,dateadd(MONTH ,t1.Duration.value('(/Time/Mo)[1]','int') , dateadd(year, t1.Duration.value('(/Time/Yr)[1]','int') , StartDate) ))) as EndTime
FROM temp t1

Number of weeks and partial weeks between two days calculated wrong

Why is this returning 4 instead of 6?
SET DATEFIRST 1
SELECT DATEDIFF(WEEK, CAST('2017-01-01' AS DATE), CAST('2017-01-31' AS DATE))
Is week as datepart calculating only weeks from monday - sunday (whole weeks)? How to get all weeks - including those which doesn't have seven days ? In the case above answer should be 6.
DATEDIFF counts transitions, not periods (e.g. look at DATEDIFF(year,'20161231','20170101')). It also treats Sunday as the first day of the week. So, how do we compensate for these features? First, we shift our dates so that Mondays are the new Sundays, and second we add 1 to compensate for the Fence-Post error:
declare #Samples table (
StartAt date not null,
EndAt date not null,
SampleName varchar(93) not null
)
insert into #Samples (StartAt,EndAt,SampleName) values
('20170101','20170131','Question - 6'),
('20170102','20170129','Exactly 4'),
('20170102','20170125','3 and a bit, round to 4'),
('20170101','20170129','4 and 1 day, round to 5')
--DATEDIFF counts *transitions*, and always considers Sunday the first day of the week
--We subtract a day from each date so that we're effectively treating Monday as the first day of the week
--We also add one because DATEDIFF counts transitions but we want periods (FencePost/FencePanel)
select *,
DATEDIFF(WEEK, DATEADD(day,-1,StartAt), DATEADD(day,-1,EndAt)) +1
as NumWeeks
from #Samples
Results:
StartAt EndAt SampleName NumWeeks
---------- ---------- -------------------------- -----------
2017-01-01 2017-01-31 Question - 6 6
2017-01-02 2017-01-29 Exactly 4 4
2017-01-02 2017-01-25 3 and a bit, round to 4 4
2017-01-01 2017-01-29 4 and 1 day, round to 5 5
If this doesn't match what you want, perhaps you can adopt and adapt my #Samples table to show the results you do expect.
What you ask though, is how many weeks are covered by a range, not how many weeks are between two dates.
DATEDIFF always uses Sunday when calculating week transitions.
This isn't a bug, it's done to ensure the function is deterministic and returns the same value, for every query, no matter the DATEFIRST setting. From the documentation
Specifying SET DATEFIRST has no effect on DATEDIFF. DATEDIFF always uses Sunday as the first day of the week to ensure the function is deterministic.
One solution would be to calculate the difference between the week numbers of the start and end dates, when the first day is Monday. 1 is added to the difference to take account of the first week as well:
SET DATEFIRST 1;
select 1 +datepart(WEEK,'20170131') - datepart(WEEK,'20170101')
That's a fragile calculation though that breaks if DATEFIRST changes or if one of the dates is on a different year.
You could use ISO Weeks to get rid of SET DATEFIRST:
select 1 +datepart(ISO_WEEK,'20170131') - datepart(ISO_WEEK,'20170101')
but that would fail for 2017-01-01 because Sunday is counted as Week 52 of the previous year.
A far better solution though would be to count the distinct week numbers using a Calendar table that contains dates and different week numbers to cover multiple business requirements, eg both normal and ISO Week numbers, or business calendars based on a 4-4-5 calendar.
In this case, you could just count distinct week numbers:
SELECT COUNT(DISTINCT Calendar.IsoWeek )
from Calendar
where date between '20170101' and '20170131'
If the table doesn't have an ISO Week column, you can use DATEPART:
select count (distinct datepart(ISO_WEEK,date) )
from Calendar
where date between '20170101' and '20170131'

Get records from a specific hour every day in sql server

I have a requirement to pull records from SQL Server from a specific hour every day, say at 4 PM.
So if I am running query at 3 PM today, it should pull records from previous day 4 PM till today 3 PM (23 hours).
On the other hand, if run query at 4:30 PM today, it should pull query from today 4 PM to 4:30 PM (only for half an hour) and so on
How could I do this in plain SQL in SQL Server?
Here is how I would do it:
SELECT *
FROM TableName
WHERE ColumnName <= GETDATE()
AND ColumnName >=
CASE WHEN DATEPART(HOUR, GETDATE()) < 4 THEN
DATEADD(HH, 4, CAST(CAST(GETDATE()-1 AS date) AS datetime))
ELSE
DATEADD(HH, 4, CAST(CAST(GETDATE() AS date) AS datetime))
END
Points of interest:
CAST(GETDATE() AS date) will return the date part of the current datetime.
Casting back to datetime allows you to use DateAdd to get the hour you want
using -1 with date values subtracts a day from the date (+1 will add a day, of course)
using the CASE...WHEN allows you to write a single where clause that fits both scenarios described in the question.
Please try the below query, i have created it for stored procedures that had been modified on specific date:
Select * from sys.procedures where CONVERT(date,modify_date,101) =
CONVERT(date,DATEADD(DD,-5, GETDATE()),101)
AND DATEPART(hh,modify_date) >= 1 AND DATEPART(hh,modify_date) <= 22
You can pass "CONVERT(date,DATEADD(DD,-5, GETDATE()),101)" GEDATE only instead to - minus five days, that was done only to fetch records for me.
and in hours you can specify the range as you want.
Hope this will work for you.
I think this might help you
select 1 from table where Col1 between case when hour(getdate())>4 then convert(datetime,convert(varchar(20),year(getdate()))+'-'+convert(varchar(20),month(getdate()))+'-'+convert(varchar(20),day(getdate()))+' 04:00') else dateadd(hh,-23,getdate()) end and getdate()

Resources