Grouping and aggregation based on a specific condition - oracle

My result set from a query looks like this
trackingnumber type price
------------------------------------------
12799467 AVRM 674.0536
12799467 AVRM 860.7415
12799467 PRICESTD 200.00
12799468 PRICESTD 590.85
12799469 PRICESTD 800
12799470 PRICESTD 640
12799471 PRICESTD 160
12799472 PRICESTD 2080
12799473 PRICESTD 354.3779
I want to group this by the trackingnumber and in cases where the count of grouped result set is greater than 1 return the SUM of all the price which has type as AVRM else return the individual price as it is. If the count
is more that zero and the none of them has type AVRM then it's total price would be null
The expected result would be this
trackingnumber Total Price
-----------------------------------------
12799467 1534.7951 --sum of price excluding 200
12799468 590.85
12799469 800
12799470 640
12799471 160
12799472 2080
12799473 354.3779
I couldn't think of a way to get this done except for grouping by trackingnumber and checking for type by using case statement in the select part but that I believe would not work since we do not group by type
I'm not sure if this can be achieved using a single query.

Yes, it can be done in a single query.
with sample_data ( tracking_Number, "TYPE", price ) as
(
SELECT 12799467,'AVRM',674.0536 FROM DUAL UNION ALL
SELECT 12799467,'AVRM',860.7415 FROM DUAL UNION ALL
SELECT 12799467,'PRICESTD',200.00 FROM DUAL UNION ALL
SELECT 12799468,'PRICESTD',590.85 FROM DUAL UNION ALL
SELECT 12799469,'PRICESTD',800 FROM DUAL UNION ALL
SELECT 12799470,'PRICESTD',640 FROM DUAL UNION ALL
SELECT 12799471,'PRICESTD',160 FROM DUAL UNION ALL
SELECT 12799472,'PRICESTD',2080 FROM DUAL UNION ALL
SELECT 12799473,'PRICESTD',354.3779 FROM DUAL )
SELECT tracking_number,
case when count(*) > 1 THEN
sum(decode("TYPE",'AVRM',price,null)) ELSE
sum(price) END price
from sample_data
group by tracking_number
order by tracking_Number;

For example next solution. I add condition to exlude rows with type not equla 'AVRM' if rows with 'AVRM' exests
with s (trackingnumber ,type ,price)
as (
select 12799467,'AVRM',674.0536 from dual union all
select 12799467 ,'AVRM', 860.7415 from dual union all
select 12799467 ,'PRICESTD', 200.00 from dual union all
select 12799468 ,'PRICESTD', 590.85 from dual union all
select 12799469 ,'PRICESTD', 800 from dual union all
select 12799470 ,'PRICESTD', 640 from dual union all
select 12799471 ,'PRICESTD', 160 from dual union all
select 12799472 ,'PRICESTD', 2080 from dual union all
select 12799473 ,'PRICESTD', 354.3779 from dual )
select trackingnumber,
sum(price)
from (select s.*,rownum as rn from s
where not exists (select null
from s subs
where s.trackingnumber = subs.trackingnumber
and s.type != 'AVRM'
and subs.type = 'AVRM')
)
group by trackingnumber,
case when type = 'AVRM' then 0 else rn end;

Related

Duplicated rows numbering

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

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.

Grouping based on value by date range

Hi i have a data in daily basis below:
daytime value
01.01.2017 20000
02.01.2017 20000
03.01.2017 20000
04.01.2017 35000
05.01.2017 35000
06.01.2017 40000
07.01.2017 40000
08.01.2017 50000
How can i have in date range format such as below?
FromDate ToDate Value
01.01.2017 03.01.2017 20000
04.01.2017 05.01.2017 35000
06.01.2017 07.01.2017 40000
08.01.2017 08.01.2017 50000
Thanks!
Tabibitosan handles this very easily:
WITH your_table AS (SELECT to_date('01/01/2017', 'dd/mm/yyyy') daytime, 20000 VALUE FROM dual UNION ALL
SELECT to_date('02/01/2017', 'dd/mm/yyyy') daytime, 20000 VALUE FROM dual UNION ALL
SELECT to_date('03/01/2017', 'dd/mm/yyyy') daytime, 20000 VALUE FROM dual UNION ALL
SELECT to_date('04/01/2017', 'dd/mm/yyyy') daytime, 35000 VALUE FROM dual UNION ALL
SELECT to_date('05/01/2017', 'dd/mm/yyyy') daytime, 35000 VALUE FROM dual UNION ALL
SELECT to_date('06/01/2017', 'dd/mm/yyyy') daytime, 40000 VALUE FROM dual UNION ALL
SELECT to_date('07/01/2017', 'dd/mm/yyyy') daytime, 40000 VALUE FROM dual UNION ALL
SELECT to_date('08/01/2017', 'dd/mm/yyyy') daytime, 50000 VALUE FROM dual UNION ALL
SELECT to_date('09/01/2017', 'dd/mm/yyyy') daytime, 20000 VALUE FROM dual)
-- end of mimicking your table with data in it. See SQL below:
SELECT MIN(daytime) fromdate,
MAX(daytime) todate,
VALUE
FROM (SELECT daytime,
VALUE,
row_number() OVER (ORDER BY daytime) - row_number() OVER (PARTITION BY VALUE ORDER BY daytime) grp
FROM your_table)
GROUP BY grp,
VALUE
ORDER BY MIN(daytime);
FROMDATE TODATE VALUE
---------- ---------- ----------
01/01/2017 03/01/2017 20000
04/01/2017 05/01/2017 35000
06/01/2017 07/01/2017 40000
08/01/2017 08/01/2017 50000
09/01/2017 09/01/2017 20000
What this does is compare the row number for all the rows ordered by date, and then the row number for all the rows for each value ordered by date. If the value rows are consecutive in the main set of data, then the difference between the two sets of data remains the same, so you can then group by that. If there is a gap, then the difference increases.
In your example above, the first three rows for value = 20000 happen to be the first three rows of the whole set, so the difference will be 0. However the fourth value = 20000 row is the 9th row in the whole set, so the difference is now 5. You can easily see that the value of 20000 falls into two groups, and as such, you can find the min/max daytime for each group separately by including that difference calculation in the group by clause.
N.B. This does assume that the dates in your data are consecutive or that if there are missing dates that you assume the value stays the same for the missing dates. If you do have missing days and you want the values across a gap to show in different groups, you'd need to outer join to a subquery that contains the missing dates. In that case, I think GurV's answer (with the additional clause in the case statement that I mentioned in the comments) would be the best one to use, as that would avoid the need to outer join to a list of consecutive dates.
If I understand correctly, you want to group the value only if they are same for consecutive dates.
You can use window functions to generate groups based on value and increasing date order and then find the required aggregates.
with your_table(daytime ,value) as (
select to_date('13.02.2017','dd.mm.yyyy'),25000 from dual union all
select to_date('14.02.2017','dd.mm.yyyy'),20000 from dual union all
select to_date('15.01.2017','dd.mm.yyyy'),90000 from dual union all
select to_date('16.01.2017','dd.mm.yyyy'),90000 from dual union all
select to_date('17.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('18.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('19.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('20.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('21.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('22.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('23.01.2017','dd.mm.yyyy'),95800 from dual union all
select to_date('24.01.2017','dd.mm.yyyy'),90000 from dual union all
select to_date('25.01.2017','dd.mm.yyyy'),90000 from dual union all
select to_date('26.01.2017','dd.mm.yyyy'),90000 from dual
)
select
min(daytime) fromdate,
max(daytime) todate,
value
from (
select
t.*,
sum(x) over (order by daytime) grp
from (
select
t.*,
case when value = lag(value) over (order by daytime)
then 0 else 1 end x
from your_table t
) t
) t group by grp, value
order by fromdate;
Produces:
FROMDATE TODATE VALUE
15-JAN-17 16-JAN-17 90000
17-JAN-17 23-JAN-17 95800
24-JAN-17 26-JAN-17 90000
13-FEB-17 13-FEB-17 25000
14-FEB-17 14-FEB-17 20000

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.

Oracle: elegant way to parse a number to 9,99 format

With Oracle 11g I want to parse a number to strip decimals if their value is 0 and keep two decimal figure after the decimal separator ',' if the value of decimals is different from 0
Example:
1,00 -> 1
1,001 -> 1
0,203 -> 0,20
And so on.
I've obtained something like that in a very unelegant way
select replace(trim(to_char (trunc ('0,2345',2),'9999999990.99')), '.', ',')
from dual
Do you know more elegant way? The output should be a char (not number).
Not sure it's much more elegant, but assuming your replace is to deal with different locales, this might work for you:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
)
select n, regexp_replace(to_char(trunc(n, 2), '9999999990D00',
'NLS_NUMERIC_CHARACTERS='',.'''), '[,.]00$', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
The nls_param argument to to_char let's you dictate whether it used a comma or a period as the decimal separator. If you can set that at session level then the query looks a bit simpler. The regexp_replace strips ,00 (or .00, which come to think of it is overkill) from the end of th string.
As ThinkJet noted the regexp_replace is a bit excessive, and since the decimal seperator is defined in the column clase (and the format has no group separators anyway) it can be done with a plan replace:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 13.08 from dual
)
select n, replace(trim(
to_char(trunc(n, 2), '9999999990D00', 'NLS_NUMERIC_CHARACTERS='',.''')),
',00', null) as new_n
from t;
N NEW_N
---------- --------------
1 1
1.001 1
0.203 0,20
0.2345 0,23
112.999 112,99
13.08 13,08
Still not sure this can be described as 'elegant' though.
To achieve correct results you must deal with numbers, not strings:
with t as (
select 1.00 as n from dual
union all select 1.001 from dual
union all select 0.203 from dual
union all select 0.2345 from dual
union all select 112.999 from dual
union all select 112.105 from dual
union all select 0 from dual
union all select -12.307 from dual
)
select
n,
decode( trunc(n,2) - trunc(n) ,
0, to_char(trunc(n), 'TM9', 'NLS_NUMERIC_CHARACTERS = '', '''),
to_char(trunc(n,2),'9999999990D00', 'NLS_NUMERIC_CHARACTERS = '', ''')
)
string_val
from t
SQLFiddle
P.S. Updated to get incorrect truncation instead of round, as in OP request.
Another variant
SELECT n,
to_char( trunc( n, 2 ),
case when mod( trunc( n, 2 ), 1 ) = 0
then '9990'
else '9990D00'
end, 'NLS_NUMERIC_CHARACTERS='',.''' ) val
from t
;
SQLFiddle demo

Resources