Multiple Groupings with a sum - sql

My Table :
ID | TIME OF CREATION | OWNER | STATE
1 2015-1-1 arpan A
2 2015-1-2 arpan B
My desired o/p from my query is :
DATE | OWNER | COUNT(STATE = A) | COUNT(STATE = B) | ...
I checked out SUM( CASE ) but you cant group by date and sum by owner right?
Stuck here. :(
Can someone help?

I think you just want conditional aggregation:
select date, owner, sum(case when state = 'A' then 1 else 0 end) as state_A,
sum(case when state = 'B' then 1 else 0 end) as state_b
from table t
group by date, owner;

Related

Calculate difference in Oracle table from sum

I have a table which looks as followed:
ID | Value
A | 2
A | 5
A | 6
B | 1
B | 7
B | -3
I am currently using a statement as followed
select ID, sum(VALUE)
where ...
group by ID.
Now I need the difference from A and B.
Could anyone send me on the right path? I am working with Oracle.
Use conditional aggregation:
SELECT SUM(CASE WHEN id = 'A' THEN "Value" ELSE 0 END) -
SUM(CASE WHEN id = 'B' THEN "Value" ELSE 0 END) "Difference"
FROM tablename;
See the demo.

Using CASE to properly count items with if/else logic in SQL

Say I have a table table in the form:
| user | class |
|------|-------|
| 1 | a |
| 1 | b |
| 1 | b |
| 2 | b |
| 3 | a |
There are only two classes.
I want to write a query such that we count the number of users in each class such that any user who has label a and b gets sorted into a, any user with just a gets sorted into a and then any user with just b gets into b. If applied to the table snippet above we would get:
| class | count |
|-------|-------|
| a | 2 |
| b | 1 |
Also acceptable is the transpose, like:
| a | b |
|---|---|
| 2 | 1 |
My current solution involves two CTEs:
WITH a_users AS
(
SELECT
user,
SUM(CASE WHEN class = 'a' THEN 1 ELSE 0 END) AS a_class
FROM
table
WHERE
class in ('a', 'b')
GROUP BY
user
),
labeled_users as (
SELECT
user,
CASE WHEN a_class >=1 then 'a' ELSE 'b' END as label
FROM
a_users
)
SELECT
label,
COUNT(DISTINCT user)
FROM
labeled_users;
Is there a (1) more efficient way to solve for this or (2) a more concise/readable solution?
Basically, you want "a" for a user who has "a" at all. A subquery is the first approach:
select sum(case when num_as > 0 then 1 else 0 end) as num_class_a,
sum(case when num_as = 0 then 1 else 0 end) as num_class_b
from (select user, sum(case when class = 'a' then 1 else 0 end) as num_as
from t
group by user
) t;
With a little trick, you can eliminate the subquery:
select count(distinct case when class = 'a' then user end) as num_as,
count(distinct user) - count(distinct case when class = 'a' then user end) as num_bs
from t;
Something like this should work, if a and b really are your classes. Otherwise adjust the min/max as needed.
; with CTE as (
Select user, min(class) as Class
from Labeled_Users
group by user)
Select Class, count(*)
from CTE
group by Class
Here is a straight forward query to get the job done using a subquery and conditional aggregation. It should return the second version of your expected result (pivoted) :
SELECT
SUM(CASE WHEN x.minc <> x.maxc OR x.maxc = 'a' THEN 1 ELSE 0 END) a,
SUM(CASE WHEN x.minc = x.maxc AND x.maxc = 'b' THEN 1 ELSE 0 END) b
FROM (
SELECT user, MAX(class) maxclass, MIN(class) minclass
FROM mytable
GROUP BY user
) x
The subquery computes the minimum and maximum class of each user. Then the outer query separatly counts users :
a : users that belong to both classes or just to class a
b : users that belong to class b only
This is standard SQL syntax that will work on most RDBMS (obviously, even those who do not support CTEs, such as pre-8.0 MySQL versions).
Using String_agg():
with usr_class as(
SELECT DISTINCT usr,
string_agg(txt,':') as all_class
FROM abc
GROUP BY usr
)
select count(usr),
case when POSITION('a' in all_class)>0 THEN 'a'
ELSE 'b'
END AS CLASS
FROM usr_class
GROUP BY case when POSITION('a' in all_class)>0 THEN 'a'
ELSE 'b'
END;

How Do you select group that doesnt contain certain value but must have specific values

I have table Order
ID | State |
===================
1 | A |
1 | B |
1 | C |
1 | D |
1 | E |
2 | A |
2 | B |
2 | E |
3 | A |
3 | B |
3 | E |
4 | A |
4 | B |
4 | C |
4 | D |
From where I like to select group of Ids which must have state value B and E AND must not have state value C and D.
From the above table - The right result should have id 2 and 3
Thanks,
SELECT *
FROM Order
WHERE State IN ('B','E')
That's it. The fact that you're stating the value can only be 'B' or 'E' means you're already excluding any values of 'C' or 'D', or anything else really.
Hope this helps:
SELECT id FROM ORDER
WHERE STATE = E AND STATE = B
You may use the set operator: EXCEPT
SELECT ID FROM Order WHERE State IN ('B','E')
EXCEPT
SELECT ID FROM Order WHERE State IN ('C','D')
The following should work (there might be a better alternative using windowing functions or depending on the specific features available in your dbms)
SELECT ID FROM
(
SELECT
ID,
CASE STATE
WHEN 'B' THEN 'Y'
ELSE 'N'
END AS HasB,
CASE STATE
WHEN 'E' THEN 'Y'
ELSE 'N'
END AS HasE,
CASE STATE
WHEN 'C' THEN 'Y'
ELSE 'N'
END AS HasC,
CASE STATE
WHEN 'D' THEN 'Y'
ELSE 'N'
END AS HasD
FROM TABLE
)
GROUP BY ID
HAVING MAX(HasB) = 'Y' AND MAX(HasE) = 'Y' AND MAX(HasC) = 'N' AND MAX(HasD) = 'N'
A simple way to do this uses aggregation and a having clause:
select id
from t
where sum(case when state = 'B' then 1 else 0 end) > 0 and
sum(case when state = 'E' then 1 else 0 end) > 0 and
sum(case when state = 'C' then 1 else 0 end) = 0 and
sum(case when state = 'D' then 1 else 0 end) = 0;
Each condition in the having clause counts the number of times that a given value is present. The = 0 means there are no matches and > 0 means there is at least one.

Rewriting SQL query to get record id and customer number

Sample data
+-------------------+-------------+-----------------+---------------------+
| RECORD_ID | CUST_NO | IsAccntClosed | Code |
+-------------------+-------------+-----------------+---------------------+
|159045 | 2439123 | N | 13 |
+-------------------+-------------+-----------------+---------------------+
|159048 | 6376150 | Y | 13 |
+-------------------+-------------+-----------------+---------------------+
|159048 | 9513035 | N | 13 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 2398524 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6349269 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6350690 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6372163 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6393810 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6402062 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 2677512 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 6349382 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 6378137 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 2336197 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6349293 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6350682 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6367895 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159060 | yyyyyy | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
IsAccntClosed column indicates if the account is Open (Y) or account is closed (Y).
I need to select Record_ID and cust_no for only those rows for which which Record_Id satisfies one of the below condition :
1. Only one cust account is open , there might be one or multiple closed customers
2. No open customer and only one closed customer
Expected output :
159045 2439123
159048 9513035
159049 2398524
159060 yyyyyy
A query like this would take each row as a single group and the count will come as 1
select RECORD_ID, CUST_NO, IsAccntClosed, count(IsAccntClosed), Code
from table1
group by RECORD_ID, CUST_NO, IsAccntClosed, Code
Any suggestions on how this query could be written to get the expected output?
Being IsAccntClosed a CHAR column, you should be able to get what you want with a query like this:
SELECT a.record_id,
(CASE
WHEN a.CountOpen=1 THEN a.CustNoOpen
ELSE a.CustNoClosed
END) AS cust_no
FROM (
SELECT b.record_id,
MAX(CASE WHEN b.IsAccntClosed='N' THEN b.cust_no ELSE NULL END) AS CustNoOpen ,
SUM(CASE WHEN b.IsAccntClosed='N' THEN 1 ELSE 0 END) AS CountOpen ,
MAX(CASE WHEN b.IsAccntClosed='Y' THEN b.cust_no ELSE NULL END) AS CustNoClosed,
SUM(CASE WHEN b.IsAccntClosed='Y' THEN 1 ELSE 0 END) AS CountClosed
FROM table1 b
GROUP BY b.record_id
) a
WHERE a.CountOpen=1 OR (a.CountOpen=0 AND a.CountClosed=1)
The inner query is grouping the table. It counts the open and closed accounts and takes one (random) cust_no of any of the closed accounts and one (random) of any of the open accounts, per group.
The outer query filters the data and cleans everything up, placing the open or closed cust_no in the output result column.
Notice that the WHERE condition of the outer query has the collateral effect that, since you are looking for records that have just a single open or a single closed account, those random cust_no which have been selected by the inner query, are now significant.
EDIT: I fixed the query and tested it on SQLFiddle.
In the below I added another record:
insert into tbl values (159060, 'zzzzzz', 'N', 12);
to illustrate what would happen if a record_id has just one open cust_no and just one closed cust_no. Note how in the result the cust_no returned is the zzzzz one, because that account is open, which you mentioned wanting to take precendence over closed, in the event of a tie 1:1 (zzzzzz should take over yyyyyy in this case, because yyyyyy is closed whereas zzzzzz is open)
Fiddle: http://sqlfiddle.com/#!4/5cb60/1/0
with one_open as
(select record_id
from tbl
where IsAccntClosed = 'N'
group by record_id
having count(distinct cust_no) = 1),
one_closed as
(select record_id
from tbl
where IsAccntClosed = 'Y'
group by record_id
having count(distinct cust_no) = 1),
bothy as
(select record_id from one_open intersect select record_id from one_closed)
select *
from tbl
where (record_id in (select record_id from one_open) and
IsAccntClosed = 'N')
or (record_id not in (select record_id from one_open) and
record_id in (select record_id from one_closed) and
IsAccntClosed = 'Y' and
record_id not in (select record_id from bothy))
select
record_id,
cust_no
from (
select
record_id,
cust_no,
count(case when isAccntClosed='Y' then 1 else null end)
over (partition by record_id) closed_accounts,
count(case when isAccntClosed='N' then 1 else null end)
over (partition by record_id) open_accounts
from
table1
)
where (open_accounts = 1)
or (open_accounts = 0 and closed_accounts = 1)
You can do this with conditional aggregation:
select RECORD_ID,
(case when sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 1
then max(case when IsAccntClosed = 'N' then max(CUST_NO) end)
else max(CUST_NO)
end) as cust_no
from table1
group by RECORD_ID
having sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 1 or
(sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 0 and
sum(case when IsAccntClosed = 'Y' then 1 else 0 end) = 1
)
It is probably easier to understand the logic using subqueries:
select recordId,
(case when numOpen > 0 then OpenCustNo else closedCustNo end) as CustNo
from (select t1.RecordId, sum(case when IsAccntClosed = 'N' then 1 else 0 end) as numOpen,
sum(case when IsAccntOpen = 'N' then 1 else 0 end) as numClosed,
max(case when IsAccntClosed = 'N' then cust_no end) as OpenCustNo,
max(case when IsAccntClosed = 'Y' then cust_no end) as ClosedCustNo
from table1 t1
group by record_id
) r
where numOpen = 1 or numOpen = 0 and numClosed = 1;

DB2 Select with group by and sum based on another column

I have the following situation:
ACCT_TABLE
ID|TYPE|AMT
--+----+---
A |CR | 5
A |DR | 5
B |CR | 2
B |CR | 4
B |DR | 2
B |DR | 2
C |CR | 1
C |CR | 1
I am trying to build a query that produces the following results:
DESIRED RESULT
ID|BAL
--+---
A | 0
B | 2
C | 2
ACTUAL RESULT
ID|BAL
--+----
A | -10
B | -8
C | 2
I'm not sure how to sum the values in amount based on the value in the TYPE column.
I have the following:
select id, sum(
case
when
type = 'CR'
then AMT
else -AMT
end
) as BAL
from acct_table
group by id;
I have tested your query in mysql, postgresql, sql-server,oracle and it produced desired results.
Your Query
SELECT id
,SUM(CASE WHEN type = 'CR' THEN AMT
ELSE -AMT
END) AS BAL
FROM acct_table
GROUP BY id;
My Query
SELECT id
,SUM(CASE WHEN type = 'CR' THEN AMT
ELSE ( AMT * -1 )
END) AS BAL
FROM acct_table
GROUP BY id;
oracle test - http://sqlfiddle.com/#!4/0b8cb/1
mysql test- http://sqlfiddle.com/#!2/0b8cbd
postgresql test - http://sqlfiddle.com/#!15/0b8cb/1
Please verify that data in your table matches what you have shown in the question.
I tested a query performing summing like that in DB2 (9.7 LUW), and it also worked.
There appears to be nothing wrong with your query; it must be something else.