one sql instead of 2 for counting - sql

I have read a thread on this but when I tried it I can`t manage to make it work.
I want to count all the male and females from a table like so:
Select
count(case when substr(id,1, 1) in (1,2) then 1 else 0 end) as M,
count(case when substr(id,1, 1) in (3,4) then 1 else 0 end) as F
from users where activated=1
The ideea is that a user having an id starting with 1 or 2 is male
My table has 3 male entries and 2 are activated and it returns (the case statement doesn`t work)
M,F
2,2
Any input would be appreciated
id activated
123 1
234 0
154 1

You should use SUM instead. COUNT will count all non null values.
Select
SUM(case when substr(id,1, 1) in (1,2) then 1 else 0 end) as M,
SUM(case when substr(id,1, 1) in (3,4) then 1 else 0 end) as F
from users where activated=1

COUNT will give you the number of non-null values, whatever they are. Try SUM instead.

If your Oracle version is 10g or later, as an alternative, you can use regexp_count function. I assume that the ID column is of number data type, so in the example it explicitly converted to varchar2 data type using TO_CHAR function. If the data type of the ID column is varchar2 or char then there is no need of any type of data type conversion.
Here is an example:
SQL> create table M_F(id, activated) as(
2 select 123, 1 from dual union all
3 select 234, 0 from dual union all
4 select 434, 1 from dual union all
5 select 154, 1 from dual
6 );
Table created
SQL> select sum(regexp_count(to_char(id), '^[12]')) as M
2 , sum(regexp_count(to_char(id), '^[34]')) as F
3 from M_F
4 where activated = 1
5 ;
M F
---------- ----------
2 1
Demo

Related

how to count two different values from same column in oracle

I have a table connection_master in that table column name status_conn have 1 value for ON conn. and 2 for OFF conn. now I want to get only counts of ON and OFF connections in one query
I want output like this
on_counts off_counts
110 55
Use conditional aggregation:
SELECT
COUNT(CASE WHEN status_conn = 1 THEN 1 END) AS on_counts,
COUNT(CASE WHEN status_conn = 2 THEN 1 END) AS off_counts
FROM connection_master;
SELECT COUNT(status_conn) AS CONN_ON,COUNT(status_conn) AS CONN_OFF
FROM connection_master where substr(status_conn,1,1) IN (1,2)
You can use a PIVOT:
SELECT *
FROM connection_master
PIVOT (COUNT(*) FOR status_conn IN (1 AS on_count, 2 AS off_count));
Which, for the sample data:
CREATE TABLE connection_master (status_conn) AS
SELECT 1 FROM DUAL CONNECT BY LEVEL <= 110 UNION ALL
SELECT 2 FROM DUAL CONNECT BY LEVEL <= 55;
Outputs:
ON_COUNT
OFF_COUNT
110
55
fiddle

SQL -- Multiple rows, similar value in a row, need to not show specific values

Here is the issue:
Table name = a
1 2 3
123 1 A
123 1 A
123 2 A
332 1 A
332 1 A
321 2 B
321 2 A
321 1 A
So far what I have is this:
select distinct 1,2,3 from a where a.2='1' and a.3='B';
What it returns is each result (except for 321).
I only want to select values column 1 as long as that value is not in a row where there is a 2 in column 2 or a B in column 3. Is this possible?
"not in a row where there is a 2 in column 2 or a B in column 3" can be expressed as
select distinct 1,2,3 from a where a.2!='2' or a.3!='B';
or
select distinct 1,2,3 from a where a.2 <> '2' or a.3 <> 'B';
I would use group by and having:
select col1
from t
group by col1
having sum(case when col2 = 2 then 1 else 0 end) = 0 and
sum(case when col3 = 'B' then 1 else 0 end) = 0;

Oracle - Order by Alpha numeric

I need to order the rows in my result set by a column that holds varchar2 K-12.
Example:
ID Grade Expense
1 1 500
1 10 500
1 11 500
1 12 500
1 2 500
1 3 500
1 4 500
1 5 500
1 6 500
1 7 500
1 8 500
1 9 500
1 K 500
This is my order by clause which works, but I would like to have the
row with Grade = K as the first row for each ID in my result set.
order by ID, to_number(regexp_substr(grade, '^[[:digit:]]*'))
As it stands, the result set has the row with ID = K is last and not
first. How can i make it the first row for each ID in my result set?
ID Grade Expense
1 K 500
1 1 500
1 2 500
1 3 500
1 4 500
1 5 500
1 6 500
1 7 500
1 8 500
1 9 500
1 10 500
1 11 500
1 12 500
Thanks in advance
Simply use a case statement to set K to something below 1. this has the advantage if you have a Pre-K later, you can modify the case to handle it as well.
With CTE as
(SELECT '1' as grade from dual union
SELECT '2' from dual union
select '10' from dual union
select 'K' from dual)
SELECT * FROM CTE
ORDER BY CASE GRADE when 'K' then -1
else to_number(regexp_substr(grade, '^[[:digit:]]*')) end
This is a bit of a kludge, but since the regex for 'K' returns null, change the order by to:
order by ID, nvl(to_number(regexp_substr(grade, '^[[:digit:]]*')),0)
This will return 0 for 'K' and sort it properly.
You can do the following:
WITH g1 AS (
SELECT 1 AS id, TO_CHAR(level) AS grade, 500 AS expense FROM dual
CONNECT BY level <= 12
UNION
SELECT 1, 'K', 500 FROM dual
UNION
SELECT 1, 'J', 500 FROM dual
)
SELECT g1.*, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+'))
, DECODE(grade, 'K', -1, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+')))
FROM g1
ORDER BY DECODE(grade, 'K', -1, TO_NUMBER(REGEXP_SUBSTR(grade, '^\d+'))) NULLS LAST
In this query I'm using CONNECT BY to build your grade table; of course you'll want to ignore that part. Note I added an extra row with a J for the grade level.
In my order by I am using DECODE() so that if grade = 'K', it will give a value of -1. For any grades that can be converted to numeric values (that is, if they start with at least one digit), I use a regex to get as many digits if possible (you can use [:digit:] or [0-9] in place of \d; but \d is nice and short).
I am specifying NULLS LAST so that any rows for which grade cannot be converted to a number, other than K, will be last.
I'm including the extra computed columns just to give a glimpse into what is actually going on and how the values are generated. They aren't needed for the query.
Please see SQL Fiddle demo here.
Only change the ORDER BY clause, this way:
order by ID asc, decode(grade,'K',-1,grade) asc

Exclude value of a record in a group if another is present

In the example table below, I'm trying to figure out a way to sum amount over id for all marks where mark 'C' doesn't exist within an id. When mark 'C' does exist in an id, I want the sum of amounts over that id, excluding the amount against mark 'A'. As illustration, my desired output is at the bottom. I've considered using partitions and the EXISTS command, but I'm having trouble conceptualizing the solution. If any of you could take a look and point me in the right direction, it would be greatly appreciated :)
sample table:
id mark amount
------------------
1 A 1
2 A 3
2 B 2
3 A 2
4 A 1
4 B 3
5 A 1
5 C 3
6 A 2
6 C 2
desired output:
id sum(amount)
-----------------
1 1
2 5
3 2
4 4
5 3
6 2
select
id,
case
when count(case mark when 'C' then 1 else null end) = 0
then
sum(amount)
else
sum(case when mark <> 'A' then amount else 0 end)
end
from sampletable
group by id
Here is my effort:
select id, sum(amount) from table t where not t.id = 'A' group by id
having id in (select id from table t where mark = 'C')
union
select id, sum(amount) from table t where t.id group by id
having id not in (select id from table t where mark = 'C')
SELECT
id,
sum(amount) AS sum_amount
FROM atable t
WHERE mark <> 'A'
OR NOT EXISTS (
SELECT *
FROM atable
WHERE id = t.id
AND mark = 'C'
)
GROUP BY
id
;

How to transpose recordset columns into rows

I have a query whose code looks like this:
SELECT DocumentID, ComplexSubquery1 ... ComplexSubquery5
FROM Document
WHERE ...
ComplexSubquery are all numerical fields that are calculated using, duh, complex subqueries.
I would like to use this query as a subquery to a query that generates a summary like the following one:
Field DocumentCount Total
1 dc1 s1
2 dc2 s2
3 dc3 s3
4 dc4 s4
5 dc5 s5
Where:
dc<n> = SUM(CASE WHEN ComplexSubquery<n> > 0 THEN 1 END)
s <n> = SUM(CASE WHEN Field = n THEN ComplexSubquery<n> END)
How could I do that in SQL Server?
NOTE: I know I could avoid the problem by discarding the original query and using unions:
SELECT '1' AS TypeID,
SUM(CASE WHEN ComplexSubquery1 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery1) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery1) T
UNION ALL
SELECT '2' AS TypeID,
SUM(CASE WHEN ComplexSubquery2 > 0 THEN 1 END) AS DocumentCount
SUM(ComplexSubquery2) AS Total
FROM (SELECT DocumentID, BLARGH ... AS ComplexSubquery2) T
UNION ALL
...
But I want to avoid this route, because redundant code makes my eyes bleed. (Besides, there is a real possibility that the number of complex subqueries grow in the future.)
WITH Document(DocumentID, Field) As
(
SELECT 1, 1 union all
SELECT 2, 1 union all
SELECT 3, 2 union all
SELECT 4, 3 union all
SELECT 5, 4 union all
SELECT 6, 5 union all
SELECT 7, 5
), CTE AS
(
SELECT DocumentID,
Field,
(select 10) As ComplexSubquery1,
(select 20) as ComplexSubquery2,
(select 30) As ComplexSubquery3,
(select 40) as ComplexSubquery4,
(select 50) as ComplexSubquery5
FROM Document
)
SELECT Field,
SUM(CASE WHEN RIGHT(Query,1) = Field AND QueryValue > 1 THEN 1 END ) AS DocumentCount,
SUM(CASE WHEN RIGHT(Query,1) = Field THEN QueryValue END ) AS Total
FROM CTE
UNPIVOT (QueryValue FOR Query IN
(ComplexSubquery1, ComplexSubquery2, ComplexSubquery3,
ComplexSubquery4, ComplexSubquery5)
)AS unpvt
GROUP BY Field
Returns
Field DocumentCount Total
----------- ------------- -----------
1 2 20
2 1 20
3 1 30
4 1 40
5 2 100
I'm not 100% positive from your example, but perhaps the PIVOT operator will help you out here? I think if you selected your original query into a temporary table, you could pivot on the document ID and get the sums for the other queries.
I don't have much experience with it though, so I'm not sure how complex you can get with your subqueries - you might have to break it down.