Grouping by date range combined with another field? - sql

In SQL Server 2008, I have something like the following:
Create table #RateHistory (RatePlan char(1), EventDate datetime)
Insert into #RateHistory (RatePlan, EventDate)
VALUES
('a','10/01/2013')
,('a','10/04/2013')
,('a','10/06/2013')
,('a','10/08/2013')
,('b','10/21/2013')
,('b','11/05/2013')
,('b','11/12/2013')
,('b','12/05/2013')
,('a','12/08/2013')
,('a','12/09/2013')
,('a','12/10/2013')
,('a','12/15/2013')
I'd like to see an output like this:
Rateplan MinDate MaxDate
-------- ----------- -----------
a 2013-10-01 2013-10-08
b 2013-10-21 2013-12-05
a 2013-12-08 2013-12-15
(originally this was a bit different, but I believe this result set makes it clearer what I actually need, which is the correct grouping)
Note that RatePlan "a" shows up twice, and that I want it to be grouped separately - once for the 10/1/2013 to 10/8/2013 data, and once for the 12/8/2013 to 12/15/2013 data. I've got the solution I need with this :
-- Get initial row numbers
;with Test as (
Select
*
,RowNumber = ROW_NUMBER() over (order by EventDate)
from #RateHistory
)
-- Get initial row numbers
, Test2 as (
SelecT
Main.RowNumber
,Main.EventDate
,Main.RatePlan
,FollowingRatePlan = Following.RatePlan
,NewGroup =
case
when Main.RatePlan <> Following.RatePlan
-- if Following RatePlan is null, that means this is the last record
or (Following.RatePlan is null )
then Main.EventDate
else null
end
from Test Main
left join Test following
on Following.RowNumber = Main.RowNumber + 1
)
, Test3 as (
select
#RateHistory.RatePlan
,#RateHistory.EventDate
,MaxDate = min(Test2.NewGroup)
from #RateHistory
join Test2
on #RateHistory .RatePlan = Test2.RatePlan
and #RateHistory .EventDate <= Test2.NewGroup
where Test2.NewGroup is not null
group by
#RateHistory.RatePlan
,#RateHistory.EventDate
)
select Rateplan, MinDate = MIN(EventDate) , MaxDate
from Test3
group by RatePlan,MaxDate
...but I'm thinking - there's GOT to be a better, more elegant way of doing this. Thoughts? If nobody has anything better, I'll just go ahead and put this in as an answer...
Thanks!

I can think of a solution using correlated scalar sub-queries. You tell me if it's more elegant. Or better performing.
select distinct
rh0.RatePlan,
(
select min(EventDate)
from RateHistory rh1
where rh1.RatePlan = rh0.RatePlan
and rh1.EventDate <= rh0.EventDate
and not exists
(
select * from RateHistory rh2
where rh2.RatePlan != rh0.RatePlan
and rh2.EventDate > rh1.EventDate
and rh2.EventDate < rh0.EventDate
)
) as mindate,
(
select max(EventDate)
from RateHistory rh1
where rh1.RatePlan = rh0.RatePlan
and rh1.EventDate >= rh0.EventDate
and not exists
(
select * from RateHistory rh2
where rh2.RatePlan != rh0.RatePlan
and rh2.EventDate < rh1.EventDate
and rh2.EventDate > rh0.EventDate
)
) as maxdate
from RateHistory rh0
order by mindate
Check out the SQLFiddle. BTW 2012 has some cool features that could make your version of the query more elegant.

Related

is it possible to use a column from a query, check its value and create a new column based on that value?

I hope my title is not too confusing, I basically have a query that looks something like that:
Select tblA.Field1, convert(Varcharr(10),
Select Min(f) from (
values (tblA.Date1),
(tblA.Date2),
(tblA.Date3) ) As Fields(f) ), 101) as MinDate
Let's say I get the following results:
Field1 MinDate
17 10/02/2016
29 03/02/2018
now, if in that same query, I want to create a new column, say MinDate2, and update the MinDate in the above result set, can I do something like that, say if I want to take the MinDate and do something like: (basically I want to add this in my query above)
, if MinDate = even year, then add 1 year, if MinDate is odd year then add 2 years
So that my results look like this:
Field1 MinDate MinDate2
17 10/02/2016 10/02/2017
29 03/02/2018 03/02/2020
Is something like that doable?
The reason why I'm trying to do it this way is because I am pulling the Min date from over 80 different columns, so as is, the query is a monster and I would like to keep it as short as possible (as long as it does what I need it to do)
You can use SQL like this to reuse calculated expressions. A subquery would also work too, it's more of a preference call.
WITH INTERIM_RESULT AS
( SELECT YT.SomeField1 + YT.SomeField2 Your_Value
FROM Your_Table YT
)
SELECT Your_Value + 5 AS New_Value
FROM INTERIM_RESULT
In a subquery it'll look like this:
SELECT Your_Value,
Your_Value + 5 AS New_Value
FROM (SELECT YT.SomeField1 = YT.SomeField2 AS Your_Value
FROM Your_Table
) INTERIM_RESULT
For your example in the comments:
SELECT Field1,
Field2,
Case When Field2 = 1 then 'Hey'
Else 'Bye'
END as Field3
FROM ( SELECT Field1,
Something AS Field2
FROM SOME_TABLE
) TMP
An example to do this (not sure if your looking to do this from your top query or subset of data) and you dont appear to have actual table structures in your query so an example to do it and you can convert it to be in your code:
DECLARE #DateColumn AS DATETIME = GETDATE()
-- the %2 does a check and if 0 is returned it is even, otherwise null and the case statement does the dateadd for you
SELECT #DateColumn, CASE WHEN YEAR(#DateColumn) % 2 = 0 THEN DATEADD(yy, 1, #DateColumn) ELSE DATEADD(yy, 2, #DateColumn) END
This takes the DateColumn year and if results of YourValue % 2 returns 1 means it ODD, otherwise Value is Even
A CROSS APPLY could be handy here.
That way the minimum date can be re-used.
And a modulus % can be used to calculate the odd + 2 year & even + 1 year.
Sample data:
-- Temporary test table
create table #tblTest (Field1 int, Date1 date, Date2 date, Date3 date);
-- Sample data
insert into #tblTest (Field1, Date1, Date2, Date3) values
(17, '2016-02-20', '2016-02-10', '2016-02-15'),
(29, '2017-02-15', '2017-02-09', '2017-02-03');
Query:
SELECT
t.Field1,
CONVERT(VARCHAR(10), ca.MinDate, 101) as MinDate,
CONVERT(VARCHAR(10), DATEADD(year, 1+YEAR(ca.MinDate)%2, ca.MinDate), 101) as MinDate2
FROM #tblTest t
CROSS APPLY (
SELECT MIN(v.dt) as MinDate
FROM (VALUES
(t.Date1), (t.Date2), (t.Date3)
) As v(dt)
) ca;
Result:
Field1 MinDate MinDate2
17 02/10/2016 02/10/2017
29 02/03/2017 02/03/2019

SQL Update Row where First Row NULL

I'm using SQL Server 2008 R2 and need to update the first row for each SOrder from another table where the date value is null. In the example below I would evaluate whether SOrder = 000791 and REVDE2 is null, if so I would
SELECT SOrder, MyDates.NewDate FROM MyDates
UPDATE MyTable SET DateVate = MyDates.NewDate WHERE REVDE2 IS NULL
REVDE3 must remain null until the MyDates.NewDate > REVDE2 DateValue
SOrder Line FieldName DateValue
000791 0001 REVDEL 01/12/2013
000791 0001 REVDE2 NULL
000791 0001 REVDE3 NULL
000992 0001 REVDEL 05/01/2014
000992 0001 REVDE2 08/12/2014
000992 0001 REVDE3 NULL
I'm not sure if a cursor would work, my only concern is the speed for running a cursor.
WITH t AS (
SELECT *,ROW_NUMBER() OVER(PARTITION BY SOrder ORDER BY FieldName) AS rownum
FROM MyDates
WHERE DateValue IS NULL
)
UPDATE t
SET DateValue = NewDate
WHERE rownum = 1
Here's a Fully Working Code Sample: http://sqlfiddle.com/#!3/fd3d1/15
Here's the actual SQL Code that will do the updates. The SQL Fiddle above has the Schema and Inserts and Updates.
The assumption in this code is that FieldValue will not go beyond REVDE9, because if it hits REVDE10, then the sorting in the query will go whack!
;WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY SOrder ORDER BY FieldName ASC) AS rn
FROM MyTable
WHERE DateValue IS NULL
)
-- -- SELECT * FROM CTE
UPDATE MyTable
SET DateValue = md.NewDate
FROM MyTable mt
INNER JOIN MyDates md ON mt.SOrder = md.SOrder
INNER JOIN cte c ON mt.SOrder = c.SOrder
AND mt.FieldName = c.FieldName
AND c.rn = 1;

Remove duplicates in SQL Result set of ONE table

Afternoon/Evening all,
I'm looking for the final touches to the below query. I need to remove the duplicate occurrences of a column in a particular row. Currently using the below SQL:
SELECT CBNEW.*
FROM CallbackNewID CBNEW
INNER JOIN (SELECT IDNEW, MAX(CallbackDate) AS MaxDate
FROM CallbackNewID
GROUP BY IDNEW) AS groupedCBNEW
ON (CBNEW.CallbackDate = groupedCBNEW.MaxDate) AND (CBNEW.IDNEW = groupedCBNEW.IDNEW);
My result set looks like the below
ID RecID Comp Rem Date_ IDNEW IDOLD CB? CallbackDate
138618 83209 1 0 2012-03-16 12:40:00 83209 83209 2 16-Mar-12
138619 83209 1 0 2012-03-16 12:40:00 83209 83209 2 16-Mar-12
110470 83799 1 0 2011-07-27 11:46:00 83799 83799 10 27-Jul-11
110471 83799 1 0 2011-07-27 11:46:00 83799 83799 10 27-Jul-11
This however gives me duplicate values in the CallBackDate and IDNEW Column because in the table there are some different Primary Keys with the same IDNEW and CallbackDate values.
If I dump this result into Excel, I can just use remove duplicates on the first ID column, and the problem's solved.
But what I want to do is make sure my result only includes the FIRST instance of the ID column, where IDNEW and CallbackDate are duplicated.
I'm sure I just need to append a tiny piece of SQL, but I'm stuck if I can find the answer so far.
Your help is very much appreciated.
Try adding MIN(ID) to the inner query and then adding it also on the ON clause:
SELECT CBNEW.*
FROM CallbackNewID CBNEW
INNER JOIN (SELECT IDNEW, MIN(ID) AS MinId, MAX(CallbackDate) AS MaxDate
FROM CallbackNewID
GROUP BY IDNEW) AS groupedCBNEW
ON (CBNEW.CallbackDate = groupedCBNEW.MaxDate)
AND (CBNEW.IDNEW = groupedCBNEW.IDNEW)
AND (CBNEW.ID = groupedCBNEW.MinId) ;
sqlfiddle demo
Here is a rather "brute force" approach. It just takes the results of your original query and does Min() on [ID], Max() on [Comp] and [Rem], and GROUP BY on everything else:
SELECT
Min(t.ID) AS MinOfID,
t.RecID,
Max(t.Comp) AS MaxOfComp,
Max(t.Rem) AS MaxOfRem,
t.Date_,
t.IDNEW,
t.IDOLD,
t.[CB?],
t.CallbackDate
FROM
(
SELECT CBNEW.*
FROM
CallbackNewID CBNEW
INNER JOIN
(
SELECT IDNEW, MAX(CallbackDate) AS MaxDate
FROM CallbackNewID
GROUP BY IDNEW
) AS groupedCBNEW
ON (CBNEW.CallbackDate = groupedCBNEW.MaxDate)
AND (CBNEW.IDNEW = groupedCBNEW.IDNEW)
) t
GROUP BY
t.RecID,
t.Date_,
t.IDNEW,
t.IDOLD,
t.[CB?],
t.CallbackDate;
It might not be terribly elegant, but if it works....
In MS SQL Server, I think you are looking for the ROW_NUMBER() function.
Something like this should help you get what you are looking for:
SELECT
X.*
FROM
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY DBNEW.IDNEW, DBNEW.MaxDate) [row_num]
FROM
CallbackNewID CBNEW
INNER JOIN
(
SELECT
IDNEW,
MAX(CallbackDate) AS MaxDate
FROM
CallbackNewID
GROUP BY
IDNEW
) AS groupedCBNEW ON (CBNEW.CallbackDate = groupedCBNEW.MaxDate) AND (CBNEW.IDNEW = groupedCBNEW.IDNEW)
) X
WHERE
X.row_num = 1
SELECT
A.*
FROM
(SELECT
*,
ROW_NUMBER() OVER (PARTITION BY IDNEW ORDER BY CallbackDate DESC)
AS [row_num]
FROM CallbackNewID
) A
WHERE
A.row_num = 1

Subquery within SubQuery in SQL - DB2

I am having issue when trying to make a the sub query shown in the first filter dynamically based on one of the results returned from the query. Can someone please tell me what I am doing wrong. In the first subquery it worked.
( SELECT
MAX( MAX_DATE - MIN_DATE ) AS NUM_CONS_DAYS
FROM
(
SELECT
MIN(TMP.D_DAT_INDEX_DATE) AS MIN_DATE,
MAX(TMP.D_DAT_INDEX_DATE) AS MAX_DATE,
SUM(INDEX_COUNT) AS SUM_INDEX
FROM
(
SELECT
D_DAT_INDEX_DATE,
INDEX_COUNT,
D_DAT_INDEX_DATE - (DENSE_RANK() OVER(ORDER BY D_DAT_INDEX_DATE)) DAYS AS G
FROM
DWH.MQT_SUMMARY_WATER_READINGS
WHERE
N_COD_METER_CNTX_KEY = 79094
) AS TMP
GROUP BY
TMP.G
ORDER BY
1
) ) AS MAX_NUM_CONS_DAYS
Above is the subquery I am trying to replace 123456 with CTXTKEY or CTXT.N_COD_METER_CNTX_KEY from query. Below is the full code. Please note than in the subquery before "MAX_NUM_CONS_DAYS" it worked. However, it was only one subquery down.
SELECT
N_COD_WM_DWH_KEY,
V_COD_WM_SN_2,
N_COD_SP_ID,
CTXKEY,
V_COD_MIU_SN,
N_COD_POD,
MIU_CAT,
V_COD_SITR_ASSOCIATED,
WO_INST_DATE,
WO_MIU_CAT,
DAYSRECEIVED3,
MAX_NUM_CONS_DAYS,
( CASE WHEN ( DAYSRECEIVED3 = 3 ) THEN 'Y' ELSE 'N' END ) AS GREEN,
( CASE WHEN ( DAYSRECEIVED3 < 3 AND DAYSRECEIVED3 > 0 ) THEN 'Y' ELSE 'N' END ) AS BLUE,
( CASE WHEN ( DAYSRECEIVED3 = 0 AND MAX_NUM_CONS_DAYS >= 5 ) THEN 'Y' ELSE 'N' END ) AS ORANGE,
( CASE WHEN ( DAYSRECEIVED3 = 0 AND MAX_NUM_CONS_DAYS BETWEEN 1 and 4 ) THEN 'Y' ELSE 'N' END ) AS RED
FROM
(
SELECT
WMETER.N_COD_WM_DWH_KEY,
WMETER.V_COD_WM_SN_2,
WMETER.N_COD_SP_ID,
CTXT.N_COD_METER_CNTX_KEY AS CTXKEY,
CTXT.V_COD_MIU_SN,
CTXT.N_COD_POD,
MIU.N_COD_MIU_CATEGORY AS MIU_CAT,
CTXT.V_COD_SITR_ASSOCIATED,
T1.D_DAT_PLAN_INST AS WO_INST_DATE,
T1.N_COD_MIU_CATEGORY AS WO_MIU_CAT,
( SELECT COUNT( DISTINCT D_DAT_INDEX_DATE ) FROM DWH.MQT_SUMMARY_WATER_READINGS WHERE ( N_COD_METER_CNTX_KEY = CTXT.N_COD_METER_CNTX_KEY ) AND D_DAT_INDEX_DATE BETWEEN ( '2013-07-10' ) AND ( '2013-07-12' ) ) AS DAYSRECEIVED3,
( SELECT
MAX( MAX_DATE - MIN_DATE ) AS NUM_CONS_DAYS
FROM
(
SELECT
MIN(TMP.D_DAT_INDEX_DATE) AS MIN_DATE,
MAX(TMP.D_DAT_INDEX_DATE) AS MAX_DATE,
SUM(INDEX_COUNT) AS SUM_INDEX
FROM
(
SELECT
D_DAT_INDEX_DATE,
INDEX_COUNT,
D_DAT_INDEX_DATE - (DENSE_RANK() OVER(ORDER BY D_DAT_INDEX_DATE)) DAYS AS G
FROM
DWH.MQT_SUMMARY_WATER_READINGS
WHERE
N_COD_METER_CNTX_KEY = 79094
) AS TMP
GROUP BY
TMP.G
ORDER BY
1
) ) AS MAX_NUM_CONS_DAYS
FROM DWH.DWH_WATER_METER AS WMETER
LEFT JOIN DWH.DWH_WMETER_CONTEXT AS CTXT
ON WMETER.N_COD_WM_DWH_KEY = CTXT.N_COD_WM_DWH_KEY
LEFT JOIN DWH.DWH_MIU AS MIU
ON CTXT.V_COD_MIU_SN = MIU.V_COD_MIU_SN
LEFT JOIN
( SELECT V_COD_CORR_WAT_METER_SN, D_DAT_PLAN_INST, N_COD_MIU_CATEGORY
FROM DWH.DWH_ORDER_MANAGEMENT_FACT
JOIN DWH.DWH_MIU
ON DWH.DWH_ORDER_MANAGEMENT_FACT.V_COD_MIU_SN = DWH.DWH_MIU.V_COD_MIU_SN
) AS T1
ON WMETER.V_COD_WM_SN_2 = T1.V_COD_CORR_WAT_METER_SN
WHERE
( V_COD_SITR_ASSOCIATED = 'X' )
AND ( ( MIU.N_COD_MIU_CATEGORY <> 4 ) OR ( ( MIU.N_COD_MIU_CATEGORY IS NULL ) AND ( ( T1.N_COD_MIU_CATEGORY <> 4 ) OR ( T1.N_COD_MIU_CATEGORY IS NULL ) ) ) )
)
Error I am getting is:
Error Code: -204, SQL State: 42704
I would say that a good option here would be to use a CTE, or Common Table Expression. You can do something similar to the following:
WITH CTE_X AS(
SELECT VAL_A
,VAL_B
FROM TABLE_A)
,CTE_Y AS(
SELECT VAL_C
,VAL_B
FROM TABLE_B)
SELECT VAL_A
,VAL_B
FROM CTE_X X
JOIN CTE_Y Y
ON X.VAL_A = Y.VAL_C;
While this isn't specific to your example, it does show that CTE's create a sort of temporary "in memory" table that you can access in a subsequent query. This should allow you to issue your inner two subselects as a CTE, and then use the CTE in the "SELECT MAX( MAX_DATE - MIN_DATE ) AS NUM_CONS_DAYS" query.
You cannot reference columns from the outer select in the subselect, no more than 1 level deep anyway. If I correctly understand what you're doing, you'll probably need to join DWH.MQT_SUMMARY_WATER_READINGS and DWH.DWH_WMETER_CONTEXT in the outer select.

Query which gives list of dates between two date ranges

I am sorry for this but my previous question was not properly framed, so creating another post.
My question is similar to following question:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:14582643282111
I need to write inner query which will give me a list of dates between two date ranges to outer query.
My inner query returns following 2 rows:
SELECT request.REQ_DATE, request.DUE_DATE FROM myTable where id = 100
REQ_DATE DUE_DATE
3/19/2013 3/21/2013
3/8/2013 3/8/2013
So I need inner query which will return following dates to outer query:
3/19/2013
3/20/2013
3/21/2013
3/8/2013
The answer in above post has start date and end date hard coded and in my case, it is coming from other table. So I am trying to write query like this which does not work:
 
Select * from outerTable where my_date in
(
select to_date(r.REQ_DATE) + rownum -1 from all_objects,
(
SELECT REQ_DATE, DUE_DATE
FROM myTable where id = 100
) r
where rownum <= to_date(r.DUE_DATE,'dd-mon-yyyy')-to_date(r.REQ_DATE,'dd-mon-yyyy')+1;
)
with
T_from_to as (
select
trunc(REQ_DATE) as d_from,
trunc(DUE_DATE) as d_to
FROM myTable
where id = 100
),
T_seq as (
select level-1 as delta
from dual
connect by level-1 <= (select max(d_to-d_from) from T_from_to)
)
select distinct d_from + delta
from T_from_to, T_seq
where d_from + delta <= d_to
order by 1