SQL Server Indexes of DateTime Parts such as YEAR, MONTH - sql-server

Is it possible to have indexes on DateTime parts such as DATEPART(YEAR, bp.[CreatedOn]) and DATEPART(MONTH, bp.[CreatedOn])? For example, I have the following T-SQL query:
DECLARE #year AS INT = 2012;
DECLARE #month AS INT = 8;
SELECT bp.Title, bp.CreatedOn FROM BlogPosts bp
WHERE (DATEPART(YEAR, bp.[CreatedOn]) = #year) AND (DATEPART(MONTH, bp.[CreatedOn]) = #month)
ORDER BY bp.CreatedOn;
And this is the execution plan I have: https://gist.github.com/3551450
Currently, there are not many records so it is not a big problem in terms of perf but the records will grow over the time.

You'd better construct criterion using datetime field:
declare #startMonth datetime = '20120801'
SELECT bp.Title, bp.CreatedOn
FROM BlogPosts bp
WHERE bp.[CreatedOn] >= #startMonth
AND bp.[CreatedOn] < dateadd (month, 1, #startMonth)
ORDER BY bp.CreatedOn;
This way query executor will be able to use index on CreatedOn.

Yes is it possible to put index on year and month. Here is an example:
create table testt(d datetime)
alter table testt add year as year(d) PERSISTED -- after marc_s advise. Thx
alter table testt add month as month(d) PERSISTED --
create index idx_year on testt(year)
create index idx_month on testt(month)
However I would always use a variation of Nikola's solution, so +1 to Nikola.
This is how you can rewrite month and year into a date:
DECLARE #year AS INT = 2012;
DECLARE #month AS INT = 8;
DECLARE #from DATE = dateadd(month, (#year-1900)*12 + #month - 1, 0)

I suggest you store date parts on different fields. Clearly this is a requirement which exist in your domain as such needs to be part of your model.
What you lose is that you have to construct the datetime for every model you load or you set the value but that would be a few CPU cycles.

Related

Find End Date of month (Exclude Sunday and Saturday)

I have wrote a script (from the internet) but it's not working because of some logical error I guess.
ALTER FUNCTION EstDateCheck (#CoustDesireddate varchar(10))
RETURNS varchar(20)
AS
BEGIN
declare #testDate Date = (SELECT STUFF(#CoustDesireddate, 4, 0, '01/'));
declare #lastWorkDay date = iif(DATEPART(WEEKDAY, EOMONTH(#testDate)) <= 5
,EOMONTH(#testDate)
,DATEADD(DAY, -(7-DATEPART(WEEKDAY, EOMONTH(#testDate))),EOMONTH(#testDate)))
RETURN #lastWorkDay
END
This is how I execute
select dbo.EstDateCheck('07/2022')
I would, honestly, consider creating a calendar table. There are plenty of examples out there, so I'm not going to cover how to create one; plus what columns you need vary.
What you will have, however, is a column to denote if the day is a weekday (and probably a working day), as well as what month and year it belongs to. As a result you can then simply get the last weekday with the following query:
SELECT MAX(CalendarDate)
FROM dbo.CalendarTable
WHERE CalendarYear = 2022
AND CalendarMonth = 7
AND Weekday = 1;
If you therefore wanted to do this as a function, you could use an inline table value function:
CREATE FUNCTION dbo.MonthLastWeekday (#Year int, #Month int)
RETURNS table
AS RETURN
SELECT MAX(CalendarDate) AS LastWeekday
FROM dbo.CalendarTable
WHERE CalendarYear = #Year
AND CalendarMonth = #Month
AND Weekday = 1;
And then you would call the function as follows:
SELECT LastWeekday
FROM dbo.MonthLastWeekday(#Year,#Month);

How to correctly include the right columns in a SQL index

I am struggling to get the correct index for a small query I want to run. I was wondering if anyone had any tips for this kind of query?
My table columns are:
Id int (not used in query) PK
DateField datetime
IntField1 int
IntField2 int
IntField3 int
IntField4 int
IntField5 int (Not used in query)
IntField6 int (Not used in query)
My query looks like this
DECLARE #datefield datetime = '2019-01-01';
DECLARE #intfield1 int 1;
DECLARE #intfield2 int 2;
SELECT
SUM([sum])
FROM table1
WHERE
DATEPART(YEAR, [DateField]) = DATEPART(YEAR, #datefield)
AND DATEPART(MONTH, [DateField]) = DATEPART(MONTH, #datefield)
AND [IntField1] = #intfield1
AND [IntField2] = #intfield2
AND [IntField3] = [IntField4]
The best index I have found on the execution plan is one that looks like this
CREATE INDEX idx_test_1 ON table1([DateField], [IntField1])
I have also tried something like this...
CREATE INDEX idx_test_2 ON table1([DateField], [IntField1], [IntField2], [IntField3], [IntField4])
But this turned out worse in the execution plan
I've been trying to follow this guide, but any other help would be appreciated.
https://www.sqlshack.com/sql-server-index-design-basics-and-guidelines/
Here is the execution plan for the query
https://www.brentozar.com/pastetheplan/?id=SyW3LQk5r
The function on the [DFateField] makes the index useless. You need to change the condition:
DATEPART(YEAR, [DateField]) = DATEPART(YEAR, #datefield)
AND DATEPART(MONTH, [DateField]) = DATEPART(MONTH, #datefield)
You need to rewrite your query to compare the field [DateField] direct with the parameters.
Both indexes should work. But depends on the selectivity, test1 index may be good enough.
You can calculate the date range for the month:
DECLARE #start_month date;
DECLARE #end_month date;
SET #start_month=DATEADD(DAY,1-DATEPART(DAY, #datefield),#datefield);
SET #end_month=DATEADD(MONTH,1,#start_month);
Then change the above condition to:
[DateField] >= #start_month AND [DateField] < #end_month

How to get date from yyyy-mm-dd to yyyy-mm-dd in SQL?

I want to get date from yyyy-mm-dd to yyyy-mm-dd in SQL.
Example: I have two parameter #startdate : 2015-12-28 and #enddate : 2016-01-02, and database in SQLServer, datatype is varchar(10)
DATE_ORDER
28-12-2015
30-12-1996
29-12-2016
30-12-1997
24-12-2015
27-12-1993
03-01-2016
01-01-1992
02-01-2016
etc...
Ok,now I want to get data from #startdate : 2015-12-28 and #enddate : 2016-01-02. I use SELECT * FROM TABLE_X WHERE DATE_ORDER >= #startdate AND DATE_ORDER <= #enddate . But the results are not what I expected. Here are the results I want
28-12-2015
30-12-1996
29-12-2016
30-12-1997
01-01-1992
02-01-2016
I think to solve this problem, I need to do two things :
First, get date range from #startdate to #enddate , in here 28/12/2015, 29/12/2015, 30/12/2015, 31/12/2015, 01/01/2016, 02/01/2016.
The second: get the date in database same in range 28/12, 29/12, 30/12, 31/12, 01/01, 02/01, ignoring the year.
Can you give me some ideas about this ?
Your actual format is "105-italian" find details here.
You can convert your existing VARCHAR(10)-values with this line to real datetime
SELECT CONVERT(DATETIME,YourColumn,105)
Next thing to know is, that you should not use BETWEEN but rather >=StartDate AND < NakedDateOfTheFollowingDay to check date ranges
So to solve your need Get date-range from 2015-12-28 to 2016-01-02 you might do something like this:
DECLARE #Start DATETIME={d'2015-12-28'};
DECLARE #End DATETIME={d'2016-01-02'};
SELECT *
FROM YourTable
WHERE CONVERT(DATETIME,YourDateColumn,105)>=#Start AND CONVERT(DATETIME,YourDateColumn,105)<#End+1
Attention Be aware, that the conversion lets your expression be not sargable. No index will be used.
Better was to store your date as correctly typed data to avoid conversions...
Try this query
SET DATEFIRST 1
DECLARE #wk int SET #wk = 2
DECLARE #yr int SET #yr = 2011
--define start and end limits
DECLARE #todate datetime, #fromdate datetime
SELECT #fromdate = dateadd (week, #wk, dateadd (YEAR, #yr-1900, 0)) - 4 -
datepart(dw, dateadd (week, #wk, dateadd (YEAR, #yr-1900, 0)) - 4) + 1
SELECT #todate = #fromdate + 6
;WITH DateSequence( Date ) AS
(
SELECT #fromdate AS Date
UNION ALL
SELECT dateadd(DAY, 1, Date)
FROM DateSequence
WHERE Date < #todate
)
--select result
SELECT * FROM DateSequence OPTION (MaxRecursion 1000)
So, after the 2nd or 3rd edit, it slowly becomes clear, what you want (i hope).
So you REALLY WANT to get the dates with the year beeing ignored.
As someone pointed out already, date-values are stored internally not as string, but as internal datatype date (whatever that is in memory, i don't know).
If you want to compare DATES, you cannot do that with ignorance of any part. If you want to, you have to build a NEW date value of day and month of given row and a hard coded year (2000 or 1 or whatever) for EVERY row.
SELECT * FROM TABLE_X WHERE convert(date,'2000' + substring(convert(char(8),convert(datetime, 'DATE_ORDER', 105),112),5,4),112) >= #startdate AND convert(date,'2000' + substring(convert(char(8),convert(datetime, 'DATE_ORDER', 105),112),5,4),112) <= #enddate
If your startdate and enddate go OVER sylvester, you have to do 2 queries, on from startdate to 1231, one from 0101 to enddate.

Group by ISO Week and year [duplicate]

I am currently working on creating my first Data_warehouse using sql-server
I have a Date dimension i want to populate it using SSIS it has a field called ISO_year
can somebody tell me how do i get it ?
i tried this query ::
select year(getdate()) -- but i dont think is this ISO_year ?
And i need to know which is best practice to load dimensions into DB using ssis ?
i reffered this http://michaelmorley.name/how-to/create-date-dimension-table-in-sql-server
Here is a function for iso_year, the logic behind it is that the thursday of the week from the parameter date determine the year:
CREATE FUNCTION [dbo].[f_isoyear]
(
#p_date datetime
)
RETURNS int
as
BEGIN
RETURN datepart(yy, dateadd(wk, datediff(d, 0, #p_date)/7, 3))
END
Here is a Connect item that requests a function to calculate ISO_YEAR.
DATEPART - ISO_YEAR for ISO_WEEK
In the workaround section you have this function that you can use.
CREATE FUNCTION [dbo].[ISOyear](#date DATETIME)
returns SMALLINT
AS
BEGIN
DECLARE #isoyear SMALLINT = CASE
WHEN Datepart(isowk, #date)=1
AND Month(#date)=12 THEN Year(#date)+1
WHEN Datepart(isowk, #date)=53
AND Month(#date)=1 THEN Year(#date)-1
WHEN Datepart(isowk, #date)=52
AND Month(#date)=1 THEN Year(#date)-1
ELSE Year(#date)
END;
RETURN #isoyear;
END;

find rows that fall between a day and time of the week in sql server

I have a table of rows in MS SQL that contain a start and end day of the week, hour, and time. I need a T-SQL query that can pull rows from that table where GETDATE matches the day of week and time of those rows. Specifically, I need the query to work if a row has a day/time that starts on one day of the week and ends on the next day.
Here's the structure I'm working with:
_start_day_of_week (int) = 5_start_hour (int) = 15_start_minute (int) = 30
_end_day_of_week (int) = 6_end_hour (int) = 2
_end_minute (int) = 30
_title (string) = 'My Sample Row'
_id (int) = 1
How would I retrieve this row given the current DATETIME?
you could try something like:
select * from [yourtable]
where yourdatefield between getDate() and (getDate()+1 )
You should be able to use datepart function to go with getdate function:
http://msdn.microsoft.com/en-us/library/aa258265%28v=sql.80%29.aspx
I am still unclear about your requirements as they seem a bit impracticle. But here is my go.
DECLARE #A AS TABLE(
_start_day_of_week int,
_start_hour int,
_start_minute int,
_end_day_of_week int,
_end_hour int,
_end_minute int,
_title varchar(10),
_id int
)
--Above is temp table for example
INSERT #A
SELECT 5,15,30,6,2,30,'SAMPLE 1', 1 UNION ALL
SELECT 6,15,30,6,17,30,'SAMPLE 2', 2
DECLARE #month int = datepart(month,getdate())
DECLARE #currentDay int = datepart(dw,getdate())
DECLARE #currentHour int = datepart(hour, getdate())
DECLARE #currentMinute int = datepart(minute, getdate())
SELECT * FROM #A --Don't use select *. This is just an example.
WHERE --Where GETDATE matches
_start_day_of_week = #currentDay --The day of week
AND
_start_hour = #currentHour --and time of those rows
AND
_start_minute = #currentMinute
--I have not done any matching against the _end columns as
--your criteria are vague. Should it exclude all days except
--the current day and the currentday+1?
-- But you would use a similar method.

Resources