oracle select two count values in one row - oracle

I need help with this select in oracle. I have table with 2 columns: (table > date, value) for example:
1.1.2017, 16
1.1.2017, 16
1.1.2017, 16
1.1.2017, 17
1.2.2017, 16
1.2.2017, 16
1.2.2017, 17
1.2.2017, 17
1.3.2017, 16
Result must be:
1.1.2017 as date, 3 as count of 16, 1 as count of 17
1.2.2017, 2, 2
1.3.2017, 1, 0
Current SQL:
select date, count(value) from table group by date, value
However, this does not return the same date with one row with count of both values.

You need conditional counting, something like this:
with
your_table ( dt, value ) as (
select to_date('1.1.2017', 'mm.dd.yyyy'), 16 from dual union all
select to_date('1.1.2017', 'mm.dd.yyyy'), 16 from dual union all
select to_date('1.1.2017', 'mm.dd.yyyy'), 16 from dual union all
select to_date('1.1.2017', 'mm.dd.yyyy'), 17 from dual union all
select to_date('1.2.2017', 'mm.dd.yyyy'), 16 from dual union all
select to_date('1.2.2017', 'mm.dd.yyyy'), 16 from dual union all
select to_date('1.2.2017', 'mm.dd.yyyy'), 17 from dual union all
select to_date('1.2.2017', 'mm.dd.yyyy'), 17 from dual union all
select to_date('1.3.2017', 'mm.dd.yyyy'), 16 from dual
)
-- end of test data; solution (SQL query) begins below this line
select dt, count(case value when 16 then 'x' end) as ct_16,
count(case value when 17 then 'x' end) as ct_17
from your_table
group by dt;
DT CT_16 CT_17
---------- ----- -----
01/01/2017 3 1
01/02/2017 2 2
01/03/2017 1 0

select date, value, count(value) from table group by date, value;

Related

How to select the first 5 dates from each group and put them in a single column separated by comma in Oracle?

I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>

PL/SQL program using CURSOR for frequency distribution in table

finaltableA has two columns WORDS, WCOUNTS. The program should insert every word of intitaltableA into finaltableA exactly once. In the column WCOUNTS the program should put how often the word occurs.
intitaltableA
COVID
is
a
disease
COVID
can
be
treated
with
antibodies
COVID
is
a
serious
disease
there
is
a
vaccination
available
for
COVID
finaltableA should look like this -
WORDS
WCOUNTS
a
3
antibodies
1
available
1
be
1
can
1
COVID
4
disease
2
for
1
is
3
serious
1
there
1
treated
1
vaccination
1
with
1
I should use a cursor to insert and update the table. I am new to PL/SQL.
It is quite obvious that you can do this without any cursor in PLSQL, so I guess that means that you are trying to solve a homework problem or a training question. Anyway, here you can see two simple options to get the result you want, with and without PLSQL.
Option 1 - Without PLSQL
with x ( words )
as
(
select 'COVID' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'disease' from dual union all
select 'COVID' from dual union all
select 'can' from dual union all
select 'be' from dual union all
select 'treated' from dual union all
select 'with' from dual union all
select 'antibodies' from dual union all
select 'COVID' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'serious' from dual union all
select 'disease' from dual union all
select 'there' from dual union all
select 'is' from dual union all
select 'a' from dual union all
select 'vaccination' from dual union all
select 'available' from dual union all
select 'for' from dual union all
select 'COVID' from dual
)
select words, count(words) as counter
from x
group by words
order by 2 desc;
If you execute it, it gives you the output you want
SQL> with x ( words )
2 as
3 (
4 select 'COVID' from dual union all
5 select 'is' from dual union all
6 select 'a' from dual union all
7 select 'disease' from dual union all
8 select 'COVID' from dual union all
9 select 'can' from dual union all
10 select 'be' from dual union all
11 select 'treated' from dual union all
12 select 'with' from dual union all
13 select 'antibodies' from dual union all
14 select 'COVID' from dual union all
15 select 'is' from dual union all
16 select 'a' from dual union all
17 select 'serious' from dual union all
18 select 'disease' from dual union all
19 select 'there' from dual union all
20 select 'is' from dual union all
21 select 'a' from dual union all
22 select 'vaccination' from dual union all
23 select 'available' from dual union all
24 select 'for' from dual union all
25 select 'COVID' from dual
26 )
27 select words, count(words) as counter
28 from x
29 group by words
30* order by 2 desc
SQL> /
WORDS COUNTER
----------- ----------
COVID 4
is 3
a 3
disease 2
there 1
available 1
for 1
serious 1
antibodies 1
can 1
vaccination 1
WORDS COUNTER
----------- ----------
treated 1
be 1
with 1
14 rows selected.
Option 2 - Using PLSQL and a CURSOR
In the below example, I'm going to use dbms_output to show the results. Keep in mind that here there are dozens of ways to do the same, I am just using the simplest one I can think of.
SQL> create table t ( words varchar2(40) ) ;
Table created.
SQL> insert into t
2 with x ( words )
3 as
4 (
5 select 'COVID' from dual union all
6 select 'is' from dual union all
7 select 'a' from dual union all
8 select 'disease' from dual union all
9 select 'COVID' from dual union all
10 select 'can' from dual union all
11 select 'be' from dual union all
12 select 'treated' from dual union all
13 select 'with' from dual union all
14 select 'antibodies' from dual union all
15 select 'COVID' from dual union all
16 select 'is' from dual union all
17 select 'a' from dual union all
18 select 'serious' from dual union all
19 select 'disease' from dual union all
20 select 'there' from dual union all
21 select 'is' from dual union all
22 select 'a' from dual union all
23 select 'vaccination' from dual union all
24 select 'available' from dual union all
25 select 'for' from dual union all
26 select 'COVID' from dual
27* ) select words from x
SQL> /
22 rows created.
SQL> commit ;
Commit complete.
SQL> declare
2 begin
3 for i in ( select words, count(words) as counter from t group by words order by 2 desc )
4 loop
5 dbms_output.put_line( 'Words: '||i.words||' - Wcount is: '||i.counter||' ');
6 end loop;
7* end;
SQL> /
Words: COVID - Wcount is: 4
Words: is - Wcount is: 3
Words: a - Wcount is: 3
Words: disease - Wcount is: 2
Words: antibodies - Wcount is: 1
Words: for - Wcount is: 1
Words: be - Wcount is: 1
Words: vaccination - Wcount is: 1
Words: can - Wcount is: 1
Words: available - Wcount is: 1
Words: treated - Wcount is: 1
Words: there - Wcount is: 1
Words: with - Wcount is: 1
Words: serious - Wcount is: 1
PL/SQL procedure successfully completed.

Oracle query to keep looking until value is not 0 anymore

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>

How can we get multiple rows data as single row in oracle

In image I have given table structure and sample data and I need output result as mentioned
With sample data you provided (lines #1 - 8), this returns desired result. Will it work for all other cases, I have no idea as the question lacks in quite a lot of information so YMMV.
SQL> with employee (id, name, type, visit_date) as
2 (select 1, 'Mohan', '01', date '2010-09-09' from dual union all
3 select 1, 'Mohan', '02', date '2010-09-10' from dual union all
4 --
5 select 1, 'Gani' , '01', date '2010-09-01' from dual union all
6 select 1, 'Gani' , '01', date '2010-09-02' from dual union all
7 select 1, 'Gani' , '01', date '2010-09-03' from dual
8 ),
9 --
10 type1 as
11 (select id, name, visit_date
12 from employee
13 where type = '01'
14 ),
15 type2 as
16 (select id, name, visit_date
17 from employee
18 where type = '02'
19 )
20 select
21 a.id,
22 a.name,
23 a.visit_date type1date,
24 b.visit_date type2date
25 from type1 a left join type2 b on a.id = b.id and a.name = b.name
26 order by a.id, a.name desc, a.visit_date;
ID NAME TYPE1DATE TYPE2DATE
---------- ----- ---------- ----------
1 Mohan 09/09/2010 10/09/2010
1 Gani 01/09/2010
1 Gani 02/09/2010
1 Gani 03/09/2010
SQL>

Oracle - creating a group based on group spacing

How to make a query that will create groups that have a space between them greater than "n"?
Data:
01-01-2000
02-01-2000
03-01-2000
06-01-2000
07-01-2000
19-02-2001
10-01-2002
11-01-2002
I would like to get a result for the interval between records, e.g. 2 days:
DATE GROUP
01-01-2000 1
02-01-2000 1
03-01-2000 1
06-01-2000 2
07-01-2000 2
19-02-2001 3
10-01-2002 4
11-01-2002 4
For 10 days:
01-01-2000 1
02-01-2000 1
03-01-2000 1
06-01-2000 1
07-01-2000 1
19-02-2001 2
10-01-2002 3
11-01-2002 3
Another example with integers:
with x as (
select 1 as A from dual
union all
select 2 as A from dual
union all
select 3 as A from dual
union all
select 10 as A from dual
union all
select 20 as A from dual
union all
select 22 as A from dual
union all
select 33 as A from dual
union all
select 40 as A from dual
union all
select 50 as A from dual
union all
select 100 as A from dual
union all
select 101 as A from dual
union all
select 102 as A from dual
) select A
from x;
I need to create groups for a value increase of more than 3:
Example result:
1 1
2 1
3 1
10 2
20 3
22 3
33 4
40 5
50 6
100 7
101 7
102 7
Here is one way to do it
CREATE TABLE TEST (
DATE_IN DATE
);
INSERT INTO TEST VALUES (TO_DATE('01-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('02-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('03-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('06-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('07-01-2000','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('19-02-2001','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('10-01-2002','DD-MM-YYYY'));
INSERT INTO TEST VALUES (TO_DATE('11-01-2002','DD-MM-YYYY'));
--HERE IS AN EXAMPLE FOR 1 DAY. Just change the value in the > 1 TO >10
--if you want to create a group if there is a gap of more than 10days
SELECT DATE_IN, SUM(NEW_GROUP) OVER ( ORDER BY DATE_IN) AS GROUPE FROM (
SELECT
DATE_IN,
CASE WHEN DATE_IN - LAG(DATE_IN,1,TO_DATE('01-01-1900','DD-MM-YYYY')) OVER ( ORDER BY DATE_IN) > 1 THEN 1 ELSE 0 END AS NEW_GROUP
FROM TEST
)
-- Result
DATE_IN GROUPE
2000-01-01T00:00:00Z 1
2000-01-02T00:00:00Z 1
2000-01-03T00:00:00Z 1
2000-01-06T00:00:00Z 2
2000-01-07T00:00:00Z 2
2001-02-19T00:00:00Z 3
2002-01-10T00:00:00Z 4
2002-01-11T00:00:00Z 4
Example with integer:
with x as (
select 1 as A from dual
union all
select 2 as A from dual
union all
select 3 as A from dual
union all
select 10 as A from dual
union all
select 20 as A from dual
union all
select 22 as A from dual
union all
select 33 as A from dual
union all
select 40 as A from dual
union all
select 50 as A from dual
union all
select 100 as A from dual
union all
select 101 as A from dual
union all
select 102 as A from dual
) SELECT A, SUM(NEW_GROUP) OVER ( ORDER BY A) AS GROUPE FROM (
SELECT
A,
CASE WHEN A - LAG(A,1,1) OVER ( ORDER BY A) > 5 THEN 1 ELSE 0 END AS NEW_GROUP
FROM X
)
order by A;

Resources