Use having to find avg - sql

I'm currently writing an SQL statement that requires me to list the workers who have a greater hours worked than the average.
Here is my current code:
Select T.TotalCandy,Sum(T.Amountofcandy) "Total Candy"
From ToralChocolate
Inner Join Ompaloompa O
On T.CandyNo = O.CandyNo and T.BoatNO = O.BoatNo
Inner Join Transport TR
On TR.Transport = T.Transport
Having Sum(T.AmountofCandy) > (Select Avg(AmountofCandy) from TotalChocolate)
Group By T.TotalCandy
Order By 1;
I run into my problem at the subquery in the having statement as I'm comparing it to the average hours worked not the average of the SUM of hours worked!
Any help would be appreciated.

Select W.WrkName,Sum(TI.HrsWorked) "Total Hours"
From Timelog TI
Inner Join Allocation A
On TI.WrkID = A.WrkID and TI.TaskID = A.TaskID
Inner Join Worker W
On TI.WrkID = W.WrkID
Group By W.WrkName
Having Sum(TI.HrsWorked) >
(Select Avg(HrsWorked)
From
(Select WrkID, SUM(HrsWorked) As HrsWorked
From Timelog
Group By WrkID)
)
Order By 1;
Have you tried something like that?

Select W.WrkName,Sum(TI.HrsWorked) "Total Hours"
From Timelog TI
Inner Join Allocation A
On TI.WrkID = A.WrkID and TI.TaskID = A.TaskID
Inner Join Worker W
On TI.WrkID = W.WrkID
Group By W.WrkName
Having Sum(TI.HrsWorked) > (Select Avg(HrsWorked) from Timelog)
Order By 1;
HAVING CLAUSE AFTER GROUP!

Related

Query optimization for multiple inner joins and sub-query

I need help regarding query optimization of the below query.
SELECT pr.todate , pr.descr, cmp.company_id
FROM employee AS emp
INNER JOIN company AS cmp ON emp.emp_comp_id = cmp.company_id
INNER JOIN profile AS pr ON emp.acca_id = pr.profile_id
INNER JOIN acondition ON as_id = as_ac_id
WHERE as_closed = 0
AND (pr.ac_act_id = 20)
AND (pr.todate = (SELECT MIN(todate) AS Expr1
FROM profile pro
INNER JOIN employee empl ON empl.acca_id = pro.profile_id
JOIN acondition ON as_id = as_ac_id
WHERE (pro.ac_act_id = 20
AND empl.emp_comp_id = cmp.company_id)
AND as_closed = 0))
Since there are duplicate joins in the main query and sub query, is there any way to remove those joins in the subquery?
Since, as you clarified, your sub-query is almost identical to your main query you might be able to use the window function RANK as a filter condition. RANK assigns the same number to ties, meaning if multiple records per company match you will get them all e.g.
SELECT todate, descr, company_id
FROM (
SELECT pr.todate, pr.descr, cmp.company_id
, RANK() OVER (PARTITION BY cmp.company_id ORDER BY pr.todate ASC) RankNumber
FROM employee AS emp
INNER JOIN company AS cmp ON emp.emp_comp_id = cmp.company_id
INNER JOIN profile AS pr ON emp.acca_id = pr.profile_id
INNER JOIN acondition ON as_id = as_ac_id
WHERE as_closed = 0 AND pr.ac_act_id = 20
) X
where RankNumber = 1;
Does this work for you?
SELECT ca.todate , pr.descr, cmp.company_id
FROM employee AS emp
INNER JOIN company AS cmp ON emp.emp_comp_id = cmp.company_id
CROSS APPLY (
SELECT TOP(1) pr.todate
FROM profile pr
INNER JOIN acondition ON as_id = as_ac_id
WHERE emp.acca_id = pr.profile_id AND (pr.ac_act_id = 20) AND as_closed = 0
ORDER BY pr.todate ASC
) AS ca

Duplicate records while retrieving using inner join in SQL Server

I am facing issues as I get records from SQL. The output of records was not right and numbers returned are much bigger than what I expected it to be.
Here is my query:
var query=
$#"select CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime as DATE) as 'Date',
CustomerTaskDetails.CustomerTaskTypeId, COUNT(*) as 'Count' from CustomerAssignedTaskExec
inner join CustomerAssignedTask on CustomerAssignedTask.Id = CustomerAssignedTaskExec.CustomerAssignedTaskId
inner join CustomerTaskDetails on CustomerTaskDetails.Id = CustomerAssignedTaskExec.CustomerTaskDetailsId
inner join CustomerAssignedTaskItemStatus on CustomerAssignedTaskItemStatus.Id =
CustomerAssignedTaskExec.AssignedTaskItemStatusId
inner join customers on customers.CustomerId = CustomerAssignedTask.CustomerId
where Customers.StoreId = #storeId and CustomerAssignedTask.TaskStatusId = #runningTaskStatusId
group by CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime AS DATE),
CustomerTaskTypeId, CustomerAssignedTaskItemStatus.Id";
This is my expected result :
Count CustomerTaskTypeId ScheduledDispatchedDateTime
852 7 2019-08-20
but what I get is :
Count CustomerTaskTypeId ScheduledDispatchedDateTime
4694 7 2019-08-20
What are the reasons why this is happening? Thank you for the help.
You may try this. You may use 1 instead of *, it will count only the distinct rows created by your group by clause.
var query= $#"select CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime as DATE) as 'Date',
CustomerTaskDetails.CustomerTaskTypeId, COUNT(1) as 'Count'
from CustomerAssignedTaskExec
inner join CustomerAssignedTask on CustomerAssignedTask.Id = CustomerAssignedTaskExec.CustomerAssignedTaskId
inner join CustomerTaskDetails on CustomerTaskDetails.Id = CustomerAssignedTaskExec.CustomerTaskDetailsId
inner join CustomerAssignedTaskItemStatus on CustomerAssignedTaskItemStatus.Id = CustomerAssignedTaskExec.AssignedTaskItemStatusId
inner join customers on customers.CustomerId = CustomerAssignedTask.CustomerId
where Customers.StoreId = #storeId and CustomerAssignedTask.TaskStatusId = #runningTaskStatusId
group by CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime AS DATE), CustomerTaskTypeId, CustomerAssignedTaskItemStatus.Id";
try remove your CustomerAssignedTaskItemStatus.Id and CustomerTaskTypeId in your grouping and give me the results.
select CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime as DATE) as 'Date',
COUNT(1) as 'Count'
from CustomerAssignedTaskExec
inner join CustomerAssignedTask on CustomerAssignedTask.Id = CustomerAssignedTaskExec.CustomerAssignedTaskId
inner join CustomerTaskDetails on CustomerTaskDetails.Id = CustomerAssignedTaskExec.CustomerTaskDetailsId
inner join CustomerAssignedTaskItemStatus on CustomerAssignedTaskItemStatus.Id = CustomerAssignedTaskExec.AssignedTaskItemStatusId
inner join customers on customers.CustomerId = CustomerAssignedTask.CustomerId
where Customers.StoreId = #storeId and CustomerAssignedTask.TaskStatusId = #runningTaskStatusId
group by CAST(CustomerAssignedTaskExec.ScheduledDispatchedDateTime AS DATE)
Try to use count(distinct <value>) instead of count(*) maybe that could help as there might be other columns associated leading to redundant data

SQL joins not giving me correct totals

I'm trying to get a report for figures from multiple transaction tables. Each table has a foreign key that is a lookup for the day it was taken on, and what location it was taken at called Site_Lookup_Id. It's giving me figures that are much larger than they should be.
#Site_Lookup_Ids dbo.Id_List READONLY
SELECT SL.Site_Lookup_Id, D.[Start],SUM(I.Amount) AS Income,
SUM(P.Amount) AS Payouts,SUM(DP.Amount) AS Deposit
FROM CashUp_Site_Lookup SL
INNER JOIN #Site_Lookup_Ids IDs ON Ids.Id = SL.Site_Lookup_Id
INNER JOIN CashUp_Day D ON SL.Day_Id = D.Day_Id
LEFT JOIN CashUp_Deposit DP ON DP.Redeemed_Site_Lookup_Id = SL.Site_Lookup_Id
AND DP.No_Show != 1
LEFT JOIN CashUp_Income I ON I.Site_Lookup_Id = SL.Site_Lookup_Id
LEFT JOIN CashUp_Payout P ON P.Site_Lookup_Id = SL.Site_Lookup_Id
GROUP BY SL.Site_Lookup_Id, D.[Start]
Not all sums will have a value as some days no transactions for a given table will be taken on the day - In this the value should be zero.
The issue is that running this gives me crazy high values - £7500 for income against one day, when if I do a simple check it's £40 for that day like so.
SELECT SUM(Amount) FROM Cashup_Income WHERE Site_Lookup_Id IN (values...)
Maybe something like...
It really depends on the relationships and when you want the values to be summed.
SELECT SL.Site_Lookup_Id
, D.[Start]
, SUM(I.Amount) over (partition by Key of I table) AS Income
, SUM(P.Amount) over (partition by Key of P table) AS Payouts
, SUM(DP.Amount) over (partition by Key of DP Table) AS Deposit
FROM CashUp_Site_Lookup SL
INNER JOIN #Site_Lookup_Ids IDs ON Ids.Id = SL.Site_Lookup_Id
INNER JOIN CashUp_Day D ON SL.Day_Id = D.Day_Id
LEFT JOIN CashUp_Deposit DP ON DP.Redeemed_Site_Lookup_Id = SL.Site_Lookup_Id
AND DP.No_Show != 1
LEFT JOIN CashUp_Income I ON I.Site_Lookup_Id = SL.Site_Lookup_Id
LEFT JOIN CashUp_Payout P ON P.Site_Lookup_Id = SL.Site_Lookup_Id
GROUP BY SL.Site_Lookup_Id, D.[Start]
The problem stems from the fact that your tables are 1:M Causing values to repeat. These repeated values are then getting added to your sum. The joins cause this issue. So I think you can sum using a partition to eliminate the duplicates or:
Use derived tables or a CTE and sum the values BEFORE you join.
Using CTE's (Common Table Expressions)
WITH DP AS (SELECT sum(Amount) As Deposit
, Redeemed_Site_Lookup_ID
FROM CashUp_Deposit
WHERE No_Show !=1
GROUP BY Redeemed_Site_Lookup_ID),
I AS (SELECT sum(Amount) as Income
, Site_Lookup_Id
FROM CashUp_Income
GROUP BY Site_Lookup_Id),
P AS (SELECT sum(Amount) as Payouts
, Site_Lookup_Id
FROM CashUp_Payout
GROUP BY Site_Lookup_Id)
SELECT SL.Site_Lookup_Id
, D.[Start]
, Income
, Payouts
, Deposit
FROM CashUp_Site_Lookup SL
INNER JOIN #Site_Lookup_Ids IDs
ON Ids.Id = SL.Site_Lookup_Id
INNER JOIN CashUp_Day D
ON SL.Day_Id = D.Day_Id
LEFT JOIN DP
ON DP.Redeemed_Site_Lookup_Id = SL.Site_Lookup_Id
LEFT JOIN I
ON I.Site_Lookup_Id = SL.Site_Lookup_Id
LEFT JOIN P
ON P.Site_Lookup_Id = SL.Site_Lookup_Id
Presumably, you are generating a Cartesian product with your joins. Because you have no filtering, do the aggregation before the joins:
LEFT JOIN
(SELECT DP.Redeemed_Site_Lookup_Id, SUM(DP.Amount) AS Deposit
FROM CashUp_Deposit DP
WHERE DP.No_Show != 1
GROUP BY DP.Redeemed_Site_Lookup_Id
) DP
ON DP.Redeemed_Site_Lookup_Id = SL.Site_Lookup_Id LEFT JOIN
(SELECT I.Site_Lookup_Id, SUM(I.Amount) AS Income
FROM CashUp_Income I
GROUP BY I.Site_Lookup_Id
) I
ON I.Site_Lookup_Id = SL.Site_Lookup_Id LEFT JOIN
(SELECT P.Site_Lookup_Id, SUM(P.Amount) AS Payout
FROM CashUp_Payout P
GROUP BY I.Site_Lookup_Id
) P
ON P.Site_Lookup_Id = SL.Site_Lookup_Id
Then adjust the rest of your query to remove the GROUP BY and SUM()s.

Working Out a Percentage of a Count Total

Morning All
Ok so I know how to get the percentage using like.
ROUND(CAST(SUM(Col1)AS FLOAT) *100.0 / COUNT(*),2)
But here is where I am Stuck.
DECLARE #pYWK INT
SET #pYWK = (SELECT YWK FROM CHDS_Management.dbo.Calendar
WHERE DT = (SELECT DATEADD(WEEK,-3, CONVERT(DATE,GETDATE()))));
SELECT
ORD.ProductWhsLocation AS 'Location',
COUNT(ORD.ProductWhsLocation) AS 'Picked'
FROM CHDS_Common.dbo.OMOrder AS ORD
INNER JOIN CHDS_Management.dbo.PickZoneControl AS PZC ON
LEFT(ORD.ProductWhsLocation,3) = PZC.PickZone
INNER JOIN CHDS_Management.dbo.Calendar AS CAL ON CAL.DT =
ORD.EarliestPickDate
WHERE CAL.YWK = #pYWK AND ORD.PickedQty <> 0
GROUP BY ORD.ProductWhsLocation
What I am trying to work out is the % of what was picked from each loaction from the total picked.
Its Monday Morning here so I am sorry if its somthing stupid I am missing.
Thank you for any help on this one.
This is a tricky question, because to obtain the percentages of each group (requiring one aggregation) we need to normalize those counts with the total count of the entire query (another aggregation). One approach is to place the base join query into a CTE. Then, query that CTE and also get the total count via a subquery on the same CTE.
WITH cte AS (
SELECT
ORD.ProductWhsLocation AS 'Location'
FROM CHDS_Common.dbo.OMOrder AS ORD
INNER JOIN CHDS_Management.dbo.PickZoneControl AS PZC
ON LEFT(ORD.ProductWhsLocation,3) = PZC.PickZone
INNER JOIN CHDS_Management.dbo.Calendar AS CAL
ON CAL.DT = ORD.EarliestPickDate
WHERE CAL.YWK = #pYWK AND ORD.PickedQty <> 0
)
SELECT
Location,
COUNT(Location),
ROUND(100.0 * COUNT(Location) / (SELECT COUNT(*) FROM cte), 2) AS pct
FROM cte
GROUP BY
Location;
Use Count(*)Over() to get the total number of location, use it to divide the Picked to get the percentage of picked in each location
SELECT ORD.ProductWhsLocation AS 'Location',
Count(ORD.ProductWhsLocation) AS 'Picked',
( ( Count(ORD.ProductWhsLocation) * 1.0 ) / Sum(Count(ORD.ProductWhsLocation)) OVER() ) * 100.0
FROM CHDS_Common.dbo.OMOrder AS ORD
INNER JOIN CHDS_Management.dbo.PickZoneControl AS PZC
ON LEFT(ORD.ProductWhsLocation, 3) = PZC.PickZone
INNER JOIN CHDS_Management.dbo.Calendar AS CAL
ON CAL.DT = ORD.EarliestPickDate
WHERE CAL.YWK = #pYWK
AND ORD.PickedQty <> 0
GROUP BY ORD.ProductWhsLocation

Return rows where a customer bought things on same day

Can someone help me with the rest of my Query.
This query gives me Customer, AdressNr, Date, Employee, Article, ActivityNr
from all the sales in my Company.
SELECT ad.Name + ' ' + ad.Vorname AS Customer,
pa.Kunde AS CustomerNr,
CONVERT(VARCHAR(10),p.datum,126) AS Date,
(SELECT a.name + ' ' + a.Vorname AS Name FROM PRO_Mitarbeiter m LEFT JOIN ADR_Adressen a ON a.AdressNrADR=m.AdressNrADR WHERE m.MitNrPRO = l.MitNrPRO) as Employee,
p.Artikel_1 AS Article,
l.AufgabenNrCRM AS OrderNr
FROM ZUS_Therapie_Positionen p
INNER JOIN CRM_AufgabenLink l ON l.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN CRM_Aufgaben ab ON ab.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN PRO_Auftraege pa ON pa.AuftragNrPRO = ab.AuftragNrPRO
INNER JOIN ADR_Adressen ad ON ad.AdressNrADR = pa.Kunde
INNER JOIN ADR_GruppenLink gl ON gl.AdressNrADR = ad.AdressNrADR
INNER JOIN ADR_Gruppen g ON g.GruppeADR = gl.GruppeADR
WHERE l.MitNrPRO != 0
GROUP BY l.AufgabenNrCRM,ad.Name,ad.Vorname,pa.Kunde,p.datum,p.Artikel_1,l.MitNrPRO
ORDER BY pa.Kunde,p.datum,l.AufgabenNrCRM
My goal is to filter this so i get only rows back where the customer has bought more then 1 Thing on the same day. It doesn't matter if a customer bought the same Article twice on the same day. I want too see this also.
It's to complicated to write some SQL Fiddle for you but in this Picture you can see what my goal is. I want to take away all rows with an X on the left side and thoose with a Circle i want to Keep.
As I don't speak German, I won't target this specifically to your SQL. But see the following quasi-code for a similar example that you should be able to apply to your own script.
SELECT C.CustomerName, O.OrderDate, O.OrderNumber
FROM CUSTOMER C
JOIN ORDERS O ON O.Customer_ID = C.Customer_ID
JOIN
(SELECT Customer_ID, OrderDate
FROM ORDERS
GROUP BY Customer_ID, OrderDate
HAVING COUNT(*) > 1) SRC
ON SRC.Customer_ID = O.Customer_ID AND SRC.OrderDate = O.OrderDate
In the script above, the last query (a subquery) would only return results where a customer had more than one order in a given day. By joining that to your main query, you would effectively produce the result asked in the OP.
Edit 1:
Regarding your comment below, I really recommend just going over your datamodel, trying to understand what's happening here, and fixing it on your own. But there is an easy - albeit hardly optimal solution to this by just using your own script above. Note, while this is not disastrous performance-wise, it's obviously not the cleanest, most effective method either. But it should work:
;WITH CTE AS (SELECT ad.Name + ' ' + ad.Vorname AS Customer,
pa.Kunde AS CustomerNr,
CONVERT(VARCHAR(10),p.datum,126) AS [Date],
(SELECT a.name + ' ' + a.Vorname AS Name FROM PRO_Mitarbeiter m LEFT JOIN ADR_Adressen a ON a.AdressNrADR=m.AdressNrADR WHERE m.MitNrPRO = l.MitNrPRO) as Employee,
p.Artikel_1 AS Article,
l.AufgabenNrCRM AS OrderNr
FROM ZUS_Therapie_Positionen p
INNER JOIN CRM_AufgabenLink l ON l.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN CRM_Aufgaben ab ON ab.AufgabenNrCRM = p.Id_Aktivitaet
INNER JOIN PRO_Auftraege pa ON pa.AuftragNrPRO = ab.AuftragNrPRO
INNER JOIN ADR_Adressen ad ON ad.AdressNrADR = pa.Kunde
INNER JOIN ADR_GruppenLink gl ON gl.AdressNrADR = ad.AdressNrADR
INNER JOIN ADR_Gruppen g ON g.GruppeADR = gl.GruppeADR
WHERE l.MitNrPRO != 0
GROUP BY l.AufgabenNrCRM,ad.Name,ad.Vorname,pa.Kunde,p.datum,p.Artikel_1,l.MitNrPRO
ORDER BY pa.Kunde,p.datum,l.AufgabenNrCRM)
SELECT C.*
FROM CTE C
JOIN (Select CustomerNr, [Date]
FROM CTE B
GROUP BY CustomerNr, [Date]
HAVING COUNT(*) > 1) SRC
ON SRC.CustomerNr = C.CustomerNr AND SRC.[Date] = C.[Date]
This should work directly. But as I said, this is an ugly workaround where we're basically all but fetching the whole set twice, as opposed to just limiting the sub query to just the bare minimum of necessary tables. Your choice. :)
Tried that also and it didnt work. I also made a new query trying to Keep it so simple as possible and it doesnt work either. It still give me Single values back..
SELECT p.Datum,a.AufgabenNrCRM,auf.Kunde FROM CRM_Aufgaben a
LEFT JOIN ZUS_Therapie_Positionen p ON p.Id_Aktivitaet = a.AufgabenNrCRM
LEFT JOIN PRO_Auftraege auf ON auf.AuftragNrPRO = a.AuftragNrPRO
LEFT JOIN
(SELECT pa.Datum,au.Kunde FROM CRM_Aufgaben aa
LEFT JOIN ZUS_Therapie_Positionen pa ON pa.Id_Aktivitaet = aa.AufgabenNrCRM
LEFT JOIN PRO_Auftraege au ON au.AuftragNrPRO = aa.AuftragNrPRO
GROUP BY pa.Datum,au.Kunde
HAVING COUNT(*) > 1) SRC
ON SRC.Kunde = auf.Kunde
WHERE p.datum IS NOT NULL
GROUP BY p.Datum,a.AufgabenNrCRM,auf.Kunde
ORDER BY auf.Kunde,p.Datum