I am using Oracle 11.
I have 2 tables
TblA with columns id, entity_id and effective_date.
TblADetail with columns id and value.
If Value = 0 for the effective date, I want to keep looking for the next effective date until I found value <> 0 anymore.
The below query only look for value on 3/10/21.
If value = 0, I want to look for value on 3/11/21. If that's not 0, I want to stop.
But, if that's 0, I want to look for value on 3/12/21. If that's not 0, I want to stop.
But, if that's 0, I want to keep looking until value is not 0.
How can I do that ?
SELECT SUM(pd.VALUE)
FROM TblA p,TblADetail pd
WHERE p.id = pd.id
AND p.effective_date = to_date('03/10/2021','MM/DD/YYYY')
AND TRIM (p.entity_id) = 123
Sample data:
TblA
id entity_id effective_date
1 123 3/10/21
2 123 3/11/21
3 123 3/12/21
TblADetail
id value
1 -136
1 136
2 2000
3 3000
In the above data, for entity_id 123, starting from effective_date 3/10/21, I would like to to return value 2000 (from TblADetail) effective_date 3/11/21.
So, starting from a certain date, I want the results from the minimum date that has non-zero values.
Thank you.
You can do what you need to do by grouping the sum on the effective date, and using the MIN analytic function to find the earliest date. Once you've done that, you simply need to select the date that matches the earliest date.
E.g.:
with tbla as (select 1 id, ' 123' entity_id, to_date('10/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 2 id, ' 123' entity_id, to_date('11/03/2021', 'dd/mm/yyyy') effective_date from dual union all
select 3 id, ' 123' entity_id, to_date('12/03/2021', 'dd/mm/yyyy') effective_date from dual),
tbla_detail as (select 1 id, -136 value from dual union all
select 1 id, 136 value from dual union all
select 2 id, 2000 value from dual union all
select 3 id, 3000 value from dual),
results as (select a.effective_date,
sum(ad.value) sum_value,
min(case when sum(ad.value) != 0 then a.effective_date end) over () min_effective_date
from tbla a
inner join tbla_detail ad on a.id = ad.id
where a.effective_date >= to_date('10/03/2021', 'dd/mm/yyyy')
and trim(a.entity_id) = '123'
group by a.effective_date)
select sum_value
from results
where effective_date = min_effective_date;
SUM_VALUE
----------
2000
Straightforward; read comments within code. Sample data in lines #1 - 13, query begins at line #14.
SQL> with
2 -- sample data
3 tbla (id, entity_id, effective_date) as
4 (select 1, 123, date '2021-03-10' from dual union all
5 select 2, 123, date '2021-03-11' from dual union all
6 select 3, 123, date '2021-03-12' from dual
7 ),
8 tblb (id, value) as
9 (select 1, -136 from dual union all
10 select 1, 136 from dual union all
11 select 2, 2000 from dual union all
12 select 3, 3000 from dual
13 ),
14 tblb_temp as
15 -- simple grouping per ID
16 (select id, sum(value) value
17 from tblb
18 group by id
19 )
20 -- return TBLA values whose ID equals TBLB_TEMP's minimum ID
21 -- whose value isn't zero
22 select a.id, a.entity_id, a.effective_date
23 from tbla a
24 where a.id = (select min(b.id)
25 from tblb_temp b
26 where b.value > 0
27 );
ID ENTITY_ID EFFECTIVE_
---------- ---------- ----------
2 123 03/11/2021
SQL>
Imagine that you have a query and you want to only show that one who has ALL the values of a subquery. For example, we have the following table:
CREATE TABLE test
(
code VARCHAR2(4),
year VARCHAR2(4),
action VARCHAR2(50),
CONSTRAINT pk PRIMARY KEY (code, year)
);
And the following registers:
INSERT INTO test
VALUES ('1','2020','Departure');
INSERT INTO test
VALUES ('1','2021','Arrival');
INSERT INTO test
VALUES ('2','2020','Departure');
Imagine that a subquery returns me the following values:
('Departure','Arrival')
So I want to make a query that returns me only those codes and years which match both of the values that have been returned at the subquery. Looking at the registers, it should return only return ('1','2020') and ('1','2021') because they are the only ones whose actions are 'Arrival' and 'Departure'. How could I do it?
With a little bit expanded sample data, where CODEs 1 and 3 have both Arrival and Departure:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action,
11 count(distinct action) over () cnt_da
12 from test
13 )
14 select a.code, a.year
15 from test a join subq s on a.action = s.action
16 where s.cnt_da = (select count(distinct action)
17 from test b
18 where b.code = a.code
19 );
CODE YEAR
---------- ----------
1 2021
1 2020
3 2019
3 2018
SQL>
Yet another option, using the MINUS set operator:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action from test) --> this is your "subquery"
11 select code, year
12 from test a
13 where (select s.action from subq s
14 minus
15 select b.action from test b where b.code = a.code
16 ) is null;
CODE YEAR
---------- ----------
1 2020
1 2021
3 2018
3 2019
SQL>
Enclose your query inside a CTE like this:
with cte as (
<your query here>
)
select t.*
from test t
where
t.action in (select action from cte)
and
code in (
select code
from test
where action in (select action from cte)
group by code
having count(distinct action) = (select count(*) from cte)
)
The subquery of IN returns all the codes that contain all the actions that your query returns.
See the demo.
Results:
> CODE | YEAR | ACTION
> :--- | :--- | :--------
> 1 | 2021 | Arrival
> 1 | 2020 | Departure
As a general solution, I'd look for an opportunity to use HAVING count(*) = #. Roughly,
SELECT code FROM table WHERE action in (SUBSELECT) GROUP BY code HAVING count(*) = (SELECT count(*) from SUBSELECT)
Of course if you can have multiple Arrival for a single code, you have to include a DISTINCT as well. Roughly,
SELECT code FROM (SELECT code, distinct(action) FROM table) WHERE action in...
I worry about the performance of a query like this, but you'd have to check it in situ since most database engines have the ability to transform complex queries like this for efficiency.
===
I think a JOIN will also work if you're sure that there's only one action of each type per code. For example.
SELECT t.code FROM SUBQUERY as s RIGHT JOIN table as t on t.action = s.action GROUP by t.code HAVING count(*) = (SELECT COUNT(*) FROM SUBQUERY)
(and yes in either case, you should use CTE features like #forpas suggests if you're going to inline the subquery to avoid repeatedly executing it).
I've got some help turning my table of the sort:
Col
23
25
15
53
...
into something like 23,25,15,53...
The query that does it is
SELECT max(ltrim(sys_connect_by_path(flow_run_id, ','), ','))
FROM
(select flow_run_id, rownum rn
from table
where CREATED_DATE < sysdate - 32
and flow_id = 3
order by 1 desc)
START WITH rn = 1
CONNECT BY PRIOR rn = rn - 1
(this beaulty was given by Michael in here)
My current problem is that the result is too long (ORA-01489 over the 4k chars from varchar2). I'm still learning about these sys_connected_by_path so I'd need some help sorting this. How could I make this query return me multiple rows instead of one super long line? i.e.:
Instead of
419,1,2,3,411,418,4,415,887,413,414,201,888,890,401,417,610,412,416,5,6,922,1080,1422,1423,1411,1412,1413,1414,1415,1416,1417,1418,1419,1964,2217,1636,2037,1988,1970,2038,1989,2000,2040,1993,2043,1994,2001,2044,1658,1995,2045,2224,1996,2019,1678,1997,2022,2201,1680,2219,2024,2207,1677,2209,2220,1959,2211,1961,2026,2212,1962,2028,2215,1675,1676,2035,2216,1986,1963,2017,1983,1935,2002,2018,1985,1936,2003,2020,2032,1937,2004,2021,2033,1938,1943,2023,2034,1939,1944,2025,2225,1941,1950,2027,2036,1942,1955,2029,2041,1945,1956,2030,2227,1946,1957,2031,2039,1947,2005,1974,2042,1948,2006,1976,2228,1949,2007,1978,1951,2009,1979,1929,1952,2012,1980,1931,1953,2013,1981,1933,1954,2015,2334,2350,2311,2239,2240,2241,2242,2245,2246,2249,2250,2336,2312,2008,2010,2011,2014,2251,2253,2016,2243,2244,2247,2351,2248,(...)
get
419,1,2,3,411,418,4,415,887,413,414,201,888,890,401,417,610,412,416,5,6,922,1080
1423,1411,1412,1413,1414,1415,1416,1417,1418,1419,1964,2217,1636,2037,1988,1970,2038
2000,2040,1993,2043,1994,2001,2044,1658,1995,2045,2224,1996,2019,1678,1997,2022,2201
(...)
Any tips?
Thanks!
f.
the following query will cut your big string in parts:
SQL> SELECT root_rn, MAX(concat)
2 FROM (SELECT connect_by_root(rn) root_rn,
3 ltrim(sys_connect_by_path(flow_run_id, ','), ',') concat
4 FROM (SELECT flow_run_id, rownum rn
5 FROM (SELECT round(dbms_random.VALUE(1, 10000))
6 AS flow_run_id
7 FROM dual
8 CONNECT BY ROWNUM <= 2000)
9 ORDER BY 1 DESC)
10 START WITH MOD(rn, 10) = 1
11 CONNECT BY PRIOR rn = rn - 1
12 AND MOD(rn, 10) != 1)
13 GROUP BY root_rn
14 ORDER BY root_rn;
ROOT_RN MAX(CONCAT)
---------- -------------------------------------------------------------------
1 654,6710,5297,5481,5085,2793,7646,9170,1051,2387
11 1882,8285,5430,4928,267,3779,3843,1151,3085,1446
21 4721,6087,6755,9904,805,2776,4633,2772,7785,5818
31 5189,5307,6481,2099,3832,9788,5970,8068,6605,3904
41 53,7013,1314,7717,9320,7069,907,5367,5013,7637
51 3903,2318,2611,7954,5751,5598,6148,6555,9724,984
[...]
You can replace "10" with a bigger number if you want more elements on each row.
Some little modifications to keep order
SELECT 10*frn+1 root,ltrim(sys_connect_by_path(flow_run_id,','),',') FROM
(SELECT flow_run_id,mod(rn,10) mrn,floor(rn/10) frn,count(*)over(partition by floor(rn/10))-1 crn FROM
(SELECT flow_run_id, row_number()over(order by flow_run_id)-1 rn FROM
(SELECT round(dbms_random.VALUE(1, 10000)) AS flow_run_id FROM dual CONNECT BY ROWNUM <= 2000
)
)
)
WHERE crn = mrn
START WITH mrn = 0
CONNECT BY PRIOR mrn = mrn-1 AND PRIOR frn = frn