Access SQL: subquery into IIF? - sql

I have the following query that works fine:
SELECT NomComplet, IIF(Count(FS3.Index) = 0, '0 (RAS)', Count(FS3.Index))
FROM ControleAcces INNER JOIN (
Employes LEFT JOIN (
SELECT FS1.Index, FS1.OTP, FS1.OTP, FS1.Axe, FS1.FaitSaillant, FS1.Utilisateur, FS2.DateInsertion
FROM FaitsSaillants AS FS1 INNER JOIN (
SELECT Axe, Index, Max(FaitsSaillants.DateInsertion) AS DateInsertion
FROM FaitsSaillants
WHERE DateValue(DateInsertion) > #2010-01-01#
AND DateValue(DateInsertion) < #2011-12-31#
GROUP BY Axe, Index
) AS FS2
ON (FS1.DateInsertion = FS2.DateInsertion
AND FS1.Index = FS2.Index)
WHERE FS1.Axe = 'Project' AND FS2.Axe = 'Project'
) AS FS3
ON Employes.CIP = FS3.Utilisateur
)
ON ControleAcces.Valeur = Employes.CIP
GROUP BY NomComplet
ORDER BY NomComplet
Don't bother to fully understand it, all I want it to edit my IIF condition on the first line. Actually, the condition doesn't do much, it checks how many FS3.Index the query returns and concatenate (RAS) if it's 0. However, in fact, I would like it to check if there is any row in FaitsSaillants where Axe = 'RAS'. If the Count() of this is > 0, then the condition is met.
Can I do a subquery into the IIF segment, something like SELECT COUNT(Index) FROM FaitsSaillants WHERE Axe = 'RAS' AND Utilisateur = FS1.Utilisateur? If the result is 0, then I add the RAS to my second field's results. If not, it stays Count(FS3.Index).
I tried it and while the syntax is correct, the problem is it can't check for the Utilisateur = FS1.Utilisateur condition because FS1 is in the main query. However, I must check this because this is the only way to be sure that I'm looking for the right thing: it must be the same Utilisateur whether I'm in the main query or the subquery.
EDIT:
Here is a shorter version of what I tried from the answers/comments below.
SELECT NomComplet, IIf(FS2.AxeCount > 0, "0 (RAS)", count(FS3.index))
FROM ControleAcces INNER JOIN (Employes LEFT JOIN (SELECT FS2.AxeCount, FS1.Index, FS1.OTP, FS1.OTP, FS1.Axe, FS1.FaitSaillant, FS1.Utilisateur, FS2.DateInsertion
FROM FaitsSaillants AS FS1 INNER JOIN (
SELECT Axe, Index, Max(FaitsSaillants.DateInsertion) AS DateInsertion, SUM(IIf(Axe = 'RAS', 1, 0)) As AxeCount
FROM FaitsSaillants
GROUP BY Axe, Index
) AS FS2
ON (FS1.DateInsertion = FS2.DateInsertion
AND FS1.Index = FS2.Index)
) AS FS3 ON Employes.CIP = FS3.Utilisateur) ON ControleAcces.Valeur = Employes.CIP
GROUP BY NomComplet;
I still got an error about FS2.AxeCount that isn't a part of the aggregate function (iff).
I've also tried this:
SELECT NomComplet, IIf((select count(*) from FaitsSaillants where axe='RAS' and Utilisateur=ControleAcces.Valeur) > 0, "0 (RAS)", count(FS3.index))
FROM ControleAcces INNER JOIN (Employes LEFT JOIN (SELECT FS2.AxeCount, FS1.Index, FS1.OTP, FS1.OTP, FS1.Axe, FS1.FaitSaillant, FS1.Utilisateur, FS2.DateInsertion
FROM FaitsSaillants AS FS1 INNER JOIN (
SELECT Axe, Index, Max(FaitsSaillants.DateInsertion) AS DateInsertion, SUM(IIf(Axe = 'RAS', 1, 0)) As AxeCount
FROM FaitsSaillants
GROUP BY Axe, Index
) AS FS2
ON (FS1.DateInsertion = FS2.DateInsertion
AND FS1.Index = FS2.Index)
) AS FS3 ON Employes.CIP = FS3.Utilisateur) ON ControleAcces.Valeur = Employes.CIP
GROUP BY NomComplet, ControleAccess.Valeur;

FS3.Index is NULL if there is no corresponding record, because of the LEFT JOIN. Wouldn't a test
IIf(IsNull(FS3.Index), ..., ...)
... be sufficient? I'm not sure so, since other conditions and joins are involved as well.
UPDATE (recaptulation of comments)
We can get the desired count (AxeCount) from the innermost nested SELECT (FS2):
SELECT
Axe, Index, Max(FaitsSaillants.DateInsertion) AS DateInsertion,
SUM(IIf(Axe = 'RAS', 1, 0)) As AxeCount
FROM FaitsSaillants
...
This intermediate result must be passed to the outermost SELECT by including it in the select list of the intermediate SELECT (FS3):
SELECT FS2.AxeCount, FS1.Index, ...
The outer most SELECT has a GROUP BY clause. In this case, all fields of the select list must either be included in the GROUP BY clause or must be included in an aggregate function. The GROUP BY clause groups rows by the fields listed in this very clause. This usually reduces the number of rows, as several rows similar in their group fields are condensed to form one row. This means that the values of the remaining fields of the select list (not in the group fields) must be combined together. This is what the aggregate function does. Aggregate functions are
Avg (average)
Count
First, Last
Min, Max (minimum, maximum)
StDev, StDevP (standard deviation)
Sum
Var, VarP (variance)
See SQL Aggregate Functions (Access)
Now, we can add this in the outermost select list
IIf(SUM(FS2.AxeCount) > 0, ..., ...)

I'm a little unclear on exactly what you want to check in the FaitsSaillants table; you said:
I would like it to check if there is any row in FaitsSaillants where
Axe = 'RAS'. If the Count() of this is > 0, then the condition is met.
But you also stated:
something like SELECT COUNT(Index) FROM FaitsSaillants WHERE Axe =
'RAS' AND Employes.CIP = FS3.Utilisateur
My guess is that you meant SELECT COUNT(Index) FROM FaitsSaillants WHERE Axe = 'RAS', because in the second SQL statement you're joining to two tables that aren't being referenced in the FROM clause of your subquery.
What about using DCount in your IIF statement?
IIF(DCount("Index", "FaitsSaillants", "Axe='RAS'"), '0 (RAS)', Count(FS3.Index))
This should work if you meant SELECT COUNT(Index) FROM FaitsSaillants WHERE Axe = 'RAS', but it would need to be modified if you had something else in mind.
Just a caveat though: I would try to use the "domain" functions (DCount, DLookup, etc...) sparingly, because they are slow.
By the way, I believe you can also use a subquery in your IIF statement (is it giving you an error? It seems to work for me):
IIF((SELECT COUNT(*) FROM FaitsSaillants WHERE Axe = 'RAS'), '0 (RAS)', Count(FS3.Index))
Just make sure you are putting the subquery in parenthesis.

Related

Aggregate by another table after annotate

I have next annotate
qs.annotate(
goods_with_sales=Count('goods', filter=Q(goods__history__sales__gt=0)),
)
Same goods_with_sales_percent=goods_with_sales / sum_sales * 100
I need get percent of goods_with_sales to sum of all goods__history__sales. I try it with Window but not happiness...
This is raw sql query:
SELECT
"wb_brand"."id",
"wb_brand"."name",
COUNT("wb_good"."id") FILTER (
WHERE
"wb_stockshistory"."sales" > 0
) AS "goods_with_sales"
FROM
"wb_brand"
LEFT OUTER JOIN "wb_good" ON (
"wb_brand"."id" = "wb_good"."brand_id"
)
LEFT OUTER JOIN "wb_stockshistory" ON (
"wb_good"."id" = "wb_stockshistory"."good_id"
)
GROUP BY
"wb_brand"."id"
Also I tried this with CTE, but not happiness also.
How can I solve it with Django ORM (prefer) or with SQL ?
P.S. Also must be CASE/WHEN condition for divisizon by zero if sum_sales == 0.

SQL add a column with COUNT(*) to my query

I need to add a column with the content of this query :
SELECT COUNT(*) FROM account_subscriptiongroups WHERE account_subscriptiongroups.active = true AND account_subscriptiongroups.user_id = account_user.id
to this query :
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
LEFT JOIN
account_subscriptiongroup ON account_adminaction.sub_group_id = account_subscriptiongroup.id
WHERE
account_adminaction.created_on >= '2021-04-07' AND account_adminaction.created_on <= '2021-04-13' AND
((account_adminaction.description LIKE 'Arrêt de l''abonnement%') OR (account_adminaction.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY
subscription_ended_on
I tried adding a LEFT JOIN like that:
LEFT JOIN
account_subscriptiongroup all_sg ON account_user.id = account_subscriptiongroup.user_id
with this line in my WHERE statement :
AND all_sg.active = true
and this line in my SELECT :
COUNT(all_sg.id)
but I get an error :
ERROR: column "account_user.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 2: account_user.id as user_id, account_user.email, account_us...
^
I don't understand how I could perform this action properly
To count something, you need to specify a group where that count applies.
So every column that you select (and is not used in an aggregate function, like COUNT or SUM), you need to mention in the GROUP BY clause.
Or to put it the other way around: the non-aggregate columns must apply to all rows that are contained in that particular COUNT.
So between the WHERE and ORDER BY clauses, add a GROUP BY clause:
GROUP BY account_user.id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id,
account_adminaction.description,
account_adminaction.id,
account_adminaction.created_on
If, on the other hand, you want a count from a different table, you can add a sub-select:
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on,
(SELECT COUNT(*)
FROM account_subscriptiongroups
WHERE account_subscriptiongroups.active = true
AND account_subscriptiongroups.user_id = account_user.id) AS groupcount
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
You can left join to to a derived table that does the grouping and counting:
SELECT au.id as user_id, au.email, au.first_name, au.last_name, au.phone,
asg.id as sub_group_id,
ad.description,
ad.id as admin_action_id,
ad.created_on as subscription_ended_on,
asgc.num_groups
FROM account_adminaction ad
LEFT JOIN account_user au ON au.id = ad.user_id
LEFT JOIN account_subscriptiongroups asg on ON ad.sub_group_id = asg.id
LEFT JOIN (
SELECT user_id, count(*) as num_groups
FROM account_subscriptiongroups ag
WHERE ag.active
GROUP by user_id
) asgc on asgc.user_id = au.id
WHERE ad.created_on >= '2021-04-07'
AND ad.created_on <= '2021-04-13'
AND ((ad.description LIKE 'Arrêt de l''abonnement%') OR (ad.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY subscription_ended_on
It's not entirely clear to me, what you are trying to count, but another option (most probably slower) could be to use a window function combined with a filter clause:
count(*) filter (where asg.active) over (partition by asg.user_id) as num_groups
EDIT: my answer is the same as submitted by a_horse_with_no_name
Two answers, a literal one just solving the problem you posed, and then another one questioning whether what you asked for is really what you want.
Simple answer: modify your desired query to add user_id to the Select and remove user_id from the Where clause. Now you have a table that can be left-joined to the rest of your larger query.
...
Left Join (Select user_id, count(*) as total_count
From account_subscriptiongroup
Where account_subscriptiongroups.active = true
Group By user_id) Z
On Z.user_id=account_user.id
I question whether this count is what you really want here. This counts every account_subscriptiongroup entry for all time but only the active ones. Your larger query brings back inactive as well as active records, so if your goal is to create a denominator for a percentage, you are mixing 'apples and oranges' here.
If you decide you want a total by user of the records in your query instead, then you can add one more element to your larger query without adding any more tables. Use a windowing function like this:
Select ..., Sum(Case When account_subscriptiongroup.active Then 1 else 0 End) Over (Group By account_user.id) as total count
This just counts the records within the date range and having the desired actions.

SQL Server aggregate functions issue

I tried to show staff name, purchase date and count of transactions.
Below is my code; and I get this error:
Msg 147, Level 15, State 1, Line 260
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
Code:
select
mt.StaffName,
mp.PurchaseDate,
[TotalTransaction] = count(mp.PurchaseID)
from
MsStaff mt
join
MsPurchase mp on mt.staffid = mp.staffid
where
mt.staffname like '%o%'
and count(mp.PurchaseID) > 1
You need to use GBH — GROUP BY and HAVING clauses.
SELECT mt.StaffName,
mp.PurchaseDate,
[TotalTransaction] = COUNT(mp.PurchaseID)
FROM MsStaff mt JOIN MsPurchase mp ON mt.staffid = mp.staffid
WHERE mt.staffname LIKE '%o%'
GROUP BY mt.StaffName, mp.PurchaseDate
HAVING COUNT(mp.PurchaseID) > 1
Note that COUNT(mp.PurchaseID) only counts the non-NULL values in the mp.PurchaseID column. It is slower than COUNT(*) unless the optimizer can determine that there are no NULL values in mp.PurchaseID.

How to default to a value in SQL to use for a join if conditions aren't met

I hope I can explain this clearly.
I have a four digit NAICS code lookup table that I am trying to join some records to. The root table has a six-digit NAICS code that I trim and compare to the table (emd) that I want to join to. There are three conditions that I want to join to emd on:
State from root table (x) matches emd
The year in the emd table is the max year in emd for that state
The NAICS code in table x matches the NAICS code in table emd
WHEN #3 is not met, I want the query to use '0000' as the NAICS code.
I.E. - If a record from table X with NAICS "6242" and State "AZ" exists, but table emd doesn't have a corresponding row for "AZ' with "6242", I want to use the "AZ" row with "0000".
I will paste the query below. To initially get this to kind of work, on the join to table emd, I added a case statement, basically saying "If the NAICS codes match, use it. Otherwise, use "0000". However, that is creating duplication.
When I test the query and run a query for a state that does have a NAICS code in the table (not 0000), it still returns a row for the '0000' entry for the corresponding state.
Let me know if you need more information! I can't figure this out.
select x.NAICS, x.StateProvince, x.StateProvinceCode,
emd.FinalDemandOutput,
(x.JobsMaintained * isnull(wd.AvgSalary2017, 0)) * emd.FinalDemandOutput TotalEconomicImpact,
(x.HeadcountExtrapolated * isnull(wd.AvgSalary2017, 0)) * emd.FinalDemandOutput TotalEconomicImpact_HC,
st.MinWage,
x.JobsMaintained * isnull(wd.AvgSalary2017, 0) TotalWages,
x.HeadcountExtrapolated * isnull(wd.AvgSalary2017, 0) TotalWages_HC,
from(
select c.CorporationKey, c.SupplierKey, c.SurveyPeriodKey, sp.year, sp.Quarter, sp.QuarterNum, c.Headcount, c.PYAllocatedRevenue,
c.PYAllocatedJob, c.CYAllocatedRevenue, c.extrapolationfactor, left(concat(ic.Code, '000000'), 6) NAICS, sl.Description StateProvince,
sl.StateCode StateProvinceCode,
(jm.Pct * c.Headcount) as JobsMaintained,
c.CSId_, c.CSSupplierId_, c.ImpactFactor3_Annual, ((c.Headcount/c.extrapolationfactor) * c.ImpactFactor3_Annual) HeadcountExtrapolated
from x
left join WageData wd on Substring(left(concat(x.NAICS, '000000'), 6), 1,4) = substring(wd.NAICS, 1, 4)
left join EconomicMultiplierData4DigitNAICS emd on x.StateProvince = emd.statename
and emd.year = (select max(emd2.year) from EconomicMultiplierData4DigitNAICS emd2 where emd2.statename = x.StateProvince)
and (case when Substring(left(concat(x.NAICS, '000000'), 6), 1,4) = substring(emd.NAICS, 1, 4)
then Substring(left(concat(x.NAICS, '000000'), 6), 1,4)
else '0000'
end) = substring(emd.NAICS, 1, 4)
That is incredibly hard to read, but I think you want something like this:
For the join:
left join EconomicMultiplierData4DigitNAICS emd on x.StateProvince = emd.statename
and emd.year = (select max(emd2.year) from EconomicMultiplierData4DigitNAICS emd2 where emd2.statename = x.StateProvince)
and emd.NAICS = x.NAICS
For the Select:
ISNULL(emd.NAICS,'000000')
Left join will return NULL, then you can use ISNULL to instead select your default value of '000000' in that situation.
EDIT: After comment below, this instead may be closer to the desired result:
For the join:
left join EconomicMultiplierData4DigitNAICS emd on x.StateProvince = emd.statename
and emd.year = (select max(emd2.year) from EconomicMultiplierData4DigitNAICS emd2 where emd2.statename = x.StateProvince)
and emd.NAICS = x.NAICS
left join EconomicMultiplierData4DigitNAICS emdFallback on x.StateProvince = emdFallback.statename
and emdFallback.year = (select max(emd2.year) from EconomicMultiplierData4DigitNAICS emd2 where emd2.statename = x.StateProvince)
and emdFallback.NAICS = '000000'
For the Select:
ISNULL(emd.[FieldName],emdFallback.[FieldName]) [FieldName]
This does make the select a bit more tedious, and may need a DISTINCT.
I imagine there is a better/more clever way to do what you want, though I can't think of it at the moment.

SQL - Derived tables issue

I have the following SQL query:
SELECT VehicleRegistrations.ID, VehicleRegistrations.VehicleReg,
VehicleRegistrations.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations
INNER JOIN VehicleType ON VehicleRegistrations.VehicleType = VehicleType.ID
LEFT OUTER JOIN (SELECT TOP (1) ID, VehicleRegID, DateEffective, IVehHire,
FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts
WHERE (DateEffective <= GETDATE())
ORDER BY DateEffective DESC) AS dt
ON dt.VehicleRegID = VehicleRegistrations.ID
What I basically want to do is always select the top 1 record from the 'VehicleFixedCosts' table, where the VehicleRegID matches the one in the main query. What is happening here is that it's selecting the top row before the join, so if the vehicle registration of the top row doesn't match the one we're joining to it returns nothing.
Any ideas? I really don't want to have use subselects for each of the columns I need to return
Try this:
SELECT vr.ID, vr.VehicleReg,
vr.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations vr
INNER JOIN VehicleType ON vr.VehicleType = VehicleType.ID
LEFT OUTER JOIN (
SELECT ID, VehicleRegID, DateEffective, IVehHire, FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts vfc
JOIN (
select VehicleRegID, max(DateEffective) as DateEffective
from VehicleFixedCosts
where DateEffective <= getdate()
group by VehicleRegID
) t ON vfc.VehicleRegID = t.VehicleRegID and vfc.DateEffective = t.DateEffective
) AS dt
ON dt.VehicleRegID = vr.ID
Subquery underneath dt might need some grouping but without schema (and maybe sample data) it's hard to say which column should be involved in that.