Firebird can't recognize calculated column in group by clause - sql

I have the following SQL:
select
inv.salesman_id,
(select salesman_goals.goal from salesman_goals
where salesman_goals.salesman_id = inv.salesman_id
and salesman_goals.group_id = g.group_id
and salesman_goals.subgroup_id = sg.subgroup_id
and salesman_goals.variation_id = v.variation_id)
as goal,
sum(i.quantity) as qnt
from invoiceitem i
inner join invoice inv on inv.invoice_id = i.invoice_id
inner join product p on p.product_id = i.product_id
left join groups g on g.group_id = p.group_id
left join subgroup sg on sg.group_id = g.group_id and sg.subgroup_id = p.subgroup_id
left join variation v on v.group_id = sg.group_id and v.subgroup_id = sg.subgroup_id and v.variation_id = p.variation_id
group by
1,2
which returns three columns, the first one is the salesman id, the second is a sub select to get the sales quantity goal, and the third is the actual sales quantity.
Even grouping by the first and second columns, firebird throws an error when executing the query:
Invalid expression in the select list (not contained in either an aggregate function or the GROUP BY clause).
What's the reason for this?

There is a column "in the select list (not contained in either an aggregate function or the GROUP BY clause)". Namely each column you mention in your subselect other than inv.salesman_id. Such a column has many values per group. When there is a GROUP BY (or just a HAVING, implicitly grouping by all columns) a SELECT clause returns one row per group. There is no single value to return. So you want (as you put in an answer yourself):
group by
inv.salesman_id,
g.group_id,
sg.subgroup_id,
v.variation_id

OK guys i found the solution for this problem.
The thing is, if you have a sub query in a column which will be in the group by clause, the parameters inside this sub query must also appear in the group by. So in this case, all i had to do was:
group by
inv.salesman_id,
g.group_id,
sg.subgroup_id,
v.variation_id
And that's it. Hope it helps if someone has the same issue in the future.

Related

Counting subquery results SQL Oracle

So the code I have is trying to count the number of ratings given to a movie per state. That's all easy done. I also need to count the number of ratings given to award winning movies, per state.
SELECT DISTINCT ad.state "State",
COUNT(r.ratingid) OVER (PARTITION BY ad.state) "Number of Ratings",
COUNT(
SELECT DISTINCT r.ratingid
FROM netflix.ratings100 r JOIN netflix.movies_awards a
ON r.movieid = a.movieid
JOIN netflix.addresses ad
ON ad.custid = r.custid
WHERE a.awardid IS NOT NULL
) OVER (PARTITION BY ad.state) "Number of Award Winning Movies Rated"
FROM netflix.addresses ad JOIN netflix.ratings100 r
ON ad.custid = r.custid
JOIN netflix.movies_awards a
ON r.movieid = a.movieid
GROUP BY "State"
The second count statement should be counting the number of ratings made where the awardID is not null. That subquery alone works, and returns distinct ratingIDs, but the thing as a whole does not work. I get ORA-00936: missing expression. Solutions?
You haven't got brackets around the subquery - you have the brackets to indicate the count, but you need an extra set to indicate that it's a subquery.
E.g;
count( (select ....) ) over ...
Moreover, you're reusing the aliases from your outer query in your inner query, plus there's nothing to correlate the subquery to your outer query, so I don't think you're going to get the results you're after.
Additionally, you've labelled a column with an identifier that's over 30 characters, so unless you're on 12.2 with the extended identifiers set, you're going to get ORA-00972: identifier is too long.
Finally, I don't think you need that subquery at all; I think you can just use a conditional count, e.g.:
SELECT DISTINCT ad.state "State",
COUNT(r.ratingid) over(PARTITION BY ad.state) "Number of Ratings",
COUNT(DISTINCT CASE WHEN a.awardid IS NOT NULL THEN r.ratingid END) over(PARTITION BY ad.state) "Num Award Winning Movies Rated"
FROM netflix.addresses ad
JOIN netflix.ratings100 r
ON ad.custid = r.custid
JOIN netflix.movies_awards a
ON r.movieid = a.movieid
GROUP BY "State";
You may not even need that distinct; it depends on your data. Hopefully you can play around with that and get it to work for your requirements.
That seems like a complicated query. This should be an aggregation query . . . with a correlated subquery:
SELECT ad.state, COUNT(DISTINCT r.ratingId) as num_rated,
COUNT(DISTINCT CASE WHEN a.awardId IS NOT NULL THEN r.ratingid END) as num_rated_with_award
FROM netflix.addresses ad JOIN
netflix.ratings100 r
ON ad.custid = r.custid LEFT JOIN
netflix.movies_awards a
ON r.movieid = a.movieid
GROUP BY ad.state;
Notes:
There is no reason to give a column an alias equivalent to its original name. So, as "State" is unnecessary, unless you really care about capitalization.
A movie could have more than one award, so to get the number of ratings, use count(distinct).
SELECT DISTINCT is almost never appropriate with GROUP BY.
The query has no need of window functions.

GROUP BY not working in left join query

I m trying to use group by clause in left join sql query and it is not working.
Please help me out, thanks in advance.
SELECT Cust_Mst_Det.Cust_Hd_Code,
Cust_Mst_Det.First_Name,
SL_HEAD20152016.vouch_date AS invoice_2,
SL_HEAD20142015.vouch_date AS invoice_1,
Cust_Mst_Hd.EMail
FROM Cust_Mst_Det
LEFT JOIN SL_HEAD20142015 ON Cust_Mst_Det.Cust_Hd_Code=SL_HEAD20142015.Member_Code
LEFT JOIN SL_HEAD20152016 ON Cust_Mst_Det.Cust_Hd_Code=SL_HEAD20152016.Member_Code
LEFT JOIN Cust_Mst_Hd ON Cust_Mst_Det.Cust_Hd_Code=Cust_Mst_Hd.Cust_Hd_Code
WHERE cust_mst_det.first_name!='NIL'
GROUP BY Cust_Mst_Det.Cust_Hd_Code
ORDER BY SL_HEAD20152016.vouch_date DESC,
SL_HEAD20142015.vouch_date
I'm not sure which DBMS you are using, but on an Oracle your query will not work at all.
First issue: The GROUP BY statement is used in conjunction with the aggregate functions to group the result-set by one or more columns. You do not have any aggregating function in your SELECT statement (count, max, etc.)
Second issue: you must specify all columns from SELECT statement in your GROUP BY statement (excluding columns that represents results of aggregation).
As I said I don't know which DB is used by you, but those two points should be applicable for the most of SQL standards.
It appears that it is impossible to use an ORDER BY on a GROUP BY summarisation. My fundamental logic is flawed. I will need to run the following subquery.
ex :
SELECT p.*, pp.price
FROM products p
LEFT JOIN ( SELECT price FROM product_price ORDER BY date_updated DESC ) pp
ON p.product_id = pp.product_id GROUP BY p.product_id;
This will take a performance hit but as it is the same subquery for each row it shouldn't be too bad.

Missing rows when selecting from 2 tables

Hi and sorry if that's a stupud question but I am trying to get results from two different tables and since one of them might have zero records I am not sure how to proceed. Here is the query:
SELECT aid.*, T.ItemId, T.Total, T.Stack
FROM (SELECT ItemId, Stack, count(ItemId) as Total FROM auction_house WHERE Sale = 0 GROUP BY ItemId, Stack) as T, ahbot_item_data aid
WHERE T.ItemId = aid.ItemId
AND T.Stack = aid.Stack
Basically I want to get a list of items from the aid table, and the current count of those items in the ah table. But since the count might just be zero, it might not return that row. I want the row and the Total to be 0.
Thank you in advance ^^;;
Similar to what #Twelfth was saying, you want to have a left outer join, which always outputs data from the left table even if there is no matching data in the right table.
SELECT
aid.ItemId,
count(ah.ItemId) AS "Total",
aid.Stack
FROM
ahbot_item_data aid
LEFT OUTER JOIN auction_house ah
ON (aid.ItemID = ah.ItemID AND aid.Stack = ah.Stack)
GROUP BY
aid.ItemID, aid.Stack
You are using old syntax...I'm going to switch to join syntax (newer and more accepted...same effect, easier to read). What you want is an 'outer join', which will produce nulls where no record is found. I'll arrange the tables and we'll use a left outer join
SELECT aid.*, T.ItemId, T.Total, T.Stack
FROM ahbot_item_data aid left join (SELECT ItemId, Stack, count(ItemId) as Total FROM auction_house WHERE Sale = 0 GROUP BY ItemId, Stack) as T,
on T.ItemId = aid.ItemId
AND T.Stack = aid.Stack
I'm not entirely sure on the syntax anymore...but it's also possible to use != to do this outer join in the syntax you've used (where aid.itemID != t.itemID I think?)

SQL - why is this 'where' needed to remove row duplicates, when I'm already grouping?

Why, in this query, is the final 'WHERE' clause needed to limit duplicates?
The first LEFT JOIN is linking programs to entities on a UID
The first INNER JOIN is linking programs to a subquery that gets statistics for those programs, by linking on a UID
The subquery (that gets the StatsForDistributorClubs subset) is doing a grouping on UID columns
So, I would've thought that this would all be joining unique records anyway so we shouldn't get row duplicates
So why the need to limit based on the final WHERE by ensuring the 'program' is linked to the 'entity'?
(irrelevant parts of query omitted for clarity)
SELECT LmiEntity.[DisplayName]
,StatsForDistributorClubs.*
FROM [Program]
LEFT JOIN
LMIEntityProgram
ON LMIEntityProgram.ProgramUid = Program.ProgramUid
INNER JOIN
(
SELECT e.LmiEntityUid,
sp.ProgramUid,
SUM(attendeecount) [Total attendance],
FROM LMIEntity e,
Timetable t,
TimetableOccurrence [to],
ScheduledProgramOccurrence spo,
ScheduledProgram sp
WHERE
t.LicenseeUid = e.lmientityUid
AND [to].TimetableOccurrenceUid = spo.TimetableOccurrenceUid
AND sp.ScheduledProgramUid = spo.ScheduledProgramUid
GROUP BY e.lmientityUid, sp.ProgramUid
) AS StatsForDistributorClubs
ON Program.ProgramUid = StatsForDistributorClubs.ProgramUid
INNER JOIN LmiEntity
ON LmiEntity.LmiEntityUid = StatsForDistributorClubs.LmiEntityUid
LEFT OUTER JOIN Region
ON Region.RegionId = LMIEntity.RegionId
WHERE (
[Program].LicenseeUid = LmiEntity.LmiEntityUid
OR
[LMIEntityProgram].LMIEntityUid = LmiEntity.LmiEntityUid
)
If you were grouping in your outer query, the extra criteria probably wouldn't be needed, but only your inner query is grouped. Your LEFT JOIN to a grouped inner query can still result in multiple records being returned, for that matter any of your JOINs could be the culprit.
Without seeing sample of duplication it's hard to know where the duplicates originate from, but GROUPING on the outer query would definitely remove full duplicates, or revised JOIN criteria could take care of it.
You have in result set:
SELECT LmiEntity.[DisplayName]
,StatsForDistributorClubs.*
I suppose that you dublicates comes from LMIEntityProgram.
My conjecture: LMIEntityProgram - is a bridge table with both LmiEntityId an ProgramId, but you join only by ProgramId.
If you have several LmiEntityId for single ProgramId - you must have dublicates.
And this dublicates you're filtering in WHERE:
[LMIEntityProgram].LMIEntityUid = LmiEntity.LmiEntityUid
You can do it in JOIN:
LEFT JOIN LMIEntityProgram
ON LMIEntityProgram.ProgramUid = Program.ProgramUid
AND [LMIEntityProgram].LMIEntityUid = LmiEntity.LmiEntityUid

Sum SQL statement outputs many rows when I expect only one

So I have 3 tables joined as shown:
What I want to do is query for the sum of all the holdings that fall into the criteria specified for the clients in my query. Here is what I have:
SELECT Sum(Holdings.HoldingValue) AS SumOfHoldingValue
FROM (Clients INNER JOIN Accounts
ON Clients.ClientID = Accounts.ClientID)
INNER JOIN Holdings
ON Accounts.AccountID = Holdings.AccNum
GROUP BY Holdings.HoldingDate, Clients.Active, Clients.RiskCode, Clients.NewClient, Clients.BaseCurrency, Clients.ClientID
HAVING (((Holdings.HoldingDate)=#3/31/2013#)
AND ((Clients.Active)=True)
AND ((Clients.RiskCode) In (1,2))
AND ((Clients.NewClient)=True)
AND ((Clients.BaseCurrency)='GBP')
AND ((Clients.ClientID) Not In (10022,10082,10083)));
Here's an example of what I get as the result:
SumOfHoldingValue
1056071.96
466595.6
1074459.38
371142.54
814874.42
458203.65
8308697.09
254733.94
583796.33
443897.76
203787.11
1057445.84
1058751.26
317507.43
So there are quite a few criteria for the client table but the result is a list of SumOfHoldingValue when what I want is just one number. I.e. the sum of all the holding values. Why is it not grouping them all together to form one total?
Since you're not computing any aggregates on the values in the HAVING clause, I think you just want this:
SELECT Sum(Holdings.HoldingValue) AS SumOfHoldingValue
FROM (Clients INNER JOIN Accounts
ON Clients.ClientID = Accounts.ClientID)
INNER JOIN Holdings
ON Accounts.AccountID = Holdings.AccNum
WHERE (((Holdings.HoldingDate)=#3/31/2013#)
AND ((Clients.Active)=True)
AND ((Clients.RiskCode) In (1,2))
AND ((Clients.NewClient)=True)
AND ((Clients.BaseCurrency)='GBP')
AND ((Clients.ClientID) Not In (10022,10082,10083)));
Which, with no GROUP clause will produce a single GROUP (over the entire set) and produce a single row.
If you just want totals - remove the group by. With the group by clause it gives you totals for every group separately.
If you need to filter data put the condition into Where clause instead
Your query contains a group by clause which returns each group on its own line.
You are also using a having clause. The having clause is applied after the group by. Usually, it would contain aggregation functions -- such as having count(*) > 1. In your case, it is used as a where clause.
Try rewriting the query like this:
SELECT Sum(Holdings.HoldingValue) AS SumOfHoldingValue
FROM (Clients INNER JOIN Accounts
ON Clients.ClientID = Accounts.ClientID)
INNER JOIN Holdings
ON Accounts.AccountID = Holdings.AccNum
WHERE (((Holdings.HoldingDate)=#3/31/2013#)
AND ((Clients.Active)=True)
AND ((Clients.RiskCode) In (1,2))
AND ((Clients.NewClient)=True)
AND ((Clients.BaseCurrency)='GBP')
AND ((Clients.ClientID) Not In (10022,10082,10083)));