Trouble with oracle sql query - sql

I am trying to make a query of
"What are the names of the producers
with at least 2 properties with areas
with less than 10"
I have made the following query that seems to work:
select Producers.name
from Producers
where (
select count(Properties.prop_id)
from Properties
where Properties.area < 10 and Properties.owner = Properties.nif
) >= 2;
yet, my lecturer was not very happy about it. He even thought (at least gave me the impression of) that this kind of queries wouldn't be valid in oracle.
How should one make this query, then? (I have at the moment no way of getting to speak with him btw).
Here are the tables:
Producer (nif (pk), name, ...)
Property (area, owner (fk to
producer), area, ... )

The having clause is typically used to filter on aggregate data (like counts, sums, max, etc).
select
producers.name,
count(*)
from
producers,
property
where
producers.nif = property.owner and
property.area < 10
group by
producers.name
having
count(*) >= 2

select P.name
from Producers p, Properties pr
where p.nif = pr.Owner
AND Properties.area < 10
GROUP BY Producers.name
having Count(*) >= 2

Related

Select only the row with the max value, but the column with this info is a SUM()

I have the following query:
SELECT DISTINCT
CAB.CODPARC,
PAR.RAZAOSOCIAL,
BAI.NOMEBAI,
SUM(VLRNOTA) AS AMOUNT
FROM TGFCAB CAB, TGFPAR PAR, TSIBAI BAI
WHERE CAB.CODPARC = PAR.CODPARC
AND PAR.CODBAI = BAI.CODBAI
AND CAB.TIPMOV = 'V'
AND STATUSNOTA = 'L'
AND PAR.CODCID = 5358
GROUP BY
CAB.CODPARC,
PAR.RAZAOSOCIAL,
BAI.NOMEBAI
Which the result is this. Company names and neighborhood hid for obvious reasons
The query at the moment, for those who don't understand Latin languages, is giving me clients, company name, company neighborhood, and the total value of movements.
in the WHERE clause it is only filtering sales movements of companies from an established city.
But if you notice in the Select statement, the column that is retuning the value that aggregates the total amount of value of sales is a SUM().
My goal is to return only the company that have the maximum value of this column, if its a tie, display both of em.
This is where i'm struggling, cause i can't seem to find a simple solution. I tried to use
WHERE AMOUNT = MAX(AMOUNT)
But as expected it didn't work
You tagged the question with the whole bunch of different databases; do you really use all of them?
Because, "PL/SQL" reads as "Oracle". If that's so, here's one option.
with temp as
-- this is your current query
(select columns,
sum(vrlnota) as amount
from ...
where ...
)
-- query that returns what you asked for
select *
from temp t
where t.amount = (select max(a.amount)
from temp a
);
You should be able to achieve the same without the need for a subquery using window over() function,
WITH T AS (
SELECT
CAB.CODPARC,
PAR.RAZAOSOCIAL,
BAI.NOMEBAI,
SUM(VLRNOTA) AS AMOUNT,
MAX(VLRNOTA) over() AS MAMOUNT
FROM TGFCAB CAB
JOIN TGFPAR PAR ON PAR.CODPARC = CAB.CODPARC
JOIN TSIBAI BAI ON BAI.CODBAI = PAR.CODBAI
WHERE CAB.TIPMOV = 'V'
AND STATUSNOTA = 'L'
AND PAR.CODCID = 5358
GROUP BY CAB.CODPARC, PAR.RAZAOSOCIAL, BAI.NOMEBAI
)
SELECT CODPARC, RAZAOSOCIAL, NOMEBAI, AMOUNT
FROM T
WHERE AMOUNT=MAMOUNT
Note it's usually (always) beneficial to join tables using clear explicit join syntax. This should be fine cross-platform between Oracle & SQL Server.

SQL - How to display only certain values of the Count Aggregate Function?

what I'm trying to do is to only display only rows with a count value greater than 3.
select pharm_name, count(1) as "Number of Staff"
from pharmacies p, pharmacy_staff_store pss
where p.pharm_id = pss.pharmacy_id
group by pharm_name;
For example this query might return me 5 rows where under the "Number of Staff" it'll say for instance 5,4,3,2,1 but I only want it to return me those rows where the count is 3 and above. Is there a feasible way to do this?
use the having :
select pharm_name, count(1) as "Number of Staff"
from pharmacies p, pharmacy_staff_store pss
where p.pharm_id = pss.pharmacy_id
group by pharm_name
having count(1) > 3
or you can write in this way:
select * from (
select pharm_name, count(1) as x
from pharmacies p, pharmacy_staff_store pss
where p.pharm_id = pss.pharmacy_id
group by pharm_name)
where x>3
First dont use WHERE join
Promote the use of explict JOIN sintaxis, Aaron Bertrand wrote a nice article Bad habits to kick : using old-style JOINs about it.
Then use HAVING to filter from the result.
SELECT pharm_name,
Count(1) AS "Number of Staff"
FROM pharmacies p
JOIN pharmacy_staff_store pss
ON p.pharm_id = pss.pharmacy_id
GROUP BY pharm_name
HAVING COUNT(1) > 3;
Also I wouldnt use COUNT(1) if someone change the order of the fields on db your query wouldnt notice and will show wrong behavior. Use Count(fieldname)

SQL query, sub queries

I have a table storing sports results for a series of events: ONS_Skippers
The relevant columns from this table for the question are:
FK_EventID, FK_SkipperID and intResult.
I'm presenting different statistics from this database, but I've not succeeded to generate the query for the most advanced one: I would like to list average performance for each participant (FK_SkipperID). I've defined performance to be 100% for an event win, 0% for last place in an event and performance on a linear curve between the two extents. The formula for this is:
Performance = 100*(1-(intResult-1)/(NumberOfParticipantsInTheEvent-1))
NumberOfParticipantsInTheEvent varies from each event, hence needs to be counted from each group of FK_EventID. All my attempts so far has failed:
Example:
SELECT FK_SkipperID, AVG((1-(intResult-1.0)/((SELECT Count(FK_EventID)
FROM ONS_Skippers AS ONS_Skippers2
WHERE ONS_Skippers.FK_EventID = ONS_Skippers2.FK_EventID AND FK_SkipperID > 0
GROUP BY FK_EventID)-1))*100)
FROM ONS_Skippers
GROUP BY FK_SkipperID
This gives error messages "Cannot perform an aggregate function on an expression containing an aggregate or a subquery".
Any idea on how to produce the wanted output?
Try to join to the subquery instead:
SELECT
FK_SkipperID,
AVG((1-(intResult-1.0)/(e.events-1))*100)
FROM ONS_Skippers o
INNER JOIN
(
SELECT Count(FK_EventID) AS events
FROM ONS_Skippers AS ONS_Skippers2
WHERE FK_SkipperID > 0
GROUP BY FK_EventID
) e
ON o.FK_EventID = e.FK_EventID
GROUP BY FK_SkipperID
I think you could achieve this by joining to an inline table as follows...
select SkipperID,
AVG(100*(1-(Result-1)/(p.NumParticipants-1))) as Performance
from Spike.Skippers s
inner join (
select EventId, Count(EventId) as NumParticipants -- Or Max(Result)
from Spike.Skippers
group by EventID
) p on s.EventID = p.EventID
group by SkipperID
[Edit] Apologies for not sticking to your column naming conventions - my OCD insisted I adhere to my own personal standard. Fussy, I know. [/Edit]

SQL "Count (Distinct...)" returns 1 less than actual data shows?

I have some data that doesn't appear to be counting correctly. When I look at the raw data I see 5 distinct values in a given column, but when I run an "Count (Distinct ColA)" it reports 4. This is true for all of the categories I am grouping by, too, not just one. E.g. a 2nd value in the column reports 2 when there are 3, a 3rd value reports 1 when there are 2, etc.
Table A: ID, Type
Table B: ID_FK, WorkID, Date
Here is my query that summarizes:
SELECT COUNT (DISTINCT B.ID_FK), A.Type
FROM A INNER JOIN B ON B.ID_FK = A.ID
WHERE Date > 5/1/2013 and Date < 5/2/2013
GROUP BY Type
ORDER BY Type
And a snippet of the results:
4|Business
2|Design
2|Developer
Here is a sample of my data, non-summarized. Pipe is the separator; I just removed the 'COUNT...' and 'GROUP BY...' parts of the query above to get this:
4507|Business
4515|Business
7882|Business
7889|Business
7889|Business
8004|Business
4761|Design
5594|Design
5594|Design
5594|Design
7736|Design
7736|Design
7736|Design
3132|Developer
3132|Developer
3132|Developer
4826|Developer
5403|Developer
As you can see from the data, Business should be 5, not 4, etc. At least that is what my eyes tell me. :)
I am running this inside a FileMaker 12 solution using it's internal ExecuteSQL call. Don't be concerned by that too much, though: the code should be the same as nearly anything else. :)
Any help would be appreciated.
Thanks,
J
Try using a subquery:
SELECT COUNT(*), Type
FROM (SELECT DISTINCT B.ID_FK, A.Type Type
FROM A
INNER JOIN B ON B.ID_FK = A.ID
WHERE Date > 5/1/2013 and Date < 5/2/2013) x
GROUP BY Type
ORDER BY Type
This could be a FileMaker issue, have you seen this post on the FileMaker forum? It describes the same issue (a count distinct smaller by 1) with 11V3 back in 03/2012 with a plug in, then updated with same issue with 12v3 in 11/2012 with ExecuteSQL. It didn't seem to be resolved in either case.
Other considerations might be if there are any referential integrity constraints on the joined tables, or if you can get a query execution plan, you might find it is executing the query differently than expected. not sure if FileMaker can do this.
I like Barmar's suggestion, it would sort twice.
If you are dealing with a bug, directing the COUNT DISTINCT, Join and/or Group By by structuring the query to make them happen at different times might work around it:
SELECT COUNT (DISTINCT x.ID), x.Type
FROM (SELECT A.ID ID, A.Type Type
FROM A
INNER JOIN B ON B.ID_FK = A.ID
WHERE B.Date > 5/1/2013 and B.Date < 5/2/2013) x
GROUP BY Type
ORDER BY Type
you might also try replacing B.ID_FK with A.ID, who knows what context it applies, such as:
SELECT COUNT (DISTINCT A.ID), A.Type

Speed up SQLite query, can I do it without a union?

Hi everybody of the stackoverflow community! I've been visiting this site for years and here comes my first post
Lets say I have a database with three tables:
groups (GroupID,GroupType,max1,size)
candies (candyID,name,selected)
members (groupID,nameID)
Example: The candy factory.
In the candy factory 10 types of candy bags are produced out of 80 different candies.
So: There are 10 unique group types(bags) with 3 different sizes: (4,5,6); a group is combination out of 80 unique candies.
Out of this I make a database, (with some rules about which candy combinations gets into a group).
At this point I have a database with 40791 unique candy bags.
Now I want to compare a collection of candies with all the candy bags in the DB, as a result I want the bags out of the DB which are missing 3 or less candies with the compare collection.
-- restore candy status
update candies set selected = 0, blacklisted = 0;
-- set status for candies to be selected
update candies set selected = 1 where name in ('candy01','candy02','candy03','candy04');
select groupId, GroupType, max, count(*) as remainingNum, group_concat(name,', ') as remaining
from groups natural join members natural join candies
where not selected
group by groupid having count(*) <= 3
UNION -- Union with groups which dont have any remaining candies and have a 100% match
select groupid, GroupType, max, 0 as remainingNum, "" as remaining
from groups natural join members natural join candies
where selected
group by groupid having count(*) =groups.size;
The above query does this. But the thing I am trying to accomplish is to do this without the union, because speed is of the essence. And also I am new to sql and are very eager to learn/see new methods.
Greetings, Rutger
I'm not 100% sure about what you are accomplishing through these queries, so I haven't looked at a fundamentally different approach. If you can include example data to demonstrate your logic, I can have a look at that. But, in terms of simply combining your two queries, I can do that. There is a note of caution first, however...
SQL is compiled in to query plans. If the query plan for each query is significantly different from the other, combining them into a single query may be a bad idea. What you may end up with is a single plan that works for both cases, but is not very efficient for either. One poor plan can be a lot worse than two good plans => Shorter, more compact, code does not always give faster code.
You can put selected in to your GROUP BY instead of your WHERE clause; the fact that you have two UNIONed queries shows that you are treating them as two separate groups already.
Then, the only difference between your queries is the filter on count(*), which you can accommodate with a CASE WHEN statement...
SELECT
groups.groupID,
groups.GroupType,
groups.max,
CASE WHEN Candies.Selected = 0 THEN count(*) ELSE 0 END as remainingNum,
CASE WHEN Candies.Selected = 0 THEN group_concat(candies.name,', ') ELSE '' END as remaining
FROM
groups
INNER JOIN
members
ON members.GroupID = groups.GroupID
INNER JOIN
candies
ON Candies.CandyID = members.CandyID
GROUP BY
Groups.GroupID,
Groups.GroupType,
Groups.max,
Candies.Selected
HAVING
CASE
WHEN Candies.Selected = 0 AND COUNT(*) <= 3 THEN 1
WHEN Candies.Selected = 1 AND COUNT(*) = Groups.Size THEN 1
ELSE 0
END
=
1
The layout changes are simply because I disagree with using NATURAL JOIN for maintenance reasons. They are a short-cut in initial build and a potential disaster in later development. But that's a different issue, you can read about it on line if you feel you want to.
Don't update the database when you're doing a select, your first update update candies set selected = 0, blacklisted = 0; will apply to the entire table, and rewrite every record. You should try without using selected and also changing your union to UNION ALL. Further to this, you try inner join instead of natural join (but I don't know your schema for candy to members)
select groupId, GroupType, max, count(*) as remainingNum, group_concat(name,', ') as remaining
from groups
inner join members on members.groupid = groups.groupid
inner join candies on candies.candyid = member.candyid
where name NOT in ('candy01','candy02','candy03','candy04')
group by groups.groupid
having count(*) <= 3
UNION ALL -- Union with groups which dont have any remaining candies and have a 100% match
select groupid, GroupType, max, 0 as remainingNum, "" as remaining
from groups
inner join members on members.groupid = groups.groupid
inner join candies on candies.candyid = member.candyid
where name in ('candy01','candy02','candy03','candy04')
group by groupid
having count(*) = groups.size;
This should at least perform better than updating all records in the table before querying it.