Using ROW_NUMBER() to remove duplicates in SQL server - sql-server

My current query returns too many lines per Subject_ID, so I want to use ROW_NUMBER() to limit the resulting set to 1 line per Subject_ID. I've added this line to my SELECT statement:
, ROW_NUMBER() over(partition by CS.Subject_ID order by CS.Subject_ID) rn
But when I try to put WHERE rn = 1 anywhere in the FROM statement, I get the error:
Incorrect syntax near the keyword 'WHERE'
And when I try to change it to AND rn = 1 (and add it on to another AND/OR line) I get the error:
Invalid column name 'rn'
So my first question is: When I add a field to my SELECT statement using that ROW_NUMBER() line, what table does this column belong to? Do I need to append it to something like Table.rn? My second question is where should I put this rn = 1 line and how should I write it in?
Full query:
SELECT
Groups.Group_Name
, CT.Created
, CT.Subject_Id
INTO #temp
FROM SubjectZ_Task CT
INNER JOIN
SubjectZ_Task_Users On CT.SubjectZ_Task_Id = SubjectZ_Task_Users.SubjectZ_Task_Id
INNER JOIN
Groups ON Groups.Group_ID = SubjectZ_Task_Users.Group_Reference
WHERE Group_Name LIKE 'Team 1'
AND CT.Created >= '1/1/2019' AND CT.Created < DATEADD(Day,1,'12/31/2019')
GROUP BY Groups.group_name, CT.Created, CT.Subject_ID
SELECT
CT.Group_Name
, CT.Created
, CS.Topic_Start_Date
, CS.Subject_ID
, P.FirstName
, P.LastName
, CS.Subject_Match_ID
, SubjectX.Firstname AS SubjectX_firstname
, CS.SubjectY
, AEC.AEC AS Max_AEC
, SubjectX.Email_id As SubjectX_Email
, Phone.Phone
, ROW_NUMBER() over(partition by CS.Subject_ID order by CS.Subject_ID) rn
FROM #temp CT
LEFT JOIN QE_Topic_Summary CS ON CS.Subject_ID = CT.Subject_Id
AND (Topic_Status LIKE 'In Progress'
OR Topic_Status LIKE 'Pending')
AND CS.Topic_Start_Date >= DATEADD(Day,-60,CT.Created) AND CS.Topic_Start_Date <= DATEADD(Day,60,CT.Created)
INNER JOIN Subjects P ON P.Subject_ID = CS.Subject_ID
LEFT JOIN Subjects SubjectX ON SubjectX.Subject_ID = CS.SubjectX_ID
LEFT JOIN QE_TB_MAX_AEC AEC ON AEC.Subject_ID = CS.Subject_ID
INNER JOIN Subject_Identifiers PI ON PI.Subject_ID = P.Subject_ID
LEFT JOIN Subject_Identifiers PIP ON PIP.Subject_ID = SubjectX.Subject_ID
LEFT JOIN Subject_Phone Phone On Phone.Subject_ID = P.Subject_ID WHERE Phone.Voice = 1
drop table #temp

I don't see a reference to rn in your WHERE clause, but my guess is that you need to wrap it in another query like so:
SELECT *
FROM(
SELECT
CT.Group_Name
, CT.Created
, CS.Topic_Start_Date
, CS.Subject_ID
, P.FirstName
, P.LastName
, CS.Subject_Match_ID
, SubjectX.Firstname AS SubjectX_firstname
, CS.SubjectY
, AEC.AEC AS Max_AEC
, SubjectX.Email_id As SubjectX_Email
, Phone.Phone
, ROW_NUMBER() over(partition by CS.Subject_ID order by CS.Subject_ID) rn
FROM #temp CT
LEFT JOIN QE_Topic_Summary CS ON CS.Subject_ID = CT.Subject_Id
AND (Topic_Status LIKE 'In Progress'
OR Topic_Status LIKE 'Pending')
AND CS.Topic_Start_Date >= DATEADD(Day,-60,CT.Created) AND CS.Topic_Start_Date <= DATEADD(Day,60,CT.Created)
INNER JOIN Subjects P ON P.Subject_ID = CS.Subject_ID
LEFT JOIN Subjects SubjectX ON SubjectX.Subject_ID = CS.SubjectX_ID
LEFT JOIN QE_TB_MAX_AEC AEC ON AEC.Subject_ID = CS.Subject_ID
INNER JOIN Subject_Identifiers PI ON PI.Subject_ID = P.Subject_ID
LEFT JOIN Subject_Identifiers PIP ON PIP.Subject_ID = SubjectX.Subject_ID
LEFT JOIN Subject_Phone Phone On Phone.Subject_ID = P.Subject_ID
WHERE Phone.Voice = 1
)t
WHERE t.rn = 1

Related

FOLLOW UP to SQL query to retrieve the latest status of a process

The original question and schema are shown at the following link:
SQL query to retrieve the latest status of a process
The solution provided by #mendosi was perfect. However, now that the deadline for submission is past, management wants more information. I've been able to give them the information they want using the following query (incorporating the aforementioned solution into the "EXISTS" clause):
SELECT
proposalPackage.proposalPackageID, refProposalType.name, proposalPackage.title,
[user].lastName, [user].firstName, [user].email, [user].phone,
proposalReviewAction.approvalTypeID
FROM
proposalReviewAction, proposalPackage
INNER JOIN
refProposalType ON proposalPackage.proposalTypeID = refProposalType.proposalTypeID
INNER JOIN
proposalManagerAssignment ON proposalPackage.proposalPackageID = proposalManagerAssignment.proposalPackageID
INNER JOIN
[user] ON proposalManagerAssignment.userID = [user].userID
WHERE
EXISTS (SELECT ls.*
FROM
(SELECT
r.proposalPackageID, r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM
proposalReviewAction AS r
JOIN
proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE
pp.proposalCallID = 7) AS ls
WHERE
ls.RowNr = 1
AND (ls.approvalTypeID = 50))
GROUP BY
proposalPackage.proposalTypeID, [user].lastName, [user].firstName,
[user].email, [user].phone, proposalPackage.title,
refProposalType.name, proposalManagerAssignment.isPrimary,
proposalPackage.proposalCallID, approvalTypeID,
proposalPackage.proposalPackageID, proposalReviewAction.approvalTypeID
HAVING
(proposalManagerAssignment.isPrimary = 1)
AND (proposalPackage.proposalCallID = 7)
AND (approvalTypeID = 50)
ORDER BY
proposalPackage.proposalPackageID
My problem seems to be that the subquery in the Exists clause returns 95 rows (as it should) limiting the results to those with a status of 50.
As I understand the EXISTS clause, the results should be limited to those records that "exist" in the subquery that follows... right? So, in this case, if a record does not exist in the subquery, it will not exist in the final result...??
The problem is, I'm getting 112 records when there are only 95 records to choose from (or join on) in the results list of the subquery.
So, I try to limit is by adding some additional qualifiers and joins to the subquery:
SELECT
proposalPackage.proposalPackageID, refProposalType.name,
proposalPackage.title,
[user].lastName, [user].firstName, [user].email, [user].phone,
proposalReviewAction.approvalTypeID
FROM
proposalReviewAction, proposalPackage
INNER JOIN
refProposalType ON proposalPackage.proposalTypeID = refProposalType.proposalTypeID
INNER JOIN
proposalManagerAssignment ON proposalPackage.proposalPackageID = proposalManagerAssignment.proposalPackageID
INNER JOIN
[user] ON proposalManagerAssignment.userID = [user].userID
WHERE
EXISTS (SELECT ls.*
FROM
(SELECT
r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM
proposalReviewAction AS r
JOIN
proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE
pp.proposalCallID = 7) AS ls
WHERE
ls.RowNr = 1
AND (ls.approvalTypeID = 50)) AS distinctified
INNER JOIN
proposalPackage ON distinctified.proposalPackageID = proposalPackage.proposalPackageID
INNER JOIN
refProposalApprovalType ON distinctified.approvalTypeID = refProposalApprovalType.approvalTypeID
GROUP BY
proposalPackage.proposalTypeID, [user].lastName, [user].firstName,
[user].email, [user].phone, proposalPackage.title, refProposalType.name,
proposalManagerAssignment.isPrimary, proposalPackage.proposalCallID,
approvalTypeID, proposalPackage.proposalPackageID, proposalReviewAction.approvalTypeID
HAVING
(proposalManagerAssignment.isPrimary = 1)
AND (proposalPackage.proposalCallID = 7)
AND (distinctified.approvalTypeID = 50)
ORDER BY
proposalPackage.proposalPackageID
Now, when I add the "AS distinctified" statement with a couple of JOINS to the subquery, I get a "SYNTAX ERROR near AS" error. I also get an "Expecting ( or SELECT" at each of the "HAVING" qualifiers.
I don't think I'm making this too complicated but that remains a possibility. It seems to me it is a matter (at this point) of overlooking a character somewhere.
Thanks in advance for the assist... AGAIN!!
This isn't really an answer to your much more complex example, but it should explain what the root cause is hopefully?
DECLARE #x TABLE (id INT);
INSERT INTO #x SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3;
DECLARE #y TABLE (id INT);
INSERT INTO #y SELECT 1 UNION ALL SELECT 3;
--This is what you are doing
SELECT * FROM #x WHERE EXISTS (SELECT * FROM #y);
--This is what you should be doing
SELECT * FROM #x x WHERE EXISTS (SELECT * FROM #y y WHERE y.id = x.id);
I really have no idea what you are trying to accomplish but here is what your query might look like with some aliases and formatting. I also moved the joins before the where clause and removed the alias on your EXISTS predicate. But you are referencing distinctified in the code which I just don't get. As such there are some issues in this query still but without an understanding of what the need is I don't know what to do to help.
SELECT pp.proposalPackageID
, pt.name
, pp.title
, u.lastName
, u.firstName
, u.email
, u.phone
, pra.approvalTypeID
FROM proposalReviewAction pra
--, proposalPackage pp --why is this table here? It is joined to again later
INNER JOIN refProposalType pt ON pp.proposalTypeID = pt.proposalTypeID
INNER JOIN proposalManagerAssignment ma ON pp.proposalPackageID = ma.proposalPackageID
INNER JOIN [user] u ON ma.userID = u.userID
INNER JOIN proposalPackage pp ON distinctified.proposalPackageID = pp.proposalPackageID
INNER JOIN refProposalApprovalType pat ON distinctified.approvalTypeID = pat.approvalTypeID
WHERE EXISTS
(
SELECT ls.*
FROM
(
SELECT r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM proposalReviewAction AS r
JOIN proposalPackage AS pp2 ON pp2.proposalPackageID = r.proposalPackageID
WHERE pp2.proposalCallID = 7
) AS ls
WHERE ls.RowNr = 1
AND ls.approvalTypeID = 50
)
GROUP BY pp.proposalTypeID
, u.lastName
, u.firstName
, u.email
, u.phone
, pp.title
, pt.name
, ma.isPrimary
, pp.proposalCallID
, approvalTypeID
, pp.proposalPackageID
, pra.approvalTypeID
HAVING ma.isPrimary = 1
AND pp.proposalCallID = 7
AND distinctified.approvalTypeID = 50
ORDER BY pp.proposalPackageID
So, I figured it out... once I realized I had the wrong understanding of EXISTS (thanks #Richard Hansel). Final query: (properly formatted and aliased)
SELECT pp.proposalPackageID
, r_pt.name
, pp.title
, u.lastName
, u.firstName
, u.email
, u.phone
, pra.approvalTypeID
FROM proposalReviewAction AS pra
, proposalPackage AS pp
INNER JOIN refProposalType AS r_pt ON pp.proposalTypeID = r_pt.proposalTypeID
INNER JOIN proposalManagerAssignment AS pma ON pp.proposalPackageID = pma.proposalPackageID
INNER JOIN [user] AS u ON pma.userID = u.userID
WHERE EXISTS
(SELECT ls.*
FROM
(SELECT r.proposalPackageID,
r.approvalTypeID,
RowNr = ROW_NUMBER() OVER (PARTITION BY r.proposalPackageID ORDER BY r.reviewedDate DESC)
FROM proposalReviewAction AS r
JOIN proposalPackage AS pp ON pp.proposalPackageID = r.proposalPackageID
WHERE pp.proposalCallID = 7) AS ls
WHERE ls.RowNr = 1
AND (ls.approvalTypeID = 50)
AND (pra.proposalPackageID = pp.proposalPackageID))
GROUP BY pp.proposalTypeID
, u.lastName
, u.firstName
, u.email
, u.phone
, pp.title
, r_pt.name
, pma.isPrimary
, pp.proposalCallID
, approvalTypeID
, pp.proposalPackageID
, pra.approvalTypeID
HAVING (pma.isPrimary = 1)
AND (pp.proposalCallID = 7)
AND (pra.approvalTypeID = 50)
ORDER BY pp.proposalTypeID

Multiple aggregate functions in SQL Server

I need a query for [Contribution]. I used this query:
with ttt as
(
select
(DYG.U_StyleId)[DYG Style]
,Max(O1.CardCode) [Party Group Code],
MAX(O1.CardName) [Party Group Name]
,MAX(OR1.DocDate) [Date]
,sum(CONVERT(NUMERIC(15,2),(RDR1.PriceBefDi*RDR1.Quantity))) [JobAmount]
,CONVERT(NUMERIC(15,2),SUM(RDR1.Quantity)) [Mtr]
,CONVERT(NUMERIC(15,2),SUM(RDR1.U_Pcs))[Pcs]
,(select sum(RDR1.PriceBefDi*RDR1.Quantity) from RDR1) tqty
from
ORDR OR1
left join RDR1 on RDR1.DocEntry = OR1.DocEntry
left join OITM on RDR1.ItemCode = oitm.ItemCode
LEFT JOIN OCRD ON OCRD.CardCode = OR1.CardCode
LEFT JOIN OCRG ON OCRG.GroupCode = OCRD.GroupCode
LEFT JOIN OCRD O1 ON O1.U_BCode = OCRD.U_GrpCod
LEFT JOIN
( SELECT U_StyleId FROM RDR1 WHERE U_StyleId in
('BLOOM','BLOOMING','DYD','DYD-R','DYED','Ex.CLR.','RAINBOW'))
DYG ON DYG.U_StyleId = RDR1.U_StyleId
group by
DYG.U_StyleId
)
select
Style, [Party Group Code],
[Party Group Name], JobAmount,
(sum(JobAmount) / tqty * 100) [Contribution],
[Date], [Pcs]
from
ttt
group by
Style
I need Sum of last jobamount to divide it with above tqty.
But it shows this error.
'Column 'ttt.Party Group Code' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.'
Please help me with the query to get right [Contribution] amount.
Try this:
select Style,[Party Group Code],[Party Group Name],JobAmount,[Date],[Pcs],
100.0 * (sum(JobAmount) OVER (PARTITION BY Style))/tqty [Contribution]
from ttt;

Top N percent Desc and Top M percent Asc

I am trying to get top 5 customertypes and show data for each 5 customer types, The balance (which can be any amount) I show them as "Other Customer Types". my issue is since the rows can be random and not perfectly divisible by a number then there can be repeated values in the top 5 showing up in the "Other" group which overstates the Total sales.
the Data is also being rendered in SSRS
My code using TOP PERCENT:
select final.[description], sum(final.YTDSales$) as YTDSales$
FROM(
select top 25 percent pytd2.[Description], sum(pytd2.YTDSales$) as YTDSales$
FROM(
-- ytd sales
select re.SIC_Desc as [description], sum((ol.NetAmt - ol.WhlOrdDiscAmt) / #exrt) AS YTDSales$
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN #FStartDate AND #EndDate AND ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.SIC_Desc
)PYTD2
group by pytd2.[description]
order by sum(pytd2.YTDSales$) DESC
UNION ALL
select top 75 percent 'Other' as 'description', sum(pytd.YTDSales$) as YTDSales$
FROM(
-- ytd sales
select re.SIC_Desc as [description], sum((ol.NetAmt - ol.WhlOrdDiscAmt) / #exrt) AS YTDSales$
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN #FStartDate AND #EndDate AND ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.SIC_Desc
)PYTD
group by Ppytd.[description]
order by sum(pytd.YTDSales$)
)final
group by final.[Description]
order by sum(final.YTDSales$) DESC
my results:
As you can see the Large Independent and Other has the same figure of $2280.60 in YTDQty since it is being repeated
I was picturing something like this:
with data as (
-- your base query here grouped and summarized by customer type
), rankedData as (
select *, row_number() over (order by YTDSales$ desc) as CustTypeRank
from data
)
select
case when CustTypeRank <= 5 then min("description") else 'Others' end as "description",
sum(YTDSales$) as YTDSales$
from rankedData
group by case when CustTypeRank <= 5 then CustTypeRank else 999 end
order by case when CustTypeRank <= 5 then CustTypeRank else 999 end
I actually used RANK instead which worked great :-
select 0 as rankytd, RANK() OVER(ORDER BY sum(ol.NetAmt - ol.WhlOrdDiscAmt) DESC) as rankpytd, re.sic, ol.VendId, vd.name, re.SIC_Desc As [description], 0 AS YTDQty, sum(ol.Quantity) AS PYTDQty
from dbo.order_line_invoice ol
INNER JOIN dbo.Vendor vd ON ol.Cono = vd.Cono AND vd.VendId = ol.VendId
inner join dbo.Product p on ol.Cono = p.Cono and ol.prodid = p.ProdId and p.ProdCatId in (#pcat)
inner join Product_Warehouse pw on ol.ProdId = pw.prodid and ol.WhseId = pw.whseid and ol.cono = pw.cono
inner join Customer c on ol.custId = c.CustId and ol.Cono = c.Cono
left join MDData.dbo.RetailEnvironment re on c.SIC = re.SIC
where ol.InvoiceDate BETWEEN DATEADD(YEAR, -1,#FStartDate) AND DATEADD(YEAR, -1, #EndDate) and ol.Cono = 1 and ol.VendId IN(#Vendid) and ol.prodcatid NOT LIKE 'GP%'
group by re.sic, ol.VendId, vd.Name, re.SIC_Desc

SQLServer reference outer query

Is it possible to reference an outer query from an inner query in the context of a join? The "where (sid.ItemID = i.itemID)" of the inner query is giving me an error. I thought I did this in the past so I search through all my stored procs but apparently I did some kind of substitution to get it to work. I suspect I can delete that line and it will work but is it more effecient with that inner where clause?
SELECT departmentName
, supplierName
, so.SalesOrderID
, ss.warehouseInvoiceNo
, ss.transactionNo
, ss.storeID
, s.storeName
, s.storeNo
, tr.transactionDate
, p.period
, sooos.salesOrderID
, sooos.salesOrderOutOfStockID
, sooos.itemID
, i.itemNo
, i.itemName
, i.pack
, i.unitSize
, quantity
, wi.available
FROM SalesOrderOutOfStock sooos
JOIN Item AS i ON i.ItemID = sooos.ItemID
JOIN SalesOrder so ON so.SalesOrderID = sooos.SalesOrderID
JOIN WarehouseInventory wi ON wi.ItemID = sooos.ItemID
JOIN Store s ON s.StoreID = so.StoreID
JOIN InvoiceOrderRelationship ior ON ior.SalesOrderID = so.SalesOrderID
JOIN StockSale ss ON ss.WarehouseInvoiceNo = ior.WarehouseInvoiceNo
JOIN TransactionRegister tr ON tr.TransactionNo = ss.TransactionNo
JOIN Period p ON p.PeriodID = tr.PeriodID
JOIN Department d ON d.DepartmentID = i.DepartmentID
LEFT OUTER JOIN (SELECT TOP 1 itemID
, supplierID
FROM SupplierInvoiceDetail sid
JOIN SupplierInvoice si ON si.SupplierInvoiceID = sid.SupplierInvoiceID
--where (sid.ItemID = i.itemID)
order by InvoiceDate desc
--NEED AN ORDER BY HERE
) AS lastSupplier ON lastSupplier.ItemID = i.ItemID
JOIN supplier su ON su.SupplierID = Isnull(lastSupplier.supplierID, i.supplierID)
WHERE ss.WarehouseInvoiceNo = 10000000
--$P{invoiceNo}
You need to use OUTER APPLY here rather than LEFT JOIN:
OUTER APPLY (SELECT TOP 1 itemID
, supplierID
FROM SupplierInvoiceDetail sid
JOIN SupplierInvoice si ON si.SupplierInvoiceID = sid.SupplierInvoiceID
where (sid.ItemID = i.itemID) order by InvoiceDate desc
) AS lastSupplier
Subqueries introduced in FROM or JOIN clauses cannot refer to other table sources within the same FROM clause (effectively, they should all be evaluatable simultaneously). APPLY allows you to introduce a specific dependency in the evaluation.

GROUP BY in SQL Server in complex query

I need to group this by T.TopicID to only receive the last result.
Whatever I try I get errors like the other T. items rant included in group by or aggregate etc
ALTER PROCEDURE [dbo].[SPGetFollowingTopics]
#id int = null
,#UserGroupId int = null
,#lastvisit DateTime = null
AS
SELECT *
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY TopicOrder DESC,
(CASE
WHEN M.MessageCreationDate > T.TopicCreationDate
THEN M.MessageCreationDate
ELSE T.TopicCreationDate
END) DESC) AS RowNumber,
T.TopicId, T.TopicTitle, T.TopicShortName,
T.TopicDescription, T.TopicCreationDate, T.TopicViews,
T.TopicReplies, T.UserId, T.TopicTags, T.TopicIsClose,
T.TopicOrder, T.LastMessageId, U.UserName,
M.MessageCreationDate, T.ReadAccessGroupId,
T.PostAccessGroupId, TF.userid AS Expr1, U.UserGroupId,
U.UserPhoto, U.UserFullName, M.UserId AS MessageUserId,
MU.UserName AS MessageUserName
FROM
Topics AS T
LEFT OUTER JOIN
Messages AS M ON M.TopicId = T.TopicId AND M.Active = 1 AND M.MessageCreationDate < #lastvisit
INNER JOIN
topicfollows AS TF ON T.TopicId = TF.topicid
INNER JOIN
Users AS U ON U.UserId = T.UserId
LEFT JOIN
Users MU ON MU.UserId = M.UserId
WHERE
(TF.userid = #id)
) T
It isn't clear what the requirement is (in my view) but I think you are seeking:
"the latest message"
PER TOPIC
for a given user
In this situation ROW_NUMBER() is a good option but I believe you need to PARTITION the ROW_NUMBER as well as ordering it.
SELECT
*
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY TF.userid, T.TopicId
ORDER BY
(CASE
WHEN M.MessageCreationDate > T.TopicCreationDate THEN M.MessageCreationDate
ELSE T.TopicCreationDate
END) DESC) AS ROWNUMBER
, T.TopicId, T.TopicTitle, T.TopicShortName, T.TopicDescription
, T.TopicCreationDate, T.TopicViews, T.TopicReplies, T.UserId
, T.TopicTags, T.TopicIsClose, T.TopicOrder, T.LastMessageId
, U.UserName, M.MessageCreationDate, T.ReadAccessGroupId
, T.PostAccessGroupId, TF.userid AS EXPR1
, U.UserGroupId, U.UserPhoto, U.UserFullName
, M.UserId AS MESSAGEUSERID, MU.UserName AS MESSAGEUSERNAME
FROM Topics AS T
LEFT OUTER JOIN Messages AS M ON M.TopicId = T.TopicId
AND M.Active = 1
AND M.MessageCreationDate < #lastvisit
INNER JOIN topicfollows AS TF ON T.TopicId = TF.topicid
INNER JOIN Users AS U ON U.UserId = T.UserId
LEFT JOIN Users MU ON MU.UserId = M.UserId
WHERE (TF.userid = #id)
) T
WHERE ROWNUMBER = 1
You could change your left join to any outer apply, and add TOP 1:
SELECT ...
FROM
Topics AS T
OUTER APPLY
( SELECT TOP 1 M.MessageCreationDate, M.UserId
FROM Messages AS M
WHERE M.TopicId = T.TopicId
AND M.Active = 1
AND M.MessageCreationDate < #lastvisit
ORDER BY M.MessageCreationDate DESC
) AS m
This allows you to use TOP 1 and still get one row per topicID
Alternatively you can use ROW_NUMBER() OVER(PARTITION BY m.TopicID ORDER BY M.MessageCreationDate DESC)
SELECT ...
FROM
Topics AS T
LEFT OUTER JOIN
( SELECT M.TopicId,
M.MessageCreationDate,
M.UserId,
RowNum = ROW_NUMBER() OVER(PARTITION BY m.TopicID ORDER BY M.MessageCreationDate DESC)
FROM Messages AS M
WHERE M.Active = 1
AND M.MessageCreationDate < #lastvisit
) AS m
ON M.TopicId = T.TopicId
AND m.RowNum = 1
I would test both methods and see which one works best for you.

Resources