Select rows with no date range overlap - sql

Imagine the following Loans table:
BorrowerID StartDate DueDate
=============================================
1 2012-09-02 2012-10-01
2 2012-10-05 2012-10-21
3 2012-11-07 2012-11-09
4 2012-12-01 2013-01-01
4 2012-12-01 2013-01-14
1 2012-12-20 2013-01-06
3 2013-01-07 2013-01-22
3 2013-01-15 2013-01-18
1 2013-02-20 2013-02-24
How would I go about selecting the distinct BorrowerIDs of those who have only ever taken out a single loan at a time? This includes borrowers who have only ever taken out a single loan, as well as those who have taken out more than one, provided if you were to draw a time line of their loans, none of them would overlap. For example, in the table above, it should find borrowers 1 and 2 only.
I've tried experimenting with joining the table to itself, but haven't really managed to get anywhere. Any pointers much appreciated!

Solution for dbo.Loan with PRIMARY KEY
To solve this you need a two step approach as detailed in the following SQL Fiddle. I did add a LoanId column to your example data and the query requires that such a unique id exists. If you don't have that, you need to adjust the join clause to make sure that a loan does not get matched to itself.
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.Loans
(LoanID INT, [BorrowerID] int, [StartDate] datetime, [DueDate] datetime)
GO
INSERT INTO dbo.Loans
(LoanID, [BorrowerID], [StartDate], [DueDate])
VALUES
(1, 1, '2012-09-02 00:00:00', '2012-10-01 00:00:00'),
(2, 2, '2012-10-05 00:00:00', '2012-10-21 00:00:00'),
(3, 3, '2012-11-07 00:00:00', '2012-11-09 00:00:00'),
(4, 4, '2012-12-01 00:00:00', '2013-01-01 00:00:00'),
(5, 4, '2012-12-01 00:00:00', '2013-01-14 00:00:00'),
(6, 1, '2012-12-20 00:00:00', '2013-01-06 00:00:00'),
(7, 3, '2013-01-07 00:00:00', '2013-01-22 00:00:00'),
(8, 3, '2013-01-15 00:00:00', '2013-01-18 00:00:00'),
(9, 1, '2013-02-20 00:00:00', '2013-02-24 00:00:00')
GO
First you need to find out which loans overlap with another loan. The query uses <= to compare the start and due dates. That counts loans where the second one starts the same day the first one ends as overlapping. If you need those to not be overlapping use < instead in both places.
Query 1:
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L2.LoanID <> L1.LoanID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate)
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM dbo.Loans L1;
Results:
| LOANID | BORROWERID | STARTDATE | DUEDATE | HASOVERLAPPINGLOAN |
|--------|------------|----------------------------------|---------------------------------|--------------------|
| 1 | 1 | September, 02 2012 00:00:00+0000 | October, 01 2012 00:00:00+0000 | 0 |
| 2 | 2 | October, 05 2012 00:00:00+0000 | October, 21 2012 00:00:00+0000 | 0 |
| 3 | 3 | November, 07 2012 00:00:00+0000 | November, 09 2012 00:00:00+0000 | 0 |
| 4 | 4 | December, 01 2012 00:00:00+0000 | January, 01 2013 00:00:00+0000 | 1 |
| 5 | 4 | December, 01 2012 00:00:00+0000 | January, 14 2013 00:00:00+0000 | 1 |
| 6 | 1 | December, 20 2012 00:00:00+0000 | January, 06 2013 00:00:00+0000 | 0 |
| 7 | 3 | January, 07 2013 00:00:00+0000 | January, 22 2013 00:00:00+0000 | 1 |
| 8 | 3 | January, 15 2013 00:00:00+0000 | January, 18 2013 00:00:00+0000 | 1 |
| 9 | 1 | February, 20 2013 00:00:00+0000 | February, 24 2013 00:00:00+0000 | 0 |
Now, with that information you can determine the borrowers that have no overlapping loans with this query:
Query 2:
WITH OverlappingLoans AS (
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L2.LoanID <> L1.LoanID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate)
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM dbo.Loans L1
),
OverlappingBorrower AS (
SELECT BorrowerID, MAX(HasOverlappingLoan) HasOverlappingLoan
FROM OverlappingLoans
GROUP BY BorrowerID
)
SELECT *
FROM OverlappingBorrower
WHERE hasOverlappingLoan = 0;
Or you could even get more information by counting the loans as well as counting the number of loans that have overlapping other loans for each borrower in the database. (Note, if loan A and loan B overlap, both will be counted as overlapping loan by this query)
Results:
| BORROWERID | HASOVERLAPPINGLOAN |
|------------|--------------------|
| 1 | 0 |
| 2 | 0 |
Query 3:
WITH OverlappingLoans AS (
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L2.LoanID <> L1.LoanID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate)
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM dbo.Loans L1
)
SELECT BorrowerID,COUNT(1) LoanCount, SUM(hasOverlappingLoan) OverlappingCount
FROM OverlappingLoans
GROUP BY BorrowerID;
Results:
| BORROWERID | LOANCOUNT | OVERLAPPINGCOUNT |
|------------|-----------|------------------|
| 1 | 3 | 0 |
| 2 | 1 | 0 |
| 3 | 3 | 2 |
| 4 | 2 | 2 |
Solution for dbo.Loan without PRIMARY KEY
UPDATE: As the requirement actually calls for a solution that does not rely on a unique identifier for each loan, I made the following changes:
1) I added a borrower that has two loans with the same start and due dates
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.Loans
([BorrowerID] int, [StartDate] datetime, [DueDate] datetime)
GO
INSERT INTO dbo.Loans
([BorrowerID], [StartDate], [DueDate])
VALUES
( 1, '2012-09-02 00:00:00', '2012-10-01 00:00:00'),
( 2, '2012-10-05 00:00:00', '2012-10-21 00:00:00'),
( 3, '2012-11-07 00:00:00', '2012-11-09 00:00:00'),
( 4, '2012-12-01 00:00:00', '2013-01-01 00:00:00'),
( 4, '2012-12-01 00:00:00', '2013-01-14 00:00:00'),
( 1, '2012-12-20 00:00:00', '2013-01-06 00:00:00'),
( 3, '2013-01-07 00:00:00', '2013-01-22 00:00:00'),
( 3, '2013-01-15 00:00:00', '2013-01-18 00:00:00'),
( 1, '2013-02-20 00:00:00', '2013-02-24 00:00:00'),
( 5, '2013-02-20 00:00:00', '2013-02-24 00:00:00'),
( 5, '2013-02-20 00:00:00', '2013-02-24 00:00:00')
GO
2) Those "equal date" loans require an additional step:
Query 1:
SELECT BorrowerID, StartDate, DueDate, COUNT(1) LoanCount
FROM dbo.Loans
GROUP BY BorrowerID, StartDate, DueDate;
Results:
| BORROWERID | STARTDATE | DUEDATE | LOANCOUNT |
|------------|----------------------------------|---------------------------------|-----------|
| 1 | September, 02 2012 00:00:00+0000 | October, 01 2012 00:00:00+0000 | 1 |
| 1 | December, 20 2012 00:00:00+0000 | January, 06 2013 00:00:00+0000 | 1 |
| 1 | February, 20 2013 00:00:00+0000 | February, 24 2013 00:00:00+0000 | 1 |
| 2 | October, 05 2012 00:00:00+0000 | October, 21 2012 00:00:00+0000 | 1 |
| 3 | November, 07 2012 00:00:00+0000 | November, 09 2012 00:00:00+0000 | 1 |
| 3 | January, 07 2013 00:00:00+0000 | January, 22 2013 00:00:00+0000 | 1 |
| 3 | January, 15 2013 00:00:00+0000 | January, 18 2013 00:00:00+0000 | 1 |
| 4 | December, 01 2012 00:00:00+0000 | January, 01 2013 00:00:00+0000 | 1 |
| 4 | December, 01 2012 00:00:00+0000 | January, 14 2013 00:00:00+0000 | 1 |
| 5 | February, 20 2013 00:00:00+0000 | February, 24 2013 00:00:00+0000 | 2 |
3) Now, with each loan range unique, we can use the old technique again. However, we also need to account for those "equal date" loans. (L1.StartDate <> L2.StartDate OR L1.DueDate <> L2.DueDate) prevents a loan getting matched with itself. OR LoanCount > 1 accounts for "equal date" loans.
Query 2:
WITH NormalizedLoans AS (
SELECT BorrowerID, StartDate, DueDate, COUNT(1) LoanCount
FROM dbo.Loans
GROUP BY BorrowerID, StartDate, DueDate
)
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate
AND (L1.StartDate <> L2.StartDate
OR L1.DueDate <> L2.DueDate)
)
OR LoanCount > 1
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM NormalizedLoans L1;
Results:
| BORROWERID | STARTDATE | DUEDATE | LOANCOUNT | HASOVERLAPPINGLOAN |
|------------|----------------------------------|---------------------------------|-----------|--------------------|
| 1 | September, 02 2012 00:00:00+0000 | October, 01 2012 00:00:00+0000 | 1 | 0 |
| 1 | December, 20 2012 00:00:00+0000 | January, 06 2013 00:00:00+0000 | 1 | 0 |
| 1 | February, 20 2013 00:00:00+0000 | February, 24 2013 00:00:00+0000 | 1 | 0 |
| 2 | October, 05 2012 00:00:00+0000 | October, 21 2012 00:00:00+0000 | 1 | 0 |
| 3 | November, 07 2012 00:00:00+0000 | November, 09 2012 00:00:00+0000 | 1 | 0 |
| 3 | January, 07 2013 00:00:00+0000 | January, 22 2013 00:00:00+0000 | 1 | 1 |
| 3 | January, 15 2013 00:00:00+0000 | January, 18 2013 00:00:00+0000 | 1 | 1 |
| 4 | December, 01 2012 00:00:00+0000 | January, 01 2013 00:00:00+0000 | 1 | 1 |
| 4 | December, 01 2012 00:00:00+0000 | January, 14 2013 00:00:00+0000 | 1 | 1 |
| 5 | February, 20 2013 00:00:00+0000 | February, 24 2013 00:00:00+0000 | 2 | 1 |
This query logic did not change (other than switching out the beginning).
Query 3:
WITH NormalizedLoans AS (
SELECT BorrowerID, StartDate, DueDate, COUNT(1) LoanCount
FROM dbo.Loans
GROUP BY BorrowerID, StartDate, DueDate
),
OverlappingLoans AS (
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate
AND (L1.StartDate <> L2.StartDate
OR L1.DueDate <> L2.DueDate)
)
OR LoanCount > 1
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM NormalizedLoans L1
),
OverlappingBorrower AS (
SELECT BorrowerID, MAX(HasOverlappingLoan) HasOverlappingLoan
FROM OverlappingLoans
GROUP BY BorrowerID
)
SELECT *
FROM OverlappingBorrower
WHERE hasOverlappingLoan = 0;
Results:
| BORROWERID | HASOVERLAPPINGLOAN |
|------------|--------------------|
| 1 | 0 |
| 2 | 0 |
4) In this counting query we need to incorporate the "equal date" loan counts again. For that we use SUM(LoanCount) instead of a plain COUNT. We also have to multiply hasOverlappingLoan with the LoanCount to get the correct overlapping count again.
Query 4:
WITH NormalizedLoans AS (
SELECT BorrowerID, StartDate, DueDate, COUNT(1) LoanCount
FROM dbo.Loans
GROUP BY BorrowerID, StartDate, DueDate
),
OverlappingLoans AS (
SELECT
*,
CASE WHEN EXISTS(SELECT 1 FROM dbo.Loans L2
WHERE L2.BorrowerID = L1.BorrowerID
AND L1.StartDate <= L2.DueDate
AND L2.StartDate <= l1.DueDate
AND (L1.StartDate <> L2.StartDate
OR L1.DueDate <> L2.DueDate)
)
OR LoanCount > 1
THEN 1
ELSE 0
END AS HasOverlappingLoan
FROM NormalizedLoans L1
)
SELECT BorrowerID,SUM(LoanCount) LoanCount, SUM(hasOverlappingLoan*LoanCount) OverlappingCount
FROM OverlappingLoans
GROUP BY BorrowerID;
Results:
| BORROWERID | LOANCOUNT | OVERLAPPINGCOUNT |
|------------|-----------|------------------|
| 1 | 3 | 0 |
| 2 | 1 | 0 |
| 3 | 3 | 2 |
| 4 | 2 | 2 |
| 5 | 2 | 2 |
I strongly suggest finding a way to use my first solution, as a loan table without a primary key is a, let's say "odd" design. However, if you really can't get there, use the second solution.

I got it working but in a bit convoluted way. It first gets borrowers that don't meet criteria in the inner query and returns the rest. The inner query has 2 parts:
Get all overlapping borrowings not starting on the same day.
Get all borrowings starting on the same date.
select distinct BorrowerID from borrowings
where BorrowerID NOT IN
(
select b1.BorrowerID from borrowings b1
inner join borrowings b2
on b1.BorrowerID = b2.BorrowerID
and b1.StartDate < b2.StartDate
and b1.DueDate > b2.StartDate
union
select BorrowerID from borrowings
group by BorrowerID, StartDate
having count(*) > 1
)
I had to use 2 separate inner queries as your table doesn't have a unique identifier for each record and using b1.StartDate <= b2.StartDate as I should have makes a record join to itself. It would be good to have a separate identifier for each record.

try
with cte as
(
  select *,
row_number() over (partition by b order by s) r
from loans
)
select l1.b
from loans l1
except
select c1.b
from cte c1
where exists (
 select 1
 from cte c2
where c2.b = c1.b
 and c2.r <> c1.r
and (c2.s between c1.s and c1.e
      or c1.s between c2.s and c2.e)
)

If you're on SQL 2012, you can do it like this:
with cte as (
select
BorrowerID,
StartDate,
DueDate,
lag(DueDate) over (partition by borrowerid order by StartDate, DueDate) as PrevDueDate
from test
)
select
distinct BorrowerID
from cte
where BorrowerID not in
(select BorrowerID
from cte
where StartDate <= PrevDueDate)

Related

MSSQL Count Multiple Columns

Say I have a table like this in ms sql 2008:
+------+--------+---------+
| year | JAN | FEB |
+------+--------+---------+
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | 8Z | 8Z |
| 2016 | R5205 | R5205 |
| 2016 | 5K2 | 5K2 |
| 2016 | 5K2 | 5K2 |
| 2016 | NULL | NULL |
| 2016 | TE | NULL |
| 2016 | TE | NULL |
| 2016 | 8Z | 8Z |
+------+--------+---------+
And I want to get a count for each column, something like this
+------+--------+---------+
| opt | JAN_cnt| FEB_cnt |
+------+--------+---------+
| 5K2 | 5 | 4 |
| 8Z | 2 | 2 |
| R5205| 1 | 1 |
| TE | 2 | 0 |
| NULL | 1 | 4 |
+------+--------+---------+
First, can this be done? Second, how? I have searched, but cant find exactly what I am looking for.
I think the simplest way is to use UNION ALL with conditional aggregation using CASE EXPRESSION :
SELECT s.opt,
COUNT(CASE WHEN s.ind_from = 1 THEN 1 END) as jan_cnt,
COUNT(CASE WHEN s.ind_from = 2 THEN 1 END) as feb_cnt
FROM (
SELECT t1.jan as opt,1 as ind_from FROM YourTable t1
UNION ALL
SELECT t2.feb,2 FROM YourTable t2) s
GROUP BY s.opt
I would advise putting the values into a different format:
opt
month
cnt
You can do this as:
select opt, mon, count(*) as cnt
from ((select jan as opt, 'jan' as mon from t) union all
(select feb as opt, 'feb' as mon from t)
) o
group by opt, mon;
It is easy enough to switch this to your format:
select opt, sum(jan) as jan, sum(feb) as feb
from ((select jan as opt, 1 as jan, 0 as feb from t) union all
(select feb as opt, 0, 1, from t)
) o
group by opt;
I just prefer the first format. It is easier to generalize to more columns.
SELECT COALESCE(t1.JAN, t2.FEB), t1.JAN_cnt, t2.FEB_cnt
FROM
(
SELECT JAN, COUNT(*) AS JAN_cnt
FROM yourTable
GROUP BY JAN
) t1
FULL OUTER JOIN
(
SELECT FEB, COUNT(*) AS FEB_cnt
FROM yourTable
GROUP BY FEB
) t2
ON t1.JAN = t2.FEB

Get the Last Two Non-Null Values for a column in SQL Server 2008 R2

Right now, I have a select statement that returns this result set:
| date | id | price |
+--------+-----+-------+
| Jan 01 | XYZ | 5.00 |
| Jan 02 | XYZ | NULL |
| Jan 03 | XYZ | NULL |
| Jan 06 | XYZ | NULL |
| Jan 07 | XYZ | 3.00 |
| Jan 08 | XYZ | NULL |
The problem is that I want to get the last known price for the product with the id of XYZ in the row if the value is NULL, but only to grab this if it is within two days. So for Jan 02 & Jan 03 I'd like to see the value from Jan 01, but not for Jan 06.
Here's what I mean:
| date | id | price |
+--------+-----+-------+
| Jan 01 | XYZ | 5.00 |
| Jan 02 | XYZ | 5.00 | (Jan 01)
| Jan 03 | XYZ | 5.00 | (Jan 01)
| Jan 06 | XYZ | NULL | << Stays NULL
| Jan 07 | XYZ | 3.00 |
| Jan 08 | XYZ | 3.00 | (Jan 07)
Here's the table definition:
CREATE TABLE dbo.catalogue
(
[date] DATE NOT NULL ,
[id] VARCHAR(3) NOT NULL ,
[price] FLOAT
)
And the Sample Data:
INSERT INTO dbo.catalogue
( [date], [id], [price] )
VALUES
( '2015-01-01', 'XYZ', 5.00 ),
( '2015-01-02', 'XYZ', NULL ),
( '2015-01-03', 'XYZ', NULL ),
( '2015-01-06', 'XYZ', NULL ),
( '2015-01-07', 'XYZ', 3.00 ),
( '2015-01-08', 'XYZ', NULL )
Edit: Also, I should mention that this is within a stored procedure's subquery, so performance will definitely matter.
Thanks in advance.
Another solution using APPLY:
SELECT
c.[date],
c.id,
price = ISNULL(c.price, x.price)
FROM dbo.catalogue c
OUTER APPLY(
SELECT TOP 1 price
FROM dbo.catalogue
WHERE
DATEDIFF(DAY, [date], c.[date]) <= 2
AND c.[date] > [date]
AND id = c.id
ORDER BY date DESC
)x
If you were using 2012+ this would be a bit easier using LAG but you can do this with a cte in 2008. Thank you for posting consumable ddl and sample data. That makes this a lot easier.
Here is one way to do this.
with cte as
(
select *
, ROW_NUMBER() over (partition by id order by date) as RowNum
from catalogue
)
select c.date
, c.id
, isnull(case when DATEDIFF(day, c2.date, c.date) <= 2
then
(
select MAX(price) from cte c3
where c3.RowNum >= c2.RowNum - 1
)
end, c.price) as NewPrice
from cte c
left join cte c2 on c2.RowNum = c.RowNum - 1

How to generate a compounded view of data over time in Oracle SQL

Say I have a base number 10 and a table that has a value of 20 associated to November 2013, and a value of 10 associated to March 2014. I want to populate a list of all months, and their compounded value. So from May-November 2013, the value should be 10, then between Nov and Mar, the value should be 10+20 and afterwards it should be 10+20+10.
So in a table I have the following
MONTH VALUE
Nov-2013 20
Mar-2014 10
I'd like to have a select statement that somehow returns. There's an initial value of 10, hard-coded as the base.
MONTH VALUE
May-2013 10
Jun-2013 10
Jul-2013 10
Aug-2013 10
Sep-2013 10
Oct-2013 10
Nov-2013 30
Dec-2013 30
Jan-2014 30
Feb-2014 30
Mar-2014 40
Is this doable?
In case I understand your requirements correctly,
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE months
("MON" date, "VALUE" int)
;
INSERT ALL
INTO months ("MON", "VALUE")
VALUES (date '2013-11-01', 20)
INTO months ("MON", "VALUE")
VALUES (date '2014-03-01', 10)
SELECT * FROM dual
;
Query 1:
with months_interval as (
select date '2013-05-01' interval_start,
max(mon) interval_end
from months
)
, all_months as (
select add_months(m.interval_start,level-1) mon
from months_interval m
connect by level <= months_between(interval_end, interval_start) + 1
), data_to_sum as (
select am.mon,
decode(am.mon, first_value(am.mon) over(order by am.mon), 10, m.value) value
from months m, all_months am
where am.mon = m.mon(+)
)
select mon, value, sum(value) over(order by mon) cumulative
from data_to_sum
order by 1
Results:
| MON | VALUE | CUMULATIVE |
----------------------------------------------------------
| May, 01 2013 00:00:00+0000 | 10 | 10 |
| June, 01 2013 00:00:00+0000 | (null) | 10 |
| July, 01 2013 00:00:00+0000 | (null) | 10 |
| August, 01 2013 00:00:00+0000 | (null) | 10 |
| September, 01 2013 00:00:00+0000 | (null) | 10 |
| October, 01 2013 00:00:00+0000 | (null) | 10 |
| November, 01 2013 00:00:00+0000 | 20 | 30 |
| December, 01 2013 00:00:00+0000 | (null) | 30 |
| January, 01 2014 00:00:00+0000 | (null) | 30 |
| February, 01 2014 00:00:00+0000 | (null) | 30 |
| March, 01 2014 00:00:00+0000 | 10 | 40 |
This one is probably slightly suboptimal performance-wise (queries months table twice etc.) and should be optimized, but the idea is like this - pregenerate a list of months (I assumed your interval start is somehow fixed), left join it to your data, use analytic sum function.

weekly aggregate with CTE not behaving as expected

I have this USERS table with users that can be of two different types (A and B). I need to show a report with the aggregate per type for each week. The query I have so far works well except some weeks are not grouping properly. In the example below, the week starting Jan 28th should have one line, not two.
Week Starts |Week| Type A | Type B
------------+----+--------+------
2013-02-04 | 14 | 2 | 26
2013-01-28 | 13 | 5 | 191
2013-01-28 | 13 | 0 | 24
2013-01-21 | 12 | 1 | 134
2013-01-21 | 12 | 0 | 20
2013-01-14 | 11 | 1 | 143
2013-01-14 | 11 | 0 | 2
2013-01-07 | 10 | 0 | 233
2013-01-07 | 10 | 0 | 23
2012-12-31 | 9 | 0 | 12
2012-12-31 | 9 | 4 | 164
2012-12-31 | 9 | 0 | 20
SQL
;with cte as
(
select DATEADD(m,-3,GETDATE()) firstday, DATEADD(m,-3,GETDATE()) + 6 - DATEDIFF(day, 0, DATEADD(m,-3,GETDATE())) %7 lastday, 1 week
union all
select lastday + 1, case when GETDATE() < lastday + 7 then GETDATE() else lastday + 7 end, week + 1
from cte
where lastday < GETDATE()
)
SELECT
cast(firstday as date) 'Week Starts',
cte.week as 'Week',
Sum(CASE WHEN USR_TYPE = 'A' THEN 1 ELSE 0 END) As 'Type A',
Sum(CASE WHEN USR_TYPE = 'B' THEN 1 ELSE 0 END) As 'Type B'
FROM cte left join USERS
ON cte.firstday <= USERS.CREATED
AND cte.lastday > USERS.CREATED
GROUP BY cte.week, cte.firstday, cte.lastday, DATEPART(YEAR,USERS.CREATED), DATEPART(wk,USERS.CREATED)
ORDER BY week desc
What am I doing wrong?
Without seeing any data from your users table I am going to take a guess.
The list of dates you are generating in the CTE includes the time.
You might need to cast() your firstday and lastday values as either a date or generate the list with no time.
See a SQL Fiddle Demo
Sample from your CTE and the new dates cast:
| CASTFIRSTDAY | CASTLASTDAY | WEEK | FIRSTDAY | LASTDAY |
---------------------------------------------------------------------------------------------------------
| 2012-11-05 | 2012-11-11 | 1 | November, 05 2012 20:08:10+0000 | November, 11 2012 20:08:10+0000 |
| 2012-11-12 | 2012-11-18 | 2 | November, 12 2012 20:08:10+0000 | November, 18 2012 20:08:10+0000 |
| 2012-11-19 | 2012-11-25 | 3 | November, 19 2012 20:08:10+0000 | November, 25 2012 20:08:10+0000 |
| 2012-11-26 | 2012-12-02 | 4 | November, 26 2012 20:08:10+0000 | December, 02 2012 20:08:10+0000 |
| 2012-12-03 | 2012-12-09 | 5 | December, 03 2012 20:08:10+0000 | December, 09 2012 20:08:10+0000 |
| 2012-12-10 | 2012-12-16 | 6 | December, 10 2012 20:08:10+0000 | December, 16 2012 20:08:10+0000 |
You might want to edit your CTE to return the date only values:
;with cte as
(
select
cast(DATEADD(m,-3,GETDATE()) as date) firstday,
cast(DATEADD(m,-3,GETDATE()) + 6 - DATEDIFF(day, 0, DATEADD(m,-3,GETDATE())) %7 as DATE) lastday,
1 week
union all
select
cast(DATEADD(DAY, 1, lastday) as date),
case
when cast(GETDATE() as date) < cast(DATEADD(DAY, 7, lastday) as date)
then cast(GETDATE() as date)
else cast(DATEADD(DAY, 7, lastday) as date)
end,
week + 1
from cte
where cast(lastday as date) < cast(GETDATE() as date)
)
select *
from cte
See SQL Fiddle with Demo

Doubts in query conditions

I have two tables
1.Employee
EMP_NAME,
EMP_CODE
2.Vacations
EMP_NAME,
EMP_CODE,
VACATION_START_DATE-->date type
VACATION_END_DATE-->date type,
My question is how to query to get the EMP_NAME from table1(Employee), where the today is not in between VACATION_START_DATE and VACATION_END_DATE from table2(Vacations)..
Try this please: assuming that you do not have emp_name vacations table..as well as emp_code as the relationship between the two table. So you can use joins.
SQLFIDDLE DEMO
select e.emp_code, e.emp_name,
v.start_date, v.end_Date
from emp e
inner join
vacation v
on e.emp_code = v.emp_code
where not (Now() between v.start_date
and v.end_date)
;
| EMP_CODE | EMP_NAME | START_DATE | END_DATE |
-------------------------------------------------------------------------------------------
| 1 | john | December, 10 2012 00:00:00+0000 | December, 20 2012 00:00:00+0000 |
| 2 | kate | December, 20 2012 00:00:00+0000 | December, 30 2012 00:00:00+0000 |
| 3 | tim | December, 24 2012 00:00:00+0000 | January, 01 2013 00:00:00+0000 |
| 1 | john | January, 01 2013 00:00:00+0000 | January, 08 2013 00:00:00+0000 |
Not sure whether you meant "today" was a variable or the system date, so the following code may need to be modified, but I think it might work:
SELECT
EMP_NAME
FROM
EMPLOYEE
WHERE
NOT EXISTS (SELECT * FROM VACATIONS WHERE EMP_NAME = EMPLOYEE.EMP_NAME AND
VACATIONS.VACATION_START_DATE >= Today AND
VACATIONS.VACATION_END_DATE <= Today)