The data I have is in the following format:
ID Category Gender Cost
1 C1 M 40
2 C2 F 50
3 C3 M 60
4 C1 F 40
5 C3 M 70
6 C3 F 50
.
.
.
and so on
Only C1, C2 and C3 exist as categories
The result I need is:
Category Male % Female % Avg. Cost
C1 0.5 0.5 40
C2 0 1 50
C3 0.6 0.4 60
What I have done in the stored procedure is I declared variables for C1 count, C2 count and C3 count, C1 female count, C2 female count, C3 female count, C1 Total Cost, C2 Total Cost, C3 Total cost, you get the idea. Then I calculated the average cost and male / female percentages.
It works but it's not a very elegant solution. I couldn't get the unpivot query to work so it gives me the result I need. Can this be done using unpivot or am I misunderstanding how pivots work? I tried something like this below but couldn't figure out how to include M/F percentage and average cost per category.
Thanks.
Something like this (which is incomplete):
Declare #TotalCount int;
SELECT #TotalCount = count(*) from MyTable;
SELECT Category, p.Total, CAST(p.Total * 1.0 / #TotalCount AS float) AS Percentage FROM --How to get F or M percentage, Average Cost Per Category , etc?
(
SELECT ISNULL(SUM(c.C1), 0) AS C1,
ISNULL(SUM(c.C2), 0) AS C2,
ISNULL(SUM(c.C3), 0) AS C3
FROM
(SELECT CASE WHEN Category = 'C1' THEN 1 ELSE 0 END AS C1,
CASE WHEN Category = 'C2' THEN 1 ELSE 0 END AS C2,
CASE WHEN Category = 'C3' THEN 1 ELSE 0 END AS C3
FROM #Candidates
) c --Do I need
) a
UNPIVOT
(
Total FOR Suitability IN (
C1,
C2,
C3
)
) p
Please check below query:
select
Category,
CAST(1.00*sum(case when Gender='M' then 1 else 0 end)/COUNT(Category) AS NUMERIC(18,1)) [Male %],
CAST(1.00*sum(case when Gender='F' then 1 else 0 end)/COUNT(Category) AS NUMERIC(18,1)) [Female %],
AVG(Cost) [Avg. Cost]
from tbl
group by Category
SQL Fiddle Demo
Related
I have a file that contains customers with orders and I need to find the top 75%. It needs to be at least 75% and orders with the same number will be included. Need to figure out the where statement to select the records.
Cust | Orders | Accum Orders | Accum %
c1 10 10 29%
c2 7 17 45%
c3 5 22 63%
c4 4 26 74%
c5 3 29 83%
c6 3 32 89%
c7 2 34 94%
c8 1 35 100%
I would like to only extract c1-c6. C4 is only 74% and it needs to be 75%. c5-c6 are the same number of orders so they both need to be extracted.
Thanks
You can solve this by writing two queries and then make them union:
SELECT * FROM TABLE1
WHERE
TO_NUMBER(REPLACE(ACCUMP,'%','')) < 75
UNION
SELECT * FROM TABLE1
WHERE ORDERS IN
(
SELECT ORDERS FROM TABLE1
GROUP BY ORDERS
HAVING COUNT(*) > 1
);
Use window functions:
select t.*
from (select t.*,
sum(orders) over (order by orders desc range between unbounded preceding and current row) as running_orders,
sum(orders) over (partition by orders) as all_with_this_order,
sum(orders) over () as total_orders
from t
) t
where (running_orders - all_with_this_order) < 0.75 * total_orders;
You need a subquery with a group by Orders
select Cust
from tab
where Orders =
(
select Orders
from tab
where replace(accum,'%','') >= 75
group by Orders
having count(AccumOrders) > 1
);
Rextester Demo
I might be confusing syntax from other languages with SQL and that's why this isn't working, my subquery is definitely incorrect.
Having the following 2 tables:
TEST_RESULTS
Student_ID Test_ID Test_Result
A1 234 90
B2 234 80
C3 345 85
D4 234 95
A1 345 95
C3 456 95
TEST_DESCRIPTION
Test_ID Test_Description Passing_Score
234 Test A 85
345 Test B 90
456 Test C 95
I want to calculate the rate of passing for each test and sort by it.
The output I am looking for:
Test_ID Test_Description students_taking students_passing rate
456 Test C 1 1 1
234 Test A 3 2 0.666666667
345 Test B 2 1 0.5
This is my query
SELECT td.Test_ID, td.Test_Description, COUNT(tr.Student_ID) as
students_taking, students_passing, students_passing/students_taking as rate
FROM
(SELECT td.Test_ID, td.Test_Description, COUNT(tr.Student_ID) as
students_passing
FROM TEST_RESULTS tr
JOIN TEST_DESCRIPTION td
on tr.Test_ID = td.Test_ID
WHERE tr.Test_Result > td.)
GROUP BY td.Test_ID, td.Test_Description
ORDER BY rate DESC, td.Test_ID, td.Test_Description
My select from select is wrong, because I am getting no results for this query.
I'm using CTE, LEFT JOIN for getting the desired result.
Try this query --
;WITH CTE
AS
(
SELECT
TD.TEST_ID,
TEST_DESCRIPTION,
COUNT(TR.STUDENT_ID) AS STUDENTS_TAKING,
COUNT(CASE WHEN TR.TEST_RESULT >= TD.PASSING_SCORES THEN
TR.STUDENT_ID END) AS STUDENTS_PASSING
FROM TEST_DESCRIPTION TD
LEFT JOIN TEST_RESULTS TR
ON TD.TEST_ID = TR.TEST_ID
GROUP BY TD.TEST_ID,
TEST_DESCRIPTION
)
SELECT
TEST_ID,
TEST_DESCRIPTION,
STUDENTS_TAKING,
STUDENTS_PASSING,
STUDENTS_PASSING / CONVERT (DECIMAL(4,2),STUDENTS_TAKING) AS RATE
FROM CTE
ORDER BY TEST_DESCRIPTION
Please check below query-
SELECT TD.TEST_ID,
TD.TEST_DESCRIPTION,
STUDENT_TAKING,
STUDENT_PASSING,
RATE
FROM TEST_DESCRIPTION TD,
(SELECT TR.TEST_ID,COUNT(TR.STUDENT_ID) "STUDENT_TAKING",
COUNT(CASE WHEN TEST_RESULT>=PASSING_SCORE THEN STUDENT_ID END) STUDENT_PASSING,
TO_NUMBER(TO_CHAR(COUNT(CASE WHEN TEST_RESULT>=PASSING_SCORE THEN STUDENT_ID END)/COUNT(TR.STUDENT_ID),'9999.99')) RATE
FROM TEST_RESULTS TR,TEST_DESCRIPTION TD
WHERE TR.TEST_ID=TD.TEST_ID
GROUP BY TR.TEST_ID)SUB
WHERE SUB.TEST_ID=TD.TEST_ID ORDER BY RATE DESC;
SELECT td.Test_ID, td.Test_Description,
students_taking = counts.students_taking,
students_passing = counts.students_passing,
rate = counts.rate
FROM TEST_DESCRIPTION td
OUTER APPLY (
SELECT
students_taking = COUNT(1),
students_passing = COUNT(CASE WHEN tr.Test_Result > td.Passing_score THEN 1 ELSE NULL END),
rate = IIF(COUNT(1) <> 0, COUNT(CASE WHEN tr.Test_Result > td.Passing_score THEN 1 ELSE NULL END) / CAST(COUNT(1) AS FLOAT), 0)
FROM TEST_RESULTS tr
WHERE tr.Test_ID = td.Test_ID
) counts
ORDER BY counts.rate DESC, td.Test_ID
DECLARE #Table TABLE (CID int, C1 varchar(5), C2 decimal(18,6), C3 decimal(18,6))
INSERT INTO #Table
SELECT 1,'A',0.0,0.0
UNION ALL
SELECT 2,'A',0.1,0.0
UNION ALL
SELECT 3,'A',0.1,0.1
UNION ALL
SELECT 4,'B',0.0,0.0
UNION ALL
SELECT 5,'B',0.1,0.0
UNION ALL
SELECT 6,'B',0.1,0.1
SELECT * FROM #Table
WHERE C1 NOT IN ('B') AND (C2 >= 0 AND C3 >= 0)
/* Desired output
CID C1 C2 C3
1 A 0.0 0.0
2 A 0.1 0.0
3 A 0.1 0.1
4 B 0.0 0.0
*/
If C2 and C3 are greater than 0 then no record with C1 = B should be returned. I'm having trouble figuring out the logic. I can't seem to figure out the arithmetic. :(
Thank you
p.s. a non-subquery sln would be awesome!
Your logic is: "do not include the record if C2 > 0 AND C3 > 0 AND C1 = 'B'"
Negate it, which results in: C2 <= 0 OR C3 <= 0 OR C1 <> 'B' and use this condition to include all of the other records.
Note, if you meant to exclude records where (C2 > 0 OR C3 > 0) AND C1 = 'B', then you would negate that to include the other records: (C2 <= 0 AND C3 <= 0) OR C1 <> 'B'
Inclusive, consider the rows you do want:
SELECT * FROM #Table WHERE (C1 NOT IN ('B')) OR (C2 <= 0 AND C3 <= 0)
Exclusive, consider the rows you do not want:
SELECT * FROM #Table WHERE NOT (C1 IN ('B') AND (C2 > 0 OR C3 > 0))
These are algebraically equivalent, so pick the one that reads best.
Let's say I have a table with an ID column, and several property columns
MyTable (ID, PI, P2, P3, P4)
ID P1 P2 P3 P4
1 A1 B C1 D1
2 C1 C2 B NULL
3 C2 Z NULL NULL
4 X A1 C1 NULL
So, I need to write a query to find out how many distinct property values out there, no matter in which column they are.
Value Count
A1 2
B 2
C1 3
C2 2
X1 1
...
I think I can get this by using UNPIVOT (correct me, if I am wrong)
Now, how can I get similar count but grouped by a number of non-null values in the row (the count of non-null values per row may, or may not include key columns, doesn't matter), i.e. output like this:
Value NonNullCount Count
A1 3 1
A1 4 1
B 3 1
B 4 1
C1 2 3
C1 4 1
C2 3 1
C2 2 1
...
Here is one method, using cross apply for the unpivot:
select vals.p, t.NonNullCount, count(*)
from (select t.*,
((case when p1 is not null then 1 else 0 end) +
(case when p2 is not null then 1 else 0 end) +
(case when p3 is not null then 1 else 0 end) +
(case when p4 is not null then 1 else 0 end)
) as NonNullCount
from table t
) t cross apply
(values (p1), (p2), (p3), (p4)) vals(p)
where vals.p is not null
group by vals.p, t.NonNullCount;
I have an int field in my table scores. It can be a number between 0 and 100. There are over a 1000 rows in that table. I want to select the number of rows that fall between ranges 0-25, 26-50, 51-75 and 76-100.
So basically, if this is my scores table
score (int)
------------
10
20
12
56
43
90
87
The output of the query should look like this ( let's call the ranges R1, R2, R3, R4 )
R1 R2 R3 R4
------------------
3 1 1 2
Let me know if something's not clear.
EDIT I got this far
SELECT CASE score
WHEN score BETWEEN 0 AND 25 THEN 'R1'
WHEN score BETWEEN 26 AND 50 THEN 'R2'
WHEN score BETWEEN 51 AND 75 THEN 'R3'
ELSE 'R4'
END, COUNT(*)
FROM scores
But when I put a AS range after END, I get an error. (UPDATE: nvm this, it supposed to be AS 'range', with quotes)
Also, if score is 0, it shows up under R2 and not R1. Why is that?
create table scores (score int)
insert into scores values(5);
insert into scores values(15);
insert into scores values(25);
insert into scores values(30);
insert into scores values(35);
insert into scores values(40);
insert into scores values(45);
insert into scores values(55);
insert into scores values(65);
insert into scores values(80);
insert into scores values(90);
insert into scores values(95);
insert into scores values(75);
insert into scores values(50);
select SUM(case t.rank when 1 then c else 0 end) as R1,
SUM(case t.rank when 2 then c else 0 end) as R2,
SUM(case t.rank when 3 then c else 0 end) as R3,
SUM(case t.rank when 4 then c else 0 end) as R4
from
(
select TRUNCATE(score / 26, 0) + 1 rank, count(*) c
from scores
group by truncate(score / 26, 0) + 1
) t
result:
R1 R2 R3 R4
--------------
3 5 3 3
Try something along the lines of the following (caveat: not tested):
SELECT CASE WHEN score BETWEEN 0 AND 25 THEN 'R1'
WHEN score BETWEEN 26 AND 50 THEN 'R2'
WHEN score BETWEEN 51 AND 75 THEN 'R3'
WHEN score BETWEEN 76 AND 100 THEN 'R4'
END AS r,
COUNT(*)
FROM scores
GROUP BY r
Right now I cannot remember whether MySQL will let you group by an alias. If not, group by the CASE expression instead.
Updated: It seems that MySQL will, in fact, let you group by an alias.