So I have a "Sample", "Test" and "Result" table linked to each other from a database and I am trying to pull information using MS Query. Each sample has one test and each test could have roughly 20 results entered by different people attached to it.
What I want is for the sample to only display if the person's name I enter is NOT involved with entering ANY of the results.
SELECT SAMPLE.SAMPLE_NUMBER, SAMPLE.TEXT_ID, SAMPLE.STATUS, SAMPLE.DATE_COMPLETED, SAMPLE.LOCATION, TEST.ANALYSIS, RESULT.ENTERED_BY
FROM DATABASE.RESULT RESULT, DATABASE.SAMPLE SAMPLE, DATABASE.TEST TEST
WHERE TEST.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER AND RESULT.TEST_NUMBER = TEST.TEST_NUMBER
AND ((TEST.ANALYSIS='ID_META' Or TEST.ANALYSIS='ID_RIBO' Or TEST.ANALYSIS='ID_BACTERIA' Or TEST.ANALYSIS='ID_MOULD')
AND (SAMPLE.STATUS='C') AND (SAMPLE.DATE_COMPLETED Is Not Null)
AND (RESULT.ENTERED_ON Between [Start Date] And [End Date])
AND (RESULT.ENTERED_BY<>[Enter Name]))
ORDER BY SAMPLE.DATE_COMPLETED
This is the code that I have so far but the problem is if the person has entered one of 10 results then that same sample will display 9 times and just not display for the one time he didn't enter a result. Is there a way that I can say if he entered ANY result at all then the sample won't appear at all.
When you find yourself wanting to limit the rows by a condition that involves multiple rows (like "I want every test where none of the multiple results were entered by this person"), you can't do it with simple conditions like RESULT.ENTERED_BY<>[Enter Name]. That only looks at the value of each single row you're currently working with. You either need a correlated subquery or an analytical function. I think subqueries are easier to start out with, and in your case a NOT EXISTS clause makes intuitive sense.
(I'm also going to rewrite this with standard modern JOIN syntax)
select SAMPLE.SAMPLE_NUMBER, SAMPLE.TEXT_ID, SAMPLE.STATUS, SAMPLE.DATE_COMPLETED, SAMPLE.LOCATION, TEST.ANALYSIS, RESULT.ENTERED_BY
from DATABASE.SAMPLE SAMPLE
join DATABASE.TEST TEST
on TEST.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER
join DATABASE.RESULT RESULT
on RESULT.TEST_NUMBER = TEST.TEST_NUMBER
where (TEST.ANALYSIS in ('ID_META','ID_RIBO','ID_BACTERIA','ID_MOULD')
and (SAMPLE.STATUS='C') and (SAMPLE.DATE_COMPLETED Is Not Null)
and (RESULT.ENTERED_ON Between [Start Date] And [End Date])
-- up until here, it's the same as your query
and NOT EXISTS (select 1
from DATABASE.TEST T2
join DATABASE.RESULT R2
on R2.TEST_NUMBER = T2.TEST_NUMBER
where T2.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER
and T2.ANALYSIS in ('ID_META','ID_RIBO','ID_BACTERIA','ID_MOULD')
and R2.ENTERED_ON Between [Start Date] And [End Date]
and R2.ENTERED_BY = [Enter Name])
ORDER BY SAMPLE.DATE_COMPLETED;
So here we're saying to return all the samples where there "doesn't exist" any test with any result which was entered by the specific person. (I'm not sure whether you'll want the date filter on both the main query and the subquery - both RESULT and R2 - you'll have to figure that out based on your data.)
Edit: if you want one row per sample, just remove the TEST/RESULT joins from the main query:
select SAMPLE.SAMPLE_NUMBER, SAMPLE.TEXT_ID, SAMPLE.STATUS, SAMPLE.DATE_COMPLETED, SAMPLE.LOCATION
from DATABASE.SAMPLE SAMPLE
where (SAMPLE.STATUS='C') and (SAMPLE.DATE_COMPLETED Is Not Null)
and NOT EXISTS (select 1
from DATABASE.TEST T2
join DATABASE.RESULT R2
on R2.TEST_NUMBER = T2.TEST_NUMBER
where T2.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER
and T2.ANALYSIS in ('ID_META','ID_RIBO','ID_BACTERIA','ID_MOULD')
and R2.ENTERED_ON Between [Start Date] And [End Date]
and R2.ENTERED_BY = [Enter Name])
ORDER BY SAMPLE.DATE_COMPLETED;
Related
So I have a "Sample", "Test" and "Result" table linked to each other from a database and I am trying to pull information using MS Query. Each sample has one test and each test could have roughly 20 results entered by different people attached to it.
What I want is for the sample to only display if the person's name I enter is NOT involved with entering ANY of the results.
SELECT SAMPLE.SAMPLE_NUMBER, SAMPLE.TEXT_ID, SAMPLE.STATUS, SAMPLE.DATE_COMPLETED, SAMPLE.LOCATION, TEST.ANALYSIS, RESULT.ENTERED_BY
FROM DATABASE.RESULT RESULT, DATABASE.SAMPLE SAMPLE, DATABASE.TEST TEST
WHERE TEST.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER AND RESULT.TEST_NUMBER = TEST.TEST_NUMBER
AND ((TEST.ANALYSIS='ID_META' Or TEST.ANALYSIS='ID_RIBO' Or TEST.ANALYSIS='ID_BACTERIA' Or TEST.ANALYSIS='ID_MOULD')
AND (SAMPLE.STATUS='C') AND (SAMPLE.DATE_COMPLETED Is Not Null)
AND (RESULT.ENTERED_ON Between [Start Date] And [End Date])
AND (RESULT.ENTERED_BY<>[Enter Name]))
ORDER BY SAMPLE.DATE_COMPLETED
This is the code that I have so far but the problem is if Alan has entered one of 10 results then that same sample will display 9 times and just not display for the one time he didn't enter a result. Is there a way that I can say if he entered ANY result at all then the sample won't appear at all.
Edit - To include additional clauses incorporated into the query. Query pulled directly from Excel connection window (from MS Query).
This answers the original version of the question.
You seem to be describing NOT EXISTS:
SELECT s.SAMPLE_NUMBER
FROM DATABASE.SAMPLE s
WHERE NOT EXISTS (SELECT 1
FROM DATABASE.RESULT r JOIN
DATABASE.TEST t
ON r.TEST_NUMBER = t.TEST_NUMBER
WHERE t.SAMPLE_NUMBER = s.SAMPLE_NUMBER AND
R.ENTERED_ON >= DATE '2020-02-01' AND
R.ENTERED_ON >= DATE '2020-02-03' AND
R.ENTERED_BY = 'ALAN'
) AND
S..DATE_COMPLETED Is Not Null ;
I have left in your additional conditions, even though they are not mentioned in the question.
Notes:
NEVER use commas in the FROM clause.
Always use proper, explicit, standard, readable JOIN syntax.
Use proper DATE constants in Oracle.
Don't use BETWEEN with DATE particularly in Oracle. The DATE datatype has a time component, which might not be visible when you look at the data.
Please, try with below query:
SELECT DISTINCT(SAMPLE.SAMPLE_NUMBER) as SAMPLE_NUMBER
FROM DATABASE.SAMPLE SAMPLE
LEFT OUTER JOIN DATABASE.TEST TEST ON TEST.SAMPLE_NUMBER = SAMPLE.SAMPLE_NUMBER
LEFT OUTER JOIN DATABASE.RESULT RESULT ON RESULT.TEST_NUMBER = TEST.TEST_NUMBER
WHERE ((SAMPLE.DATE_COMPLETED Is Not Null)
AND (RESULT.ENTERED_ON Between CAST('01-FEB-2020' as DATE) And CAST('02-FEB-2020' as DATE))
AND (RESULT.ENTERED_BY <> 'ALAN'))
I have a table called ArchiveActivityDetails which shows the history of a Customer Repair Order. 1 Repair Order will have many visits (ActivityID) with a Technician allocated depending on who is available for that planned visit.
The system automatically allocates the time that is required for a job but sometimes a job requires longer so we manually ammend jobs.
My initial query from the customer was to pull the manually ammended jobs (ie: jobs where PlannedDuration >=60 minutes) and shows the Technician linked to that manually ammended job.
This report works fine.
My most recent request from the customer is to now ADD a column showing WHO WAS THE PREVIOUS TECHNICIAN linked that the Repair Order.
My collegues suggested I do a Cross Apply going back to the ArchiveActivityDetails table and then show "Previous Tech" but I have not used Cross Apply before and I am struggling with the syntax and unable to get the results I want. In my Cross Apply I used LAG to work out the 'PrevTech' but when pulling it into my main report, I get NULL. So I assume I am not doing the Cross Apply correctly.
DECLARE #DateFrom as DATE = '2019-05-20'
DECLARE #DATETO AS DATE = '2019-07-23'
----------------------------------------------------------------------------------
SELECT
AAD.Date
,ASM.ASM
,A.ASM as PrevASM
,ASM.KDGID2
,R.ResourceName
,R.ID_ResourceID
,A.ServiceOrderNumber
,CONCAT(EN.TECHVORNAME, ' ' , EN.TECHNACHNAME) as TechName
,A.PrevTech
,EN.TechnicianID
,AAD.ID_ActivityID
,SO.ServiceOrderNumber
,AAD.VisitNumber
,AAD.PlannedDuration
,AAD.ActualDuration
,AAD.PlannedDuration-AAD.ActualDuration as DIFF
,DR.Original_Duration
FROM
[Easy].[ASMTrans] AS ASM
INNER JOIN
[FS_OTBE].[EngPayrollNumbers] AS EN
ON ASM.KDGID2 = EN.KDGID2
INNER JOIN
[OFSA].[ResourceID] AS R
ON EN.TechnicianID = Try_cast(R.ResourceName as int)
INNER JOIN
[OFSDA].[ArchiveActivityDetails] as [AAD]
ON R.[ID_ResourceID] = AAD.ID_ResourceID
INNER JOIN
[OFSA].[ServiceOrderNumber] SO
ON SO.ID_ServiceOrderNumber = AAD.ID_ServiceOrderNumber
LEFT JOIN
[OFSE].[DurationRevision] DR
on DR.ID_ActivityID = AAD.ID_ActivityID
CROSS APPLY
(
SELECT
AD.Date
,AD.ID_CountryCode
,AD.ID_Status
,Activity_TypeID
,AD.ID_ActivityID
,AD.ID_ResourceID
,SO.ServiceOrderNumber
,ASM.ASM
,LAG(EN.TECHVORNAME+ ' '+EN.TECHNACHNAME) OVER (ORDER BY SO.ServiceOrderNumber,AD.ID_ActivityID) as PrevTech
,AD.VisitNumber
,AD.ID_ServiceOrderNumber
,AD.PlannedDuration
,AD.ActualDuration
,ROW_NUMBER() OVER (PARTITION BY AD.ID_ServiceOrderNumber Order by AD.ID_ActivityID,AD.Date) as ROWNUM
FROM
[Easy].[ASMTrans] AS ASM
INNER JOIN
[FS_OTBE].[EngPayrollNumbers] AS EN
ON ASM.KDGID2 = EN.KDGID2
INNER JOIN
[OFSA].[ResourceID] AS R
ON EN.TechnicianID = Try_cast(R.ResourceName as int)
INNER JOIN
[OFSDA].[ArchiveActivityDetails] as [AD]
ON R.[ID_ResourceID] = AD.ID_ResourceID
INNER JOIN
[OFSA].[ServiceOrderNumber] SO
ON SO.ID_ServiceOrderNumber = AD.ID_ServiceOrderNumber
WHERE
AAD.ID_ActivityID = AD.ID_ActivityID
AND
AD.ID_CountryCode = AAD.ID_CountryCode
AND AD.ID_Status = AAD.ID_Status
AND AD.ID_ResourceID = AAD.ID_ResourceID
AND AD.Activity_TypeID = AAD.Activity_TypeID
AND AD.ID_ServiceOrderNumber = AAD.ID_ServiceOrderNumber
AND AD.Date >= '2019-05-01'
) as A
WHERE
ASM.KDGID2
IN (50008323,50008326,50008329,50008332,50008335,50008338,50008341,50008344,50008347,50008350,50008353,50008356,50008359,50008362,50008365)
AND AAD.ID_Status = 1
AND AAD.ID_CountryCode = 7
AND AAD.Activity_TypeID=91
AND
(
AAD.[Date] BETWEEN IIF(#DateFrom < '20190520','20190520',#DateFrom) AND IIF(#DateTo < '20190520','20190520',#DateTo))
AND AAD.ActualDuration > 11
AND
(
(DR.Original_Duration >= 60)
OR
(DR.ID_ActivityID IS NULL AND AAD.PlannedDuration >= 60))
I expect to see the previous Tech and previous Area Sales Manager for the job that was Manually Ammended.
Business Reason: Managers want to see who initially requested for the job to be Manually Ammended. The time requested is being over estimated which is wasting time. To plan better they need to see who requests extra time at a job and try to reduce the time.
I will attach the ArchiveActivityDetail table showing the history of a Repair Order as well as expected results.
Your query results in the cross apply will appear as a table in your query, so you can use top(1) and order by descending to get the first row ordered by what you want (it looks like ActivityId? maybe VisitNumber?).
Simplifying to get at the root of the issue, say you have just one table with ServiceOrderNumber, ID_Activity, ASM, and TECH. To get the previous row for activity 2414073 you would do this:
select top(1) ASM, TECH
from OFSDA.ArchiveActivityDetails as AD
where ID_ServiceOrderNumber = 2370634229 -- same ServiceOrderNumber
and ID_Activity < 2414073 -- previous activities
order by ID_Activity desc -- highest activity less than 2414073
Instead of cross apply, you probably want to use outer apply. This is the same but you will get a row in your main query for the first activity, it will just have nulls for values in your apply. If you want the first row omitted from your results because it doesn't have a previous row, go ahead and use cross apply.
You can just put the above query into the parenthesis in outer apply() and add an alias (Previous). You link to the values for the current row in your main query, use top(1) to get the first row only, and order by ID_Activity descending to get the row with the highest ID_Activity.
select ASM, TECH,
PreviousASM, PreviousTECH
from OFSDA.ArchiveActivityDetails as AD
outer apply (
select top(1) ADInner.ASM as PreviousASM, ADInner.TECH as PreviousTECH
from OFSDA.ArchiveActivityDetails as ADInner
where ADInner.ID_ServiceOrderNumber = AD.ID_ServiceOrderNumber
and ADInner.ID_Activity < AD.ID_Activity
order by ADInnerID_Activity desc
) Previous
where ID_ServiceOrderNumber = 2370634229
I have two queries I want to join so I can get the percentages from each department of completed work orders but not sure how to go about it. I know I want a join and a crosstab query so I can display the results in a report.
The first query calculates the numerator,
SELECT
Count(MaximoReport.WorkOrder) AS CountOfWorkOrder,
MaximoReport.[Assigned Owner Group]
FROM MaximoReport
WHERE (
((MaximoReport.WorkType) In ("PMINS","PMOR","PMPDM","PMREG","PMRT"))
AND ((MaximoReport.Status) Like "*COMP")
AND ((MaximoReport.[Target Start])>=DateAdd("h",-1,[Enter the start date])
AND (MaximoReport.[Target Start])<DateAdd("h",23,[Enter the end date]))
AND ((MaximoReport.ActualLaborHours)<>"00:00")
AND ((MaximoReport.ActualStartDate)>=DateAdd("h",-11.8,[Enter the start date])
AND (MaximoReport.ActualStartDate)<DateAdd("h",23,[Enter the end date]))
)
GROUP BY MaximoReport.[Assigned Owner Group];
While the second query calculates the denominator:
SELECT
Count(MaximoReport.WorkOrder) AS CountOfWorkOrder,
MaximoReport.[Assigned Owner Group]
FROM MaximoReport
WHERE (
((MaximoReport.WorkType) In ("PMINS","PMOR","PMPDM","PMREG","PMRT"))
AND ((MaximoReport.Status)<>"CAN")
AND ((MaximoReport.[Target Start])>=DateAdd("h",-11.8,[Enter the start date])
AND (MaximoReport.[Target Start])<DateAdd("h",23,[Enter the end date])))
GROUP BY MaximoReport.[Assigned Owner Group];
Please advise how I can join the two queries to get the percentages of the departments and then do a crosstab query.
If there is a better way of doing this please also let me know.
I've have an access query that I am trying to run and can't seem to get it right.
I'm already calculating a sum of hours grouped by the name and a description column. I would like the total column basically to give a sum of the hours by the name and the description column
SELECT dbo_t_SAP_AttCodes.Description, Sum(dbo_v_MES_TcActivities.Hours) AS SumOfHours,
Left([supervisor1email],InStr([supervisor1email],".")-1) AS [Supervisor First Name],
Mid([supervisor1email],InStr([supervisor1email],".")+1,
InStr([supervisor1email],"#")-InStr([supervisor1email],".")-1)
AS [Supervisor Last Name], (SELECT sum([hours]) FROM [dbo_v_MES_TcActivities]
WHERE [costctr]="106330" AND [clockin] Between DateAdd("d",-((Weekday(Date())-1)),
Date()) And Date()) AS Total_Hours
FROM dbo_v_MES_TcActivities LEFT JOIN dbo_t_SAP_AttCodes
ON dbo_v_MES_TcActivities.AttCode = dbo_t_SAP_AttCodes.Code
WHERE (((dbo_v_MES_TcActivities.AttCode) Not Like "MEAL")
AND ((dbo_v_MES_TcActivities.CostCtr) Like "106330")
AND ((dbo_v_MES_TcActivities.ClockIn) Between DateAdd("d",-((Weekday(Date())-1)),
Date()) And Date()))
GROUP BY dbo_t_SAP_AttCodes.Description, Left([supervisor1email],
InStr([supervisor1email],".")-1),
Mid([supervisor1email],InStr([supervisor1email],".")+1,
InStr([supervisor1email],"#")-InStr([supervisor1email],".")-1),
dbo_v_MES_TcActivities.Supervisor1Email
ORDER BY Mid([supervisor1email],InStr([supervisor1email],".")+1,
InStr([supervisor1email],"#")-InStr([supervisor1email],".")-1);
EDIT:
Here is what the current output looks like:
before
Here is what I would like to see:
After
Consider turning your subquery to a correlated subquery where you match the nested SELECT statement to corresponding row's name in main query without Description (only Name) grouping. The below comes out long due to Supervisor first and last name calculated expressions.
Please note: below uses table aliases with sub and main (adjust all field references accordingly):
Subquery
SELECT --...same fields/expressions as original w/ 'main' alias...,
(SELECT SUM(sub.[hours]) FROM [dbo_v_MES_TcActivities] sub
WHERE sub.[costctr]="106330"
AND sub.AttCode Not Like "MEAL"
AND sub.[clockin] Between DateAdd("d",-((Weekday(Date())-1)), Date()) And Date()
AND Left(sub.[supervisor1email], InStr(sub.[supervisor1email],".")-1) =
Left(main.[supervisor1email], InStr(main.[supervisor1email],".")-1)
AND Mid(sub.[supervisor1email], InStr(sub.[supervisor1email],".")+1,
InStr(sub.[supervisor1email],"#") - InStr(sub.[supervisor1email],".")-1) =
Mid(main.[supervisor1email], InStr(main.[supervisor1email],".")+1,
InStr(main.[supervisor1email],"#") - InStr(main.[supervisor1email],".")-1)
) AS Total_Hours
FROM dbo_v_MES_TcActivities main
LEFT JOIN dbo_t_SAP_AttCodes sap
ON main.AttCode = sap.Code
--...same WHERE and GROUPBY as original w/ 'main' and 'sap' aliases...
Derived Tables
Alternatively, turn both aggregate queries into derived tables joined into main query. In MS Access you can even save both nested SELECT statements as separate, saved queries and reference them in FROM and JOIN clause. Again, note the table aliases:
SELECT t1.Description, t1.SumOfHours, t1.[Supervisor First Name], t1.[Supervisor Last Name],
t2.TotalHours
FROM (
SELECT --...same fields/expressions as original...
FROM dbo_v_MES_TcActivities main
LEFT JOIN dbo_t_SAP_AttCodes sap
ON main.AttCode = sap.Code
--...same WHERE and GROUPBY as original...
) As t1
INNER JOIN
(
SELECT SUM(sub.[hours]),
Left(sub.[supervisor1email], InStr(sub.[supervisor1email],".")-1)
AS [Supervisor First Name],
Mid(sub.[supervisor1email], InStr(sub.[supervisor1email],".")+1,
InStr(sub.[supervisor1email],"#") - InStr(sub.[supervisor1email],".")-1)
AS [Supervisor Last Name]
FROM [dbo_v_MES_TcActivities] sub
WHERE sub.[costctr]="106330"
AND sub.AttCode Not Like "MEAL"
AND sub.[clockin] Between DateAdd("d",-((Weekday(Date())-1)), Date()) And Date()
GROUP BY
Left(sub.[supervisor1email], InStr(sub.[supervisor1email],".")-1),
Mid(sub.[supervisor1email], InStr(sub.[supervisor1email],".")+1,
InStr(sub.[supervisor1email],"#") - InStr(sub.[supervisor1email],".")-1)
) AS t2
ON t1.[Supervisor First Name] = t2.[Supervisor First Name]
AND t1.[Supervisor Last Name] = t2.[Supervisor Last Name]
I have the following query in SQL Server 2008 R2:
SELECT
DateName(month, DateAdd(month, [sfq].[fore_quart_month], -1)) AS [Month],
[sfq].[fore_quart_so_rev] AS [Sales Orders Revenue],
[sfq].[fore_quart_so_mar] AS [Sales Orders Margin],
[sfq].[fore_quart_mac_rev] AS [MAC Revenue],
[sfq].[fore_quart_mac_mar] AS [MAC Margin],
[sfq].[fore_quart_total_rev] AS [TOTAL Revenue],
[sfq].[fore_quart_total_mar] AS [TOTAL Margin],
(SELECT SUM([FORE].[Revenue])
FROM [SO_Opportunity][SO]
LEFT JOIN [SO_Type] ON [SO].[SO_Type_RecID] = [SO_Type].[SO_Type_RecID]
LEFT JOIN [SO_Opportunity_Audit][soa] ON [so].[Opportunity_RecID] = [soa].[Opportunity_RecId]
LEFT JOIN [SO_Opportunity_Audit_Value][soav] ON [soa].[SO_Opportunity_Audit_RecId] = [soav].[SO_Opportunity_audit_recid]
LEFT JOIN [SO_Forecast_dtl] [FORE] ON [SO].[Opportunity_RecID] = [FORE].[Opportunity_RecID]
WHERE ([SO_Type].[Description] NOT LIKE '%MAC%' AND [SO_Type].[Description] NOT LIKE '%Maint%')
AND YEAR([soa].[last_Updated_utc]) = #p_year AND MONTH([soa].[last_updated_utc]) = [sfq].[fore_quart_month]
AND [soav].[audit_value] LIKE '%Closed - Won%' AND [soav].[audit_token] = 'new_value'
AND [so].[SO_Opp_Status_RecID] = 7) AS [Rev]
FROM
[authmanager2].[dbo].[sales_forecast_quarterly][sfq]
WHERE
[sfq].[fore_quart_year] = #p_year AND [sfq].[fore_quart_loc] = 'w'
ORDER BY
[sfq].[fore_quart_month]
The issue is that when including the NOT LIKE filters and the [sfq].[fore_quart_month] reference in the subquery, it runs incredibly slow (minutes), but if I remove the NOT LIKE filters or if I hard set the value instead of use the [sfq].[fore_quart_month] (which obviously means every calculation will use the wrong month except the one I hard coded), then the query runs in less than a second.
Any suggestions?
The LIKE queries with wild cards on both ends are very slow. Example: %MAC%
If you really need to search on that, consider creating a persisted computed boolean field and searching on that. Something like:
ALTER TABLE SO_Type
ADD IsMac AS CASE WHEN [Description] LIKE '%MAC%' THEN 1 ELSE 0 END PERSISTED
GO
OR
As an alternative, set ISMac whenever data is inserted
Small tip: you can group by month and join subquery to main datasource in from clause. This will let (which is not must) server perform subquery only once. And please note my comment above.
...
FROM [authmanager2].[dbo].[sales_forecast_quarterly][sfq]
INNER JOIN
(
SELECT SUM([FORE].[Revenue]) as [Revenue], MONTH([soa].[last_updated_utc]) as [Month]
FROM [SO_Opportunity][SO]
INNER JOIN [SO_Type] ON [SO].[SO_Type_RecID] = [SO_Type].[SO_Type_RecID]
...
GROUP BY MONTH([soa].[last_updated_utc])
) rev on rev.[Month] = [sfq].[fore_quart_month]