Sum all columns in a row - oracle

How can I get a total results of all my rows? (for ORACLE)
SELECT
NAME,
SUM(CASE WHEN ASSIST_1 = 'YES' THEN 1 END) WEEK1,
SUM(CASE WHEN ASSIST_2 = 'YES' THEN 1 END) WEEK2,
SUM(CASE WHEN ASSIST_3 = 'YES' THEN 1 END) WEEK3,
FROM TABLE_NAME
WHERE GROUP BY NAME;
I have this results:
Name week1 week2 week3
Anne 1 2 3
Bob 3 1 0
Charlie 4 5 1
I want this result:
Anne 1 2 3
Bob 3 1 0
Charlie 4 5 1
Total 8 8 4

How can I get a total results of all my rows?
By using rollup() extension of the group by clause. Something like this(just an example):
-- sample of date from your question
with t1(uname, c1, c2, c3) as(
select 'Anne' , 1, 2, 3 from dual union all
select 'Bob' , 3, 1, 0 from dual union all
select 'Charlie', 4, 5, 1 from dual
)
-- actual query
select case grouping(uname)
when 0 then uname
else 'Total' end
as uname1
, sum(c1) as c1
, sum(c2) as c2
, sum(c3) as c3
from t1
group by rollup(uname)
order by grouping(uname)
Result:
UNAME1 C1 C2 C3
------- ---------- ---------- ----------
Anne 1 2 3
Bob 3 1 0
Charlie 4 5 1
Total 8 8 4
4 rows selected.

Use UNION ALL with SUM values
WITH t1(name, week1, week2, week3) AS
( SELECT 'Anne', 1, 2, 3 FROM dual
UNION ALL
SELECT 'Bob', 3, 1, 0 FROM dual
UNION ALL
SELECT 'Charlie', 4, 5, 1 FROM dual
),
s AS
(SELECT 'Total' name,
SUM(week1) week1,
SUM(week2) week2,
SUM(week2) week3
FROM t1
)
SELECT * FROM t1
UNION ALL
SELECT * FROM s;
Result:
NAME WEEK1 WEEK2 WEEK3
Anne 1 2 3
Bob 3 1 0
Charlie 4 5 1
Total 8 8 8

Base on #Nicholas answer:
SELECT
CASE GROUPING(NAME) WHEN 0 THEN NAME ELSE 'TOTAL' END AS NAME,
SUM (WEEK1) AS WEEK1,
SUM (WEEK2) AS WEEK2,
SUM (WEEK3) AS WEEK3
FROM (
SELECT
NAME,
SUM(CASE WHEN ASSIST_1 = 'YES' THEN 1 END) WEEK1,
SUM(CASE WHEN ASSIST_2 = 'YES' THEN 1 END) WEEK2,
SUM(CASE WHEN ASSIST_3 = 'YES' THEN 1 END) WEEK3,
FROM TABLE_NAME
WHERE GROUP BY (NAME)
GROUP BY ROLLUP(NAME)
ORDER BY GROUPING(NAME);
Give this result:
Anne 1 2 3
Bob 3 1 0
Charlie 4 5 1
Total 8 8 4

Related

Split record with delimited value

I need to separate delimited string value into separate rows
What I need is:
FAILUREID TNO
1 t01
1 t02
1 t03
2 t04
2 t05
But using the code below I receided
1 t01
1 t02
1 t03
1 t03
1 t03
1 t02
1 t03
2 t04
2 t05
2 t05
Some lines are duplicated. Where is the bug in the code below, How to fix it ? ( distinct is not a solution)
with data as
( select 1 as FailureID
,'t01;t02;t03' as Tickets
from dual
union
select 2
,'t04;t05'
from dual
)
select FailureID
,regexp_substr(Tickets, '[^;]+', 1, level) as TNO
from data
connect by regexp_substr(tickets, '[^;]+', 1, level) is not null
order by failureid
Here's how (the difference begins at line #13):
SQL> with data as
2 ( select 1 as FailureID
3 ,'t01;t02;t03' as Tickets
4 from dual
5 union
6 select 2
7 ,'t04;t05'
8 from dual
9 )
10 select FailureID
11 ,regexp_substr(Tickets, '[^;]+', 1, column_value) as TNO
12 from data
13 cross join table(cast(multiset(select level from dual
14 connect by level <= regexp_count(tickets, ';') + 1
15 ) as sys.odcinumberlist))
16 order by failureid;
FAILUREID TNO
---------- -----------
1 t01
1 t02
1 t03
2 t04
2 t05
SQL>

How to count or sum distinct values when there is a risk of intersection?

Imagine I have a table with people and their features:
group Name red_hair tall blue_eyes programmer
1 Mark 1 1 0 1
1 Sean 1 0 1 0
1 Lucas 1 1 1 1
2 Linda 0 1 1 1
I would like to count how many people of specific sets of features are in every group. In other words, I would like to make some bins without counting a person multiple times.
There are 2^4 (16) possible combinations of those sets, but I don't need so much.
For example, if a person has red_hair I don't care whether he or she has blue eyes or he or she a programmer. This person goes to the red hair bin of this group.
If a person is a programmer I don't care whether he or she is tall, but I don't want to count people who are already in a red hair bin. Because I have already counted them.
So I have a priority:
Red hair people counts first
Programmers second
People with blue eyes third
Expected result of this dataset:
group red_hair_persons programmers blue_eyes_persons
1 3 0 0
2 0 1 0
when I do this:
select group, count(case when red_hair = 1 then name end) as red_hair,
count(case when programmer = 1 and red_hair = 0 then name end) as programmers
from table
group by group
I fear that there would be some intersections. Or the logic with CASES would be so complex I could drown in it.
Am I right?
If so how could I avoid them? Maybe I am doing everything wrong and there is a better way to do what I want to. I have an enormous table with many features in it and I don't want to screw up.
Here's how I understood it:
SQL> with test (cgroup, name, red_hair, tall, blue_eyes, programmer) as
2 (select 1, 'mark' , 1, 1, 0, 1 from dual union all
3 select 1, 'sean' , 1, 0, 1, 0 from dual union all
4 select 1, 'lucas', 1, 1, 1, 1 from dual union all
5 select 2, 'linda', 0, 1, 1, 1 from dual
6 ),
7 priority as
8 (select t.*,
9 case when red_hair = 1 then 'A'
10 when programmer = 1 then 'B'
11 when blue_eyes = 1 then 'C'
12 else 'D'
13 end priority
14 from test t
15 )
16 select cgroup,
17 sum(case when priority = 'A' then 1 else 0 end) red_hair,
18 sum(case when priority = 'B' then 1 else 0 end) programmer,
19 sum(case when priority = 'C' then 1 else 0 end) blue_eyes,
20 sum(case when priority = 'D' then 1 else 0 end) other
21 from priority
22 group by cgroup;
CGROUP RED_HAIR PROGRAMMER BLUE_EYES OTHER
---------- ---------- ---------- ---------- ----------
1 3 0 0 0
2 0 1 0 0
SQL>
priority CTE puts every person into its priority group, based on their properties
the final select counts (using SUM + CASE) them per group
With a little bit of simple math involved in the conditional aggregation:
select "group",
sum("red_hair") red_hair_persons,
sum((1 - "red_hair") * "programmer") programmers,
sum((1 - "red_hair") * (1 - "programmer") * "blue_eyes") blue_eyes_persons
from tablename
group by "group"
See the demo.
Results:
> group | RED_HAIR_PERSONS | PROGRAMMERS | BLUE_EYES_PERSONS
> ----: | ---------------: | ----------: | ----------------:
> 1 | 3 | 0 | 0
> 2 | 0 | 1 | 0

Select onle group that have positivo and negative number in plsql

I have a table that have date grouped.
I need to select only groups that have positive and negative value inside.
For example:
id value1
2 7
2 8
2 -1
3 3
3 4
4 -1
4 -2
5 7
5 -5
the result should be
id value1
2 7
2 8
2 -1
5 7
5 -5
because the group with id 3 just have positive number and the group with id 4 just have negative number.
any idea how can I do it using case (when then) in a select or using if else inside a function. Or any other idea?
Try this.
select id,value1 FROM
(
select t.*,
count( DISTINCT SIGN (value1 ) ) OVER (PARTITION BY id ) n
from yourtable t
) WHERE n = 2
;
The Sign() function gives 1 for positive and -1 for negative numbers.
DEMO
If you group by the ID, you can use the aggregate functions MIN and MAX to find out if there are both positive and negative values. You need to decide how to treat 0 though... I have treated it as positive below :)
with your_table as(
-- Your example data here, this is not really part of the solution
select 2 as id, 7 as value1 from dual union all
select 2 as id, 8 as value1 from dual union all
select 2 as id, -1 as value1 from dual union all
select 3 as id, 3 as value1 from dual union all
select 3 as id, 4 as value1 from dual union all
select 4 as id, -1 as value1 from dual union all
select 4 as id, -2 as value1 from dual union all
select 5 as id, 7 as value1 from dual union all
select 5 as id, -5 as value1 from dual
)
select *
from your_table
where id in(select id
from your_table
group by id
having min(value1) < 0
and max(value1) >= 0);

Oracle Connect By seems to produce too many rows

Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
I expect I'm just missing something, but if I run this query without the "connect by", I get 2 rows. When I add "connect by level <= 4", I would expect to get each of those 2 rows 4 times. The actual result is different.
Can anyone help me understand what's happening here? I'm not looking for a solution that only repeats each row 4 times - I've already got that. I'm just looking to understand what's happening and why.
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
)
select a.id,
b.beta_no,
level as the_level
from alpha a
inner join beta b
on b.alpha_id = a.id
connect by level <= 4
order by a.id,
b.beta_no,
level
;
ID BETA_NO THE_LEVEL
1 1 1
1 1 2
1 1 2
1 1 3
1 1 3
1 1 3
1 1 3
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 2 1
1 2 2
1 2 2
1 2 3
1 2 3
1 2 3
1 2 3
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
30 rows selected
Many thanks to mathguy. The second link he provided in the answer below had exactly what I was looking for. Specifically:
1 with t as (select 1 as id from dual union all
2 select 2 from dual)
3 --
4 select id, level
5 ,prior id
6 ,sys_connect_by_path(id,'=>') as cpath
7 from t
8* connect by level <= 3
SQL> /
ID LEVEL PRIORID CPATH
---------- ---------- ---------- --------------------------------------------------
1 1 =>1
1 2 1 =>1=>1
1 3 1 =>1=>1=>1
2 3 1 =>1=>1=>2
2 2 1 =>1=>2
1 3 2 =>1=>2=>1
2 3 2 =>1=>2=>2
2 1 =>2
1 2 2 =>2=>1
1 3 1 =>2=>1=>1
2 3 1 =>2=>1=>2
2 2 2 =>2=>2
1 3 2 =>2=>2=>1
2 3 2 =>2=>2=>2
14 rows selected.
It's clear to me from that example, but I'd be hard-pressed to succinctly put it into words.
With no condition other than "level <= 4", every row from the original table, view etc. (from the join, in this case) will produce two rows at level 2, then four more rows at level 3, and 8 more at level 4. "Connect by" is essentially a succession of joins, and you are doing cross joins if you have no condition with the PRIOR operator.
You probably want to add "and prior a.id = a.id". This will lead to Oracle complaining about cycles (because Oracle decides a cycle is reached when it sees the same values in the columns subject to PRIOR). That, in turn, is solved by adding a third condition, usually "and prior sys_guid() is not null".
(Edited; the original answer made reference to NOCYCLE, which is not needed when using the "prior sys_guid() is not null" approach.)
This has been discussed recently on OTN: https://community.oracle.com/thread/3999985
Same question discussed here: https://community.oracle.com/thread/2526535
To illustrate Mathguy's answer, you are missing some predicates out of your CONNECT BY clause:
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
)
select a.id,
b.beta_no,
level as the_level
from alpha a
inner join beta b
on b.alpha_id = a.id
connect by level <= 4
AND PRIOR a.id = a.id
AND PRIOR b.beta_no = b.beta_no
AND PRIOR sys_guid() IS NOT NULL
order by a.id,
b.beta_no,
LEVEL;
ID BETA_NO THE_LEVEL
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 1 4
1 2 1
1 2 2
1 2 3
1 2 4
An alternative would be to use the recursive with clause:
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
),
multiply (id, beta_no, rn) AS (SELECT a.id,
b.beta_no,
1 rn
FROM alpha a
INNER JOIN beta b
ON a.id = b.alpha_id
UNION ALL
SELECT ID,
beta_no,
rn + 1
FROM multiply
WHERE rn + 1 <= 4)
SELECT ID,
beta_no,
rn AS the_level
FROM multiply
order by id,
beta_no,
rn;
ID BETA_NO THE_LEVEL
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 1 4
1 2 1
1 2 2
1 2 3
1 2 4

Select Top (Max) Amount From Two Of Four Fields (Columns)

I have this query
SELECT code, username, week1money, week2money, week3money, week4money FROM(
--subquery goes here
)
How to select the top two weeks, i.e. weeks with the highest value? I want to sum the top two weeks to be precise.
If I understand correct you want to get 2 top values per every (code, username) row and (code, username) is a key of recordset.
Supposing you can have two top weeks with the same values and you don't have nulls this might be one of solutions:
SQL> with t (id, code, week1, week2, week3, week4)
2 as (
3 select 1, 'a', 10, 15, 11, 8 from dual union all
4 select 2, 'b', 7, 4, 2, 9 from dual union all
5 select 3, 'c', 3, 3, 1, 0 from dual
6 )
7 select id, code, max(week) first_top, min(week) next_top from (
8 select id, code, row_number() over(partition by id, code order by week desc) rnk, week
9 from (
10 select t.id, t.code,
11 decode(r.rn,1,week1,2,week2,3,week3,4,week4) week
12 from t,
13 (select rownum rn from dual connect by level <= 4) r
14 ))
15 where rnk in (1,2)
16 group by id, code
17 /
ID C FIRST_TOP NEXT_TOP
---------- - ---------- ----------
3 c 3 3
1 a 15 11
2 b 9 7
If you have non-null and different values in weeks you can use something like:
SQL> with t (id, code, week1, week2, week3, week4)
2 as (
3 select 1, 'a', 10, 15, 11, 8 from dual union all
4 select 2, 'b', 7, 4, 2, 9 from dual union all
5 select 3, 'c', 3, 2, 1, 0 from dual
6 )
7 select id, code
8 , greatest(week1, week2, week3, week4) first_top
9 , greatest(
10 case when week1 < greatest(week1, week2, week3, week4) then week1 else -1e28 end,
11 case when week2 < greatest(week1, week2, week3, week4) then week2 else -1e28 end,
12 case when week3 < greatest(week1, week2, week3, week4) then week3 else -1e28 end,
13 case when week4 < greatest(week1, week2, week3, week4) then week4 else -1e28 end
14 ) second_top
15 from t
16 /
ID C FIRST_TOP SECOND_TOP
---------- - ---------- ----------
1 a 15 11
2 b 9 7
3 c 3 2
But to get the right solution more details are required.
Answering my question...
select * from(
select * from(
select week1money col from dual
union
select week2money col from dual
union
select week3money col from dual
union
select week4money col from dual
) order by col desc
) where rownum < 3
Using GREATESTS() also may help.

Resources