Modifying SELECT query to create Stacked Bar Chart - sql

I have a query for a Placement Application Tracking System which shows the number of students placed and unplaced per programme of study. I'm struggling to create an APEX Stacked Bar Chart out of it, even though the query returns the desired results.
Query:
SELECT programme_name,
SUM(CASE WHEN (cv_approval_date IS NOT NULL AND application_status_id <> 7) OR
application_status_id IS NULL
THEN 1 ELSE 0 END) as Unplaced,
SUM(CASE WHEN (cv_approval_date IS NOT NULL AND application_status_id <> 7) OR
application_status_id IS NULL
THEN 0 ELSE 1 END) as Placed
FROM programme LEFT JOIN
student USING (programme_id) LEFT JOIN
application USING (student_id)
GROUP BY programme_name;
Output:
PROGRAMME_NAME | PLACED | UNPLACED
BSc (Hons) Computer Science | 2 | 2
BSc (Hons) Computing and Games Development | 1 | 0
BSc (Hons) Web Applications Development | 0 | 1
BSc (Hons) Marine Biology and Coastal Ecology | 1 | 0
The graph is supposed to look similar to this - the x axis being Programme, y axis being number of students placed, and unplaced:
http://ruepprich.files.wordpress.com/2011/03/stacked_bar.png?w=550&h=386
How might I go about doing this? Any help would be greatly appreciated!

When creating a chart in Apex you can click on "Chart Query Example" for some sample queries that will work with that chart type.
In the case of a stacked bar chart, the following example is given:
SELECT NULL LINK,
ENAME LABEL,
SAL "Salary",
COMM "Commission"
FROM EMP
ORDER BY ENAME
In your case I think you'll want your query to present the following format:
SELECT NULL LINK,
programme_name AS LABEL,
SUM(...) AS "Unplaced",
SUM(...) AS "Placed"
FROM ...

Related

Use auxiliary table to define values for another table's column

I am working with a table that currently uses multiple CASE expressions to define the behavior of one of the columns, i.e.:
SELECT
Employee
,Company
,Department
,Area
,Flag = CASE
WHEN Company = 'Amazon' and Department in ('IT', 'HR')
THEN 0
WHEN Department = 'Legal'
THEN 1
WHEN Area = 'Cloud'
THEN 1
ELSE 0
END
FROM Table1
Which would result in something like the following dummy data:
Employee
Company
Department
Area
Flag
Cindy
Amazon
IT
Support
0
Jack
Amazon
HR
Support
0
Bob
Microsoft
Legal
Contracts
1
Joe
Amazon
Legal
Research
1
Lauren
Google
IT
Cloud
1
Jane
Apple
UX
Research
0
I am trying to simplify the Flag expression by using an auxiliary Mappings table that has the following structure, in order to get the value for the Flag column:
Company
Department
Area
Flag
Amazon
IT
NULL
0
Amazon
HR
NULL
0
NULL
Legal
NULL
1
NULL
NULL
Cloud
1
The NULL values mean the column could take any value. Is it possible to achieve this without falling into multiple CASE statements?

Case statement logic and substring

Say I have the following data:
Passes
ID | Pass_code
-----------------
100 | 2xBronze
101 | 1xGold
102 | 1xSilver
103 | 2xSteel
Passengers
ID | Passengers
-----------------
100 | 2
101 | 5
102 | 1
103 | 3
I want to count then create a ticket in the output of:
ID 100 | 2 pass (bronze)
ID 101 | 5 pass (because it is gold, we count all passengers)
ID 102 | 1 pass (silver)
ID 103 | 2 pass (steel)
I was thinking something like the code below however, I am unsure how to finish my case statement. I want to substring pass_code so that we get show pass numbers e.g '2xBronze' should give me 2. Then for ID 103, we have 2 passes and 3 customers so we should output 2.
Also, is there a way to firstly find '2xbronze' if the pass_code contained lots of other things such as '101001, 1xbronze, FirstClass' - this may change so i don't want to substring, could we search for '2xbronze' and then pull out the 2??
SELECT
CASE
WHEN Passes.pass_code like '%gold%' THEN Passengers.passengers
WHEN Passes.pass_code like '%steel%' THEN SUBSTRING(passes.pass_code, 1,1)
WHEN Passes.pass_code like '%bronze%' THEN SUBSTRING(passes.pass_code, 1,1)
WHEN Passes.pass_code like '%silver%' THEN SUBSTRING(passes.pass_code, 1,1)
else 0 end as no,
Passes.ID,
Passes.Pass_code,
Passengers.Passengers
FROM Passes
JOIN Passengers ON Passes.ID = Passengers.ID
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=db698e8562546ae7658270e0ec26ca54
So assuming you are indeed using Oracle (as your DB fiddle implies).
You can do some string magic with finding position of a splitter character (in your case the x), then substringing based on that. Obviously this has it's problems, and x is a bad character seperator as well.. but based on your current set.
WITH PASSCODESPLIT AS
(
SELECT PASSES.ID,
TO_Number(SUBSTR(PASSES.PASS_CODE, 0, (INSTR(PASSES.PASS_CODE, 'x')) - 1)) AS NrOfPasses,
SUBSTR(PASSES.PASS_CODE, (INSTR(PASSES.PASS_CODE, 'x')) + 1) AS PassType
FROM Passes
)
SELECT
PASSCODESPLIT.ID,
CASE
WHEN PASSCODESPLIT.PassType = 'gold' THEN Passengers.Passengers
ELSE PASSCODESPLIT.NrOfPasses
END AS NrOfPasses,
PASSCODESPLIT.PassType,
Passengers.Passengers
FROM PASSCODESPLIT
INNER JOIN Passengers ON PASSCODESPLIT.ID = Passengers.ID
ORDER BY PASSCODESPLIT.ID ASC
Gives the result of:
ID NROFPASSES PASSTYPE PASSENGERS
100 2 bronze 2
101 5 gold 5
102 1 silver 1
103 2 steel 3
As can also be seen in this fiddle
But I would strongly advise you to fix your table design. Having multiple attributes in the same column leads to troubles like these. And the more variables/variations you start storing, the more 'magic' you need to keep doing.
In this particular example i see no reason why you don't simply have the 3 columns in Passes, also giving you the opportunity to add new columns going forward. I.e. to keep track of First class.
You can extract the numbers using regexp_substr(). So I think this does what you want:
SELECT (CASE WHEN p.pass_code LIKE '%gold%'
THEN TO_NUMBER(REGEXP_SUBSTR(p.pass_code, '^[0-9]+'))
ELSE pp.passengers
END) as num,
p.ID, p.Pass_code, pp.Passengers
FROM Passes p JOIN
Passengers pp
ON p.ID = pp.ID;
Here is a db<>fiddle.
This converts the leading digits in the code to a number. Also note the use of table aliases to simplify the query.

SQL Spatial Subquery Issue

Greetings Benevolent Gods of Stackoverflow,
I am presently struggling to get a spatially enabled query to work for a SQL assignment I am working on. The wording is as follows:
SELECT PURCHASES.TotalPrice, STORES.GeoLocation, STORES.StoreName
FROM MuffinShop
join (SELECT SUM(PURCHASES.TotalPrice) AS StoreProfit, STORES.StoreName
FROM PURCHASES INNER JOIN STORES ON PURCHASES.StoreID = STORES.StoreID
GROUP BY STORES.StoreName
HAVING (SUM(PURCHASES.TotalPrice) > 600))
What I am trying to do with this query is perform a function query (like avg, sum etc) and get the spatial information back as well. Another example of this would be:
SELECT STORES.StoreName, AVG(REVIEWS.Rating),Stores.Shape
FROM REVIEWS CROSS JOIN
STORES
GROUP BY STORES.StoreName;
This returns a Column 'STORES.Shape' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. error message.
I know I require a sub query to perform this task, I am just having endless trouble getting it to work. Any help at all would be wildly appreciated.
There are two parts to this question, I would tackle the first problem with the following logic:
List all the store names and their respective geolocations
Get the profit for each store
With that in mind, you need to use the STORES table as your base, then bolt the profit onto it through a sub query or an apply:
SELECT s.StoreName
,s.GeoLocation
,p.StoreProfit
FROM STORES s
INNER JOIN (
SELECT pu.StoreId
,StoreProfit = SUM(pu.TotalPrice)
FROM PURCHASES pu
GROUP BY pu.StoreID
) p
ON p.StoreID = s.StoreID;
This one is a little more efficient:
SELECT s.StoreName
,s.GeoLocation
,profit.StoreProfit
FROM STORES s
CROSS APPLY (
SELECT StoreProfit = SUM(p.TotalPrice)
FROM PURCHASES p
WHERE p.StoreID = s.StoreID
GROUP BY p.StoreID
) profit;
Now for the second part, the error that you are receiving tells you that you need to GROUP BY all columns in your select statement with the exception of your aggregate function(s).
In your second example, you are asking SQL to take an average rating for each store based on an ID, but you are also trying to return another column without including that inside the grouping. I will try to show you what you are asking SQL to do and where the issue lies with the following examples:
-- Data
Id | Rating | Shape
1 | 1 | Triangle
1 | 4 | Triangle
1 | 1 | Square
2 | 1 | Triangle
2 | 5 | Triangle
2 | 3 | Square
SQL Server, please give me the average rating for each store:
SELECT Id, AVG(Rating)
FROM Store
GROUP BY StoreId;
-- Result
Id | Avg(Rating)
1 | 2
2 | 3
SQL Server, please give me the average rating for each store and show its shape in the result (but don't group by it):
SELECT Id, AVG(Rating), Shape
FROM Store
GROUP BY StoreId;
-- Result
Id | Avg(Rating) | Shape
1 | 2 | Do I show Triangle or Square ...... ERROR!!!!
2 | 3 |
It needs to be told to get the average for each store and shape:
SELECT Id, AVG(Rating), Shape
FROM Store
GROUP BY StoreId, Shape;
-- Result
Id | Avg(Rating) | Shape
1 | 2.5 | Triangle
1 | 1 | Square
2 | 3 | Triangle
2 | 3 | Square
As in any spatial query you need an idea of what your final geometry will be. It looks like you are attempting to group by individual stores but delivering an average rating from the subquery. So if I'm reading it right you are just looking to get the stores shape info associated with the average ratings?
Query the stores table for the shape field and join the query you use to get the average rating
select a.shape
b.*
from stores a inner join (your Average rating query with group by here) b
on a.StoreID = b.Storeid

Taking average of two rows and adding third row of same column in sql

I'm doing a simple student marks report application and trying to create a View Object through sql statement from two entity object in the database.
StudentDetails -> rollno, name.
marks -> rollno, internal, sub1, sub2, sub3
there are two exams for the students i.e. internal=1 and internal=2 and internal=3 is for assignment submission marks.
snapshot of marks table is below. DRAWING table as i'm not able to attach snapshot here.
select * from marks shows like this:
---------------------------------
ROLLNO | INTERNAL | SUB1 | SUB2 |
1 | 1 | 12 | 15 |
1 | 2 | 15 | 17 |
1 | 3 | 2 | 5 |
2 | 1 | 10 | 14 |
--------------------------------
For calculating aggregate marks we have to avg marks from internal=1 and internal=2 and add marks of internal=3 to it for every rollno(student). Can someone please help me in framing this query.
It is unclear from your question exactly what is being averaged to give the result, but here is an attempt which you can build on.
I will break up your question into parts.
For calculating aggregate marks we have to avg marks - so we will be using the AVG( ) function.
from internal=1 and internal=2 - so that will go in the WHERE clause as WHERE internal=1 or internal=2.
and add marks of internal=3 to it tricky - will need to treat the rows with internal=3 as a separate table and use JOIN to link them.
for every rollno(student) so, we are combining rows by student, which requires a GROUP BY.
Here is the final statement:
select m1.rollno, avg(m1.sub1+m1.sub2)+m2.sub1+m2.sub2 as result
from marks as m1
inner join marks as m2
on m1.rollno = m2.rollno
where m1.internal in (1,2)
and m2.internal = 3
group by rollno
Here is a working example for you to experiment on: http://sqlfiddle.com/#!2/9ed4e0/1
The way it is averaging and adding may be incorrect, but it was difficult to discern from your question what you really needed. It should be easy to change this part.
Note that since rows with internal=3 represent a different type of data they should be in a different table unless you have the experience to be certain what you are doing is workable.

How to design db for holding Dominion cards?

I'd like to store some information about games of the card game Dominion. You don't need to know much about this game, except that:
There are around 200 unique cards
Each game includes only ten of these cards, or on occasion eleven
I'll be tracking lots more about each game (who played, who won, etc), but what I'm having trouble with is working with the "supply" (the ten included cards for this game).
I'm thinking I want three tables, something like card_name, supply, and game:
card_name supply game
id | name supply | card game | supply | player1 | player2 | ...
----+--------- --------+------ ------+--------+---------+---------+-----
1 | Village 1 | 1 301 | 1 | 'Mike' | 'Tina' | ...
2 | Moat 1 | 3
3 | Witch 1 | 200
... | ... ... | ...
200 | Armory
I think this is a reasonable way to represent "Mike and Tina played a game which contained Village, Witch, Armory, and some other cards I didn't bother typing into this example". Given this structure (or some other one, if you think mine is no good), I'd like to run queries like "which games had Witch and Village, but not Moat?" That is, I want to specify some arbitrary number of "these X cards included, these Y cards excluded" and search the game table for games satisfying the criteria.
I think this is a classic one-to-many relation, where a supply has multiple cards, but I don't understand the right way to search for a supply by multiple cards.
Your data structure is reasonable. I might suggest that you would want a game_users table as well, so the users are not listed in separate columns. This would be particularly important if games had different numbers of users. However, this aspect is not relevant to your question.
You want to solve "set-within-sets" subqueries. Your structure is useful and the supply table provides the basic information needed for this.
So, a query to get the appropriate "supply" records for "Witch", "Village", and not "Moat" would look like:
select supplyid
from supplies s join
cards c
on s.cardid = c.cardid
group by supplyid
having sum(case when cardname = 'Witch' then 1 else 0 end) > 0 and
sum(case when cardname = 'Village' then 1 else 0 end) > 0 and
sum(case when cardname = 'Moat' then 1 else 0 end) = 0;
First note that I changed the name, so the id columns contain the word "id" and the table names are in plural.
Each condition in the having clause is representing one condition on the cards. You can tweak this to get the game information by joining in games:
select g.gameid
from supplies s join
cards c
on s.cardid = c.cardid join
games g
on g.supplyid = s.gameid
group by g.gameid
having sum(case when cardname = 'Witch' then 1 else 0 end) > 0 and
sum(case when cardname = 'Village' then 1 else 0 end) > 0 and
sum(case when cardname = 'Moat' then 1 else 0 end) = 0;