Using select query for between where clause - sql

I have to find out a date falls between two other dates which are selected from a different table in Microsoft SQL Server
I.e. I want to do something like
Select A.* from ( select member.key,
case when effective_date between (select month_start and month_end
from sales_month
where month=2 and year=2013) bucket_1
then 1 else 0 from member ) as A
where a.bucket_1 != 0
I have to duplicate case statement for different months. Any ideas / help?
Thanks
Shankar.

This could be done with a JOIN:
SELECT m.*
FROM member m
JOIN sales_month sm
ON sm.month = 2
AND sm.year = 2013
AND m.effective_date BETWEEN sm.month_start AND sm.month_end;

Use variables to hold the information.
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SELECT #StartDate = month_start
, #EndDate = month_end
FROM sales_month
WHERE [month] = 2 AND [year] = 2013
SELECT *
from member
where effective_date BETWEEN #StartDate AND #EndDate

If there are no duplicates that you expect, you can use a join:
select m.*
from member m join
sales_month sm
on m.effective_date between sm.month_start and sm.month_end and
sm.month = 2 and sm.year = 2013
Otherwise, try a correlated subquery with exists:
select m.*
from member m
where exists (select 1 from sales_month where month=2 and year=2013 and m.effective_date between month_start and month_end)

Not sure why you need to get the month and year from a table. You're looking for all members whose effective date falls in February of 2013, right? What is sales_month doing for you here?
DECLARE #m INT, #y INT;
SELECT #m = 2, #y = 2013;
DECLARE #d DATETIME;
SET #d = DATEADD(MONTH, #m-1, DATEADD(YEAR, #y-1900, 0));
SELECT m.* -- don't use SELECT *!
FROM dbo.member AS m -- always use schema prefix!
WHERE effective_date >= #d
AND effective_date < DATEADD(MONTH, 1, #d); -- don't use BETWEEN!
If it needs to come from a table (e.g. your sales month is not the same as a calendar month for some reason), then use a join, but I'm not sure what your BETWEEN logic really means. I'll leave it in in case it's logical, but in most cases it's not.
SELECT m.*
FROM dbo.member AS m
INNER JOIN dbo.sales_month AS s
ON m.effective_date BETWEEN s.month_start AND s.month_end
WHERE s.[month] = 2 AND s.[year] = 2013; -- don't use reserved words as column names!
Relevant links:
Bad habits to kick : using SELECT * / omitting the column list
Bad habits to kick : avoiding the schema prefix
What do BETWEEN and the devil have in common?
Bad habits to kick : mis-handling date / range queries

Related

Join Generated Date Sequence

Currently I'm trying to join a date table to a ledger table so I can fill the gaps of the ledger table whenever there are no transactions in certain instances (e.g. there are transactions on March 1st and in March 3rd, but no transaction in March 2nd. And by joining both tables March 2nd would appear in the ledger table but with 0 for the variable we're analyzing.)
The challenge is that I can't create a Date object/table/dimension because I don't have permissions to create tables in the database. Therefore I've been generating a date sequence with this code:
DECLARE #startDate date = CAST('2016-01-01' AS date),
#endDate date = CAST(GETDATE() AS date);
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1;
So, is there the possibility to join both tables into the same statement? Let's say the ledger table looks like this:
SELECT
date,cost
FROM ledger
I'd assume it can be done by using a subquery but I don't know how.
Thank you.
There is a very good article by Aaron Bertrand showing several methods for generating a sequence of numbers (or dates) in SQL Server: Generate a set or sequence without loops – part 1.
Try them out and see for yourself which is faster or more convenient to you. (spoiler - Recursive CTE is rather slow)
Once you've picked your preferred method you can wrap it in a CTE (common-table expression).
Here I'll use your method from the question
WITH
CTE_Dates
AS
(
SELECT
DATEADD(day, number - 1, #startDate) AS dt
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT
...
FROM
CTE_Dates
LEFT JOIN Ledger ON Ledger.dt = CTE_Dates.dt
;
You can use your generated date sequence as a CTE and LEFT JOIN that to your ledger table. For example:
DECLARE #startDate date = CAST('2020-02-01' AS date);
DECLARE #endDate date = CAST(GETDATE() AS date);
WITH dates AS (
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT dates.Date, COALESCE(ledger.cost, 0)
FROM dates
LEFT JOIN (VALUES ('2020-02-02', 14), ('2020-02-05', 10)) AS ledger([Date], [cost]) ON dates.Date = ledger.Date
Output:
Date cost
2020-02-01 0
2020-02-02 14
2020-02-03 0
2020-02-04 0
2020-02-05 10
2020-02-06 0
Demo on dbfiddle

SQL Query to get oldest date

I'm new to SQL and have a large database that contains IDs and Service Dates and I need to write a query to give me the first date each ID had a service.
I tried:
SELECT dbo.table.ID, dbo.otherTable.ServiceDate AS EasliestDate
FROM dbo.table INNER JOIN dbo.table.ID = dbo.otherTable.ID
But the output is every service for every ID, which has too many results to sort through. I want the output to only show the ID and the oldest service date. Any advice is appreciated.
EDIT: To be more precise, the output I am looking for is the ID and service date if the oldest service date is during the year that I specify. I.E. if ID = 1 has a service in 2015 and 2016 and I am searching for IDs in 2016 then ID = 1 should not appear in the results because there was an earlier service in 2015.
EDIT: Thanks everyone who helped with this! The answer I accepted did exactly what I asked. Major kudos to Patty though who who elaborated on how to further filter the outcome by year.
Use GROUP BY and MIN to get the first date for each ID:
SELECT dbo.table.ID,
MIN(dbo.otherTable.ServiceDate) AS EasliestDate
FROM dbo.table
INNER JOIN otherTable
ON dbo.table.ID = dbo.otherTable.ID
GROUP BY dbo.table.ID;
ADDENDUM
In reference to a question in the comments:
how would I also restrict it to show only those who had a service in a specific year?
It would depend on your exact requirements, consider the following set:
ID ServiceDate
--------------------
1 2014-05-01
1 2015-08-01
1 2016-07-07
2 2015-08-19
You would only want to include ID = 1 if the year you specified was 2016, but assuming you still wanted to return the first date of 2014-05-01 then you would need to add a having clause with a case statement to get this.
DECLARE #Year INT = 2016;
DECLARE #YearStart DATE = DATEADD(YEAR, #Year - 1900, '19000101'),
#YearEnd DATE = DATEADD(YEAR, #Year - 1900 + 1, '19000101');
SELECT #YearStart, #YearEnd
SELECT t.ID,
MIN(o.ServiceDate) AS EasliestDate
FROM dbo.table AS t
INNER JOIN otherTable AS o
ON o.ID = r.ID
GROUP BY t.ID
HAVING COUNT(CASE WHEN o.ServiceDate >= #YearStart
AND o.ServiceDate < #YearEnd THEN 1 END) > 0;
If you only want the earliest date in 2016 the a where clause would suffice
DECLARE #Year INT = 2016;
DECLARE #YearStart DATE = DATEADD(YEAR, #Year - 1900, '19000101'),
#YearEnd DATE = DATEADD(YEAR, #Year - 1900 + 1, '19000101');
SELECT #YearStart, #YearEnd
SELECT t.ID,
MIN(o.ServiceDate) AS EasliestDate
FROM dbo.table AS t
INNER JOIN otherTable AS o
ON o.ID = r.ID
WHERE o.ServiceDate >= #YearStart
AND o.ServiceDate < #YearEnd
GROUP BY t.ID;
It is worth noting there is a very good reason I have chosen to calculate the start of the year, and the start of the next year and used
WHERE o.ServiceDate >= #YearStart
AND o.ServiceDate < #YearEnd
Instead of just
WHERE DATEPART(YEAR, o.ServiceDate) = 2016;
In the former, an index on ServiceDate can be used whereas in the latter, the DATEPART calculation must be done on every record and this can cause significant performace issues.
ADDENDUM 2
To do the following:
The exact thing I want then would be IDs who's earliest service is in the year I specify.
Then you would need a having clause, just a different one to the one I posted before:
DECLARE #Year INT = 2016;
DECLARE #YearStart DATE = DATEADD(YEAR, #Year - 1900, '19000101'),
#YearEnd DATE = DATEADD(YEAR, #Year - 1900 + 1, '19000101');
SELECT #YearStart, #YearEnd
SELECT t.ID,
MIN(o.ServiceDate) AS EasliestDate
FROM dbo.table AS t
INNER JOIN otherTable AS o
ON o.ID = r.ID
GROUP BY t.ID
HAVING MIN(o.ServiceDate) >= #YearStart
AND MIN(o.ServiceDate) < #YearEnd;
ADDENDUM 3
CREATE VIEW dbo.YourView
AS
SELECT dbo.table.ID,
MIN(dbo.otherTable.ServiceDate) AS EasliestDate
FROM dbo.table
INNER JOIN otherTable
ON dbo.table.ID = dbo.otherTable.ID
GROUP BY dbo.table.ID;
Then you can apply your criteria to the view:
SELECT *
FROM dbo.YourView
WHERE EasliestDate >= '2015-01-01'
AND EasliestDate < '2016-01-01';
You have to include a WHERE in your current query:
SELECT dbo.table.ID, dbo.otherTable.ServiceDate AS EasliestDate
FROM dbo.table INNER JOIN dbo.table.ID = dbo.otherTable.ID
WHERE Month(dbo.otherTable.ServiceDate) = 1
Or you can search with Year(dbo.otherTable.ServiceDate) = 2016
Or you can use Day(dbo.otherTable.ServiceDate) = 1
Or an specific date.
use group by and min to get records. Else you can refer http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ for better understanding.
You need to use a "Group by" statement. Try this:
SELECT dbo.table.ID, Max(dbo.otherTable.ServiceDate) AS LatestDate, Min(dbo.otherTable.ServiceDate as EarliestDate)
FROM dbo.table INNER JOIN dbo.table.ID = dbo.otherTable.ID
group by dbo.table.ID
Use nested statement to get the min date , and then just match based on ID.
select t1.ID from table1 t1 INNER JOIN
(
SELECT ID, MIN(servicedate) MinServiceDate
FROM table2
GROUP BY ID
) t2 ON t1.ID = t2.ID

How to group by month and year for entire calender year until this month in SQL

I have this query
DECLARE #DATE datetime
SELECT #Date = '2014-04-01'
SELECT #Date, COUNT(*) FROM Claim C
INNER JOIN Prop_Vehicles PV ON PV.Prop = C.Prop
WHERE PV.Vehicle IN (1,2) AND
C.DateCreate >= #DATE AND
ClaimCodeId =5
I want to group by month wise for the calnder year. For example
April 2014 - 200
May 2014 - 300
June 2014 - 500
.
.
october 2014 - 100
something like this. How to achieve it? Could someone help me how to split #Date into two fields and also group by month year wise until current month like I mentioned above?
I reckon datepart function would do? Let me also check that.
Thank you in advance.
In case some months don't have data then this would skip those months.
If you want all months data even if value is zero, then you need to construct months table and join with it
SELECT DATEADD(MONTH, DATEDIFF(MONTH,0,C.DateCreate), 0), COUNT(*) FROM Claim C
INNER JOIN Prop_Vehicles PV ON PV.Prop = C.Prop
and PV.Vehicle IN (1,2) AND
and C.DateCreate >= #DATE AND
AND ClaimCodeId =5
group by DATEADD(MONTH, DATEDIFF(MONTH,0,C.DateCreate), 0)
as per latest comment
here is the way to get all months data and also to display year and month
DECLARE #DATE datetime
SELECT #Date = '2014-04-01'
;with cte
as
(
select DATEADD(month, datediff(month,0,#Date), 0) as monthVal,1 as N
union all
select DATEADD(month, datediff(month,0,#Date)+N, 0) as monthVal, N+1
FROM cte
WHERE n <=5
)
SELECT DATENAME(year, monthval) as Year, datename(month,monthVal) as Month, COUNT(*) FROM
cte
left join Claim C
on DATEADD(month, datediff(month,0,C.DAteCreate)= cte.monthVal
INNER JOIN Prop_Vehicles PV ON PV.Prop = C.Prop
and PV.Vehicle IN (1,2) AND
and C.DateCreate >= #DATE AND
AND ClaimCodeId =5
group by DATENAME(year, monthval) , datename(month,monthVal)

How to count databases elements in a range of date?

In an SQL Server procedure, I need to get all rows matching some constraints(simple where conditions), and then group them by month.
The goal is to create a graph(in Sql server reporting services), which display all data.
I've already something like this:
Select Count(*) AS Count, Month(a.issueDate) AS Month, Year(a.issueDate) AS Year
FROM MyTable a
WHERE
....
GROUP BY YEAR(a.issueDate), MONTH(a.issueDate)
I got my data, I got my graph, but the problem is that if I've NOT any rows in "MyTable", which match my Where conditions, I won't have any rows.
The result is that I've a graph Starting with january, skipping february, and then displaying march.
I cannot post-process data since it's directly connected to the SQL Server Reporting Services report.
Since I have this problem for ~20 stored procedure, I will appreciate to have the simpliest way of doing it.
Thank you very much for your advices
Let's say you want a specific year:
DECLARE #year INT;
SET #year = 2012;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH y AS (SELECT TOP (12) rn = ROW_NUMBER() OVER (ORDER BY [object_id])-1
FROM sys.all_objects ORDER BY [object_id])
SELECT DATEADD(MONTH, y.rn, #start), COUNT(t.issueDate)
FROM y
LEFT OUTER JOIN dbo.MyTable AS t
ON t.issueDate >= DATEADD(MONTH, y.rn, #start)
AND t.issueDate < DATEADD(MONTH, y.rn + 1, #start)
GROUP BY DATEADD(MONTH, y.rn, #start);
If it's not a specific year, then you can do it slightly differently to cover any date range, as long as you provide the 1st day of the 1st month and the 1st day of the last month (or pass 4 integers and construct the dates manually):
DECLARE #startdate SMALLDATETIME, #enddate SMALLDATETIME;
SELECT #startdate = '20111201', #enddate = '20120201';
;WITH y AS (SELECT TOP (DATEDIFF(MONTH, #startdate, #enddate)+1)
rn = ROW_NUMBER() OVER (ORDER BY [object_id])-1
FROM sys.all_objects ORDER BY [object_id]
)
SELECT DATEADD(MONTH, y.rn, #startdate), COUNT(t.issueDate)
FROM y
LEFT OUTER JOIN dbo.MyTable AS t
ON t.issueDate >= DATEADD(MONTH, y.rn, #startdate)
AND t.issueDate < DATEADD(MONTH, y.rn + 1, #startdate)
GROUP BY DATEADD(MONTH, y.rn, #startdate);
In report builder, right click on the date axis, select properties, and then set the axis up as a date range, it will add the empty columns for you, and you won't have to change your SQL
You need to build a table (a Table variable would work best here) that contains all year/month combinations from your minimum to maximum.
You then need to cross join this with your main query to get results for all year/months ready for the graph.

Sql to select row from each day of a month

I have a table which store records of all dates of a month. I want to retrieve some data from it. The table is so large that I should only selecting a fews of them. If the records have a column "ric_date" which is a date, how can I select records from each of the dates in a month, while selecting only a fews from each date?
The table is so large that the records for 1 date can have 100000 records.
WITH T AS (
SELECT ric_date
FROM yourTable
WHERE rice_date BETWEEN #start_date AND #end_date -- thanks Aaron Bertrand
GROUP BY ric_date
)
SELECT CA.*
FROM T
CROSS APPLY (
SELECT TOP 500 * -- 'a fews'
FROM yourTable AS YT
WHERE YT.ric_date = T.ric_date
ORDER BY someAttribute -- not required, but useful
) AS CA
Rough idea. This will get the first three rows per day for the current month (or as many that exist for any given day - there may be days with no rows represented).
DECLARE
#manys INT = 3,
#month DATE = DATEADD(DAY, 1-DAY(GETDATE()), DATEDIFF(DAY, 0, GETDATE()));
;WITH x AS
(
SELECT some_column, ric_date, rn = ROW_NUMBER() OVER
(PARTITION BY ric_date ORDER BY ric_date)
FROM dbo.data
WHERE ric_date >= #month
AND ric_date < DATEADD(MONTH, 1, #month)
)
SELECT some_column, ric_date FROM x
WHERE rn <= #manys;
If you don't have supporting indexes (most importantly on ric_date), this won't necessarily scale well at the high end.