Sub query inside a decode statement in oracle - sql

I have data in a table like below:
EMPLOYEE_NUM START_DTM END_DTM
47567567446 5/9/2019 12:00:00 PM
76475676575675756 5/10/2019 12:00:00 PM 5/10/2019 11:59:59 PM
456756765767 5/3/2019 12:00:00 PM 5/8/2019 11:59:59 PM
74676576764565 5/2/2019 12:00:00 PM 5/8/2019 11:59:59 PM
98695689576 5/1/2019 12:00:00 PM
I want retrieve employee_num with below conditions:
if there are any entries with NULL as end_dtm,
then print employee_num with max(start_dtm) and whose end_dtm is null
if there are no entries with NULL as end_dtm,
then print employee_num with end_dtm=max(end_dtm)
I tried with
select
decode(select count(1) from employee where end_dtm is null,
0,
select employee_num where end_dtm=(select max(end_dtm) from employee),
select employee_num where start_dtm=(select max(start_dtm) from employee where end_dtm is null),
) from dual
Looks like this is not a valid query.
Could anybody please help.

So, you must distinguish between END_DTM being null vs. non-null first, and only then consider either END_DTM or START_DTM for further ordering.
The function NVL2 is exactly what you need for the first part. It takes three arguments; it returns the second argument when the first is non-null, and the third argument when it's null. So: NVL2(END_DTM, 1, 0) will return 1 when the date is non-null and 0 when it is null. And then, you can use NVL2 again, to choose END_DTM when it is not null, but START_DTM when it is null.
You can then use this in defining ROW_NUMBER(), in the ORDER BY clause, like so:
select [whatever]
from
( select t.*,
row_number() over (order by nvl2(END_DTM, 1, 0),
nvl2(END_DTM, END_DTM, START_DTM) desc) rn
from your_table t
)
where rn = 1
;
If END_DTM is always non-null (or if it is always null) then the first NVL2 value is constant, so the ordering is only by END_DTM (if END_DTM is always non-null), resp. by START_DTM (if END_DTM is always null). But if there is a mix of null and non-null END_DTM, then only those with null END_DTM are tied by the first ordering criterion, and then only those rows are considered for selecting the max START_DTM.

I would have used analytical function for this as following:
Select employee_num from
(Select t.employee_num
Sum(case when end_dtm is null then 1 end) as cnt,
Row_number()
over (order by end_dtm desc nulls last) as end_dtm_rn,
Row_number()
over (order by case when end_dtm is null then start_dtm end desc nulls last) as start_dtm_rn
From employee t)
Where case when cnt = 0
then end_dtm_rn
Else start_dtm_rn end = 1;
Cheers!!

Just use aggregation and case:
select employee_num,
(case when count(*) = count(end_dtm) -- no nulls
then max(end_dtm)
else max(start_dtm)
end)
from t
group by employee_num;
If you want this per row in the original data, use analytic functions:
select t.*,
(case when count(*) over (partition by employee_num) = count(end_dtm) over (partition by employee_num) -- no nulls
then max(end_dtm) over (partition by employee_num)
else max(start_dtm) over (partition by employee_num)
end)
from t;
EDIT:
An alternative reading of what you want suggests that you want one employee_num subquery to your conditions. If that is the case, then this is even simpler:
select t.*
from (select t.*
from t
order by (case when end_dtm is null then 1 else 2 end), -- nulls first
start_dtm desc
) t
where rownum = 1;

Related

Query database of events to find only events meeting parameters

I have a dataset [Table_1] that records all events on a new row, meaning there are multiple entries for each customer_id. The structure is this;
customer_id
recorded_at
event_type
value
123-456-789
2022-05-28
status
open
123-456-789
2022-06-01
attribute
order_placed
123-456-789
2022-06-02
attribute
order_fulfilled
123-456-789
2022-06-04
status
closed
123-456-789
2022-06-05
attribute
order_placed
123-456-789
2022-06-07
attribute
order_fulfilled
123-456-789
2022-06-10
status
open
123-456-789
2022-06-11
attribute
order_placed
123-456-789
2022-06-12
attribute
order_fulfilled
123-456-789
2022-06-15
attribute
order_placed
123-456-789
2022-06-17
attribute
order_fulfilled
987-654-321
2022-06-12
status
open
987-654-321
2022-06-15
attribute
order_placed
987-654-321
2022-06-17
attribute
order_fulfilled
987-654-321
2022-06-17
status
closed
What I'm trying to do is write a query that returns the dates of the two attributes, order_placed and order_fulfilled after the last time the status went open. My approach is to query the dataset three times, first for all customers who went open, then returning the dates when the attributes are order_placed and order_cancelled, however I'm running into issues in returning all instances where the attributes are order_placed and order_fulfilled, not just the most recent one.
With d1 as (Select customer_id,recorded_at as open_time from Table_1 where event_type = 'status' and value = 'open')
Select d1.customer_id,
d1.open_time,
order_placed.order_placed_time,
order_fulfilled.order_filled_time
from d1
left join (Select customer_id,max(recorded_at) as order_placed_time from Table_1 where event_type = 'attribute' and value = 'order_placed') order_placed
on d1.customer_id = order_placed.customer_id and order_placed.order_placed_time > d1.open_time
left join (Select customer_id,max(recorded_at) as order_fulfilled_time from Table_1 where event_type = 'attribute' and value = 'order_fulfilled') order_filled
on d1.customer_id = order_filled.customer_id and order_filled.order_fulfilled_time > d1.open_time
where order_filled.order_fulfilled_time > order_placed.order_placed_time
However, this only returns the last time an order was placed and fulfilled after the status = open, not every instance where that happened. The output I am going for would look like:
customer_id
open_time
order_placed_time
order_filled_time
123-456-789
2022-05-28
2022-06-01
2022-06-01
123-456-789
2022-06-10
2022-06-11
2022-06-12
123-456-789
2022-06-10
2022-06-15
2022-06-17
987-654-321
2022-06-12
2022-06-15
2022-06-17
What I'm trying to do is write a query that returns the dates of the two attributes, order_placed and order_fulfilled after the last time the status went open.
Consider below query:
WITH orders AS (
SELECT *, SUM(IF(value IN ('open', 'closed'), 1, 0)) OVER w AS order_group
FROM sample
WINDOW w AS (PARTITION BY customer_id ORDER BY recorded_at, event_type)
)
SELECT customer_id, open_time, pre_recorded_at AS order_placed_time, recorded_at AS order_filled_time
FROM (
SELECT *, FIRST_VALUE(IF(value = 'open', recorded_at, NULL)) OVER w AS open_time,
LAG(recorded_at) OVER w AS pre_recorded_at,
FROM orders
WINDOW w AS (PARTITION BY customer_id, order_group ORDER BY recorded_at)
)
WHERE open_time IS NOT NULL AND value = 'order_fulfilled'
;
output will be:
Note: Due to transactions below in your dataset, orders CTE has a weired event_type column in ORDER BY clause. If you have more accurate timestamp recorded_at, it can be removed. I'll leave it to you.
WINDOW w AS (PARTITION BY customer_id ORDER BY recorded_at, event_type)
987-654-321 2022-06-17 attribute order_fulfilled
987-654-321 2022-06-17 status closed
One option to solve this problem is following these steps:
keep all rows found between an open and an end, hence remove the end and the others
assign a unique id to different couples of ("order_placed","order_fulfilled")
extract the values relative to "open_time", "order_placed_time" and "order_fulfilled_time" with a CASE statement in three separate fields
apply different aggregations over "open_time" and "order_placed/fulfilled_time" separately, as long as each "open_time" can have multiple couples of orders.
These four steps are implemented in two ctes.
The first cte includes:
the first COUNT, that allows to extract even values for the open/order_placed/order_fulfilled (orders following open) values and odd values for the closed/order_placed/order_fulfilled values (orders following closed):
the second COUNT, that allows to extract different values for each couple made of ("order_placed", "order_fulfilled")
SELECT *,
COUNT(CASE WHEN value = 'open' THEN 1
WHEN value = 'closed' THEN 0 END) OVER (
PARTITION BY customer_id
ORDER BY recorded_at, event_type
) AS status_value,
COUNT(CASE WHEN value <> 'order_fulfilled' THEN 1 END) OVER(
PARTITION BY customer_id
ORDER BY recorded_at, event_type
) AS order_value
FROM tab
The second cte includes:
a WHERE clause that filters out all rows that are found between a "closed" and an "open" value, first included, last excluded
the first MAX window function, that partitions on the customer and on the previous first COUNT function, to extract the "open_time" value
the second MAX window function, that partitions on the customer and on the previous second COUNT function, to extract the "order_placed_time" value
the third MAX window function, that partitions on the customer and on the previous second COUNT function, to extract the "order_fulfilled_time" value
SELECT customer_id,
MAX(CASE WHEN value = 'open' THEN recorded_at END) OVER(
PARTITION BY customer_id, status_value
) AS open_time,
MAX(CASE WHEN value = 'order_placed' THEN recorded_at END) OVER(
PARTITION BY customer_id, order_value
) AS order_placed_time,
MAX(CASE WHEN value = 'order_fulfilled' THEN recorded_at END) OVER(
PARTITION BY customer_id, order_value
) AS order_fulfilled_time
FROM cte
WHERE MOD(status_value, 2) = 1
Note that it is not possible to use the MAX aggregation functions with a unique GROUP BY clause because the first MAX and the other two MAX aggregate on different columns respectively.
The final query uses the ctes and adds up:
a selection of DISTINCT rows (we're aggregating the output of the window functions)
a filtering operation on rows with NULL values in either the "order_placed_time" or "order_fulfilled_time" (correspond to the old "open" rows).
WITH cte AS (
SELECT *,
COUNT(CASE WHEN value = 'open' THEN 1
WHEN value = 'closed' THEN 0 END) OVER (
PARTITION BY customer_id
ORDER BY recorded_at, event_type
) AS status_value,
COUNT(CASE WHEN value <> 'order_fulfilled' THEN 1 END) OVER(
PARTITION BY customer_id
ORDER BY recorded_at, event_type
) AS order_value
FROM tab
), cte2 AS(
SELECT customer_id,
MAX(CASE WHEN value = 'open' THEN recorded_at END) OVER(
PARTITION BY customer_id, status_value
) AS open_time,
MAX(CASE WHEN value = 'order_placed' THEN recorded_at END) OVER(
PARTITION BY customer_id, order_value
) AS order_placed_time,
MAX(CASE WHEN value = 'order_fulfilled' THEN recorded_at END) OVER(
PARTITION BY customer_id, order_value
) AS order_fulfilled_time
FROM cte
WHERE MOD(status_value, 2) = 1
)
SELECT DISTINCT *
FROM cte2
WHERE order_fulfilled_time IS NOT NULL
I'd recommend to check intermediate output steps for a deep understanding of this specific solution.
Consider yet another option
with order_groups as (
select *,
countif(value in ('open', 'closed')) over order_group_sorted as group_num,
countif(value = 'order_placed') over order_group_sorted as subgroup_num,
from your_table
window order_group_sorted as (partition by customer_id order by recorded_at, event_type)
)
select * except(subgroup_num) from (
select customer_id, recorded_at, value, subgroup_num,
max(if(value = 'open', recorded_at, null)) over order_group as open_time
from order_groups
window order_group as (partition by customer_id, group_num)
)
pivot (any_value(recorded_at) for value in ('order_placed', 'order_fulfilled'))
where not open_time || order_placed is null
if applied to sample data in your question - output is
with data as (
select *, sum(case when value = 'open' then 1 end) over (partition by customer_id) as grp
from T
)
select customer_id,
min(case when value = 'open' then recorded_at end) as open_time,
...
from data
group by customer_id, grp

Collapse multiple rows into a single row based upon a break condition

I have a simple sounding requirement that has had me stumped for a day or so now, so its time to seek help from the experts.
My requirement is to simply roll-up multiple rows into a single row based upon a break condition - when any of these columns change Employee ID, Allowance Plan, Allowance Amount or To Date, then the row is to be kept, if that makes sense.
An example source data set is shown below:
and the target data after collapsing the rows should look like this:
As you can see I don't need any type of running totals calculating I just need to collapse the rows into a single record per from date/to date combination.
So far I have tried the following SQL using a GROUP BY and MIN function
select [Employee ID], [Allowance Plan],
min([From Date]), max([To Date]), [Allowance Amount]
from [dbo].[#AllowInfo]
group by [Employee ID], [Allowance Plan], [Allowance Amount]
but that just gives me a single row and does not take into account the break condition.
what do I need to do so that the records are rolled-up (correct me if that is not the right terminology) correctly taking into account the break condition?
Any help is appreciated.
Thank you.
Note that your test data does not really exercise the algo that well - e.g. you only have one employee, one plan. Also, as you described it, you would end up with 4 rows as there is a change of todate between 7->8, 8->9, 9->10 and 10->11.
But I can see what you are trying to do, so this should at least get you on the right track, and returns the expected 3 rows. I have taken the end of a group to be where either employee/plan/amount has changed, or where todate is not null (or where we reach the end of the data)
CREATE TABLE #data
(
RowID INT,
EmployeeID INT,
AllowancePlan VARCHAR(30),
FromDate DATE,
ToDate DATE,
AllowanceAmount DECIMAL(12,2)
);
INSERT INTO #data(RowID, EmployeeID, AllowancePlan, FromDate, ToDate, AllowanceAmount)
VALUES
(1,200690,'CarAllowance','30/03/2017', NULL, 1000.0),
(2,200690,'CarAllowance','01/08/2017', NULL, 1000.0),
(6,200690,'CarAllowance','23/04/2018', NULL, 1000.0),
(7,200690,'CarAllowance','30/03/2018', NULL, 1000.0),
(8,200690,'CarAllowance','21/06/2018', '01/04/2019', 1000.0),
(9,200690,'CarAllowance','04/11/2021', NULL, 1000.0),
(10,200690,'CarAllowance','30/03/2017', '13/05/2022', 1000.0),
(11,200690,'CarAllowance','14/05/2022', NULL, 850.0);
-- find where the break points are
WITH chg AS
(
SELECT *,
CASE WHEN LAG(EmployeeID, 1, -1) OVER(ORDER BY RowID) != EmployeeID
OR LAG(AllowancePlan, 1, 'X') OVER(ORDER BY RowID) != AllowancePlan
OR LAG(AllowanceAmount, 1, -1) OVER(ORDER BY RowID) != AllowanceAmount
OR LAG(ToDate, 1) OVER(ORDER BY RowID) IS NOT NULL
THEN 1 ELSE 0 END AS NewGroup
FROM #data
),
-- count the number of break points as we go to group the related rows
grp AS
(
SELECT chg.*,
ISNULL(
SUM(NewGroup)
OVER (ORDER BY RowID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),
0) AS grpNum
FROM chg
)
SELECT MIN(grp.RowID) AS RowID,
MAX(grp.EmployeeID) AS EmployeeID,
MAX(grp.AllowancePlan) AS AllowancePlan,
MIN(grp.FromDate) AS FromDate,
MAX(grp.ToDate) AS ToDate,
MAX(grp.AllowanceAmount) AS AllowanceAmount
FROM grp
GROUP BY grpNum
one way is to get all rows the last todate, and then group on that
select min(t.RowID) as RowID,
t.EmployeeID,
min(t.AllowancePlan) as AllowancePlan,
min(t.FromDate) as FromDate,
max(t.ToDate) as ToDate,
min(t.AllowanceAmount) as AllowanceAmount
from ( select t.RowID,
t.EmployeeID,
t.FromDate,
t.AllowancePlan,
t.AllowanceAmount,
case when t.ToDate is null then ( select top 1 t2.ToDate
from test t2
where t2.EmployeeID = t.EmployeeID
and t2.ToDate is not null
and t2.FromDate > t.FromDate -- t2.RowID > t.RowID
order by t2.RowID, t2.FromDate
)
else t.ToDate
end as todate
from test t
) t
group by t.EmployeeID, t.ToDate
order by t.EmployeeID, min(t.RowID)
See and test yourself in this DBFiddle
the result is
RowID
EmployeeID
AllowancePlan
FromDate
ToDate
AllowanceAmount
1
200690
CarAllowance
2017-03-30
2019-04-01
1000
9
200690
CarAllowance
2021-11-04
2022-05-13
1000
11
200690
CarAllowance
2022-05-14
(null)
850

SQL - One Table with Two Date Columns. Count and Join

I have a table (vOKPI_Tickets) that has the following columns:
|CreationDate | CompletionDate|
I'd like to get a count on each of those columns, and group them by date. It should look something like this when complete:
| Date | Count-Created | Count-Completed |
I can get each of the counts individually, by doing something like this:
SELECT COUNT(TicketId)
FROM vOKPI_Tickets
GROUP BY CreationDate
and
SELECT COUNT(TicketId)
FROM vOKPI_Tickets
GROUP BY CreationDate
How can I combine the output into one table? I should also note that this will become a View.
Thanks in advance
Simple generic approach:
select
coalesce(crte.creationdate, cmpl.CompletionDate) as theDate,
crte.cnt as created,
cmpl.cnt as completed
from
(select creationdate, count (*) as cnt from vOKPI_Tickets where creationdate is not null group by creationdate) crte
full join
(select CompletionDate, count (*) as cnt from vOKPI_Tickets where CompletionDate is not null group by CompletionDate) cmpl
on crte.creationdate = cmpl.CompletionDate
You can unpivot and aggregate. A general method is:
select dte, sum(created), sum(completed)
from ((select creationdate as dte, 1 as created, 0 as completed
from vOKPI_Tickets
) union all
(select completed as dte, 0 created, 1 as completed
from vOKPI_Tickets
)
) t
group by dte;
In SQL Server, you can use cross apply for this:
select d.dt, sum(d.is_completed) count_created, sum(d.is_completed) count_completed
from vokpi_tickets t
cross apply (values (creationdate, 1, 0), (completion_date, 0, 1)) as d(dt, is_created, is_completed)
where d.dt is not null
group by d.dt

Insert blank row to result after ORDER BY

I have SQL Query, I want to add insert blank row in result so it is easy to see the result.
I want to insert it after ORDER BY. don't know if it could be done.
Here is my select statement.
SELECT TableName.CREWACTIONFACTID
,TableName.CREWKEY as CrewKey
,TableName.EVENTKEY as EventID
,TableName.ACTIONSEQUENCE
,case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as Type
,TableName.STARTDATETIME as StartTime
,TableName.ENDDATETIME as EndTIme
,TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where
To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY')
AND To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
ORDER BY TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
You can, pretty much as Michael and Gordon did, just tack an empty row on with union all, but you need to have it before the order by:
...
and to_date(to_char(t.enddatetime, 'DD-MON-YYYY')) <=
to_date('?DATE2::?','MM/DD/YYYY')
union all
select null, null, null, null, null, null, null, null
from dual
order by eventid, starttime, actionsequence;
... and you can't use the case that Gordon had directly in the order by because it isn't a selected value - you'll get an ORA-07185. (Note that the column names in the order by are the aliases that you assigned in the select, not those in the table; and you don't include the table name/alias; and it isn't necessary to alias the null columns in the union part, but you may want to for clarity).
But this relies on null being sorted after any real values, which may not always be the case (not sure, but might be affected by NLS parameters), and it isn't known if the real eventkey can ever be null anyway. So it's probably safer to introduce a dummy column in both parts of the query and use that for the ordering, but exclude it from the results by nesting the query:
select crewactionfactid, crewkey, eventid, actionsequence, type,
starttime, endtime, duration
from (
select 0 as dummy_order_field,
t.crewactionfactid,
t.crewkey,
t.eventkey as eventid,
t.actionsequence,
case t.actiontype
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Enroute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as type,
t.startdatetime as starttime,
t.enddatetime as endtime,
t.duration
from schema_name.table_name t
where to_date(to_char(t.startdatetime, 'DD-MON-YYYY')) >=
to_date('?DATE1::?','MM/DD/YYYY')
and to_date(to_char(t.enddatetime, 'DD-MON-YYYY')) <=
to_date('?DATE2::?','MM/DD/YYYY')
union all
select 1, null, null, null, null, null, null, null, null
from dual
)
order by dummy_order_field, eventid, starttime, action sequence;
The date handling is odd though, particularly the to_date(to_char(...)) parts. It looks like you're just trying to lose the time portion, in which case you can use trunk instead:
where trunc(t.startdatetime) >= to_date('?DATE1::?','MM/DD/YYYY')
and trunc(t.enddatetime) <= to_date('?DATE2::?','MM/DD/YYYY')
But applying any function to the date column prevents any index on it being used, so it's better to leave that alone and get the variable part in the right state for comparison:
where t.startdatetime >= to_date('?DATE1::?','MM/DD/YYYY')
and t.enddatetime < to_date('?DATE2::?','MM/DD/YYYY') + 1
The + 1 adds a day, so id DATE2 was 07/12/2012, the filter is < 2012-07-13 00:00:00, which is the same as <= 2012-07-12 23:59:59.
Your question is rather complicated. SQL only guarantees the ordering of results, through the order by. It does not guarantee what happens afterwards. So, you have to put in the blank row and then add the information afterwords:
<your select query minus the order by>
union all
select NULL as CrewActionFatId, . . .
order by (case when CrewActionFactId is NULL then 1 else 0 end),
TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
In practice, #Michael's solution would normally work. But it is not guaranteed.
Also, you should decide whether you want blanks or NULLs. I'm guessing the first id is a number, so I'm setting it to NULL.
In general, such presentation niceties are handled by the calling application. Perhaps you need a better SQL query tool to see the data more cleanly.
Here is what the full query would look like (with all fields set to NULL, you can change to blank if you prefer):
SELECT TableName.CREWACTIONFACTID, TableName.CREWKEY as CrewKey,
TableName.EVENTKEY as EventID, TableName.ACTIONSEQUENCE,
(case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end) as Type,
TableName.STARTDATETIME as StartTime,
TableName.ENDDATETIME as EndTIme,
TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY') AND
To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
union all
SELECT NULL AS CREWACTIONFACTID, NULL AS CrewKey, NULL AS EventID,
NULL AS ACTIONSEQUENCE, NULL AS Type, NULL AS StartTime, NULL AS EndTime,
NULL AS Duration
from dual
ORDER BY (case when CrewActionFactId is NULL then 1 else 0 end),
TableName.EVENTKEY, TableName.STARTDATETIME, TableName.ACTIONSEQUENCE
An odd request to be sure, but yes it can be done by making a UNION against a row of literal blank values. To make sure the order-by is applied to the real query, enclose the whole thing in () and then union it against the blank row.
SELECT * FROM
(SELECT TableName.CREWACTIONFACTID
,TableName.CREWKEY as CrewKey
,TableName.EVENTKEY as EventID
,TableName.ACTIONSEQUENCE
,case TableName.ACTIONTYPE
when 'DISPATCHED' then '2-Dispatched'
when 'ASSIGNED' then '1-Assigned'
when 'ENROUTE' then '3-Entoute'
when 'ARRIVED' then '4-Arrived'
else 'unknown'
end as Type
,TableName.STARTDATETIME as StartTime
,TableName.ENDDATETIME as EndTIme
,TableName.DURATION as Duration
FROM DatabaseName.TableName TableName
where
To_Date(to_char(TableName.STARTDATETIME, 'DD-MON-YYYY')) >= To_Date('?DATE1::?','MM/DD/YYYY')
AND To_Date(to_char(TableName.ENDDATETIME, 'DD-MON-YYYY')) <= To_Date('?DATE2::?','MM/DD/YYYY')
ORDER BY TableName.EVENTKEY, TableName.STARTDATETIME,TableName.ACTIONSEQUENCE
)
UNION ALL
SELECT
'' AS CREWACTIONFACTID,
'' AS CrewKey,
'' AS EventID,
'' AS ACTIONSEQUENCE,
'' AS Type,
'' AS StartTime,
'' AS EndTime,
'' AS Duration
FROM dual
Finally, depending on how you are presenting this result, I would look into other methods of spacing out the result. Appending blank rows to a query for presentation purposes flies in the face of separation of business and presentation logic.
The result will be desplayed in an HTML page.
So, use SQL to extract the data, not to format the output.
Depending on the page structure and layout, there are a lot of solutions.
Have a look here.

SQL Question: Getting Datediff in days elapsed for each record in a group

Given this table:
How can I get the datediff in days between each status_date for each group of ID_Number? In other words I need to find the number of elapsed days for each status that the ID_Number has been given.
Some things to know:
All ID_Number will have a received_date which should be the earliest date for each ID_Number (but app doesn't enforce)
For each ID_Number there will be a status with a corresponding status_date which is the date that the ID_Number was given that particular status.
The status column doesn't always necessarily go in the same order every time (app doesn't enforce)
All ID_Number will have a closed_date which should be the latest date (but app doesn't enforce)
Sample output:
So for ID_Number 2001, the first date (received_date) is 2009-05-02 and the next date you encounter has a status of 'open' and is 2009-05-02 so elapsed days is 0. Moving on to the next date encountered is 2009-05-10 with a status of 'invest' and the elapsed days is 8 counting from the prior date. The next date encountered is 2009-07-11 and the elapsed days is 62 counting from the previous date.
Edited to add:
Is it possible to have the elapsed days end up as a column on this table/view?
I also forgot to add that this is SQL Server 2000.
What I understand is that you need the difference between the first status_date and the next status_date for the same id and so on up to the closed_date.
This will only work in SQL 2005 and up.
;with test as (
select
key,
id_number,
status,
received_date,
status_date,
closed_date,
row_number() over (partition by id order by status_date, key ) as rownum
from #test
)
select
t1.key,
t1.id_number,
t1.status,
t1.status_date,
t1.received_date,
t1.closed_date,
datediff(d, case when t1.rownum = 1
then t1.received_date
else
case when t2.status_date is null
then t1.closed_date
else t2.status_date
end
end,
t1.status_date
) as days
from test t1
left outer join test t2
on t1.id = t2.id
and t2.rownum = t1.rownum - 1
This solution will work with SQL 2000 but I am not sure how good will perform:
select *,
datediff(d,
case when prev_date is null
then closed_date
else prev_date
end,
status_date )
from (
select *,
isnull( ( select top 1 t2.status_date
from #test t2
where t1.id_number = t2.id_number
and t2.status_date < t1.status_date
order by t2.status_date desc
),received_date) as prev_date
from #test t1
) a
order by id_number, status_date
Note: Replace the #Test table with the name of your table.
Some sample output would really help, but this is a guess at what you mean, assuming you want that information for each ID_Number/Status combination:
select ID_Number, Status, EndDate - StartDate as DaysElapsed
from (
select ID_Number, Status, min(coalesce(received_date, status_date)) as StartDate, max(coalesce(closed_date, status_date)) as EndDate
from Table1
group by ID_Number, Status
) a
The tricky bit is determining the previous status and putting it on the same row as the current status. It would be simplified a little if there were a correlation between Key and StatusDate (i.e. that Key(x) > Key(y) always implies StatusDate(x) >= StatusDate(y)). Unfortunately, that doesn't seem to be the case.
PS: I am assuming Key is a unique identifier on your table; you haven't said anything to indicate otherwise.
SELECT Key,
ID_Number,
(
SELECT TOP 1 Key
FROM StatusUpdates prev
WHERE (prev.ID_Number = cur.ID_Number)
AND ( (prev.StatusDate < cur.StatusDate)
OR ( prev.StatusDate = cur.StatusDate
AND prev.Key < cur.Key
)
)
ORDER BY StatusDate, Key /*Consider index on (ID_Number, StatusDate, Key)*/
) PrevKey
FROM StatusUpdates cur
Once you have this as a basis, it's easy to extrapolate to any other info you need from the current or previous StatusUpdate. E.g.
SELECT c.*,
p.Status AS PrevStatus,
p.StatusDate AS PrevStatusDate,
DATEDIFF(d, c.StatusDate, p.StatusDate) AS DaysElapsed
FROM (
SELECT Key,
ID_Number,
Status,
SattusDate,
(
SELECT TOP 1 Key
FROM StatusUpdates prev
WHERE (prev.ID_Number = cur.ID_Number)
AND ( (prev.StatusDate < cur.StatusDate)
OR ( prev.StatusDate = cur.StatusDate
AND prev.Key < cur.Key
)
)
ORDER BY StatusDate, Key
) PrevKey
FROM StatusUpdates cur
) c
JOIN StatusUpdates p ON
p.Key = c.PrevKey