Oracle AVG COUNT - oracle

I have this query:
SELECT TRUNC(date_added,'MM'), count(*)
FROM payments_log l, product p
WHERE l.amount > 0
AND l.product_id = p.product_id
AND p.subproduct_id = 238
AND TRUNC(l.date_added) BETWEEN TO_DATE('01012020','MMDDYYYY') AND TO_DATE('01012021','MMDDYYYY')
AND l.return_code = 1
GROUP BY TRUNC(date_added,'MM')
ORDER BY TRUNC(date_added,'MM');
In addition to the count, per month, I want a column that is the average each month, of the total......not sure how to do this in the same query.

To get:
I want a column that is the average each month, of the total
You appear to want to use the AVG analytic function over the entire range:
SELECT month,
cnt,
AVG( cnt ) OVER () AS avg_cnt
FROM (
SELECT TRUNC(date_added,'MM') AS month,
COUNT(*) AS cnt
FROM payments_log l
INNER JOIN product p
ON ( l.product_id = p.product_id )
WHERE l.amount > 0
AND p.subproduct_id = 238
AND l.date_added >= DATE '2020-01-01'
AND l.date_added < DATE '2021-01-01'
AND l.return_code = 1
GROUP BY TRUNC(date_added,'MM')
)
ORDER BY month
You also should use ANSI joins rather than the (confusing) legacy comma joins and can filter on the date_added column without needing the TRUNC function (which, if you do use it, would prevent Oracle from using an index on the date_added column and would require a function-based index on TRUNC( date_added )).
(Note: BETWEEN is inclusive so that you will include 2021-01-01 in your range rather than just those dates in 2020; I am assuming that you do not want this date but if you do then you can set the upper bound to l.date_added < DATE '2021-01-02'.)
If you want the number of counts per month as a fraction of the total number of counts (this is not an average) then, again, you want to use an analytic function:
SELECT month,
cnt,
cnt / SUM( cnt ) OVER () AS fraction_of_total_cnt
FROM (
SELECT TRUNC(date_added,'MM') AS month,
COUNT(*) AS cnt
FROM payments_log l
INNER JOIN product p
ON ( l.product_id = p.product_id )
WHERE l.amount > 0
AND p.subproduct_id = 238
AND l.date_added >= DATE '2020-01-01'
AND l.date_added < DATE '2021-01-01'
AND l.return_code = 1
GROUP BY TRUNC(date_added,'MM')
)
ORDER BY month

Related

Max number of counts in a tparticular hour

I have a table called Orders, i want to get maximum number of orders for each day with respect to hours with following query
SELECT
trunc(created,'HH') as dated,
count(*) as Counts
FROM
orders
WHERE
created > trunc(SYSDATE -2)
group by trunc(created,'HH') ORDER BY counts DESC
this gets the result of all hours, I want only max hour of a day e.g.
Image
This result looks good but now i want only rows with max number of count for a day
e.g.
for 12/23/2019 max number of counts is 90 for "12/23/2019 4:00:00 PM",
for 12/22/2019 max number of counts is 25 for "12/22/2019 3:00:00 PM"
required dataset
1 12/23/2019 4:00:00 PM 90
2 12/24/2019 12:00:00 PM 76
3 12/22/2019 1:00:00 PM 25
This could be the solution and in my opinion is the most trivial.
Use the WITH clause to make a sub query then search for the greatest value in the data set on a specific date.
WITH ORD AS (
SELECT
trunc(created,'HH') as dated,
count(*) as Counts
FROM
orders
WHERE
created > trunc(SYSDATE-2)
group by trunc(created,'HH')
)
SELECT *
FROM ORD ord
WHERE NOT EXISTS (
SELECT 'X'
FROM ORD ord1
WHERE trunc(ord1.dated) = trunc(ord.dated) AND ord1.Counts > ord.Counts
)
Use ROW_NUMBER analytic function over your original query and filter the rows with number 1.
You need to partition on the day, i.e. TRUNC(dated) to get the correct result
with ord1 as (
SELECT
trunc(created,'HH') as dated,
count(*) as Counts
FROM
orders
WHERE
created > trunc(SYSDATE -2)
group by trunc(created,'HH')
),
ord2 as (
select dated, Counts,
row_number() over (partition by trunc(dated) order by Counts desc) as rn
from ord1)
select dated, Counts
from ord2
where rn = 1
The advantage of using the ROW_NUMBER is that it correct handels ties, i.e. cases where there are more hour in a day with the same maximal count. The query shows only one record and you can controll with the order by e.g. to show the first / last hour.
You can use the analytical function ROW_NUMBER as following to get the desired result:
SELECT DATED, COUNTS
FROM (
SELECT
TRUNC(CREATED, 'HH') AS DATED,
COUNT(*) AS COUNTS,
ROW_NUMBER() OVER(
PARTITION BY TRUNC(CREATED)
ORDER BY COUNT(*) DESC NULLS LAST
) AS RN
FROM ORDERS
WHERE CREATED > TRUNC(SYSDATE - 2)
GROUP BY TRUNC(CREATED, 'HH'), TRUNC(CREATED)
)
WHERE RN = 1
Cheers!!

Oracle SQL: Return a set without duplicates

Using the following query is working for me except that I need to only return one result for each TPHONE.ID.
What I'm trying to accomplish is to check two tables for three date fields (two in the TPHONE table and one in the TPHONEREQUEST table and return the TPHONE.ID of any place where I find dates within a particular range. However, if more than one entry in either table has one or more of the dates within the date range I'd still only want to return the TPHONE.ID once.
SELECT
TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03
FROM
TPHONE, TPHONEREQUEST
WHERE
TPHONE.ID = TPHONEREQUEST.DEVICEID
AND TPHONE.ID IN
(
SELECT
TPHONE.ID
FROM
TPHONE
WHERE
TPHONE.DLASTCHANGED >= '7/1/2019' AND TPHONE.DLASTCHANGED < '10/1/2019'
OR TPHONE.D02 >= '7/1/2019' AND TPHONE.D02 < '10/1/2019'
OR TPHONEREQUEST.D03 >= '7/1/2019' AND TPHONEREQUEST.D03 < '10/1/2019'
)
ORDER BY
TPHONE.LOCATIONID, TPHONE.ID
You can use aggregation and filter with a HAVING clause:
SELECT p.ID,
FROM
TPHONE p
INNER JOIN TPHONEREQUEST t ON p.ID = r.DEVICEID
GROUP BY p.ID
HAVING
MAX(
CASE WHEN
(
p.DLASTCHANGED >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND p.DLASTCHANGED < TO_DATE('10/1/2019', 'DD/MM/YYYY')
) OR (
p.D02 >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND p.D02 < TO_DATE('10/1/2019', 'DD/MM/YYYY')
) OR (
r.D03 >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND r.D03 < TO_DATE('10/1/2019', 'DD/MM/YYYY')
)
THEN 1
END
) = 1
Notes:
this returns only TPHONE.ID, since this seems to be what you are looking for; if you want more columns, then you can add them to the SELECT clause and to the GROUP BY clause (beware that this might change the grouping condition though, if these columns are not functionnaly dependant on TPHONE.ID)
I used table aliases to shorten the query
I used TO_DATE() to generate proper dates instead of relying on the default format of the database (which may change across databses and sessions). There is an assumption here that the format of your dates is DD/MM/YYYY (it could also be MM/DD/YYYY)
You could use ROW_NUMBER to enumerate the rows with the same ID and then select only the first instance:
SELECT TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03
FROM (
SELECT TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03,
ROW_NUMBER() over (PARTITION BY TPHONE.ID ORDER BY TPHONE.ID) as RN
FROM TPHONE,
TPHONEREQUEST
WHERE TPHONE.ID = TPHONEREQUEST.DEVICEID
AND TPHONE.ID IN
(
SELECT TPHONE.ID
FROM TPHONE
WHERE TPHONE.DLASTCHANGED >= '7/1/2019' AND TPHONE.DLASTCHANGED < '10/1/2019'
OR TPHONE.D02 >= '7/1/2019' AND TPHONE.D02 < '10/1/2019'
OR TPHONEREQUEST.D03 >= '7/1/2019' AND TPHONEREQUEST.D03 < '10/1/2019'
)
ORDER BY TPHONE.LOCATIONID, TPHONE.ID
)
WHERE RN = 1

Oracle count with group by returning empty rows

I am trying to build a query where it returns the count for each month (passing a start and an end date) of a certain value, the output should be like this
Month Qtn
---------
|July|0|
|Augu|0|
|Sept|0|
but I get no rows, the query is the following:
SELECT TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM') AS XMONTH, COUNT(TT_CUSTOMERS.NAMES) AS QTY FROM TT_CUSTOMERS
WHERE COMPANY = 700
AND TT_CUSTOMERS.C1_LOOKUP_ID = 100
AND TT_CUSTOMERS.C2_LOOKUP_ID = 1
AND TT_CUSTOMERS.C3_LOOKUP_ID IN (70, 80)
AND TT_CUSTOMERS.ST_LOOKUP_ID = 90
AND TO_NUMBER(TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMMDD')) >= TO_NUMBER('20170701')
AND TO_NUMBER(TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMMDD')) <= TO_NUMBER('20170930')
GROUP BY TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM')
ORDER BY TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM') ASC;
I know it's because the query is returning no rows, so the XMONTH is emtpy and so also the qty is empty, but I can't figure out a way to get the output that I've shown.
Assuming closed_date is of a date datatype...
and assuming all your other fields values are int and matches exist in the database...
I'd assume it's the numeric comparison of date values. Compare a date as a date not as numbers.
SELECT TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM') AS XMONTH
, COUNT(TT_CUSTOMERS.NAMES) AS QTY
FROM TT_CUSTOMERS
WHERE COMPANY = 700
AND TT_CUSTOMERS.C1_LOOKUP_ID = 100
AND TT_CUSTOMERS.C2_LOOKUP_ID = 1
AND TT_CUSTOMERS.C3_LOOKUP_ID IN (70, 80)
AND TT_CUSTOMERS.ST_LOOKUP_ID = 90
AND TT_CUSTOMERS.CLOSED_DATE >= TO_DATE('20170701','YYYYMMDD')
AND TT_CUSTOMERS.CLOSED_DATE <= TO_DATE('20170930','YYYYMMDD')
GROUP BY TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM')
ORDER BY TO_CHAR(TT_CUSTOMERS.CLOSED_DATE, 'YYYYMM') ASC;

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))

Query to find row till which sum less than an amount

I have an account where interest is debited corresponding to each account as below
amount Date
2 01-01-2012
5 02-01-2012
2 05-01-2012
1 07-01-2012
If the total credit in the account is 8. Ineed a query to find till what dates interest the credit amount can adjust.
Here the query should give output as 02-01-2012(2+5 < 8). I know this can be handled through cursor. But is there any method to write this as a single query in ORACLE.
SELECT pdate
FROM (
SELECT t.*,
LAG(date) OVER (ORDER BY date) AS pdate
8 - SUM(amount) OVER (ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS diff
FROM mytable t
ORDER BY
date
)
WHERE diff < 0
AND rownum = 1
Not knowing the structure of your table, here's a guess:
SELECT date from your_table
GROUP BY AMOUNT
HAVING SUM(AMOUNT) < 8
Note: this is LESS THAN 8. Change the conditional as appropriate.
Doesn't do the (2+5)<8 thing yet:
select max(cum_sum), max(date)
from (
select date,
sum(amount) over (order by date) cum_sum
) where cum_sum < 8

Resources