How to give a client's state by time - oracle

Table t_customer_statistics
trx_date - transaction date
cuid - id person(divide prospect and client)
lifecycle_status - this column must be filled
I need to give status to a client based on his condition
acquired - this month was the very first transaction
existing - there was a transaction last month
reactivated - there was no transaction last month
sleeping - there has been no transaction for the last 90 days (there have been no subsequent ones since the last transaction, more than 90 days)
I roughly made a code like this
UPDATE t_customer_statistics
SET Lifecycle_status =
case
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm'), 'mm.yyyy') then 'acquired'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 1, 'mm.yyyy') then 'existing'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') = to_char (trunc (sysdate, 'mm') - 40, 'mm.yyyy') then 'reactivated'
when to_char (trunc (trx_date, 'mm'), 'mm.yyyy') <to_char (trunc (sysdate, 'mm') - 90, 'mm.yyyy') then 'sleeping'
end
but when they gave me an example, if the client made the first transaction and then fell asleep, then he has two states in the end, and sleeping must be separated so that there is a separate
PS. must be considered by transaction from the first and last

You could use a MERGE statement like this:
MERGE INTO clients dst
USING (
SELECT rowid rid,
LEAD(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS prev_dt,
LAG(dt, 1) OVER (PARTITION BY id ORDER BY dt DESC) AS next_dt
FROM clients
) src
ON ( dst.ROWID = src.rid )
WHEN MATCHED THEN
UPDATE
SET status = CASE
WHEN prev_dt IS NULL
THEN 'acquired'
WHEN MONTHS_BETWEEN(TRUNC(dst.dt, 'MM'), TRUNC(src.prev_dt)) <= 1
THEN 'existing'
ELSE 'reactivated'
END
||
CASE
WHEN COALESCE(src.next_dt, SYSDATE) >= dst.dt + INTERVAL '90' DAY
THEN ', sleeping'
END;
Which, for the sample data:
CREATE TABLE clients (id, dt, status ) AS
SELECT 1, DATE '2020-01-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-02-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-03-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-05-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-09-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 1, DATE '2020-10-01' + INTERVAL '91' DAY, CAST( NULL AS VARCHAR2(20) ) FROM DUAL;
Then the result of the MERGE is:
ID
DT
STATUS
1
01-JAN-20
acquired
1
01-FEB-20
existing
1
01-MAR-20
existing
1
01-MAY-20
reactivated, sleeping
1
01-SEP-20
reactivated
1
01-OCT-20
existing, sleeping
1
31-DEC-20
reactivated, sleeping
db<>fiddle here

Related

(Oracle 11g DB) Calculate Number of buisiness days between current time and a date while excluding holidays in a view

So I have this working SQL script that take a date and returns the age from current time to the given date excluding dates defined in a table called exclude dates
SELECT
COUNT(*)
FROM
(
SELECT
ROWNUM rnum
FROM
all_objects
WHERE
ROWNUM <= CAST(current_timestamp AS DATE) - to_date('&2') + 1
)
WHERE
to_char(to_date('&2') + rnum - 1, 'DY') NOT IN ( 'SAT', 'SUN' )
AND NOT EXISTS (
SELECT
NULL
FROM
exclude_dates
WHERE
no_work = trunc(to_date('&2') + rnum - 1)
);
I have a table called
TICKETS
that contains columns named
ID, UPDATED_AT
I want to create a view that uses the above script to return
ID, AGE
where age is the output of the script above.
You code has a few weaknesses.
There is no need for CAST(current_timestamp AS DATE).
If you need the current DATE then simply use TRUNC(SYSDATE)
You don't need to select from all_objects. Better use hierarchical query
SELECT LEVEL as rnum FROM dual CONNECT BY LEVEL <= ...
Using to_date('&2') without a format is usually bad. Either your input value is a string, then you should include the format, e.g. to_date('&2', 'YYYY-MM-DD') or your input value is a DATE, then simply use &2 - never use TO_DATE() on a value which is already a DATE!
Final query could be this one - assuming input parameter is a DATE value:
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_day)
SELECT COUNT(*) AS buisiness_days
FROM t
WHERE TO_CHAR(the_day + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_day + d - 1)
)
However, for me it is not clear how you want to provide this as a view! You would need to create a separate view for each input date, or at least create a new view every day.
I would suggest to create a function:
CREATE OR REPLACE FUNCTION buisiness_days(the_date IN DATE) RETURN INTEGER AS
ret INTEGER;
BEGIN
WITH t AS (
SELECT LEVEL as d
FROM dual
CONNECT BY LEVEL <= TRUNC(SYSDATE) - the_date)
SELECT COUNT(*) AS buisiness_days
INTO ret
FROM t
WHERE TO_CHAR(the_date + d - 1, 'DY', 'NLS_DATE_LANGUAGE = american') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 'x'
FROM exclude_dates
WHERE no_work = TRUNC(the_date + d - 1)
);
RETURN ret;
END;
The function will return a list of dates between the date range you provide so the dates don't have to be stored in a table.
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
/
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
/
To exclude holidays you need to know what dates they fall on so there needs to be a holiday table.
create table holidays(
holiday_date DATE not null,
holiday_name VARCHAR2(20),
constraint holidays_pk primary key (holiday_date),
constraint is_midnight check ( holiday_date = trunc ( holiday_date ) )
);
INSERT into holidays (HOLIDAY_DATE,HOLIDAY_NAME)
WITH dts as (
select to_date('25-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Thanksgiving 2021' from dual union all
select to_date('29-NOV-2021 00:00:00','DD-MON-YYYY HH24:MI:SS'), 'Hanukkah 2021' from dual
)
SELECT * from dts;
This query will provide the count of days between the range, number of working days and number of holidays in the range.
SELECT COUNT (*) AS total_days
, COUNT ( CASE
WHEN h.holiday_date IS NULL
AND d.column_value - TRUNC (d.column_value, 'IW') < 5
THEN 'Business Day'
END
) AS business_days
, COUNT (h.holiday_date) AS holidays
FROM generate_dates_pipelined (DATE '2021-11-01', DATE '2021-11-30') d
LEFT JOIN holidays h ON h.holiday_date = d.column_value;
This query will provide a list of dates excluding sat, sun and holidays that fall between the range.
SELECT
COLUMN_VALUE
FROM
TABLE(generate_dates_pipelined(DATE '2021-11-01',
DATE '2021-11-30')) c
where
to_char(COLUMN_VALUE, 'DY') NOT IN ('SAT', 'SUN')
AND NOT EXISTS (
SELECT 1
FROM holidays h
WHERE c.COLUMN_VALUE = h.holiday_date
);
You don't need a function or to use a row generator function and can calculate the number of business days:
CREATE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( SYSDATE - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT SUM(
LEAST(no_work + INTERVAL '1' DAY, SYSDATE)
- GREATEST(no_work, updated_at)
)
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND SYSDATE
),
0
)
-- Subtract the holiday days.
FROM tickets;
Or, if you are not calculating using part days then you can simplify it to:
CREATE OR REPLACE VIEW business_day_ages (ID, AGE) AS
SELECT id,
( TRUNC( SYSDATE, 'IW' ) - TRUNC( updated_at, 'IW' ) ) * 5 / 7
-- Number of full weeks.
+ LEAST( TRUNC(SYSDATE) - TRUNC( SYSDATE, 'IW' ), 5 )
-- Add part weeks at the end.
- LEAST( updated_at - TRUNC( updated_at, 'IW' ), 5 )
-- Subtract part weeks at the start.
- COALESCE(
( SELECT 1
FROM exclude_dates
WHERE no_work BETWEEN TRUNC(updated_at) AND TRUNC(SYSDATE)
),
0
)
-- Subtract the holiday days.
FROM tickets;
db<>fiddle here

calculate the running total over the column contain date difference in HH:MI:SS format in oracle

I have to find the running total over the column interval.
SELECT
( ( EXTRACT(DAY FROM intrvl) * 24 ) + ( EXTRACT(HOUR FROM intrvl) ) ) ||':'||
EXTRACT(MINUTE FROM intrvl) ||':'||
EXTRACT(SECOND FROM intrvl) ||':'|| as interval
FROM
(
SELECT
( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) - ( to_timestamp(TO_CHAR(date_column_name,'dd-mon-rrrr hh:mi:ss') ) ) intrvl
FROM
dual
);
currrently Interval column of table has below data:
Interval(HH:mi:ss)
0:4:23
696:1:36
696:4:51
8760:1:18
The best I can come up with is this. Note that the interval data type does not take a format model for displaying - you can't force an interval of 25 hours to be displayed as 25:00:00 (although you can use that to INPUT an interval). Instead, it will be shown as 01 01:00:00 (meaning, a day and an hour).
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
as sum_interv
from tbl;
SUM_INTERV
--------------------
+423 00:12:08.000000
In your original attempt you were trying to get a STRING output. I am not sure that's wise, but if that's what you need you can do it like so:
with
tbl (interv) as (
select interval '0:4:23' hour(9) to second from dual union all
select interval '696:1:36' hour(9) to second from dual union all
select interval '696:4:51' hour(9) to second from dual union all
select interval '8760:1:18' hour(9) to second from dual
)
, prep (sum_interv) as (
select interval '1' day * sum(date '2000-01-01' + interv - date'2000-01-01')
from tbl
)
select to_char( extract(day from sum_interv) * 24
+ extract(hour from sum_interv), 'fm999999999' ) || ':' ||
to_char( extract(minute from sum_interv), 'fm00' ) || ':' ||
to_char( extract(second from sum_interv), 'fm00' ) as sum_interv
from prep
;
SUM_INTERV
------------------
10152:12:08

Coalesce statement to handle multiple values and NULLS?

I am trying to figure out how to create an SQL query that will check for (:FROM_DATE) and (:TO_DATE) parameters and if NULL to put the past month dates in for the two values, and if not NULL to accept whatever values are entered in the parameters.
For example:
if the user enters (01-JAN-17) as FROM_DATE, and (31-JAN-17) as TO_DATE, I want the query to not automatically pass any values for the TO_DATE and FROM_DATE.
if the user does not enter any values for TO_DATE and FROM_DATE or there are NULL values passed in, I want the query to automatically enter the the past months values (i.e., if query is run July 1st 2017, the FROM_DATE would be 01-JUN-17 and the TO_DATE would be 30-JUN-17).
I was hinted to use a coalesce statement to handle multiple values and NULLS (i.e., AND ( (coalesce(null, :P_ORG) is null) or (ORG.ORGANIZATION_ID in :P_ORG)))???
Any help would be greatly appreciated.
Something like:
SELECT *
FROM your_table
WHERE your_date_column BETWEEN TO_DATE( :from_date, 'DD-MON-YYYY' )
AND TO_DATE( :to_date, 'DD-MON-YYYY' )
OR ( ( :from_date IS NULL OR :to_date IS NULL )
AND your_date_column BETWEEN ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND TRUNC( SYSDATE, 'MM' ) - 1
);
If either (or both) :from_date or :to_date is NULL then the dates will be compared to the previous month.
If your table has dates where the time component is not always set to midnight then you will need to use:
SELECT *
FROM your_table
WHERE your_date_column BETWEEN TO_DATE( :from_date, 'DD-MON-YYYY' )
AND TO_DATE( :to_date, 'DD-MON-YYYY' )
OR ( ( :from_date IS NULL OR :to_date IS NULL )
AND your_date_column >= ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), -1 )
AND your_date_column < TRUNC( SYSDATE, 'MM' )
);
Proof of concept: consider the following query, where we have dates and values, and we want to sum the values for the dates that fall between :from_date and :to_date. If either of them is null, the query will use the first day of the prior month for from_date and the last day of the prior month for to_date. Note that this will cause problems if one date is given an actual value and the other is left null - you didn't explain how you would want that handled. But that's a different issue.
I use SQL developer, and in it I don't know how to pass in dates; I show passing in strings, and converting them to dates.
with
test_data ( dt, val ) as (
select date '2017-05-29', 200 from dual union all
select date '2017-06-13', 150 from dual union all
select date '2017-06-18', 500 from dual
)
select sum(val) as sum_val
from test_data
where dt between coalesce(to_date(:from_date, 'yyyy-mm-dd'),
add_months(trunc(sysdate, 'mm'), -1))
and coalesce(to_date(:to_date , 'yyyy-mm-dd'), trunc(sysdate, 'mm') - 1)
;
Yes, you can use COALESCE (or Oracle's NVL). When a parameter is null, replace it with the default date.
select *
from mytable
where mydate >= coalesce(:from_date, trunc(sysdate - interval '1' month), 'month')
and mydate <= coalesce(:to_date, last_day(sysdate - interval '1' month));

How to query for a specific day of the month in Oracle

Trying to automate a query that will pull data for the current month where the day of the month (in the date field) is >= the 15th. Is this possible? If so, what is the syntax to achieve this?
I want to be able to run this query each month without having to change anything. So in May, it would automatically pull any item where the date was >= 5/15/16. In June, it would pull items where the date was >= 6/15/16. And so on.....
Any help in this would be greatly appreciated. Thanks
This will allow you to use any indexes you have on your date_field column:
SELECT *
FROM table_name
WHERE date_field >= TRUNC( SYSDATE, 'MM' ) + INTERVAL '14' DAY
AND date_field < ADD_MONTHS( TRUNC( SYSDATE, 'MM' ), 1 );
If your date/time fields are of type timestamp you can do
select x from <your_table>
where EXTRACT(DAY from <timestamp field>) >=15
and EXTRACT(MONTH from <timestamp field>) = EXTRACT(MONTH FROM CURRENT_TIMESTAMP)
and EXTRACT(YEAR from <timestamp field>) = EXTRACT(YEAR FROM CURRENT_TIMESTAMP);
I think what you're after is something like:
with sample_data as (select 1 id, to_date('01/06/2016', 'dd/mm/yyyy') dt from dual union all
select 2 id, to_date('10/06/2016', 'dd/mm/yyyy') dt from dual union all
select 3 id, to_date('14/06/2016', 'dd/mm/yyyy') dt from dual union all
select 4 id, to_date('15/06/2016', 'dd/mm/yyyy') dt from dual union all
select 5 id, to_date('16/06/2016', 'dd/mm/yyyy') dt from dual union all
select 6 id, to_date('30/06/2016', 'dd/mm/yyyy') dt from dual union all
select 7 id, to_date('01/07/2016', 'dd/mm/yyyy') dt from dual)
select *
from sample_data
where dt >= trunc(sysdate, 'mm') + 14
and dt < last_day(trunc(sysdate)) + 1;
ID DT
---------- ----------
4 15/06/2016
5 16/06/2016
6 30/06/2016
(If you wanted rows with any date greater than the 15th of the current month, then remove the last predicate in the where clause.)

Count the no of saturdays and sundays in date range - oracle [duplicate]

This question already has answers here:
Number of fridays between two dates
(7 answers)
Closed 8 years ago.
I have two parameters(start_Date,end_Date) from table1
I'm trying to count no of saturdays and sundays in a date range
star_Date=8/20/2014 13:52
end_Date=8/28/2014 13:52
And result should be like this
Start_Date end_date No_of_leaves
8/20/2014 13:52 8/28/2014 13:52 2
Update Section
SELECT retouch_req_time,retouch_submit_time,(
SELECT Count(*) FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday') dt
FROM (WITH t AS (SELECT To_date (retouch_req_time, 'MM/DD/YYYY HH24:MI') start_date, To_date (retouch_submit_time, 'MM/DD/YYYY HH24:MI') end_date FROM TT))
CONNECT BY LEVEL <= end_date - start_date + 1) WHERE dt IN ('friday','saturday')) as worked_hours
FROM TT
You can try using hierarchical queries
WITH t
AS (SELECT To_date ('8/20/2014 13:52', 'MM/DD/YYYY HH24:MI') start_date,
To_date ('8/28/2014 13:52', 'MM/DD/YYYY HH24:MI') end_date
FROM dual)
SELECT Count(*)
FROM (SELECT To_char(start_date + ( LEVEL - 1 ), 'fmday') dt
FROM t
CONNECT BY LEVEL <= end_date - start_date + 1)
WHERE dt IN ( 'friday', 'saturday' );
RESULT
------
2
* The dates are listed by expanding the range.
* The TO_CHAR function is used to obtain the weekday
* Count everthing which is a friday or saturday
If you want to find the day wise count, then you can try
SELECT To_char(dat, 'DY'),
Count(*)
FROM (SELECT To_date ('8/20/2014 13:52', 'MM/DD/YYYY HH24:MI')
+ num dat
FROM (SELECT LEVEL - 1 num
FROM dual
CONNECT BY LEVEL <= Abs(To_date ('8/20/2014 13:52',
'MM/DD/YYYY HH24:MI') -
To_date (
'8/28/2014 13:52'
,
'MM/DD/YYYY HH24:MI')) - 1
))
WHERE To_char(dat, 'DY') IN ( 'FRI', 'SAT' )
GROUP BY To_char(dat, 'DY');
RESULTS
TO_CHAR(DAT,'DY') COUNT(*)
----------------- --------
FRI 1
SAT 1
You can calculate the number of saturdays and sundays like this:
with t(d) as (
select sysdate + level from dual connect by rownum < 10
)
select count(case when trim(to_char(d, 'DAY')) in ('SATURDAY', 'SUNDAY') then 1 end) cnt from t
CNT
---
2
If you don't have a range of dates then:
with t(a, b) as (
select sysdate a, sysdate + 10 b from dual connect by rownum < 10
), t2(d) as (
select a + level - 1 from t connect by rownum <= b - a
)
select count(case when trim(to_char(d, 'DAY')) in ('SATURDAY', 'SUNDAY') then 1 end) cnt from t2
CNT
---
2

Resources