Finding the # of pre-requisites each course has oracle sql - oracle-sqldeveloper

I'm having trouble figuring out this problem. This is what I currently have:
`SELECT a.SECTION_ID AS "Section ID", a.COURSE_NUM AS "Course Number", a.SEMESTER || ' ' || a.YEAR AS "Academic Year", b.PREREQ
FROM EB.SECTION a`
Pretty simple so far. I now need to find the courses that have more than one (1) prerequisite course.
These are the two tables I believe I need to use:
SECTION table
PREREQ table
I know I have to use a subquery of some sort and I first tried:
`SELECT a.SECTION_ID AS "Section ID", a.COURSE_NUM AS "Course Number", a.SEMESTER || ' ' || a.YEAR AS "Academic Year", b.PREREQ
FROM EB.SECTION a
LEFT JOIN EB.PREREQ b
ON a.COURSE_NUM = b.COURSE_NUMBER
WHERE b.COURSE_NUMBER IN
(SELECT b.COURSE_NUMBER
FROM EB.PREREQ b
GROUP BY b.COURSE_NUMBER
HAVING COUNT(b.PREREQ) > '1'
)
`
But I think that query is simply counting where a specific course appears more than once on the PREREQ table. Maybe counting the pre-reqs for each course and then adding up those numbers? I'm just a bit stumped at the moment.

If course_number appears more than once on the prereq table, then (by definition) it has more than 1 prerequisite, no?
SELECT course_number
FROM EB.Section
GROUP BY course_number
HAVING COUNT(prereq) > 1
;
You do not need single quotes around 1 in your HAVING clause. Oracle is implicitly converting the string to a number. That's not changing your query results, but you should be aware that Oracle is doing this. Other than that, your query is perfectly fine (unless there's some other unknown business logic about prerequisites that's not stated in the question).
It's also good practice to alias your tables with abbreviations/letter combinations that are more meaningful than A and B. When you get into situations where you are joining multiple tables and inline views, it's good practice to use meaningful aliases. Reformatted to read a bit more clearly (personal opinion):
SELECT
S.section_id AS "Section ID",
S.course_num AS "Course Number",
S.semester|| ' ' || S.year AS "Academic Year",
P.preqreq AS "Prerequisite Course Number"
FROM EB.Section S
LEFT JOIN EB.Prereq P
ON S.course_num = P.course_number
WHERE S.course_num IN(
SELECT course_number
FROM EB.Prereq
GROUP BY course_number
HAVING COUNT(prereq) > 1
)
;
The only business logic you should try to clarify is whether "more than one prerequisite" refers to DIRECT prerequisites, or all prerequisites through a hierarchy. If it's the latter, you have a harder problem to solve and will need to look into hierarchical queries (CONNECT BY clause) / recursive subquery factoring.

Related

Using a list of search patterns in LIKE or IN expression

The question: I have a list of sales quotations and many of them are not valid as they are simply in the system for practice or training. Usually the quotation name contains the word 'Test' or 'Dummy'. (In a couple of instances the quote_name contains 'Prova' - which happens to be Italian for 'Test').
Given that I cannot easily control the list of strings to search for, I decided to maintain the list in a second table - 'Terms to Search for'. A simple one column table with a list of terms ('Test', 'Prova', 'Dummy', ...).
In Amazon Redshift, I tried a simple CASE statement:
CASE WHEN UPPER(vx.quote_name) LIKE ('%' + UPPER(terms.term) + '%') THEN 'Y' ELSE 'N' END AS "Any DPS"
However, that seems to only get the first search term in the list.
Also, for the same quotation, which can have multiple rows due to multiple items being sold, I usually get one row set to 'Y' and the rest set to 'N'.
I modified the statement to:
---- #4a: get a list of the quotes whose quote_names match the patterns in the list
SELECT
vx.master_quote_number,
'Y' AS "Any DPS"
FROM t_quotes vx, any_dps_search_families terms
WHERE UPPER(vx.prod_fmly) IN ('%'+ UPPER(terms.term) +'%');
--- 4b: merge Any DPS results back in
select vx.*, dps."Any DPS"
from t_quotes vx
LEFT JOIN transform_data_4 dps ON (vx.master_quote_number = dps.master_quote_number)
But that isn't doing it either.
Environment: Amazon Redshit (which is mostly like Postgres). An answer to this in Postgres would be ideal. I can switch this clause to MySQL if needed but I'd rather not.
This is a case for lateral joins (untested):
SELECT vx.master_quote_number
FROM any_dps_search_families terms
CROSS JOIN LATERAL (SELECT master_quote_number
FROM t_quotes
WHERE UPPER(prod_fmly)
LIKE ('%' || UPPER(terms.term) || '%')
) vx;

Postgres undefined column when using CASE

Using this SQL, I can cast a boolean column to a text:
SELECT *, (CASE WHEN bars.some_cond THEN 'Yes' ELSE 'No' END) AS some_cond_alpha
FROM "foos"
INNER JOIN "bars" ON "bars"."id" = "foos"."bar_id";
So why do I get a PG::UndefinedColumn: ERROR: column "some_cond_alpha" does not exist when I try to use it in a WHERE clause?
SELECT *, (CASE WHEN bars.some_cond THEN 'Yes' ELSE 'No' END) AS some_cond_alpha
FROM "foos"
INNER JOIN "bars" ON "bars"."id" = "foos"."bar_id"
WHERE (some_cond_alpha ILIKE '%y%');
This is because the column is created on-the-fly and does not exist. Possibly in later editions of PG it will, but right now you can not refer to an alias'd column in the WHERE clause, although for some reason you can refer to the alias'd column in the GROUP BY clause (don't ask me why they more friendly in the GROUP BY)
To get around this, I would make the query into a subquery and then query the column OUTSIDE the subquery as follows:
select *
from (
SELECT *, (CASE WHEN bars.some_cond THEN 'Yes' ELSE 'No' END) AS some_cond_alpha
FROM "foos"
INNER JOIN "bars" ON "bars"."id" = "foos"."bar_id"
) x
WHERE (x.some_cond_alpha ILIKE '%y%')
NOTE: It is possible at some point in the future you will be able to refer to an alias'd column in the WHERE clause. In prior versions, you could not refer to the alias in the GROUP BY clause but since 9.4 + it is possible...
SQL evaluates queries in a rather counterintuitive way. It starts with the FROM and WHERE clauses, and only hits the SELECT towards the end. So aliases defined in the SELECT don't exist yet when we're in the WHERE. You need to do a subquery if you want to have access to an alias, as shown in Walker Farrow's answer.
When I read an SQL query, I try to do so in roughly this order:
Start at the FROM. You can generally read one table/view/subquery at a time from left to right (or top to bottom, depending on how the code is laid out); it's normally not permissible for one item to refer to something that hasn't been mentioned yet.
Go down, clause by clause, in the order they're written. Again, read from left to right, top to bottom; nothing should reference anything that hasn't been defined yet. Stop right before you hit ORDER BY or something which can only go after ORDER BY (if there is no ORDER BY/etc., stop at the end).
Jump up to the SELECT and read it.
Go back down to where you were and resume reading.
If at any point you see a subquery, apply this algorithm recursively.
If the query begins with WITH RECURSIVE, go read the Postgres docs for 20 minutes and figure it out.

HAVING clause in PostgreSQL

I'm rewriting the MySQL queries to PostgreSQL. I have table with articles and another table with categories. I need to select all categories, which has at least 1 article:
SELECT c.*,(
SELECT COUNT(*)
FROM articles a
WHERE a."active"=TRUE AND a."category_id"=c."id") "count_articles"
FROM articles_categories c
HAVING (
SELECT COUNT(*)
FROM articles a
WHERE a."active"=TRUE AND a."category_id"=c."id" ) > 0
I don't know why, but this query is causing an error:
ERROR: column "c.id" must appear in the GROUP BY clause or be used in an aggregate function at character 8
The HAVING clause is a bit tricky to understand. I'm not sure about how MySQL interprets it. But the Postgres documentation can be found here:
http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-HAVING
It essentially says:
The presence of HAVING turns a query
into a grouped query even if there is
no GROUP BY clause. This is the same
as what happens when the query
contains aggregate functions but no
GROUP BY clause. All the selected rows
are considered to form a single group,
and the SELECT list and HAVING clause
can only reference table columns from
within aggregate functions. Such a
query will emit a single row if the
HAVING condition is true, zero rows if
it is not true.
The same is also explained in this blog post, which shows how HAVING without GROUP BY implicitly implies a SQL:1999 standard "grand total", i.e. a GROUP BY ( ) clause (which isn't supported in PostgreSQL)
Since you don't seem to want a single row, the HAVING clause might not be the best choice.
Considering your actual query and your requirement, just rewrite the whole thing and JOIN articles_categories to articles:
SELECT DISTINCT c.*
FROM articles_categories c
JOIN articles a
ON a.active = TRUE
AND a.category_id = c.id
alternative:
SELECT *
FROM articles_categories c
WHERE EXISTS (SELECT 1
FROM articles a
WHERE a.active = TRUE
AND a.category_id = c.id)
SELECT * FROM categories c
WHERE
EXISTS (SELECT 1 FROM article a WHERE c.id = a.category_id);
should be fine... perhaps simpler ;)

Postgresql Faulty Syntax on select/join/group

What about the following is not proper syntax for Postgresql?
select p.*, SUM(vote) as votes_count
FROM votes v, posts p
where p.id = v.`voteable_id`
AND v.`voteable_type` = 'Post'
group by v.voteable_id
order by votes_count DESC limit 20
I am in the process of installing postgresql locally but wanted to get this out sooner :)
Thank you
MySQL is a lot looser in its interpretation of standard SQL than PostgreSQL is. There are two issues with your query:
Backtick quoting is a MySQL thing.
Your GROUP BY is invalid.
The first one can be fixed by simply removing the offending quotes. The second one requires more work; from the fine manual:
When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.
This means that every column mentioned in your SELECT either has to appear in an aggregate function or in the GROUP BY clause. So, you have to expand your p.* and make sure that all those columns are in the GROUP BY, you should end up with something like this but with real columns in place of p.column...:
select p.id, p.column..., sum(v.vote) as votes_count
from votes v, posts p
where p.id = v.voteable_id
and v.voteable_type = 'Post'
group by p.id, p.column...
order by votes_count desc
limit 20
This is a pretty common problem when moving from MySQL to anything else.

TSQL syntax - Is aliasing with a quoted prefix depricated?

Can anyone tell me if writing a query in the following tsql syntax is either (1) currently -- or going to be soon -- deprecated by MSFT, or (2) in opposition to some best practice of which I'm not aware?
SELECT
'CustName' = (SELECT Lastname + ', ' + Firstname FROM Cust WHERE CustID = O.CustID),
'ProdName' = (SELECT ProductName FROM Product WHERE ProductID = O.ProductID)
FROM Orders O
The specific question is putting the new column name all the way to the "front" or left of the line as opposed to writing the subquery and putting the new column name in square brackets after the subquery. Obviously both will work, but the DBAs reviewing my database code typically give me a WTF look when they see this, even though I tell them it's far more readable because all of your column names are on the left...
Is there something wrong with writing queries in this manner?
CLARIFICATION: The point isn't the subqueries in the SELECT statement, it's whether the syntax:
'NewColumnName' = OldColumnName
is going away anytime soon. I chose to demonstrate the question with a pair of subqueries rather than using the giant and esoteric custom function calls and case statements that are actually in the production code I'm using.
According to SQL 2005 BOL here (ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/instsql9/html/c10eeaa5-3d3c-49b4-a4bd-5dc4fb190142.htm) and here: http://msdn.microsoft.com/en-us/library/ms143729(SQL.90).aspx and in the 2008 doc here: http://msdn.microsoft.com/en-us/library/ms143729.aspx (look under "Transact-SQL" features) this will be deprecated in a future release (unspecified).
However, it's a bit subtle. This deprecation warning actually only applies to the use of the quotation marks in this context, not the column-alias-first format. I.E., this will be deprecated:
'AliasName' = NewValue
However, this is still valid and even listed as a replacement for it:
AliasName = NewValue
So just take the apostrophes out and you're good.
There's absolutely nothing wrong with the
column alias = expression
syntax and it isn't deemed to be either deprecated soon or bad practice!
The main problem with the query, is that you should be using joins to connect the Orders table to the Cust and Product tables. The query looks like you are assuming there is only one Customer and one Product per order - think about what would happen if that wasn't true....
SELECT
'CustName' = C.Lastname + ', ' + C.Firstname,
'ProdName' = P.ProductName
FROM Orders O
JOIN Cust C on C.CustID = O.CustID
JOIN Product P on P.ProductID= O.ProductID
I typically see this syntax when using a subquery to set a variable, #MyValue = (subquery) for example. So there was a bit of a WTF from me for a minute as well. However, I see your point, and overall I can't imagine that it is something that would be not supported in the future.
Personally though I prefer a more formatted manner, and a distinct "AS" definition. I would write it something like this.
SELECT
(SELECT Lastname + ', ' + Firstname
FROM Cust
WHERE CustID = O.CustID
) AS CustName,
(SELECT ProductName
FROM Product
WHERE ProductID = O.ProductID
) AS ProdName
FROM Orders O
I personally find this easier to read....but more than likely that is just me...
My guess is people balk at it because it's specific to MSSQL, not supported by other systems (at least, not the ones I have available to me right now). It's like listening to an unfamiliar dialect - you can see what the person is getting at, but you can't see why on Earth they would choose to speak that way.
You are aliasing a column name in a strange, unfamiliar way. i had to run it to see if it was even valid.
Rather than doing what's strange and uncommon, do what people expect:
SELECT
(SELECT Lastname + ', ' + Firstname FROM Cust WHERE CustID = O.CustID) AS CustName,
(SELECT ProductName FROM Product WHERE ProductID = O.ProductID) AS ProdName
FROM Orders O