Percentage of requests SQL - sql

So, I have this table:
Table1
|Number | abc |
|100 | No |
|200 | No |
|300 | Yes |
|400 | No |
|500 | No |
What I want is the percentage of values that is "yes". In this case, the desired OUTPUT is 20%
I thought that by dividing the number of "yes" by the total number It would do it, but i can't "join" all things.
I know that the number of "yes" is
select count(abc)
from table1
where abc='yes'
And the total number is
select count(*)
from table1
How do i get the desired output?

A query that works in all SQL engines is
select sum(case when abc = 'yes' then 1 else 0 end) * 100 / count(*)
from your_table

Related

Output several counts of one table records in one row

Here is an example table CALLRECORD:
+--------+------------+
|callid | rating |
|1 | |
|2 | 5 |
|3 | |
|4 | 1 |
|5 | |
+--------+------------+
No problem to output total number of calls, number of rated calls, average rating and number of unrated calls:
select count(*) as total from callrecord;
select count(*) as rated, avg(rating) as average_rating from callrecord where rating is not null;
select count(*) as unrated from callrecord where rating is null;
+--------+
|total |
|5 |
+--------+
+--------+------------+
|rated |average |
|2 |3 |
+--------+------------+
+--------+
|unrated |
|3 |
+--------+
I'm looking for how to output all above to one row with single SQL request:
+--------+--------+------------+---------+
|total |rated |average |unrated |
|5 |2 |3 |3 |
+--------+--------+------------+---------|
db<>fiddle here
Most aggregate functions ignore null values, so what you want is simpler that you may think:
select
count(*) total, -- total number of rows
count(rating) as rated, -- count of non-null ratings
avg(rating) average, -- avg ignore `null`
count(*) - count(rating) unrated -- count of null ratings
from mytable
Try using the SUM aggregation with a CASE statement inside of it. Example below.
Select
COUNT(*) AS 'Total',
SUM(CASE WHEN rating IS NULL THEN 0 ELSE 1 END) AS 'Rated',
(SUM(CASE WHEN rating IS NULL THEN 0 ELSE rating END)/SUM(CASE WHEN rating IS NULL THEN 0 ELSE 1 END)) AS 'Avg',
SUM(CASE WHEN rating IS NULL THEN 1 ELSE 0 END) AS 'Unrated'
From callrecord

Calculating percentage by group using Teradata

I'm trying to create a table that displays the percentage of counts per state dependent on the indicator.
Here's an example of the dataset I'm using to create my new table.
+-------------+-------+-------+
| Indicator | State | Count |
+-------------+-------+-------+
| Registered | CA | 25 |
| Registered | FL | 12 |
| Total | CA | 50 |
| Total | FL | 36 |
+-------------+-------+-------+
I'm trying to create a new table that would have a Percentage for each corresponding row like this:
+-------------+-------+-------+------------+
| Indicator | State | Count | Percentage |
+-------------+-------+-------+------------+
| Registered | CA | 25 | 50 |
| Registered | FL | 12 | 33.3 |
| Total | CA | 50 | . |
| Total | FL | 36 | . |
+-------------+-------+-------+------------+
So far, i've tried doing the below query:
select indicator, state, count
, case when (select count from table where indicator='Registered') * 100 / (select count from table where indicator='Total')
when indicator = 'Total' then . end as Percentage
from table;
This doesn't work because I get an error: "Subquery evaluated more than one row." I'm guessing its because I'm not taking into account the state in the case when statement, but i'm not sure as to how I would go about that.
What would be the best way to do this?
Just join the table back with itself.
select a.indicator, a.state, a.count
, case when (indicator='Total') then null
else 100 * a.count/b.count
end as Percentage
from table a
inner join (select state,count from table where indicator='Total') b
on a.state = b.state
;
You can use window functions:
select t.*,
(case when indicator <> 'Total'
then count * 100.0 / sum(case when indicator = 'Total' then indicator end) over (partition by state)
end) as percentage
from t;

SQL query to select rows that based on some value of other rows in the same table

Assume I have the following activity table:
id type value flag
------|------|-------|------|
1 |A | 13 | 1 |
2 |B | 29 | |
3 |C | 11 | |
4 |A | 78 | |
5 |X | 91 | |
6 |C | 2 | |
7 |B | 14 | 1 |
I want to select rows that any row with the same type has the flag 1 or the type is X. In this case, I would like to get:
id type value flag
------|------|-------|------|
1 |A | 13 | 1 |
4 |A | 78 | |
2 |B | 29 | |
7 |B | 14 | 1 |
5 |X | 91 | |
I can do an INNER JOIN to get the result like:
SELECT
"activities".*
FROM "activities"
INNER JOIN activities AS a2
ON activities.type = a2.type
AND a2.flag = 1
OR activities.type = 'X'
AND activities.id = a2.id
However, this is slow when the amount of records becomes large, especially when I need to do a COUNT(*) on top of the result. I wonder how I can rewrite the query and make it more performant.
I am using Postgres. Thanks!
Here are 3 ways to do it. Choose one which will be work faster on your dataset. To speed up these queries you have to create indexes on type and flag fields.
select a.* from activities a
JOIN (select distinct type FROM activities where type='X' or flag=1) t
ON a.type=t.type;
select a.* from activities a
where type='X'
or EXISTS(SELECT * FROM activities WHERE type=a.type AND flag=1);
select a.* from activities a
where type='X'
or type IN (SELECT type FROM activities WHERE flag=1)
SQLFiddle demo
Try this solution
SELECT id,
type,
value,
flag
FROM (SELECT *,
SUM(CASE WHEN flag = 1 THEN 1 ELSE 0 END) OVER (PARTITION BY type) AS flag_occurence_for_type
FROM activities) t
WHERE type = 'X' OR flag_occurence_for_type > 0

Multiple selects really needed?

I have the following table.
____________________________________
| carid | changeid | data1 | data2 |
|_______|__________|_______|_______|
| 1 | 1 |a |b |
| 1 | 2 |c |d |
| 1 | 3 |e |f |
| 2 | 3 |g |h |
| 2 | 2 |i |j |
| 2 | 4 |k |l |
| 3 | 5 |m |n |
| 3 | 1 |o |p |
| 4 | 6 |q |r |
| 4 | 2 |s |t |
|_______|__________|_______|_______|
I want to select the following result:
| carid | changeid | data1 | data2 |
|_______|__________|_______|_______|
| 1 | 1 |a |b |
| 1 | 2 |c |d |
| 1 | 3 |e |f |
| 3 | 5 |m |n |
| 3 | 1 |o |p |
|_______|__________|_______|_______|
In words:
If a row has changeid=1 I want to select all the rows with the same carid as the row with changeid=1.
This problem is quite easy to solve with a query using multiple selects. First select all rows with changeid=1 and take those carids and select all rows with those carids. Simple enough.
I was more wondering if it is possible to solve this problem without using multiple selects? Preferably I'm looking for a faster solution but I can try that out myself.
You can join the table back to itself
SELECT DISTINCT a.*
FROM YourTable a
INNER JOIN YourTable b ON b.carid = a.carid and b.changeid = 1
Table a is all the rows you want to output, filtered by table b which limits the set to those with changeid = 1.
This should have excellent performance as everything is done in a set oriented manner.
DISTINCT may not be necessary if changeid 1 may only occur once, and should be avoided if possible as it may introduce a significant performance hit for a large result set.
For multiple select you mean using IN?
SELECT carid, changeid, data1, data2
FROM YourTable
WHERE carid IN (SELECT carid FROM YourTable WHERE changeid = 1)
Most databases support window functions. You can do this as:
select carid, changeid
from (select t.*,
max(case when changeid = 1 then 1 else 0 end) over
(partition by carid) as HasChangeId1
from YourTable t
) t
where HasChangeId1 = 1;
If the "1" is the minimum value for the change id, this can be simplified to:
select carid, changeid
from (select t.*,
min(changeid) over (partition by carid) as MinChangeId
from YourTable t
) t
where MinChangeId = 1;
It sounds like you're after only the combinations of carid and changeid present in the table, in which case the DISTINCT will return only the unique combinations for you. Not sure if that is what you're after but give it a go and check it for your expected behaviour...
SELECT DISTINCT CARID, CHANGEID FROM UnknownTable

single-row subquery returns more than one row oracle

I have three tables like below:
Test
+--------------+--------+
| Test_Case_ID | Status |
+--------------+--------+
| 10 | PASS |
| 20 | FAIL |
| 30 | FAIL |
+--------------+--------+
Defect
+-----------+
| Defect_ID |
+-----------+
| 500 |
| 400 |
+-----------+
and link1
+--------------+-----------+
| Test_Case_ID | Defect_ID |
+--------------+-----------+
| 20 | 500 |
| 30 | 500 |
| 30 | 400 |
+--------------+-----------+
I am trying the below query
select
test.test_case_id,
test.status,
case when test.status = 'FAIL' then
(select link1.defect_id
from link1
where
test.test_case_id = link1.test_case_id)
end as defect1_id
from test test
I get the below error "Error 12/20/2012 10:05:17 AM 0:00:00.093 Toad for Data Analysts: ORA-01427: single-row subquery returns more than one row 1 78
"
Is there a way to retrieve both the records for "30" from the link table? Because i want to display that test case 30 is failing because of defect 500 & 400.
Thanks so much
You have two rows in the link table that have values of "30". This is your problem.
Which of these rows do you want?
To fix the subquery, you can either say select max(link1.defect_id) or add and rownum = 1 to the where clause.
What you want is probably a bi more complicated. How about this version, which concatenates the defects into a string:
select t.test_case_id, t.status,
listagg(cast(l.defect_id as varchar(32)) within group (order by l.defect_id) as defects
from test t left join
link1 l
on t.test_case_id = l.test_case_id
group by t.test_case_id, t.status
You don't specify the version of Oracle. If listagg is not available, then wm_concat probably is. Here is a reference on different ways to concat strings in an aggregation in Oracle.
Have you thought about using a JOIN instead of the subquery:
select
t.test_case_id,
t.status,
case when t.status = 'FAIL' then l.defect_id
end as defect1_id
from test t
left join link1 l
on t.test_case_id = l.test_case_id
See SQL Fiddle with Demo
This will return both records, then you can decide which item to return in your final result.
Result:
| TEST_CASE_ID | STATUS | DEFECT1_ID |
--------------------------------------
| 20 | FAIL | 500 |
| 30 | FAIL | 500 |
| 30 | FAIL | 400 |
| 10 | PASS | (null) |
Based on your comment, if you are using Oracle 11g, then you can use the LISTAGG() function to combine the records into one row:
select
t.test_case_id,
t.status,
case
when t.status = 'FAIL'
then listagg(l.defect_id, ', ')
within group (order by l.defect_id)
end as defect1_id
from test t
left join link1 l
on t.test_case_id = l.test_case_id
group by t.test_case_id, t.status
See SQL Fiddle with Demo
Result:
| TEST_CASE_ID | STATUS | DEFECT1_ID |
--------------------------------------
| 10 | PASS | (null) |
| 20 | FAIL | 500 |
| 30 | FAIL | 400, 500 |