How to select data from day by day with special time? - sql

I want to select data day by day. Condition: data will be cut the day from 10:00 pm to tomorrow 10 pm. For example:
| REGTIME | T8 | T9 | T10 |
| 2008-12-03 | 879 | 785| 542 |
| 2008-12-04 | 556 | 454| 321 |
| 2008-12-05 | 678 | 122| 212 |
| 2008-12-06 | 887 | 544| 214 |
(2008-12-03 data will be show from 2008-12-02 10:00pm to 2008-12-03 10:00 pm)...
That query shows me all date, but it shows day with all same value.
SELECT REGTIME2,
A.T8,T9,T10
FROM
( SELECT
SUM(CASE WHEN sdudent_type = 'AAAA1' THEN 1 ELSE 0 END) AS T8,
SUM(CASE WHEN sdudent_type = 'AAAA2' THEN 1 ELSE 0 END) AS T9,
SUM(CASE WHEN sdudent_type = 'AAAA3' THEN 1 ELSE 0 END) AS T10
FROM class_men C
WHERE REGTIME >= TO_DATE(:REGTIME_from,'YYYYMMDDHH24MISS')
AND REGTIME < TO_DATE(:REGTIME_to,'YYYYMMDDHH24MISS')
) A,
(
select distinct
to_char(REGTIME,'YYYY-MM-DD') AS REGTIME2
from class_men
group by REGTIME
order by to_char(REGTIME,'YYYY-MM-DD') desc
) B

You are getting the results duplicated for all days as you are using a CROSS JOIN (written in the legacy syntax using a comma) to join the two SELECT clauses and there is no correlation between those two statements.
You appear to want something like:
SELECT TRUNC( RegTime ) AS RegTime,
COUNT(CASE WHEN sdudent_type = 'AAAA1' THEN 1 END) AS T8,
COUNT(CASE WHEN sdudent_type = 'AAAA2' THEN 1 END) AS T9,
COUNT(CASE WHEN sdudent_type = 'AAAA3' THEN 1 END) AS T10
FROM class_men C
WHERE REGTIME >= TO_DATE(:REGTIME_from,'YYYYMMDDHH24MISS')
AND REGTIME < TO_DATE(:REGTIME_to,'YYYYMMDDHH24MISS')
GROUP BY TRUNC( RegTime );
or using TO_CHAR( RegTime, 'YYYY-MM-DD' ) instead of TRUNC( RegTime ).
Which, for the sample data:
CREATE TABLE class_men ( regTime, sdudent_type ) AS
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 250 UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA2'
FROM DUAL CONNECT BY LEVEL <= 42 UNION ALL
SELECT DATE '2021-01-01' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 13 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 99 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA2'
FROM DUAL CONNECT BY LEVEL <= 17 UNION ALL
SELECT DATE '2021-01-02' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 24 UNION ALL
SELECT DATE '2021-01-03' + INTERVAL '1' MINUTE * LEVEL, 'AAAA1'
FROM DUAL CONNECT BY LEVEL <= 23 UNION ALL
SELECT DATE '2021-01-03' + INTERVAL '1' MINUTE * LEVEL, 'AAAA3'
FROM DUAL CONNECT BY LEVEL <= 50;
Would output (for the range 20210101000000 to 20210104000000):
REGTIME | T8 | T9 | T10
:------------------ | --: | -: | --:
2021-01-01 00:00:00 | 250 | 42 | 13
2021-01-03 00:00:00 | 23 | 0 | 50
2021-01-02 00:00:00 | 99 | 17 | 24
(Note: the date format depends on the NLS_DATE_FORMAT session parameter [unless you use TO_CHAR].)
db<>fiddle here

Related

SQL statement to return the Min and Max amount of stock per article for a given Month

I have a table from which I am trying to return the quantity per day that the article was in the system.
Example is in table Bestand the are multiple palletes of a different articles that each have a Booking In and Out date; I am try to find out the Min and Max amount of stock that was in the system per article and month.
My thinking is that if I can return the stock quantity for each day and then read out the Min and Max values.
The Timespan would be set at the time of running the SQL and the articles would be fixed.
To find out the quantity for each day I have used the following SQL:
SELECT DISTINCT
a.artbez1 AS Artikelbezeichnung,
b.artikelnr AS Artikelnummer,
SUM(CASE WHEN TO_DATE('2019-11-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') BETWEEN b.neu_datum AND b.aender_datum THEN 1 * b.menge_ist ELSE 0 END) AS "01 Nov 2019"
FROM
artikel a, bestand b
WHERE
b.artikelnr IN ('273632002', .... (huge long list of numbers) ....)
AND b.artikelnr = a.artikelnr
GROUP BY
a.artbez1, b.artikelnr;
This returns for example:
ARTIKELBEZEICHNUNG
ARTIKELNUMMER
01 Nov 2019
SC-4400.CW
220450002
39
S-320.FK120
220502004
0
H-595.FK120
220800004
35
AC-548.FK209
220948032
0
AS-6800.CW
221355002
20
I would like return this for each day of the Month and then from that return the Min and Max Value for each Article
I have the following SQL to return the days of a given Month and was wondering if anyone had any ideas on how they could be combined (If at all possible):
SELECT to_date('01.11.2019','dd.mm.yyyy')+LEVEL-1
FROM dual
CONNECT BY LEVEL <= TO_CHAR(LAST_DAY(to_date('01.11.2019','dd.mm.yyyy')),'DD')
DATES
2019-11-01 00:00:00
2019-11-02 00:00:00
2019-11-03 00:00:00
2019-11-04 00:00:00
2019-11-05 00:00:00
2019-11-06 00:00:00
2019-11-07 00:00:00
The result i am try to get would be something like:
ARTIKELBEZEICHNUNG
ARTIKELNUMMER
Nov 19 Min
Nov 19 Max
SC-4400.CW
220450002
5
39
S-320.FK120
220502004
0
15
H-595.FK120
220800004
2
35
AC-548.FK209
220948032
0
0
AS-6800.CW
221355002
10
20
Is this at all possible in SQL?
Thanks for taking the time to read my post.
JeRi
You can use a partitioned outer join:
WITH calendar ( day ) AS (
SELECT DATE '2019-11-01'
FROM DUAL
UNION ALL
SELECT day + INTERVAL '1' DAY
FROM calendar
WHERE day < LAST_DAY( DATE '2019-11-01' )
),
daily_totals ( artbez1, Artikelnr, Day, total_menge_ist ) AS (
SELECT MAX( ab.artbez1 ),
ab.artikelnr,
c.day,
COALESCE( SUM( ab.menge_ist ), 0 )
FROM calendar c
LEFT OUTER JOIN
( SELECT a.artikelnr,
a.artbez1,
b.neu_datum,
b.aender_datum,
b.menge_ist
FROM artikel a
LEFT JOIN bestand b
ON ( a.artikelnr = b.artikelnr )
-- WHERE b.artikelnr IN ('273632002', .... (huge long list of numbers) ....)
) ab
PARTITION BY ( ab.artikelnr, ab.artbez1 )
ON ( c.day BETWEEN ab.neu_datum AND ab.aender_datum )
GROUP BY ab.artikelnr, c.day
)
SELECT MAX( artbez1 ) AS Artikelbezeichnung,
artikelnr AS Artikelnummer,
TRUNC( day, 'MM' ) AS month,
MIN( total_menge_ist ) AS min_total_menge_ist,
MAX( total_menge_ist ) AS max_total_menge_ist
FROM daily_totals
GROUP BY artikelnr, TRUNC( day, 'MM' );
Which, for the sample data:
CREATE TABLE artikel ( artikelnr, artbez1 ) AS
SELECT 220450002, 'SC-4400.CW' FROM DUAL UNION ALL
SELECT 220502004, 'S-320.FK120' FROM DUAL UNION ALL
SELECT 220800004, 'H-595.FK120' FROM DUAL UNION ALL
SELECT 220948032, 'AC-548.FK209' FROM DUAL UNION ALL
SELECT 221355002, 'AS-6800.CW' FROM DUAL;
CREATE TABLE bestand ( artikelnr, neu_datum, aender_datum, menge_ist ) AS
SELECT 220450002, DATE '2019-10-30', DATE '2019-11-01', 20 FROM DUAL UNION ALL
SELECT 220450002, DATE '2019-11-01', DATE '2019-11-05', 19 FROM DUAL UNION ALL
SELECT 220502004, DATE '2019-11-05', DATE '2019-11-03', 5 FROM DUAL UNION ALL
SELECT 220800004, DATE '2019-11-01', DATE '2019-11-15', 35 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-20', DATE '2019-11-05', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-25', DATE '2019-11-10', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-28', DATE '2019-11-13', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-10-30', DATE '2019-11-15', 5 FROM DUAL UNION ALL
SELECT 221355002, DATE '2019-11-05', DATE '2019-11-20', 5 FROM DUAL;
Outputs:
ARTIKELBEZEICHNUNG | ARTIKELNUMMER | MONTH | MIN_TOTAL_MENGE_IST | MAX_TOTAL_MENGE_IST
:----------------- | ------------: | :------------------ | ------------------: | ------------------:
SC-4400.CW | 220450002 | 2019-11-01 00:00:00 | 0 | 39
S-320.FK120 | 220502004 | 2019-11-01 00:00:00 | 0 | 0
AC-548.FK209 | 220948032 | 2019-11-01 00:00:00 | 0 | 0
H-595.FK120 | 220800004 | 2019-11-01 00:00:00 | 0 | 35
AS-6800.CW | 221355002 | 2019-11-01 00:00:00 | 0 | 25
db<>fiddle here

Getting a date from a 4 digit strange date format in Oracle SQL

I receive a string from decoding codebar-128, once I parse all data in the code readed I get a date in a strange 4 digits format: 'YDDD'
The 'Y' digit represents the last digit of the Year (0-9). The 'DDD' digits represent the Day of year (1-366).
The issue is the ambiguous value of the Year. The rule to solve that issue must be the follow:
The Year computed for 'Y' digit must be the nearest year to Sysdate year.
Never the difference from Sysdate year and computed year for the 'Y' digit will be greater than 4.
My code:
SELECT SYSDATE, TO_DATE('0213', 'YDDD'), TO_DATE('1212', 'YDDD'),
TO_DATE('2212', 'YDDD'), TO_DATE('3212', 'YDDD'), TO_DATE('4213', 'YDDD'),
TO_DATE('6212', 'YDDD'), TO_DATE('7212', 'YDDD'), TO_DATE('8213', 'YDDD'),
TO_DATE('9212', 'YDDD')
FROM dual;
This is that I need to get:
+-----------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+
| SYSDATE | TO_DATE('20213','YYDDD') | TO_DATE('21212','YYDDD') | TO_DATE('22212','YYDDD') | TO_DATE('23212','YYDDD') | TO_DATE('24213','YYDDD') | TO_DATE('16213','YYDDD') | TO_DATE('17212','YYDDD') | TO_DATE('18212','YYDDD') | TO_DATE('19212','YYDDD') |
+-----------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+
| 26-JUN-20 | 31-JUL-20 | 31-JUL-21 | 31-JUL-22 | 31-JUL-23 | 31-JUL-24 | 31-JUL-16 | 31-JUL-17 | 31-JUL-18 | 31-JUL-19 |
+-----------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+--------------------------+
As you can see, if I had the penultimate digit of the year there would be no issue.
This is that I'm really getting:
+-----------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+
| SYSDATE | TO_DATE('0213','YDDD') | TO_DATE('1212','YDDD') | TO_DATE('2212','YDDD') | TO_DATE('3212','YDDD') | TO_DATE('4213','YDDD') | TO_DATE('6212','YDDD') | TO_DATE('7212','YDDD') | TO_DATE('8213','YDDD') | TO_DATE('9212','YDDD') |
+-----------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+
| 26-JUN-20 | 31-JUL-20 | 31-JUL-21 | 31-JUL-22 | 31-JUL-23 | 31-JUL-24 | 31-JUL-26 | 31-JUL-27 | 31-JUL-28 | 31-JUL-29 |
+-----------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+
You can compare the single-digit value with the last digit of the current year, and if the difference is more than 4, adjust but 10 years. But it needs to go both ways; once 'today' is in 2026, you'll be adding 10 years instead.
select column_value as val,
to_date(column_value, 'YDDD') as dt1,
to_number(substr(column_value, 1, 1)) as y,
mod(extract(year from sysdate), 10) as yy,
case
when to_number(substr(column_value, 1, 1)) - mod(extract(year from sysdate), 10) > 4 then -10
when mod(extract(year from sysdate), 10) - to_number(substr(column_value, 1, 1)) > 4 then 10
else 0
end as adj,
to_date(column_value, 'YDDD')
+ case
when to_number(substr(column_value, 1, 1)) - mod(extract(year from sysdate), 10) > 4 then -10
when mod(extract(year from sysdate), 10) - to_number(substr(column_value, 1, 1)) > 4 then 10
else 0
end * interval '1' year as dt2,
add_months(to_date(column_value, 'YDDD'),
12 * case
when to_number(substr(column_value, 1, 1)) - mod(extract(year from sysdate), 10) > 4 then -10
when mod(extract(year from sysdate), 10) - to_number(substr(column_value, 1, 1)) > 4 then 10
else 0
end) as dt2
from table(sys.odcivarchar2list('0213', '1212', '2212', '3212', '4213',
'5212', '6212', '7212', '8213', '9212'));
which gets
VAL DT1 Y YY ADJ DT2 DT2
---- ---------- ---------- ---------- ---------- ---------- ----------
0213 2020-07-31 0 0 0 2020-07-31 2020-07-31
1212 2021-07-31 1 0 0 2021-07-31 2021-07-31
2212 2022-07-31 2 0 0 2022-07-31 2022-07-31
3212 2023-07-31 3 0 0 2023-07-31 2023-07-31
4213 2024-07-31 4 0 0 2024-07-31 2024-07-31
5212 2025-07-31 5 0 -10 2015-07-31 2015-07-31
6212 2026-07-31 6 0 -10 2016-07-31 2016-07-31
7212 2027-07-31 7 0 -10 2017-07-31 2017-07-31
8213 2028-07-31 8 0 -10 2018-07-31 2018-07-31
9212 2029-07-31 9 0 -10 2019-07-31 2019-07-31
I haven't verified the future-year behaviour so you probably need to test and adjust that as necessary.
Split it up in multiple with clauses so it is easier to understand, you can join it into a single query if you want.
WITH sampledata (dt) AS
(
SELECT '0213' FROM DUAL UNION
SELECT '1212' FROM DUAL UNION
SELECT '2212' FROM DUAL UNION
SELECT '3212' FROM DUAL UNION
SELECT '4213' FROM DUAL UNION
SELECT '5213' FROM DUAL UNION
SELECT '6212' FROM DUAL UNION
SELECT '7212' FROM DUAL UNION
SELECT '8213' FROM DUAL UNION
SELECT '9212' FROM DUAL
), parsed_sampledata (yr, ddd) AS
(
SELECT substr(d.dt,1, 1) + TO_CHAR(SYSDATE,'YY') as yr, substr(d.dt,2,3) as ddd
FROM sampledata d
)
SELECT TO_DATE(ddd||yr - (CASE WHEN yr - TO_CHAR(SYSDATE,'YY') < 5 THEN 0 ELSE 10 END),'DDDYY')
FROM parsed_sampledata d;
31-JUL-2020
31-JUL-2021
31-JUL-2022
31-JUL-2023
31-JUL-2024
01-AUG-2015
30-JUL-2016
31-JUL-2017
01-AUG-2018
31-JUL-2019
This should give you some ideas:
WITH DATES_LIST AS
(
SELECT '0213' AS D FROM DUAL UNION
SELECT '1212' AS D FROM DUAL UNION
SELECT '2212' AS D FROM DUAL UNION
SELECT '3212' AS D FROM DUAL UNION
SELECT '4213' AS D FROM DUAL UNION
SELECT '5213' AS D FROM DUAL UNION
SELECT '6213' AS D FROM DUAL UNION
SELECT '7212' AS D FROM DUAL UNION
SELECT '8212' AS D FROM DUAL UNION
SELECT '9212' AS D FROM DUAL
)
SELECT TO_DATE(REGEXP_REPLACE(D,'^\d{1}',
CASE WHEN BOTT_R <= UPP_R THEN BOT ELSE UPP END),'YYDDD') AS YEAR
FROM (
select D,(TO_CHAR(SYSDATE,'RR') - 10) + regexp_substr(D, '^\d{1}') BOT,
ABS((TO_CHAR(SYSDATE,'RR') - 10) + regexp_substr(D, '^\d{1}')-TO_CHAR(SYSDATE,'RR')) BOTT_R,
TO_CHAR(SYSDATE,'RR') + regexp_substr(D, '^\d{1}') UPP,
(TO_CHAR(SYSDATE,'RR') + regexp_substr(D, '^\d{1}')) - TO_CHAR(SYSDATE,'RR') UPP_R
from DATES_LIST);
If you need to convert to many variables(many) my advise is to create a DETERMINISTIC function.
Regards.

How to count ratio hourly?

I`m stuck a bit with understanding of my further actions while performing queries.
I have two tables "A"(date, response, b_id) and "B"(id, country). I need to count hourly ratio of a number of entries where response exists to the total number of entries on a specific date. The final selection should consist of columns "hour", "ratio".
SELECT COUNT(*) FROM A WHERE RESPONSE IS NOT NULL//counting entries with response
SELECT COUNT(*) FROM A//counting total number of entries
How to count the ratio? Should I create a separate variable for it?
How to count for each hour on a day? Should I make smth like a loop? + How can I get the "hour" part of a date?
What is the best way to select the hours and counted ratio? Should I make a separate table for it?
I`m rather new to make complex queries, so I woud be happy for every kind of help
You can do this as:
select to_char(datecol, 'HH24') as hour,
count(response) as has_response, count(*) as total,
count(response) / count(*) as ratio
from a
where datecol >= date '2018-09-18' and datecol < date '2018-09-19'
group by to_char(datecol, 'HH24');
You can also do this using avg() -- which is also fun:
select to_char(datecol, 'HH24'),
avg(case when response is not null then 1.0 else 0 end) as ratio
from a
where datecol >= date '2018-09-18' and datecol < date '2018-09-19'
group by to_char(datecol, 'HH24')
In this case, that requires more typing, though.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE A ( dt, response, b_id ) AS
SELECT DATE '2018-09-18' + INTERVAL '00:00' HOUR TO MINUTE, NULL, 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '00:10' HOUR TO MINUTE, 'A', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '00:20' HOUR TO MINUTE, 'B', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '01:00' HOUR TO MINUTE, 'C', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '01:10' HOUR TO MINUTE, 'D', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '02:00' HOUR TO MINUTE, NULL, 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '03:00' HOUR TO MINUTE, 'E', 1 FROM DUAL UNION ALL
SELECT DATE '2018-09-18' + INTERVAL '05:10' HOUR TO MINUTE, 'F', 1 FROM DUAL;
Query 1:
SELECT b_id,
TO_CHAR( TRUNC( dt, 'HH' ), 'YYYY-MM-DD HH24:MI:SS' ) AS hour,
COUNT(RESPONSE) AS total_response_per_hour,
COUNT(*) AS total_per_hour,
total_response_per_day,
total_per_day,
COUNT(response) / total_response_per_day AS ratio_for_responses,
COUNT(*) / total_per_day AS ratio
FROM (
SELECT A.*,
COUNT(RESPONSE) OVER ( PARTITION BY b_id, TRUNC( dt ) ) AS total_response_per_day,
COUNT(*) OVER ( PARTITION BY b_id, TRUNC( dt ) ) AS total_per_day
FROM A
)
GROUP BY
b_id,
total_per_day,
total_response_per_day,
TRUNC( dt, 'HH' )
ORDER BY
TRUNC( dt, 'HH' )
Results:
| B_ID | HOUR | TOTAL_RESPONSE_PER_HOUR | TOTAL_PER_HOUR | TOTAL_RESPONSE_PER_DAY | TOTAL_PER_DAY | RATIO_FOR_RESPONSES | RATIO |
|------|---------------------|-------------------------|----------------|------------------------|---------------|---------------------|-------|
| 1 | 2018-09-18 00:00:00 | 2 | 3 | 6 | 8 | 0.3333333333333333 | 0.375 |
| 1 | 2018-09-18 01:00:00 | 2 | 2 | 6 | 8 | 0.3333333333333333 | 0.25 |
| 1 | 2018-09-18 02:00:00 | 0 | 1 | 6 | 8 | 0 | 0.125 |
| 1 | 2018-09-18 03:00:00 | 1 | 1 | 6 | 8 | 0.16666666666666666 | 0.125 |
| 1 | 2018-09-18 05:00:00 | 1 | 1 | 6 | 8 | 0.16666666666666666 | 0.125 |
SELECT withResponses.hour,
withResponses.cnt AS withResponse,
alls.cnt AS AllEntries,
(withResponses.cnt / alls.cnt) AS ratio
FROM
( SELECT to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' hour,
count(*) AS cnt
FROM A
WHERE RESPONSE IS NOT NULL
GROUP BY to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' ) withResponses,
( SELECT to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' hour,
count(*) AS cnt
FROM A
GROUP BY to_char(d, 'DD-MM-YY - HH24') || ':00 to :59 ' ) alls
WHERE alls.hour = withResponses.hour ;
SQLFiddle: http://sqlfiddle.com/#!4/c09b9/2

How to pull number of unique items sold in oracle sql

I have orders data for all items sold in my store for the past year. The table looks like this:
order_date item_id item_name order_quantity unit_price
--------------------------------------------------------------------
01-01-2017 a123 a234 2 10
04-02-2017 b123 b234 3 12
04-09-2017 c123 c234 1 15
04-10-2017 b123 b234 2 12
I need to pull data for number of unique items sold by week, month, and quarter. The query output should look like this:
timeline number unique_item_count
week 1 1
week 14 1
week 15 2
month 1 1
month 4 2
quarter 2 2
I've tried the following:
SELECT
TO_CHAR(c.ORDER_DAY, 'Q') AS QTR,
TO_CHAR(c.ORDER_DAY, 'MM') AS MNTH,
TO_CHAR(c.ORDER_DAY, 'WW') AS WK,
COUNT(DISTINCT CASE WHEN (c.QUANTITY*c.OUR_PRICE) > 0 THEN ITEM_ID ELSE NULL END) AS SALES_CNT
FROM TABLE c
GROUP BY TO_CHAR(c.ORDER_DAY, 'Q'), TO_CHAR(c.ORDER_DAY, 'MM'), TO_CHAR(c.ORDER_DAY, 'WW');
This works for weekly data, however, monthly and quarterly is just a sum of weekly numbers, which is incorrect in this case, since same items might be ordered in two different weeks, so the monthly number should be lower.
Is there a way to pull number of unique items purchased in each week, month, quarter?
Thanks!
You can do it without using the union of multiple statements by using UNPIVOT:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_c (
order_date,
item_id,
item_name,
order_quantity,
order_price
) AS
SELECT DATE '2017-09-01' + LEVEL * 3 - 3, 1, 'aaa', 1, 1 FROM DUAL CONNECT BY LEVEL <= 10
UNION ALL
SELECT DATE '2017-09-01' + LEVEL * 4 - 4, 2, 'bbb', 1, 1 FROM DUAL CONNECT BY LEVEL <= 10
UNION ALL
SELECT DATE '2017-09-01' + LEVEL * 5 - 5, 3, 'ccc', 1, 1 FROM DUAL CONNECT BY LEVEL <= 10
Query 1:
SELECT year,
timeline,
"number",
COUNT( DISTINCT item_id )
FROM (
SELECT TO_CHAR( order_date, 'WW' ) AS week,
TO_CHAR( order_date, 'MM' ) AS month,
TO_CHAR( order_date, 'Q' ) AS quarter,
EXTRACT( YEAR from order_date ) AS year,
item_id
FROM table_c
WHERE order_quantity > 0
AND order_price > 0
)
UNPIVOT ( "number" FOR timeline IN ( week AS 'week', month AS 'month', quarter AS 'quarter' ) )
GROUP BY year, timeline, "number"
ORDER BY year, timeline, "number"
Results:
| YEAR | TIMELINE | number | COUNT(DISTINCTITEM_ID) |
|------|----------|--------|------------------------|
| 2017 | month | 09 | 3 |
| 2017 | month | 10 | 2 |
| 2017 | quarter | 3 | 3 |
| 2017 | quarter | 4 | 2 |
| 2017 | week | 35 | 3 |
| 2017 | week | 36 | 3 |
| 2017 | week | 37 | 3 |
| 2017 | week | 38 | 3 |
| 2017 | week | 39 | 3 |
| 2017 | week | 40 | 2 |
| 2017 | week | 41 | 1 |
| 2017 | week | 42 | 1 |
You can do this with union all:
SELECT 'Q' as timeline, TO_CHAR(c.ORDER_DAY, 'Q') AS number,
COUNT(DISTINCT CASE WHEN (c.QUANTITY*c.OUR_PRICE) > 0 THEN ITEM_ID END) AS SALES_CNT
FROM TABLE c
GROUP BY TO_CHAR(c.ORDER_DAY, 'Q')
UNION ALL
SELECT 'MM' as timeline, TO_CHAR(c.ORDER_DAY, 'MM') AS number,
COUNT(DISTINCT CASE WHEN (c.QUANTITY*c.OUR_PRICE) > 0 THEN ITEM_ID END) AS SALES_CNT
FROM TABLE c
GROUP BY TO_CHAR(c.ORDER_DAY, 'MM')
UNION ALL
SELECT 'WW' as timeline, TO_CHAR(c.ORDER_DAY, 'WW') AS number,
COUNT(DISTINCT CASE WHEN (c.QUANTITY*c.OUR_PRICE) > 0 THEN ITEM_ID END) AS SALES_CNT
FROM TABLE c
GROUP BY TO_CHAR(c.ORDER_DAY, 'WW');
You can use GROUPING SETS to aggregate at various levels in a single query. Like so:
SELECT CASE
WHEN GROUPING (TO_CHAR (c.order_day, 'Q')) = 0 THEN 'Quarter'
WHEN GROUPING (TO_CHAR (c.order_day, 'MM')) = 0 THEN 'Month'
WHEN GROUPING (TO_CHAR (c.order_day, 'WW')) = 0 THEN 'Week'
ELSE '??'
END
timeline,
CASE
WHEN GROUPING (TO_CHAR (c.order_day, 'Q')) = 0 THEN TO_CHAR (c.order_day, 'Q')
WHEN GROUPING (TO_CHAR (c.order_day, 'MM')) = 0 THEN TO_CHAR (c.order_day, 'MM')
WHEN GROUPING (TO_CHAR (c.order_day, 'WW')) = 0 THEN TO_CHAR (c.order_day, 'WW')
ELSE '??'
END
"NUMBER",
COUNT (DISTINCT CASE WHEN (c.quantity * c.our_price) > 0 THEN item_id ELSE NULL END) unique_item_count
FROM c
GROUP BY GROUPING SETS (
(TO_CHAR (c.order_day, 'Q')),
(TO_CHAR (c.order_day, 'MM')),
(TO_CHAR (c.order_day, 'WW')))

Split week based on weightage

I have a weights defined like below in a table.
DayNum | Day | Weight | Cumulative Weight
1 | MON | 0.3 | 0.3
2 | TUE | 0.15 | 0.45 (Sum of Mon and Tues)
3 | WED | 0.1 | 0.55 (Sum of Mon and Tues and Wed)
4 | THU | 0.1 | 0.65
5 | FRI | 0.15 | 0.8
6 | SAT | 0.2 | 1
And I have amounts in another table defined at weekly level (Mon - Sun) like below.
Item | Date | Amount
A | 30-May-16 | 10 ---- Week in May and June
A | 6-Jun-16 | 20
A | 13-Jun-16 | 30 and so on
A | 27-Jun-16 | 60 ---- Week in Jun and July
Now I want to insert into another table at a daily level, for the weeks which are overlapping between 2 different months (in above example - 30 May to 5 Jun).
Can anyone explain how I can achieve this in Oracle.
Output should be like below.
Item | Date | Amount
A | 30-May-16 | 4.5 (2 days from May which are Mon and Tues - so calculation is 10 * 0.45)
A | 1-Jun-16 | 5.5 (5 days from May which is the rest of the week - 10 minus 4.5)
A | 6-Jun-16 | 20 and so on
A | 27-Jun-16 | 39 (4 days from June which are Mon till Thurs - so calculation is 60 * 0.65)
A | 1-Jul-16 | 21 (3 days from July which is the rest of the week - 60 minus 39)
Try:
WITH some_data AS(
select a.*,
trunc( trunc( add_months( "DATE", 1 ), 'MM' ) - "DATE" )
As days_to_end_of_month,
trunc( add_months( "DATE", 1 ), 'MM' )
As start_of_next_month
from amounts a
), some_other_data AS (
SELECT some_data.*,
CASE WHEN days_to_end_of_month >= 6 THEN Amount
ELSE ( SELECT some_data.amount * "Cumulative Weight" FROM Weights w
WHERE some_data.days_to_end_of_month = DayNum )
END as new_Amount
FROM some_data
)
SELECT Item, "DATE", New_Amount as amount
FROM some_other_data
UNION ALL
SELECT Item, start_of_next_month, amount-new_amount
FROM some_other_data
WHERE days_to_end_of_month < 6
ORDER BY "DATE"
;
Oracle Setup:
CREATE TABLE Weights ( DayNum, Day, Weight ) AS
SELECT 1, 'MON', 0.3 FROM DUAL UNION ALL
SELECT 2, 'TUE', 0.15 FROM DUAL UNION ALL
SELECT 3, 'WED', 0.1 FROM DUAL UNION ALL
SELECT 4, 'THU', 0.1 FROM DUAL UNION ALL
SELECT 5, 'FRI', 0.15 FROM DUAL UNION ALL
SELECT 6, 'SAT', 0.2 FROM DUAL;
CREATE TABLE weekly_levels ( Item, "Date", Amount ) AS
SELECT 'A', DATE '2016-05-30', 10 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-06', 20 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-13', 30 FROM DUAL UNION ALL
SELECT 'A', DATE '2016-06-27', 60 FROM DUAL;
Query:
SELECT item,
start_date,
SUM( amount * weight ) AS amount
FROM (
SELECT item,
"Date" AS start_date,
LEAST( "Date" + INTERVAL '6' DAY, LAST_DAY( "Date" ) ) AS end_date,
amount
FROM weekly_levels
UNION
SELECT item,
GREATEST( "Date", TRUNC( "Date" + INTERVAL '6' DAY, 'MM' ) ) AS start_date,
"Date" + INTERVAL '6' DAY AS end_date,
amount
FROM weekly_levels
) d
INNER JOIN
Weights w
ON ( w.DayNum BETWEEN TO_CHAR( start_date, 'D' )
AND TO_CHAR( end_date, 'D' ) )
GROUP BY item, start_date
ORDER BY item, start_date;
Output:
ITEM START_DATE AMOUNT
---- ------------------- ----------
A 2016-05-30 00:00:00 4.5
A 2016-06-01 00:00:00 5.5
A 2016-06-06 00:00:00 20
A 2016-06-13 00:00:00 30
A 2016-06-27 00:00:00 39
A 2016-07-01 00:00:00 21