error executing a query with date check in where clause - sql

I have a table with year and month values saved as int datatype columns [Year] and [Month] correspondingly. I need to query the table limiting the output by a certain date using WHERE clause. I came up with a solution using DATEFROMPARTS function (avoids multiple conversions of datatypes), which works perfectly in the SELECT clause (the number of distinct dates is limited so I can check all of them), but fails when I am trying to use the composite date in a WHERE clause.
Thus query like
SELECT DISTINCT DATEFROMPARTS([Year], [Month], 1) FROM MyTable
gives
2019-04-01
2019-05-01
2019-06-01
2019-07-01
2019-08-01
2019-09-01
2019-10-01
2019-11-01
2019-12-01
2020-01-01
2020-02-01
2020-03-01
but when I add a WHERE clause like
SELECT DISTINCT DATEFROMPARTS([Year], [Month], 1) FROM MyTable
WHERE DATEFROMPARTS([Year], [Month], 1) < convert(date, getdate(), 23)
or
SELECT DISTINCT DATEFROMPARTS([Year], [Month], 1) FROM MyTable
WHERE DATEFROMPARTS([Year], [MonthNumber], 1) < convert (date, '2019-12-01', 23)
I get an error:
Cannot construct data type date, some of the arguments have values which are not valid.
I am using SQL Server Ver. 13
DISCLAIMER
The data is stored on a remote SQL server I am accessing from a cloud machine. It is a view, I have zero permissions on, only to read and use it further in an ETL process. There are no illegal values in Year and Month columns like NULL, Month > 12, etc.
UPDATE
I have tried a similar scenario on an SQL Server 2016:
CREATE TABLE MyTable(
[Year] [int] NULL,
[Month] [int] NULL)
INSERT INTO MyTable
([Year], [Month])
VALUES
(2019, 9)
,(2019, 10)
,(2019, 11)
,(2019, 12)
,(2020, 1)
,(2020, 2)
,(2020, 3)
,(2020, 4)
A combination of DATEFROMPARTS or concatenating dateparts and converting to date of different formats does not work, at the best, the date only looks ok with yyyy-mm-dd format, but fails to compare with a getdate() function.
The Solution suggested by #John-Cappelletti helped to solve the initial problem:
try_convert(date, concat([Year],'/',[Month],'/', 1))
although it still unclear to me why it is not possible to convert the combined date into different date styles.

Due to a data issue, perhaps use try_convert() with a concat()
Example
Declare #YourTable Table ([Year] int,[Month] int)
Insert Into #YourTable Values
(2020,1)
,(2020,1)
,(2020,2)
,(2020,222)
Select *
,try_convert(date,concat([Year],'/',[Month],'/', 1))
From #YourTable
Returns
Year Month (No column name)
2020 1 2020-01-01
2020 1 2020-01-01
2020 2 2020-02-01
2020 222 NULL << There's a problem

Leave off the 23:
WHERE DATEFROMPARTS([Year], [Month], 1) < convert(date, getdate())
You probably have an issue with the internationalization settings. Your version is converting getdate() to a string and back to a date.

DATEFROMPARTS(YEAR,MONTH,DAY) will return a value in a DATE format.
Simply CAST the parameter you're wanting to compare it to in the same format. Then are you comparing the same type of formats.
The error you've previously got is because you're trying to convert to different data types.
SELECT DISTINCT DATEFROMPARTS([Year], [Month], 1)
FROM MyTable
WHERE DATEFROMPARTS([Year], [Month], 1) < CAST(GETDATE() as DATE)

Related

Query not working when trying to get last 3 years data

I have a separate column for a year and month in my database table called Logs and I want to retrieve logs between 2 dates like below:
So if I say retrieve logs between 01/01/2018 to last month that is 02/01/2021 then it should retrieve all the logs but I am not getting any result with the query below:
select COUNT(*)
from Logs
where ((LogYear >= YEAR('01/01/2018') and LogMonth >=Month('01/01/2018')) and (LogYear <=YEAR('02/01/2021') and LogMonth < =Month('02/01/2021')))
Output: 0 records
When I run this query, I am getting a 0 count although I have data for all the year.
What's the issue with this query?
Sample Data:
LogID LogYear LogMonth
1 2018 1
2 2018 2
3 2018 3
4 2018 4
I am trying to retrieve data from 1-Jan-2018 to 1-February-2021.
You can't compare a date by comparing its component parts, you have to build a proper date and then compare them. Unfortunately this is unsargable i.e. unable to use indexes, you would be better of storing actual dates rather than date components.
As an aside, to avoid unexpected behaviour always use an unambiguous date format.
As you mention you are passing the values from C# just ensure you set the SqlParameter type to date not datetime to avoid any possible issues with time components.
declare #Logs table (LogId int, LogYear int, LogMonth int);
declare #StartDate date = '01 Jan 2018', #EndDate date = '01 Feb 2021';
insert into #Logs (LogID, LogYear, LogMonth)
values
(1, 2018, 1),
(2, 2018, 2),
(3, 2018, 3),
(4, 2018, 4),
(5, 2021, 2),
(6, 2021, 3);
SELECT COUNT(*)
FROM #Logs
WHERE DATEFROMPARTS(LogYear, LogMonth, 1) >= #StartDate
AND DATEFROMPARTS(LogYear, LogMonth, 1) <= #EndDate;
Sample data (tweaked to check end case) produces a result of 5 because record id 5 is in the window and id 6 isn't.
I think you need to get the date in an ordered form first, either through converting into a DATETIME or an INT.
Here an example with INT:
WITH Logs2 (ID, LogDate) AS (
SELECT ID, ((LogYear * 10000) + (LogMonth * 100) + LogDay)
FROM Logs
)
SELECT COUNT(*)
FROM Logs2
WHERE (LogDate >= 20180101) AND (LogDate < 20210201)
But of course better would be to convert the 3 columns into a DATETIME, DATETIME2 or DATETIMEOFFSET.
Try this:
DECLARE #DateFrom datetime = '01/01/2018'
, #DateTo datetime = '02/01/2021'
SELECT
COUNT(*)
FROM Logs
WHERE LTRIM(LogYear*100 + LogMonth)
BETWEEN CONVERT(varchar(6), #DateFrom , 112)
AND CONVERT(varchar(6), #DateTo , 112)
Have you checked the DATEFORMAT -> SET DATEFORMAT (Transact-SQL)
Also I suggest if possible use ISO format date values to remove any regional issues, so
where ((LogYear >= YEAR('20180101') and LogMonth >=Month('20180101')) and (LogYear <=YEAR('20210201') and LogMonth < =Month('20210201')))
select COUNT(*)
from Logs
where (LogYear between 2018 and 2020)
or (logyear=2021 and logMonth <= 2)

Converting Varchar to Date and date Comparison

I am converting two Date columns to find the most recent one
SELECT ISNULL(CONVERT(varchar(10),REPLACE('10-07-2015','/','-'), 103),'01-01-1900'),
ISNULL(CONVERT(varchar(10),REPLACE('10/7/2015','/','-'), 103),'01-01-1900'),
CASE
WHEN ISNULL(CONVERT(varchar,REPLACE('10-07-2015','/','-'), 103),'01-01-1900') = ISNULL(CONVERT(varchar,REPLACE('10/7/2015','/','-'), 103),'01-01-1900')
THEN '10-07-2015'
END
My issue is some dates missing leading Zero in Day or Month and comparison is giving false results. Is there a better way to handle this? Other issue is one column has date with '/' and other have with '-'
Currently case is only checking on '=' but will add more to get the most recent
You can just convert those 2 varchars to the DATE type, then compare them.
You can find the date/datetime styles here
For those DD/MM/YYYY datestamps the 103 style would fit.
And to calculate the most recent between them, just wrap it in a CASE.
Example snippet:
declare #T table (
id int identity(1,1) primary key,
datestamp1 varchar(10),
datestamp2 varchar(10)
);
insert into #T (datestamp1, datestamp2) values
('5/9/2018','17/9/2018')
,('9-10-2018','16-10-2018')
,('15-10-2018','13-10-2018')
;
SELECT *,
TRY_CONVERT(DATE, datestamp1, 103) as date1,
TRY_CONVERT(DATE, datestamp2, 103) as date2,
CASE
WHEN TRY_CONVERT(DATE, datestamp1, 103) >= TRY_CONVERT(DATE, datestamp2, 103) THEN datestamp1
WHEN TRY_CONVERT(DATE, datestamp2, 103) IS NULL THEN datestamp1
ELSE datestamp2
END AS MostRecentDatestamp
FROM #T;
Returns:
id datestamp1 datestamp2 date1 date2 MostRecentDatestamp
1 5/9/2018 17/9/2018 2018-09-05 2018-09-17 17/9/2018
2 9-10-2018 16-10-2018 2018-10-09 2018-10-16 16-10-2018
3 15-10-2018 13-10-2018 2018-10-15 2018-10-13 15-10-2018

SQL Server: get all previous values that are within half year of a month (not in future)?

I have values such as
201401
201411
201501
201504
201508
201606
If I select values last six months from 201501, I want to get 201411. If last six months from 201606, then nothing. If from 201508, then 201504.
I have a month column of the varchar form 201601.
How can I get the last six months relative to each month with some datatype objects such as datepart functions?
Another option which will reduce record level processing/conversions
Declare #YourTable table (SomeCol varchar(6))
Insert Into #YourTable values
(201401),
(201411),
(201501),
(201504),
(201508),
(201606)
Declare #Target varchar(6) = '201508'
Select *
From #YourTable
Where SomeCol >= format(dateadd(MONTH,-6,cast(#Target+'01' as date)),'yyyyMM')
and SomeCol < #Target
Returns
201504
Here is one method:
where left(val, 4) * 12 + right(val, 2) >= left('201501', 4) * 12 + right('201501', 2) - 6
This is rather inelegant. Basically is converts the values to months since a date.
Alternatively, you can use date arithmetic:
where cast(val + '01' as date) >= dateadd(month, -6, cast('201501' + '01' as date)
In both cases, you can add a computed column and then an index on the computed column to make the queries run faster.

SQL Server date time convert

I have a field in my database table called Month which holds its values as an INT it has the values 1 to 12 - I would like a way to convert 1 to read 1-11-2016 00:00:00 ie as a datetime field, 2 to read 1-12-2016 00:00:00, 3 to read 1-1-2017 00:00:00 - how can I convert a value like this? I am ok with doing case to switch but the convert/cast is confusing me...
You need to define a startdate (either by parameter or in a CTE), then just use dateadd()
#StartDate = '2016-10-01 00:00:00'
select dateadd(mm, t1.month, #StartDate) as NewMonth
from MyTable t1
Another response is
SELECT CONVERT(DATE, '2016-' + CAST(id AS VARCHAR(2)) + '-01') AS myDate
FROM T1
But y prefer JohmHC answer !!

Splitting full date into day/month/quarter/year columns

I have a simple table structure, where my DATE column has yyyy-mm-dd format. I'd like to split all the dates and INSERT them in to my time dimension table, that contains day, month, quarter and year columns. I'm not sure how to get going with this (trying with query as insert into).
EXAMPLE: SPLIT 2010-03-01 AND INSERT 2010 into year column, 03 into month column, 01 into day column and 1 into quarter column.
Additionally I'd like to assign the names to specific date part (for example month_name for 1 is January), is it good practice to store date-name values inside same table?
Edit: I've just realized that I probably made big mistake? Is TIME dimension in DATA WAREHOUSE is supposed to store unique data only (for description purposes?)
Typically a time dimension implies time (hours, minutes, seconds) or datetime.
It sounds like you just need a date dimension. Most of the work is already done for you here: http://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-sequences/
If you are still in the design phase of the fact table, I'd recommend going with date rather than the YYYYMMDD ID format in the tutorial for your PK on the dimension. It's a byte cheaper per row and enables date math. Before SQL 2008, the int dateid format made sense. Now that date is available, it's a more appropriate choice.
As for uniqueness, for hierarchies and associated attribute relationships in ssas I'll typically combine the necessary columns to uniquely identify the period. For example:
SELECT
CAST(YEAR(GETDATE()) as char(4)) + ' ' + DATENAME(MONTH,GETDATE()) MonthUniqueName
, CAST(YEAR(GETDATE()) as char(4)) + ' Q' + CAST(DATEPART(QUARTER,GETDATE()) as char(1)) QuarterUniqueName
returns
MonthUniqueName QuarterUniqueName
2013 March 2013 Q1
Is this what you want?
select Datename(yy,yourDateColumn) as year, Datename(quarter,yourDateColumn), Datename(month, yourDateColumn),Datename(day, yourDatecolumn),CONVERT(varchar(3), yourDateColumn, 100) from yourTable
Assuming you have column type as DateTime
Insert into yourTable (yearColumn, quarterColumn, monthColumn, dayColumn) values
Datename(yy,yourDateColumn) , Datename(quarter,yourDateColumn), Datename(month, yourDateColumn),Datename(day, yourDatecolumn)
-- First solution: date is stored and day, month, year and quarter are computed
DECLARE #Table1 TABLE (
Dt DATE NOT NULL,
DD AS CONVERT(TINYINT, DATEPART(DAY, Dt)) PERSISTED,
MM AS CONVERT(TINYINT, DATEPART(MONTH, Dt)) PERSISTED,
YYYY AS CONVERT(SMALLINT, DATEPART(YEAR, Dt)) PERSISTED,
QQ AS CONVERT(TINYINT, DATEPART(QUARTER, Dt)) PERSISTED
);
INSERT #Table1
VALUES (GETDATE());
SELECT * FROM #Table1;
-- Second solution: day, month and year are stored and date and quarter are stored
DECLARE #Table2 TABLE (
Dt AS CONVERT(DATE, DATEADD(DAY, DD-1, DATEADD(MONTH, MM-1, DATEADD(YEAR, YYYY-1, CONVERT(DATE, '00010101',112))))) PERSISTED,
DD TINYINT NOT NULL CHECK(DD BETWEEN 1 AND 31),
MM TINYINT NOT NULL CHECK(MM BETWEEN 1 AND 12),
YYYY SMALLINT NOT NULL CHECK(YYYY BETWEEN 1 AND 9999),
QQ AS CONVERT(TINYINT, ((MM-1)/3)+1) PERSISTED
);
INSERT #Table2 (DD, MM, YYYY)
VALUES (9,3,2013);
SELECT * FROM #Table2;
Results:
Dt DD MM YYYY QQ
---------- ---- ---- ------ ----
2013-03-09 9 3 2013 1
Dt DD MM YYYY QQ
---------- ---- ---- ------ ----
2013-03-09 9 3 2013 1