ThinkingSphinx with_all OR query - ruby

I'm trying to return results who's start_date or end_date fall within a date range. I'm using with_all in my TS query like such:
range = (start_date..end_date)
with_all = { start_date: [range], end_date: [range] }
However, this does not work because I believe this will require both the start_date and end_date to fall within the range.
What do I need to do so that it will return results if either the start_date or end_date fall within the range?

After much non Rock related head banging I found a working solution.
srange = date_range[:start].to_datetime.utc.beginning_of_day.to_i
erange = date_range[:end].to_datetime.utc.end_of_day.to_i
# * full range falls within start_date & end_date
# * start_date & end_date is within the full range
# * end range is within start_date & end_date
# * start range is within start_date & end_date
sphinx_select = "
*, (IF(
#{srange} >= start_date AND
#{srange} <= end_date AND
#{erange} >= start_date AND
#{erange} <= end_date, 1, 0) +
IF(
start_date >= #{srange} AND
start_date <= #{erange} AND
end_date >= #{srange} AND
end_date <= #{erange}, 1, 0) +
IF(
#{srange} <= start_date AND
#{srange} <= end_date AND
#{erange} >= start_date AND
#{erange} <= end_date, 1, 0) +
IF(
#{srange} >= start_date AND
#{srange} <= end_date AND
#{erange} >= start_date AND
#{erange} >= end_date, 1, 0)) as in_date_range"
MyModel.search('',
select: select,
where: {"in_date_range" => [1,2,3,4]})

Related

Applying case statement on date

I am working on reports where we have activity start date(Date when the activity has begun) and activity end date(When the activity ended). I have a requirement wherein if the activity has begun before 2021 then I have to set the start date as 1/1/21 and if it continues after 2021 then set the activity end date as 31/12/21.And if the start date and end date lie in the same year keeping them as it is.
How can I achieve this scenario.
You do not need a CASE expression; you can use GREATEST and LEAST:
UPDATE table_name
SET start_date = GREATEST(start_date, DATE '2021-01-01'),
end_date = LEAST(end_date, TIMESTAMP '2021-12-31 23:59:59')
WHERE start_date < DATE '2022-01-01'
AND ( start_date < DATE '2021-01-01'
OR end_date >= DATE '2022-01-01');
If you want to just select the values where the date range overlaps 2021, and limit the range to be from 2021, then:
SELECT column1,
column2,
GREATEST(start_date, DATE '2021-01-01') AS start_date,
LEAST(end_date, TIMESTAMP '2021-12-31 23:59:59') AS end_date
FROM table_name
WHERE start_date < DATE '2022-01-01'
AND end_date >= DATE '2021-01-01'
If you want a generic query for any given year, starting from the :year_start bind variable, then:
SELECT column1,
column2,
GREATEST(start_date, :year_start) AS start_date,
LEAST(end_date, ADD_MONTHS(:year_start, 12) - INTERVAL '1' SECOND) AS end_date
FROM table_name
WHERE start_date < ADD_MONTHS(:year_start, 12)
AND end_date >= :year_start;

How do i get this CASE statement right in PLSQL?

I'm getting ORA- 00936 missing expression error near > symbol while trying to run this request :
SELECT contract_ref_no,
component
FROM some_table
WHERE Contract_ref_no = '123'
AND component = 'ABC'
AND end_date
(CASE WHEN NVL(l_neg_esn_allowed,'N') = 'N'
THEN
> greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate)
ELSE
>=
greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate)
END)
How can I fix it?
With a bit reformulation based on the rules of Aristoteles you may write
you allways want the end_date is greather than your greatest expression
OR
in the else case NVL(l_neg_esn_allowed,'N') != 'N' thay may be equal
Predicate
...
AND (
end_date > greatest(nvl(l_conv_eff_date, l_contract_vdate), l_contract_vdate)
OR
NVL(l_neg_esn_allowed,'N') != 'N' and end_date = greatest(nvl(l_conv_eff_date, l_contract_vdate), l_contract_vdate)
)
which is the same as the predicate below that more resembles your original intention
....
AND (
NVL(l_neg_esn_allowed,'N') = 'N' AND end_date > greatest(nvl(l_conv_eff_date, l_contract_vdate),l_contract_vdate)
OR
NVL(l_neg_esn_allowed,'N') != 'N' AND end_date >= greatest(nvl(l_conv_eff_date, l_contract_vdate),l_contract_vdate)
)
You can change your case statement to -
SELECT contract_ref_no,
component
FROM some_table
WHERE Contract_ref_no = '123'
AND component = 'ABC'
AND CASE WHEN NVL(l_neg_esn_allowed,'N') = 'N'
AND end_date > greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate) THEN
1
WHEN end_date >= greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate) THEN
1
END = 1;
The SQL query that you write have syntax error.
The THEN > something can't work : Superior to what ? Same with the >= after, superior or equals to what ?
So, you should have something like column > value, such as you will have column = value or column LIKE 'my val'.
Finally, I suggest you to use this code:
SELECT contract_ref_no,
component
FROM some_table
WHERE Contract_ref_no = '123'
AND component = 'ABC'
AND end_date
(CASE WHEN NVL(l_neg_esn_allowed,'N') = 'N'
THEN some_date > greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate)
ELSE some_date >= greatest(nvl(l_conv_eff_date, l_contract_vdate),
l_contract_vdate)
END)

Finding no. of working days in a month till sysdate/ today's date is it is current month

I am using the following query to find out the number of working days in a month excluding the weekends
select payroll_id as payrollId,
(select count(*)
from ( select rownum rnum
from all_objects
where rownum <= to_date(to_char(last_day(to_date('01-'||'MAY'||'2017','DD-MM-YYYY')),'DD')||'MAY'||'2017','DD-MM-YYYY') - to_date('01-'||'MAY'||'2017','DD-MM-YYYY')+1 )
where to_char( to_date('01-'||'MAY'||'2017','DD-MM-YYYY')+rnum-1, 'DY' )
not in ( 'SAT', 'SUN' )) - (select count(*) from admin_holiday where to_char(holiday_date,'DD-MON-YYYY') like '%-MAY-2017%' and holiday_type_id=1) days
from employee where DEL_FLAG=1 order by payrollId
Here, payrollId is the employee_id in employee table and admin_holiday is a table that contains information about the national holidays in a month
My requirement is that if the month is current month then the working days should be upto today's date. For example the current month is MAY/2017 and till today(i.e 22/05/2017) the working days count excluding weekends is 15 (10/05/2017 is a national holiday according to Hindu calendar.
How do I obtain the required result.
NOTE this action has to be performed in a single select statement and no other pl/sql blocks, etc.
Here is how I would approach the equivalent query. By placing the date range calculation into a small cross joined query you can access the result easily throughout the remainder of the query.
SELECT
e.payroll_id AS payrollId
, cj. working_days
FROM employee e
cross join (
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
FROM (
select
to_date('20170501','yyyymmdd') as start_date
, to_date('20170522','yyyymmdd') as end_date
from dual
)
) cj
WHERE e.DEL_FLAG = 1
ORDER BY e.payrollId
The working days logic is:
Calculate leading days in first week + (Count the number on mon/fri periods) * 5 - number of trailing days in final week - the additional holidays in a table:
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
, start_date
, end_date
, holidays
, end_date - start_date
FROM (
select
trunc(sysdate,'MM') as start_date
, trunc(sysdate) as end_date
from dual
)
example (not there is zero holidays here):
+--------------+--------------+-------------+------------+-------------------------+
| WORKING_DAYS | START_DATE | END_DATE | HOLIDAYS | END_DATE-START_DATE |
+--------------+--------------+-------------+------------+-------------------------+
| 16 | 01.05.2017 | 22.05.2017 | 0 | 21 |
+--------------+--------------+-------------+------------+-------------------------+
To manually set the dates you can do this:
select
GREATEST(NEXT_DAY(start_date, 'MON') - start_date - 2, 0)
+ ((NEXT_DAY(end_date, 'MON') - NEXT_DAY(start_date, 'MON'))/7)*5
- GREATEST(NEXT_DAY(end_date, 'MON') - end_date - 3, 0)
- (
SELECT COUNT(holiday)
FROM admin_holiday
WHERE holiday_date BETWEEN start_date AND end_date
)
as working_days
FROM (
select
to_date('20170501','yyyymmdd') as start_date
, to_date('20170522','yyyymmdd') as end_date
from dual
)

Oracle: Days between two date and Exclude weekdays how to handle negative numbers

I have two date columns and trying to measure days between the two dates excluding weekends. I'm getting a negative number and need help solving.
Table
CalendarDate DayNumber FirstAssgn FirstCnt DayNumber2 Id BusinessDays
5/21/2017 Sunday 5/21/17 5/21/17 Sunday 1 -1
Query:
TRUNC(TO_DATE(A.FIRST_CONTACT_DT, 'DD/MM/YYYY')) - TRUNC(TO_DATE(A.FIRST_ASSGN_DT, 'DD/MM/YYYY'))
- ((((TRUNC(A.FIRST_CONTACT_DT,'D'))-(TRUNC(A.FIRST_ASSGN_DT,'D')))/7)*2)
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english') ='SUN' THEN 1 ELSE 0 END)
- (CASE WHEN TO_CHAR(A.FIRST_CONTACT_DT,'DY','nls_date_language=english')='SAT' THEN 1 ELSE 0 END)
- (SELECT COUNT(1) FROM HUM.CALENDAR CAL
WHERE 1=1
AND CAL.CALENDAR_DATE >= A.FIRST_ASSGN_DT
AND CAL.CALENDAR_DATE < A.FIRST_CONTACT_DT
--BETWEEN A.FIRST_ASSGN_DT AND A.FIRST_CONTACT_DT
AND CAL.GRH_HOLIDAY_IND = 'Y'
) AS Business_Days
Looks like below piece needs editing...
- (CASE WHEN TO_CHAR(A.FIRST_ASSGN_DT,'DY','nls_date_language=english')='SUN' THEN 1 ELSE 0 END)
Adapted from my answer here:
Get the number of days between the Mondays of both weeks (using TRUNC( datevalue, 'IW' ) as an NLS_LANGUAGE independent method of finding the Monday of the week) then add the day of the week (Monday = 1, Tuesday = 2, etc., to a maximum of 5 to ignore weekends) for the end date and subtract the day of the week for the start date. Like this:
SELECT ( TRUNC( end_date, 'IW' ) - TRUNC( start_date, 'IW' ) ) * 5 / 7
+ LEAST( end_date - TRUNC( end_date, 'IW' ) + 1, 5 )
- LEAST( start_date - TRUNC( start_date, 'IW' ) + 1, 5 )
AS WeekDaysDifference
FROM your_table
With RANGE_TEMP as (
SELECT
STARTPERIOD start_date,
ENDPERIOD end_date
FROM
TABLE_DATA -- YOUR TABLE WITH ALL DATA DATE
), DATE_TEMP AS (
SELECT
(start_date + LEVEL) DATE_ALL
FROM
RANGE_TEMP
CONNECT BY LEVEL <= (end_date - start_date)
), WORK_TMP as (
SELECT
COUNT(DATE_ALL) WORK_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') NOT IN ('1','7')
), BUSINESS_TMP as (
SELECT
COUNT(DATE_ALL) BUSINESS_DATE
FROM
DATE_TEMP
WHERE
TO_CHAR(DATE_ALL,'D', 'NLS_DATE_LANGUAGE=ENGLISH') IN ('1','7')
)
SELECT
L.WORK_DATE,
H.BUSINESS_DATE
FROM
BUSINESS_TMP H,
WORK_TMP L
;

How to exclude holidays between two dates?

I have two dates and I have to find out the number of Sundays and holidays fall between those two dates. Can I do this using BETWEEN? If so, how?
SELECT date1, date2, trunc(deposit_date - transaction_date) TOTAL
FROM Table_Name FULL OUTER JOIN Holidays ON date2 = hdate
WHERE hdate IN (date1, date2)
Using this I can definitely check whether there is a holiday on either of the two days, i.e. date1 or date2 but what I am not able to find out that whether there lies a holiday or a Sunday between these two dates. Help!
The solution you've posted is horribly inefficient; you can do all of this in a single SQL statement:
Firstly generate all possible dates between the two you have:
select trunc(:min_date) + level - 1
from dual
connect by level <= trunc(:min_date) - trunc(:max_date)
Then use your HOLIDAY table to restrict to what you want:
with all_dates as (
select trunc(:min_date) + level - 1 as the_date
from dual
connect by level <= trunc(:min_date) - trunc(:max_date)
)
select count(*)
from all_dates a
left outer join holiday b
on a.the_date = b.hdate
where b.hdate is null
and to_char(a.the_date, 'DY') <> 'SUN'
If you want to check if hdate is between the two dates you can query using
where hdate between date1 and date2
If you want to check if hdate is on the same day as date1 or date two you can query like this
where trunc(hdate) in (trunc(date1) ,trunc(date2))
The trunc function removed the time.
You should create a table with the holidays and maintain it on your own.
CREATE TABLE holidays
(
holiday VARCHAR2(100)
, d_date DATE
);
INSERT INTO holidays VALUES ('National Developer Day', DATE'2013-06-01');
SELECT *
FROM holidays;
-- National Developer Day 2013-06-01 00:00:00
The rest is just a matter of a SQL statment
Scenario 1: EXISTS
SELECT COUNT
(
CASE
WHEN TRIM(TO_CHAR(d.start_date_level, 'DAY')) = 'SUNDAY'
OR CASE
WHEN EXISTS (SELECT 1 FROM holidays h WHERE d.start_date_level = h.d_date)
THEN 1
ELSE NULL
END = 1
THEN 1
ELSE NULL
END
) AS holiday_check
FROM
(
SELECT start_date + (LEVEL - 1) AS start_date_level
FROM
(
SELECT start_date, end_date, end_date - start_date AS diff_date
FROM
(
SELECT TRUNC(ADD_MONTHS(SYSDATE, -2)) AS start_date
, TRUNC(SYSDATE) AS end_date
FROM DUAL
)
)
CONNECT BY
LEVEL <= (diff_date + 1)
) d
Scenario 2: LEFT JOIN
SELECT COUNT
(
CASE
WHEN TRIM(TO_CHAR(d.start_date_level, 'DAY')) = 'SUNDAY'
OR h.d_date IS NOT NULL
THEN 1
ELSE NULL
END
) AS holiday_check
FROM
(
SELECT start_date + (LEVEL - 1) AS start_date_level
FROM
(
SELECT start_date, end_date, end_date - start_date AS diff_date
FROM
(
SELECT TRUNC(ADD_MONTHS(SYSDATE, -2)) AS start_date
, TRUNC(SYSDATE) AS end_date
FROM DUAL
)
)
CONNECT BY
LEVEL <= (diff_date + 1)
) d
LEFT JOIN holidays h
ON d.start_date_level = h.d_date
9 Sundays + 1 "National Developer Day" = 10
CREATE OR REPLACE FUNCTION workdays (dt1 DATE, dt2 DATE) RETURN NUMBER IS
weekday_count NUMBER := 0;
date1 DATE := dt1;
date2 DATE := dt2;
cur_dt date;
holiday_count number;
begin
if date1 = date2 then
return 0;
end if;
cur_dt := transaction_date;
while cur_dt <= date2 loop
if cur_dt = date2 then
null;
else
SELECT count(*) INTO holiday_count
FROM holiday
WHERE hdate = cur_dt;
IF holiday_count = 0 THEN
IF to_char(cur_dt,'DY') NOT IN ('SUN') THEN
weekday_count := weekday_count + 1;
END IF;
END IF;
END IF;
cur_dt := cur_dt +1;
END LOOP;
RETURN weekday_count;
END;
And then I queried my database and got the right results. Do post if you have an optimal solution for this.
Here is an even better and efficient solution to the problem,
SELECT A.ID,
COUNT(A.ID) AS COUNTED
FROM tableA A
LEFT JOIN TableB B
ON A.tableB_id=B.id
LEFT JOIN holiday C
ON TRUNC(C.hdate) BETWEEN (TRUNC(a.date1) +1) AND TRUNC(B.date2)
WHERE c.hdate IS NOT NULL
GROUP BY A.ID;
where TableA contains date1 and tableB contains date2. Holiday contains the list of holidays and Sundays. And this query excludes 'date1' from the count.
RESULT LOGIC
trunc(date2) - trunc(date1) = x
x - result of the query
Make a table T$HOLIDAYS with your holidays (HDATE column). These dates will be excluded from calculation of working days within given period (sdate is start date and edate end date of period). Here is the function that calculates working days within given period excluding holidays, saturdays and sundays:
CREATE OR REPLACE FUNCTION WorkingDays(sdate IN DATE,edate IN DATE) RETURN NUMBER IS
days NUMBER;
BEGIN
WITH dates AS (SELECT sdate+LEVEL-1 AS d FROM DUAL CONNECT BY LEVEL<=edate-sdate+1)
SELECT COUNT(*) INTO days
FROM dates
WHERE d NOT IN (SELECT hdate FROM t$holidays) --exclude holidays
AND TO_CHAR(d,'D') NOT IN (6,7); --exclude saturdays + sundays
RETURN days;
END WorkingDays;
/
select sum(qq) from (
select case when to_number(to_char((trunc(sysdate-10) + level - 1),'D'))<=5 then 1 else 0 end as qq
from dual
connect by level <= trunc(sysdate) - trunc(sysdate-10))

Resources