MS SQL.Summation of various values across columns - sql

Table T1 has 3 columns as C1, C2 and C3 having values as R, G, B
C1 C2 C3
R R R
R R R
G R R
G G R
B G B
B B B
I want a new table in the below structure:
R G B
9 4 5
In the above table, the distinct values of the Table T1 has to be displayed as the column name and the total count of the R, G, B values from the whole table has to be displayed.

Use the COUNT aggregation function on each column with a CASE expression to filter by the correct character value:
SQL Fiddle
Oracle and/or MS SQL Server Setup:
CREATE TABLE table_name(
C1 CHAR(1),
C2 CHAR(1),
C3 CHAR(1)
);
INSERT INTO table_name VALUES ( 'R', 'R', 'R' );
INSERT INTO table_name VALUES ( 'R', 'R', 'R' );
INSERT INTO table_name VALUES ( 'G', 'R', 'R' );
INSERT INTO table_name VALUES ( 'G', 'G', 'R' );
INSERT INTO table_name VALUES ( 'B', 'G', 'B' );
INSERT INTO table_name VALUES ( 'B', 'B', 'B' );
Query 1:
SELECT COUNT( CASE C1 WHEN 'R' THEN 1 END )
+ COUNT( CASE C2 WHEN 'R' THEN 1 END )
+ COUNT( CASE C3 WHEN 'R' THEN 1 END ) AS R,
COUNT( CASE C1 WHEN 'G' THEN 1 END )
+ COUNT( CASE C2 WHEN 'G' THEN 1 END )
+ COUNT( CASE C3 WHEN 'G' THEN 1 END ) AS G,
COUNT( CASE C1 WHEN 'B' THEN 1 END )
+ COUNT( CASE C2 WHEN 'B' THEN 1 END )
+ COUNT( CASE C3 WHEN 'B' THEN 1 END ) AS B
FROM table_name
Results:
| R | G | B |
|---|---|---|
| 9 | 4 | 5 |
Query 2 or you can use UNPIVOT:
SELECT COUNT( CASE value WHEN 'R' THEN 1 END ) AS R,
COUNT( CASE value WHEN 'G' THEN 1 END ) AS G,
COUNT( CASE value WHEN 'B' THEN 1 END ) AS B
FROM table_name
UNPIVOT ( value FOR id IN ( C1, C2, C3 ) ) AS u -- Do not need AS keyword in Oracle
Results:
| R | G | B |
|---|---|---|
| 9 | 4 | 5 |

Related

Group by with having and merge back to original table

I have a table like this
A_Count
B_Count
A
B
C
1
0
A
NULL
C1
0
1
NULL
B
C1
1
1
A
B
C2
1
1
A
B
C2
and I want to have a result table (only need to show column A and B) like:
A_Count
B_Count
A
B
C
1
1
A
B
C1
1
1
A
B
C2
1
1
A
B
C2
So my goal is to merge two row having the following condiction:
both rows belong to same group C and only merge when one row has A being null and one row has B being null.
so its like:
group by C
having sum(A_COUNT) =1 AND sum(B_COUNT) =1
but the problem is, I want to keep those rows that are not merged (ROW 3 & 4) , can someone tell me how to do that? many thanks!
You can use conditional analytical function and group by as follows:
Select max(a) as a, max(b) as b, c from
(Select a, b, c,
case when nulla = 1 and nullb = 1 and (a is null or b is null)
then 0
else row_number() over (partition by c order by 1)
end as rn
from (Select a, b, c,
count(case when a is null then 1 end) over(partition by c) as nulla,
count(case when b is null then 1 end) over (partition by c) as nullb
From your_table t
)
)
Group by c, rn
DB<>Fiddle Thanks to MT0. Used the sample data from MT0's fiddle.
If you were using Oracle 12 then you could use MATCH_RECOGNIZE:
SELECT a_count, b_count, a, b, c
FROM (
SELECT t.*,
NVL2(
A,
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( B, 1, 0 ) DESC, ROWNUM ),
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( A, 1, 0 ) DESC, ROWNUM )
) AS rn
FROM table_name t
)
MATCH_RECOGNIZE (
PARTITION BY C
ORDER BY rn, A NULLS LAST
MEASURES
FIRST( a_count ) AS a_count,
LAST( b_count ) AS b_count,
FIRST( a ) AS a,
LAST( b ) AS b
PATTERN ( a b? )
DEFINE
a AS a.a IS NOT NULL,
b AS a.b IS NULL AND b.a IS NULL AND b.b IS NOT NULL
)
Before that Oracle version, you can get a similar effect using analytic functions to determine which rows to aggregate:
SELECT SUM( a_count ) AS a_count,
SUM( b_count ) AS b_count,
MAX( a ) AS a,
MAX( b ) AS b,
c
FROM (
SELECT t.*,
NVL2(
A,
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( B, 1, 0 ) DESC, ROWNUM ),
ROW_NUMBER() OVER ( PARTITION BY C ORDER BY NVL2( A, 1, 0 ) DESC, ROWNUM )
) AS rn
FROM table_name t
)
GROUP BY c, rn
Which, for the sample data (in an unordered state, with additional rows to demonstrate grouping additional pairs of rows):
CREATE TABLE table_name ( A_Count, B_Count, A, B, C ) AS
SELECT 1, 0, 'A', NULL, 'C1' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C1' FROM DUAL UNION ALL
SELECT 1, 1, 'A', 'B', 'C2' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 0, 'A', NULL, 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 0, 'A', NULL, 'C2' FROM DUAL UNION ALL -- Added row
SELECT 1, 1, 'A', 'B', 'C2' FROM DUAL UNION ALL
SELECT 0, 1, NULL, 'B', 'C2' FROM DUAL -- Added row
Both output:
A_COUNT | B_COUNT | A | B | C
------: | ------: | :- | :- | :-
1 | 1 | A | B | C1
1 | 1 | A | B | C2
1 | 1 | A | B | C2
1 | 1 | A | B | C2
1 | 1 | A | B | C2
db<>fiddle here
You can do this with join:
select (t1.a_count + coalesce(t2.a_count, 0)) as a_count,
(t1.b_count + coalesce(t2.b_count, 0)) as b_count,
coalesce(t1.a, t2.a) as a,
coalesce(t1.b, t2.b) as b,
t1.c
from t t1 left join
t t2
on t1.c = t2.c and
t1.a is not null and t2.a is null and
t1.b is null and t2.b is not null
where t1.a is not null;
As you've described the problem, aggregation doesn't seem necessary.
Here is a db<>fiddle with your original data.

Get Distinct values without null

I have a table like this;
--Table_Name--
A | B | C
-----------------
A1 NULL NULL
A1 NULL NULL
A2 NULL NULL
NULL B1 NULL
NULL B2 NULL
NULL B3 NULL
NULL NULL C1
I want to get like this ;
--Table_Name--
A | B | C
-----------------
A1 B1 C1
A2 B2 NULL
NULL B3 NULL
How should I do that ?
Here's one option:
sample data is from line #1 - 9
the following CTEs (lines #11 - 13) fetch ranked distinct not null values from each column
the final query (line #15 onward) returns desired result by outer joining previous CTEs on ranked value
SQL> with test (a, b, c) as
2 (select 'A1', null, null from dual union all
3 select 'A1', null, null from dual union all
4 select 'A2', null, null from dual union all
5 select null, 'B1', null from dual union all
6 select null, 'B2', null from dual union all
7 select null, 'B3', null from dual union all
8 select null, null, 'C1' from dual
9 ),
10 --
11 ta as (select distinct a, dense_rank() over (order by a) rn from test where a is not null),
12 tb as (select distinct b, dense_rank() over (order by b) rn from test where b is not null),
13 tc as (select distinct c, dense_rank() over (order by c) rn from test where c is not null)
14 --
15 select ta.a, tb.b, tc.c
16 from ta full outer join tb on ta.rn = tb.rn
17 full outer join tc on ta.rn = tc.rn
18 order by a, b, c
19 /
A B C
-- -- --
A1 B1 C1
A2 B2
B3
SQL>
If you have only one value per column, then I think a simpler solution is to enumerate the values and aggregate:
select max(a) as a, max(b) as b, max(c) as c
from (select t.*,
dense_rank() over (partition by (case when a is null then 1 else 2 end),
(case when b is null then 1 else 2 end),
(case when c is null then 1 else 2 end)
order by a, b, c
) as seqnum
from t
) t
group by seqnum;
This only "aggregates" once and only uses one window function, so I think it should have better performance than handling each column individually.
Another approach is to use lateral joins which are available in Oracle 12C -- but this assumes that the types are compatible:
select max(case when which = 'a' then val end) as a,
max(case when which = 'b' then val end) as b,
max(case when which = 'c' then val end) as c
from (select which, val,
dense_rank() over (partition by which order by val) as seqnum
from t cross join lateral
(select 'a' as which, a as val from dual union all
select 'b', b from dual union all
select 'c', c from dual
) x
where val is not null
) t
group by seqnum;
The performance may be comparable, because the subquery removes so many rows.

Exclude columns with no data in them

Using Oracle with TOAD.
I have a table that looks something like this (columns 2 and 4 are empty and columns 1, 3 and 5 have data in them):
column_1 column_2 column_3 column_4 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
I would like to do a simple select that excludes columns with no data in them (= columns 2 and 4) or ain other words, to select only the columns that have data in them.
Is there a select command such as SELECT * FROM test_table WHERE columns ARE NOT NULL (this is pseudo code just for clarification for my problem).
The result should look like this:
column_1 column_3 column_5
a1 b1 c1
a2 b2 c2
a3 b3 c3
a4 b4 c4
You have to do a three-step approach but it is largely tedious but do-able in sqlplus
1)First identify the columns which are empty
2)Define the headers without those columns
3)define the body without those columns
WITH data AS
(
SELECT '1' a,
'' b ,
2 c ,
'' d,
5 e
FROM dual
UNION ALL
SELECT '7' a,
'' b ,
2 c ,
'' d,
6
FROM dual
UNION ALL
SELECT '3' a,
'' b ,
3 c ,
'' d,
7
FROM dual
UNION ALL
SELECT '4' a,
'' b ,
3 c ,
'' d,
8
FROM dual
UNION ALL
SELECT '5' a,
'' b ,
2 c ,
'' d,
9
FROM dual),d1 AS
(
SELECT First_value(a) ignore nulls over (PARTITION BY a ORDER BY ROWNUM) ca,
first_value(b) ignore nulls over (PARTITION BY b ORDER BY ROWNUM) cb,
first_value(c) ignore nulls over (PARTITION BY c ORDER BY ROWNUM) cc,
first_value(d) ignore nulls over (PARTITION BY d ORDER BY ROWNUM) cd,
first_value(e) ignore nulls over (PARTITION BY e ORDER BY ROWNUM) ce
FROM data
WHERE ROWNUM=1 ),
d2 as (SELECT 0 rw,
CASE
WHEN ca IS NOT NULL THEN 'a'
ELSE ''
END
||chr(9)
||
CASE
WHEN cb IS NOT NULL THEN 'b'
ELSE ''
END
||chr(9)
||
CASE
WHEN cc IS NOT NULL THEN 'c'
ELSE ''
END
||chr(9)
||
CASE
WHEN cd IS NOT NULL THEN 'd'
ELSE ''
END
||chr(9)
||
CASE
WHEN ce IS NOT NULL THEN 'e'
ELSE ''
END as DATA1
FROM d1
UNION ALL
SELECT
rownum rw,
a
||chr(9)
||b
||chr(9)
||c
||chr(9)
||d
||chr(9)
||e
FROM data)
select /*ansiconsole*/ DATA1
from d2 order by rw asc;

Effective way to check whether particular data exist in each row and column or not?

1) I want to pick row which contain data x in below table
2) I want to pick column which contain data x in below table
xtable
ID C1 C2 C3 C4
--- -- -- --- --
1 A - - -
2 - A - -
3 A - A -
4 - A - -
5 - - - -
Solution i tried
SELECT CASE
WHEN exists (SELECT 1 FROM xtable WHERE C1 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C2 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C3 = 'A') THEN ROWNUM
WHEN exists (SELECT 1 FROM xtable WHERE C4 = 'A') THEN ROWNUM
ELSE 0
END "Exist"
FROM xtable;
But i wont work
Give some effective solution for above problem
Sample data:
SQL> select * From test;
ID C1 C2 C3 C4
---------- --- --- --- ---
1 A
2 A
3 A A
4 A
5
Rows that contain 'A':
SQL> select id
2 from test
3 where c1 = 'A' or c2 = 'A' or c3 = 'A' or c4 = 'A';
ID
----------
1
2
3
4
SQL>
Columns that contain 'A':
SQL> select
2 case when sum(case when c1 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c1,
3 case when sum(case when c2 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c2,
4 case when sum(case when c3 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c3,
5 case when sum(case when c4 = 'A' then 1 else 0 end) > 0 then 'yes' else 'no' end c4
6 from test;
C1 C2 C3 C4
--- --- --- ---
yes yes yes no
SQL>
Do you just want to unpivot and select?
select t.*
from ((select id, c1 as c, 'c1' as which from t
) union all
(select id, c2, 'c2' as which from t
) union all
(select id, c3, 'c3' as which from t
) union all
(select id, c4, 'c4' as which from t
) union all
(select id, c5, 'c5' as which from t
)
) t
where c = 'A'
A query for both 1 & 2.
SELECT ID
, CASE WHEN C1=X.C THEN 'yes' WHEN C1 IS NOT NULL THEN 'no' END AS C1
, CASE WHEN C2=X.C THEN 'yes' WHEN C2 IS NOT NULL THEN 'no' END AS C2
, CASE WHEN C3=X.C THEN 'yes' WHEN C3 IS NOT NULL THEN 'no' END AS C3
, CASE WHEN C4=X.C THEN 'yes' WHEN C4 IS NOT NULL THEN 'no' END AS C4
FROM XTABLE T
CROSS JOIN (SELECT 'A' C FROM DUAL) X
WHERE X.C IN (C1,C2,C3,C4)
A test on db<>fiddle here
Example of result:
ID | C1 | C2 | C3 | C4
-: | :--- | :--- | :--- | :---
1 | yes | null | null | null
2 | null | yes | null | no
3 | yes | null | yes | null
4 | null | null | null | yes
If you're looking for whether at least one non-null value exists in the rows or columns of your table, then one option to use would be combinations of decode(), sign() and nvl2() functions together ;
For rows :
SELECT ID,
decode( sign( nvl2(C1,1,0)+nvl2(C2,1,0)+nvl2(C3,1,0)+nvl2(C4,1,0) ), 1, 'yes','no')
as "Exists"
FROM xtable
For columns :
SELECT decode( sign(sum( nvl2(C1,1,0) )), 1, 'yes','no') as "Exists in C1",
decode( sign(sum( nvl2(C2,1,0) )), 1, 'yes','no') as "Exists in C2",
decode( sign(sum( nvl2(C3,1,0) )), 1, 'yes','no') as "Exists in C3",
decode( sign(sum( nvl2(C4,1,0) )), 1, 'yes','no') as "Exists in C4"
FROM xtable
Demo
If you're looking for exact matching for the letter 'A', then consider using :
SELECT ID,
decode( sign( decode(C1,'A',1,0)+
decode(C2,'A',1,0)+
decode(C3,'A',1,0)+
decode(C4,'A',1,0) ), 1, 'yes','no') as "Exists"
FROM xtable
and
SELECT decode( sign(sum( decode(C1,'A',1,0) )), 1, 'yes','no') as "Exists in C1",
decode( sign(sum( decode(C2,'A',1,0) )), 1, 'yes','no') as "Exists in C2",
decode( sign(sum( decode(C3,'A',1,0) )), 1, 'yes','no') as "Exists in C3",
decode( sign(sum( decode(C4,'A',1,0) )), 1, 'yes','no') as "Exists in C4"
FROM xtable
e.g.replace nvl2(Cn,1,0) expressions with decode(Cn,'A',1,0)
Demo

SQL Query with group by clause, but counting two distinct values as if they were the same

I have a simple table with two columns, like the one below:
Id | Name
0 | A
1 | A
2 | B
3 | B
4 | C
5 | D
6 | E
7 | E
I want to make a SQL query which will count how many times each "Name" appears on the table. However, I need a few of these values to count as if they were the same. For example, a normal group by query would be:
select Name, count(*)
from table
group by Name
The above query would produce the result:
Name | Count
A | 2
B | 2
C | 1
D | 1
E | 2
but I need the query to count "A" and "B" as if they were only "A", and to count "D" and "E" as if they were only "D", so that the result would be like:
Name | Count
A | 4 // (2 "A"s + 2 "B"s)
C | 1
D | 3 // (1 "D" + 2 "E"s)
How can I make this kind of query?
You can make translation with case. Also, you can use subquery or CTE so you don't have to repeat yourself:
with cte as (
select
case Name
when 'B' then 'A'
when 'E' then 'D'
else Name
end as Name
from table
)
select Name, count(*)
from cte
group by Name
or with with online translation table:
select
isnull(R.B, t.Name), count(*)
from table as t
left outer join (
select 'A', 'B' union all
select 'E', 'D'
) as R(A, B) on R.A = t.Name
group by isnull(R.B, t.Name)
If you need A and B, D and E, to count the same, you can build a query like this:
SELECT
CASE Name WHEN 'B' THEN 'A' WHEN 'E' THEN 'D' ELSE Name END as Name
, COUNT(*)
FROM table
GROUP BY CASE Name WHEN 'B' THEN 'A' WHEN 'E' THEN 'D' ELSE Name END
Demo on sqlfiddle.
With a layer of abstraction and a CASE (SQL Fiddle example):
;WITH x AS
(
SELECT CASE Name WHEN 'B' THEN 'A'
WHEN 'E' THEN 'D'
ELSE Name
END AS Name
FROM Table1
)
SELECT Name, COUNT(1)
FROM x
GROUP BY Name
With a translation table (SQL Fiddle):
CREATE TABLE Translate(FromName char(1), ToName char(1));
INSERT INTO Translate VALUES ('B', 'A'), ('E', 'D');
SELECT COALESCE(t.ToName, a.Name) Name, COUNT(1)
FROM Table1 a
LEFT OUTER JOIN Translate t ON a.Name = t.FromName
GROUP BY COALESCE(t.ToName, a.Name)
FWIW, you can also do this with a VALUES derived table instead of a real table (SQL Fiddle):
SELECT COALESCE(t.ToName, a.Name) Name, COUNT(1)
FROM Table1 a
LEFT OUTER JOIN
(
VALUES ('B', 'A'),
('E', 'D')
) t(FromName, ToName) ON a.Name = t.FromName
GROUP BY COALESCE(t.ToName, a.Name)
this works
select t.a,count(t.id) from (
select case name when 'A' then 'A' when 'B' then 'A'
when 'C' then 'C' when 'D' then 'C'
when 'D' then 'D' when 'E' then 'D' end as A,id
from test) as t
group by A;