Query not working when trying to get last 3 years data - sql

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)

Related

error executing a query with date check in where clause

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)

How to pull data from Starting month of year to the given month based on the month parameter value

IF I Select #rpmMonth = SEP 2019 it should pull the data from JAN 2019 to SEP 2019
**I Have only One Parameter Value i.e #rpmMonth of VARCHAR Type.
Declare #firstDay Date=(
select convert(varchar(10),DATEADD(Year,-1*DateDiff(Year,getdate(),0),0),120))
Declare #CurDate Date=(
SELECT convert(varchar(10),DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE())+1,0)),120))
select * from YOURTABLE
where colname between #firstDay and #CurDate
Ideally, you should be using a date datatype, not a varchar. If we do, however, have to use a varchar then you could do something like this:
SELECT {Columns}
FROM {Your Schema And Table}
WHERE {DateColumn} >= CONVERT(date,CONCAT(RIGHT(#rpmMonth),'0101'))
AND {DateColumn} < DATEADD(MONTH, 1, CONVERT(date, '01 ' + #rpmMonth));
(Obviously replace the parts in braces ({}) and assumes the language of the LOGIN is an English based language.)
This would give you all rows on or after Jan 01 of the year for #rpmMonth and before the month after #rpmMonth.
If you use a proper date, and pass the first date of the month i.e. (2019-09-01 for the sample you gave), then you could do the below:
SELECT {Columns}
FROM {Your Schema And Table}
WHERE {DateColumn} >= DATEADD(YEAR, DATEDIFF(YEAR, 0, #rpmMonth),0)
AND {DateColumn} < DATEADD(MONTH, 1, #rpmMonth);
SQL Server is pretty good about converting date values. You should be able to do:
select . . .
from t cross join
(values (convert(date, #rpmMonth)) as v(dte)
where datecol >= datefromparts(year(v.dte), 1, 1) and
datecol < datefromparts(year(dateadd(month, 1, v.dte)), month(dateadd(month, 1, v.dte)), 1)

Birthday SQL Function

I am trying to create an expression named "Birthday" to use in a given filter that reads "Birthday is equal to #today" to return any birthdays in a given day. The only way to get the birthday is based on the Date of Birth field. Obviously the DOB YEAR differs from the current YEAR. I need help creating a SQL expression that recognizes somebody's birthday without taking the year into consideration.
This should do the trick:
DECLARE #THEDAY INT = 1
DECLARE #THEMONTH INT = 1
IF OBJECT_ID('BDAY_TABLE') IS NOT NULL
DROP TABLE BDAY_TABLE
CREATE TABLE BDAY_TABLE (ID INT,
BDAY DATETIME)
INSERT INTO BDAY_TABLE (ID, BDAY)
VALUES (1, '1/1/2000'),
(2, '2/10/2000'),
(2, '1/1/2010'),
(2, '10/30/2005')
SELECT * FROM BDAY_TABLE
SELECT * FROM BDAY_TABLE
WHERE DATEPART(DAY, BDAY) = #THEDAY
AND DATEPART(MONTH, BDAY) = #THEMONTH
results:
1 2000-01-01 00:00:00.000
2 2010-01-01 00:00:00.000
Note the use of DATEPART, that way we don't really care about the year.
You can use the DATEADD and YEAR functions to calculate the Birthday in a given year. Supposed that you have a variable #TODAY that holds the current date, you could use this as your WHERE clause to get only those people that celebrate their birthday today:
WHERE DATEADD(year, YEAR(#TODAY)-YEAR(DOB), DOB) = #TODAY
This will also overcome the leap-year problem that way that such people will be returned on March 1st in a non-leapyear.
But most likely you want to get informed on birthdays let's say 2 weeks ahead, so I suggest to indeed calculate the next Birthday in order to be able to use whatever criteria on that, for example:
DECLARE #TODAY datetime = DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0);
SELECT LastName, FirstName, Email, DOB, Birthday, YEAR(Birthday)-YEAR(DOB) AS becoming
FROM People
CROSS APPLY ( VALUES (
CASE WHEN DATEADD(year, YEAR(#TODAY)-YEAR(DOB), DOB) < #TODAY
THEN DATEADD(year, 1 + YEAR(#TODAY)-YEAR(DOB), DOB)
ELSE DATEADD(year, YEAR(#TODAY)-YEAR(DOB), DOB)
END)
) bd (Birthday)
WHERE Birthday <= DATEADD(day, 14, #TODAY);

tsql grouping with duplication based on variable

I want to create some aggregations from a table but I am not able to figure out a solution.
Example table:
DECLARE #MyTable TABLE(person INT, the_date date, the_value int)
INSERT INTO #MyTable VALUES
(1,'2017-01-01', 10),
(1,'2017-02-01', 5),
(1,'2017-03-01', 5),
(1,'2017-04-01', 10),
(1,'2017-05-01', 2),
(2,'2017-04-01', 10),
(2,'2017-05-01', 10),
(2,'2017-05-01', 0),
(3,'2017-01-01', 2)
For each person existing at that time, I want to average the value for the last x (#months_back) months given some starting date (#start_date):
DECLARE #months_back int, #start_date date
set #months_back = 3
set #start_date = '2017-05-01'
SELECT person, avg(the_value) as avg_the_value
FROM #MyTable
where the_date <= #start_date and the_date >= dateadd(month, -#months_back, #start_date)
group by person
This works. I now want to do the same thing again but skip back some months (#month_skip) from the starting date. Then I want to union those two tables together. Then, I again want to skip back #month_skip months from this date and do the same thing. I want to continue doing this until I have skipped back to some specified date (#min_date).
DECLARE #months_back int, #month_skip int, #start_date date, #min_date date
set #months_back = 3
set #month_skip = 2
set #start_date = '2017-05-01'
set #min_date = '2017-03-01'
Using the above variables and the table #MyTable the result should be:
person | avg_the_value
1 | 5
2 | 6
1 | 6
3 | 2
Only one skip is made here since #min_date is 2 months back but I would like to be able to do multiple skips based on what #min_date is.
This example table is simple but the real one has many more automatically created columns and therefore it is not feasible to use a table variable where I would have to declare the scheme of the resulting table.
I asked a related question Here but did not manage to get any of the answers to work for this problem.
It sounds like what you're trying to do is the following:
Starting with a date (e.g. 2017-05-01), look back #months_back months and define a range of dates. For example, if we go 3 months back, we're defining a range from 2017-02-01 through 2017-05-01.
After we define this range, we go back to our starting date and define a new starting date, going back #month_skip months. For example, with an initial starting date of 2017-05-01, we might skip back 2 months, giving us a new starting date of 2017-03-01.
We take this new starting date, and define a range of corresponding dates (as we did above). This produces the range 2016-12-01 through 2017-03-01.
We repeat this as needed through the minimum date specified, to produce a list of date ranges we want to do calculations for:
2017-03-01 through 2017-05-01
2016-12-01 through 2017-03-01
... etc ...
For each of these periods, look at a person and calculate the average of their value.
The query below should do what is described above: rather than taking a value and iterating back to calculate previous values, we use a numbers table to calculate offsets on an interval, which is used to determine the ending and starting dates for each interval/period. This query was built using SQL Server 2008 R2 and should be compatible with future versions.
/* Table, data, variable declarations */
DECLARE #MyTable TABLE(person INT, the_date date, the_value int)
INSERT INTO #MyTable VALUES
(1,'2017-01-01', 10),
(1,'2017-02-01', 5),
(1,'2017-03-01', 5),
(1,'2017-04-01', 10),
(1,'2017-05-01', 2),
(2,'2017-04-01', 10),
(2,'2017-05-01', 10),
(2,'2017-05-01', 0),
(3,'2017-01-01', 2)
DECLARE #months_back int, #month_skip int, #start_date date, #min_date date
set #months_back = 3
set #month_skip = 2
set #start_date = '2017-05-01'
set #min_date = '2017-01-01'
/* Common table expression to build list of Integers */
/* reference http://www.itprotoday.com/software-development/build-numbers-table-you-need if you want more info */
declare #end_int bigint = 50
; WITH IntegersTableFill (ints) AS
(
SELECT
CAST(0 AS BIGINT) AS 'ints'
UNION ALL
SELECT (T.ints + 1) AS 'ints'
FROM IntegersTableFill T
WHERE ints <= (
CASE
WHEN (#end_int <= 32767) THEN #end_int
ELSE 32767
END
)
)
/* What we're going to do is define a series of periods.
These periods have a start date and an end date, and will simplify grouping
(in place of the calculate-and-union approach)
*/
/* Now, we start defining the periods
#months_Back_start defines the end of the range we need to calculate for.
#month_skip defines the amount of time we have to jump back for each period
*/
/* Using the number table we defined above and the data in our variables, calculate start and end dates */
,periodEndDates as
(
select ints as Period
,DATEADD(month, -(#months_back*ints), #start_date) as endOfPeriod
from IntegersTableFill itf
)
,periodStartDates as
(
select *
,DATEADD(month, -(#month_skip), endOfPeriod) as startOfPeriod
from periodEndDates
)
,finalPeriodData as
(
select (period) as period, startOfPeriod, endOfPeriod from periodStartDates
)
/* Link the entries in our original data to the periods they fall into */
/* NOTE: The join criteria originally specified allows values to fall into multiple periods.
You may want to fix this?
*/
,periodTableJoin as
(
select * from finalPeriodData fpd
inner join #MyTable mt
on mt.the_date >= fpd.startOfPeriod
and mt.the_date <= fpd.endOfPeriod
and mt.the_date >= #min_date
and mt.the_date <= #start_date
)
/* Calculate averages, grouping by period and person */
,periodValueAggregate as
(
select person, avg(the_value) as avg_the_value from
periodTableJoin
group by period, person
)
select * from periodValueAggregate
The method I propose is set-based, not iterative.
(I am not following your problem exactly, but please follow along and we can iron out any discrepancies)
Essentially, you are looking to divide a calendar up in to periods of interest. The periods are all equal in width and are sequential.
For this, I propose you build a calendar table and mark the periods using division as illustrated in the code;
DECLARE #CalStart DATE = '2017-01-01'
,#CalEnd DATE = '2018-01-01'
,#CalWindowSize INT = 2
;WITH Numbers AS
(
SELECT TOP (DATEDIFF(MONTH, #CalStart, #CalEnd)) N = CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS INT) - 1
FROM syscolumns
)
SELECT CalWindow = N / #CalWindowSize
,CalDate = DATEADD(MONTH, N, #CalStart)
FROM Numbers
Once you have correctly configured the variables, you should have a calendar that represents the windows of interest.
It is then a matter of affixing this calendar to your dataset and grouping by not only the person but the CalWindow too;
DECLARE #MyTable TABLE(person INT, the_date date, the_value int)
INSERT INTO #MyTable VALUES
(1,'2017-01-01', 10),
(1,'2017-02-01', 5),
(1,'2017-03-01', 5),
(1,'2017-04-01', 10),
(1,'2017-05-01', 2),
(2,'2017-04-01', 10),
(2,'2017-05-01', 10),
(2,'2017-05-01', 0),
(3,'2017-01-01', 2)
----------------------------------
-- Build Calendar
----------------------------------
DECLARE #CalStart DATE = '2017-01-01'
,#CalEnd DATE = '2018-01-01'
,#CalWindowSize INT = 2
;WITH Numbers AS
(
SELECT TOP (DATEDIFF(MONTH, #CalStart, #CalEnd)) N = CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS INT) - 1
FROM syscolumns
)
,Calendar AS
(
SELECT CalWindow = N / #CalWindowSize
,CalDate = DATEADD(MONTH, N, #CalStart)
FROM Numbers
)
SELECT TB.Person
,AVG(TB.the_value)
FROM #MyTable TB
JOIN Calendar CL ON TB.the_date = CL.CalDate
GROUP BY CL.CalWindow, TB.person
Hope I have understood your problem.

recurring period in sql script

The situation:
The user creates a case record that includes a date field (DateOpened), and wants to send the client a follow up every 30 days until the case is closed.
The user will run the query periodically (probably weekly) and provide a 'From' and 'To' date range to specify the period in which a record may fall within the mutliple of 30 days.
The request:
I need a method to identify records where the user specified date range includes those records which are a multiple of 30 days since the DateOpened date.
UPDATE
This is what came to me all of a sudden while watching a third rate TV show last night!!!
SELECT
....
FROM
....
WHERE
(CAST((DATEDIFF(dd, Invoice.DateOpened #EndDate)/30) AS INT) - CAST((DATEDIFF(dd, Invoice.DateOpened, #StartDate)/30) AS INT)) >=1
OR DATEDIFF(dd, Invoice.DateOpened, #StartDate) % 30 = 0 --this line to capture valid records but where From and To dates are the same
Is this Microsoft SQL? Is this Express edition? As long as it's not Express, you may want to look into using the SQL Agent service, which lets you schedule tasks that can run against the database. What do you want it to do with the record once it hits 30 days?
You can use the DATEDIFF function to calculate the difference between dates in days. You can use the modulus (%) operator to get the "remainder" of a division operation. Combining the two gives you:
SELECT
....
FROM
....
WHERE
--In MS T-SQL, BETWEEN is inclusive.
DateOpened BETWEEN #UserSuppliedFromDate AND #UserSuppliedToDate
AND DATEDIFF(dd, DateOpened, getdate()) % 30 = 0
which should give you the desired result.
Edit (Give this example a try in MSSQL):
DECLARE #Table TABLE
(
ID integer,
DateOpened datetime
)
DECLARE #FromDate as datetime = '1/1/2012'
DECLARE #ToDate as datetime = '12/31/2012'
INSERT INTO #Table VALUES (0, '1/1/1982')
INSERT INTO #Table values (1, '1/1/2012')
INSERT INTO #Table VALUES (2, '2/17/2012')
INSERT INTO #Table VALUES (3, '3/16/2012')
INSERT INTO #Table VALUES (4, '4/16/2012')
INSERT INTO #Table VALUES (5, '5/28/2012')
INSERT INTO #Table VALUES (6, '1/31/2012')
INSERT INTO #Table VALUES (7, '12/12/2013')
DECLARE #DateLoop as datetime
DECLARE #ResultIDs as table ( ID integer, DateLoopAtTheTime datetime, DaysDifference integer )
--Initialize to lowest possible value
SELECT #DateLoop = #FromDate
--Loop until we hit the maximum date to check
WHILE #DateLoop <= #ToDate
BEGIN
INSERT INTO #ResultIDs (ID,DateLoopAtTheTime, DaysDifference)
SELECT ID, #DateLoop, DATEDIFF(dd,#DateLoop, DateOpened)
FROM #Table
WHERE
DATEDIFF(dd,#DateLoop, DateOpened) % 30 = 0
AND DATEDIFF(dd,#DateLoop,DateOpened) > 0 -- Avoids false positives when #DateLoop and DateOpened are the same
AND DateOpened <= #ToDate
SELECT #DateLoop = DATEADD(dd, 1, #DateLoop) -- Increment the iterator
END
SELECT distinct * From #ResultIDs