Duplicated rows numbering - oracle

I need to number the rows so that the row number with the same ID is the same. For example:
Oracle database. Any ideas?

Use the DENSE_RANK analytic function:
SELECT DENSE_RANK() OVER (ORDER BY id) AS row_number,
id
FROM your_table
Which, for the sample data:
CREATE TABLE your_table ( id ) AS
SELECT 86325 FROM DUAL UNION ALL
SELECT 86325 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86352 FROM DUAL UNION ALL
SELECT 86353 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL;
Outputs:
ROW_NUMBER
ID
1
86325
1
86325
2
86326
2
86326
3
86352
4
86353
5
86354
5
86354
db<>fiddle here

Related

Group By inside Rtrim(Xmlagg (Xmlelement (e,element || ',')).extract ( '//text()' ).GetClobVal(), ',')

I need to group values ​​inside a query using (or not) the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), but I can't find any literature where explain a way to group the data inside this command. The code is very simple, as you can see below:
SELECT ID,
Rtrim(Xmlagg (Xmlelement (and, CONTRACTS || ',')).extract ( '//text()' ).GetClobVal(), ',') AS CONTRACTS
FROM TABLE_A
GROUP BY ID
The result in CONTRACTS is always repeated when the ID is found, thats ok, it´s working!
ID
CONTRACTS
876
1,1,1,2,3,3
But what I really need is this return:
ID
CONTRACTS
876
1,2,3
It´s not necessary to use the command Rtrim(Xmlagg (Xmlelement (e,column || ',')).extract ( '//text()' ).GetClobVal(), ','), instead, I just use to concatenate element with comma "," in the same column.
If anyone can help me, I would be very grateful!
If your values will fit into a VARCHAR2 data type (rather than a CLOB) then you can use a nested sub-query to get the DISTINCT values for each ID:
SELECT ID,
LISTAGG(contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM ( SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Or, from Oracle 19c, it is built-in to LISTAGG:
SELECT ID,
LISTAGG(DISTINCT contracts, ',') WITHIN GROUP (ORDER BY contracts) AS CONTRACTS
FROM TABLE_A
GROUP BY ID
If you want a CLOB then you can use the same technique as the first query:
SELECT ID,
Rtrim(
Xmlagg(
Xmlelement(name, CONTRACTS || ',')
ORDER BY contracts
).extract ( '//text()' ).GetClobVal(),
','
) AS CONTRACTS
FROM (SELECT DISTINCT id, contracts FROM TABLE_A)
GROUP BY ID
Which, for the sample data:
CREATE TABLE table_a (id, contracts) AS
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL;
All output:
ID
CONTRACTS
876
1,2,3
db<>fiddle here
It's much easier to do all those operation in XML functions: DBFiddle
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;
Full example with test data:
with table_a (id, contracts) AS (
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 1 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 2 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL UNION ALL
SELECT 876, 3 FROM DUAL
)
SELECT--+ NO_XML_QUERY_REWRITE
id,
xmlquery(
'string-join(distinct-values($R/R/X/text()),",")'
passing
Xmlelement(
R,
Xmlagg(
Xmlelement (X, CONTRACTS)
order by CONTRACTS
)) as R
RETURNING CONTENT
) AS CONTRACTS
FROM TABLE_A
GROUP BY ID;

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.

REGEXP_SUBSTR not able to process only current row

(SELECT LISTAGG(EVENT_DESC, ',') WITHIN GROUP (ORDER BY EVENT_DESC) FROM EVENT_REF WHERE EVENT_ID IN
( SELECT REGEXP_SUBSTR(AFTER_VALUE,'[^,]+', 1, level) FROM DUAL
CONNECT BY REGEXP_SUBSTR(AFTER_VALUE, '[^,]+', 1, level) IS NOT NULL
)
)
A table from which I am fetching AFTER_VALUE has values of integer which is comma seperated like
AFTER_VALUE data
Expected output
1
Event1
1,2
Event1,Event2
1,12,2,5
Event1,Event12,Event2,Event5
15,13
Event15,Event13
these are Ids in EVENT_REF table which have some description. I am trying to basically present
ex. 1,2 as Event1, Event2 and send back from query. There are multiple events so using REPLACE would be very tedious.
When using above query I'm getting error as “ORA-01722: invalid number” whenever there is more than one value in AFTER_VALUE column Ex. if there exists only one id , then the query works but for values like 1,2 or 1,13 etc it throws invalid number error.
PS: The event names are not Event1,Event2 etc , I have just put for reference.
You don't even need regular expressions for this assignment. Standard string function replace() can do the same thing, and faster. You only need an extra 'Event' at the beginning of the string, since that one doesn't "replace" anything.
Like this: (note that you don't need the with clause; I included it only for quick testing)
with
event_ref (after_value) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select after_value,
'Event' || replace(after_value, ',', ',Event') as desired_output
from event_ref
;
AFTER_VALUE DESIRED_OUTPUT
----------- -----------------------------
1 Event1
1,2 Event1,Event2
1,12,2,5 Event1,Event12,Event2,Event5
15,13 Event15,Event13
Ah,ok, looks, like you have other characters in your comma-separated list, so you can use this query:
with EVENT_REF(EVENT_ID,EVENT_DESC) as (
select 1, 'Desc 1' from dual union all
select 2, 'Desc 2' from dual union all
select 3, 'Desc 3' from dual union all
select 4, 'Desc 4' from dual union all
select 5, 'Desc 5' from dual union all
select 12, 'Desc12' from dual union all
select 13, 'Desc13' from dual union all
select 15, 'Desc15' from dual
)
select
(SELECT LISTAGG(EVENT_DESC, ',')
WITHIN GROUP (ORDER BY EVENT_DESC)
FROM EVENT_REF
WHERE EVENT_ID IN
( SELECT to_number(REGEXP_SUBSTR(AFTER_VALUE,'\d+', 1, level))
FROM DUAL
CONNECT BY level<=REGEXP_COUNT(AFTER_VALUE, '\d+')
)
)
from (
select '1' AFTER_VALUE from dual union all
select '1,2' AFTER_VALUE from dual union all
select '1,12,2,5' AFTER_VALUE from dual union all
select '15,13' AFTER_VALUE from dual
);
PS. And do not forget that to_number has 'default on conversion error' now: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/sqlrf/TO_NUMBER.html
There is no need to split and concatenate substrings, just use regexp_replace:
with EVENT_REF (AFTER_VALUE) as (
select '1' from dual union all
select '1,2' from dual union all
select '1,12,2,5' from dual union all
select '15,13' from dual
)
select regexp_replace(AFTER_VALUE,'(\d+)','Event\1') from EVENT_REF;
REGEXP_REPLACE(AFTER_VALUE,'(\D+)','EVENT\1')
-----------------------------------------------
Event1
Event1,Event2
Event1,Event12,Event2,Event5
Event15,Event13

How to use the query builder of Symfony to make a date range counter and fill the gaps with zeros

I have a query that count the user by grouping them by sign up date.
return $this->createQueryBuilder('s')
->select(' date(s.created_at) as x, count(1) as y')
->where("s.created_at between datesub(now(), :months, 'Month') and now()")
->setParameter('months', $months)
->groupBy('x')
->orderBy('x')
->getQuery()
->getResult();
But their is currently gaps in my dataset.
So I have the sql request to fill the gaps, but I don't know how to create a complicated request with the Symfony's query builder.
SELECT ranger.ranger_date AS x, COALESCE(counter.counter_value, 0) as y
FROM (
SELECT DATE(s.created_at) AS counter_date, count(*) AS counter_value
FROM statistic AS s
WHERE s.created_at between DATE_SUB(NOW(), INTERVAL 3 MONTH) and now()
GROUP BY counter_date
) AS counter
RIGHT JOIN (
SELECT DATE(DATE_SUB(NOW(), INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY)) AS ranger_date
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)units
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)tens
CROSS JOIN (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9)hundreds
WHERE DATE_SUB(NOW(), INTERVAL units.i + tens.i * 10 + hundreds.i * 100 DAY) BETWEEN DATE_SUB(NOW(), INTERVAL 3 MONTH) AND NOW()
) AS ranger
ON ranger.ranger_date = counter.counter_date
ORDER BY ranger.ranger_date
I have already tried with the createQuery method, but it did not work...
If your complex native sql query is successfully returning the result set you want:
You can simply prepare and execute the query as documented by Symfony.
If you need to hydrate entities then you can use the NativeQuery class.

Select Maximum record

Here is my Table EMP_EARN_DETAILS.
Emp_Ern_No is the primary key.
I need to get the amount for each emp_no for each earn_no where the emp_earn_no is the maximum.
The output should be as follows.
0004321 ERN001 2345 11
0004321 ERN002 345 10
0004321 ERN003 345 9
000507 ER-01 563 4
000732 ERN001 2345 12
000732 ERN002 9 13
000732 ERN003 678 8
Please help me with the query
You can aggregate by the fields you need and, at the same time, order by the EMP_EARN_NO value; this can be a solution, by analytic functions:
WITH TEST(emp_no, earn_no, amount, emp_earn_no) AS
(
SELECT '0004321' , 'ERN001' ,2345 ,11 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN002' ,345 , 10 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN003' ,345 ,9 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,56 ,1 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 , 2 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,3 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,4 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,123 ,7 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,2345 ,12 FROM DUAL UNION ALL
SELECT '00732' , 'ERN002' ,9 ,13 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,67 ,5 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,456 ,6 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,678 ,8 FROM DUAL
)
SELECT emp_no, earn_no, amount, emp_earn_no
FROM (
SELECT emp_no,
earn_no,
amount,
emp_earn_no, ROW_NUMBER() OVER ( PARTITION BY EMP_NO, EARN_NO ORDER BY emp_earn_no DESC) AS ROW_NUM
FROM TEST
)
WHERE ROW_NUM = 1
Give this a shot,
SELECT EMP_NO, SUM(AMOUNT)
FROM EMP_EARN_DETAILS
GROUP BY EMP_NO
HAVING EMP_EARN_NO = MAX(EMP_EARN_NO)
Try this query:
select emp_no, earn_no,
sum(amount) keep (dense_rank last order by emp_earn_no) as sum_amount
from emp_earn_details
group by emp_no, earn_no
First by following query , your conditions achieved :
select t.emp_no a ,t.earn_no b ,max(t.amount) c
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
Only things that you must specify , in a same record with different EMP_EARN_NO. You have to specify in same record which must be in result.
So if you want maximum EMP_EARN_NO be in result you can use following query as final query (exactly your target in question):
select t.emp_no a ,t.earn_no b ,max(t.amount) c, max(t.emp_earn_no) emp_earn_no
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
If you want minimum or others EMP_EARN_NO be in result you can above query replace max function by your conditions.

Resources