Related
I need to find the first and last Order for each Customer by OrderDate, and the name and SKU of the item with the highest business volume in each of those orders. For reference, the Customer table has >150k records, and Orders and OrderDetails (these are the Items) a lot more.
Note: Both Orders and their respective items should be in the same row as the Customer
Orders
OrderID OrderDate CustomerID BusinessVolumeTotal Subtotal
13212 '2021-09-06' 512312 500.00 25.60
OrderDetails
OrderID ItemCode ItemDescription BusinessVolume
13212 'SKW-BS' 'Some item' 450.00
In my first query I attempted to stick to joining instead of subqueries, which resulted in this
select distinct(c.CustomerID), c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fo.OrderID,fo.OrderDate,fo.SubTotal,fo.Country, fod.ItemCode, fod.ItemDescription, fopt.PriceTypeID,
lo.OrderID,lo.OrderDate,lo.SubTotal,lo.Country, lod.ItemCode, lod.ItemDescription, lopt.PriceTypeID
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
left join Orders fo on fo.CustomerID = c.CustomerID -- First Order
left join Orders lo on lo.CustomerID = c.CustomerID -- Last Order
left join OrderDetails fod on fod.OrderID = fo.OrderID
left join OrderDetails lod on lod.OrderID = lo.OrderID
left join PriceTypes fopt on fo.PriceTypeID = fopt.PriceTypeID
left join PriceTypes lopt on lo.PriceTypeID = lopt.PriceTypeID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3)
and pv.PeriodTypeID = 2
/* First Order */
and fo.OrderID = (select top 1(OrderID) from Orders where CustomerID = c.CustomerID and OrderStatusID>=7 order by OrderDate )
and fod.ItemID = (select top 1(ItemID) from OrderDetails where OrderID = fo.OrderID order by BusinessVolume)
/* Last Order */
and lo.OrderID = (select top 1(OrderID) from Orders where CustomerID = c.CustomerID and OrderStatusID>=7 order by OrderDate desc)
and lod.ItemID = (select top 1(ItemID) from OrderDetails where OrderID = lo.OrderID order by BusinessVolume desc)
and pv.PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate <= #now and EndDate >= #now)
But this ends up executing for about 6-7 minutes. From the explain plan, it looks like most of it is taken up by a Key Lookup for Orders based on OrderStatusID >= 7.
So I tried to use window functions to achieve the same:
select distinct(c.CustomerID), c.FirstName + ' ' + c.LastName as Name, cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type, pv.Volume80 as G3,
fal.*
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
left join(
select
CustomerID,
max(case when MinDate = 1 then OrderID end) FirstOrderID,
max(case when MinDate = 1 then OrderDate end) FirstOrderDate,
max(case when MinDate = 1 then BusinessVolumeTotal end) FirstBVTotal,
max(case when MinDate = 1 then PriceTypeDescription end) FirstPriceType,
max(case when MinDate = 1 then ItemCode end) FirstItemCode,
max(case when MinDate = 1 then ItemDescription end) FirstItemDescription,
max(case when MaxDate = 1 then OrderID end) LastOrderID,
max(case when MaxDate = 1 then OrderDate end) LastOrderDate,
max(case when MaxDate = 1 then BusinessVolumeTotal end) LastBVTotal,
max(case when MaxDate = 1 then PriceTypeDescription end) LastPriceType,
max(case when MaxDate = 1 then ItemCode end) LastItemCode,
max(case when MaxDate = 1 then ItemDescription end) LastItemDescription
from
(
select distinct o.CustomerID,
o.OrderID,
o.OrderDate,
o.BusinessVolumeTotal,
PT.PriceTypeDescription,
RANK() over (partition by o.CustomerID order by OrderDate) as MinDate,
RANK() over (partition by o.CustomerID order by OrderDate desc) as MaxDate,
FIRST_VALUE(ItemCode) over (partition by od.OrderID order by BusinessVolume desc) as ItemCode,
FIRST_VALUE(ItemDescription) over (partition by od.OrderID order by BusinessVolume desc) as ItemDescription
from Orders o
left join OrderDetails od on od.OrderID = o.OrderID
left join PriceTypes PT on o.PriceTypeID = PT.PriceTypeID
where o.OrderStatusID >= 7
) fal
group by CustomerID
) fal on c.CustomerId = fal.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3)
and pv.PeriodTypeID = 2
/* CurrentG3 */
and pv.PeriodID = (select PeriodID from Periods where PeriodTypeID=2 and StartDate <= #now and EndDate >= #now)
Alas, this ended up executing even longer. I need a way to optimize this if possible.
Secondary query
I do also need a count and sum of volume per Order in the last 3, 6 and 12 months. I currently do this programatically as secondary queries after the original returns a result, and I forward the CustomerIDs, like this:
select count(OrderID) as Cnt, sum(BusinessVolumeTotal) as Bv, CustomerID
from Orders where OrderStatusID > 6 and OrderTypeID in (1,4,8,11)
and OrderDate >= #timeAgo and CustomerID in #ids group by CustomerID
Times 3, because 3, 6 and 12 months. Ideally, I would also like to make this part of the original but I don't really have a good idea on how to do it, especially with how convoluted the joining is with the orders.
So ideally I'd end up with a result table like this
CustomerID Name CustomerStatus CustomerType FirstOrderID FirstOrderDate FirstBVTotal FirstItemCode FirstItemDesc FirstPriceType LastOrderID LastOrderDate LastBVTotal LastItemCode LastItemDesc LastPriceType ThreeMonthCount ThreeMonthTotal SixMonthCount SixMonthTotal TwelveMonthCount TwelveMonthTotal
512312 'Jane Doe' 'Active' 'Retail' 13212 '2020-06-06' 50.00 'Item1' 'Item 1 desc' 'Retail' 14321 '2021-09-01' 200.00 'Item2' 'Item 2 desc' 'Retail' 45 4305.00 76 8545.60 183 21542.95
Any help and advice on how to optimize or reduce the query, as well as anything you believe Im doing wrong would be GREATLY appreciated.
P.S. I don't know if the title is fitting and if I'd be able to change it later, it's been a while since I've used SO to ask a question.
UPDATE
Actual Execution Plan for Query 1:
https://www.brentozar.com/pastetheplan/?id=SJd56RSmK
Actual Execution Plan for Query 2:
https://www.brentozar.com/pastetheplan/?id=BJ7QHk87Y
I think you need to keep in mind two main points with this type of query:
The key to good performance with window functions is to not introduce an unnecessary sort. So while you can use ROW_NUMBER to get the first order in either direction, you should not use another opposing ROW_NUMBER to get the last. Rather use LEAD to check if the next row exists, thereby telling you if this is the last row. You can then use conditional aggregation.
There are generally two ways to calculate first/last: a row-numbering solution, as above, or an APPLY, which picks out the exact one you need.
I think that for the OrderDetails we should use an apply, because there are only two orders per customer that we need to find. This does need good indexing, so if OrderDetails is not well indexed, then you may want to switch to a row-numbering solution for this also.
select
c.CustomerID,
c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3,
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
fod.ItemCode as FirstItemCode,
fod.ItemDescription as FirstItemDescription,
fopt.PriceTypeDescription as FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
lod.ItemCode as LastItemCode,
lod.ItemDescription as LastItemDescription,
lopt.PriceTypeDescription as LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID = 2
and p.StartDate <= #now
and p.EndDate >= #now
)
left join (
select
o.CustomerID,
min(case when rn = 1 then OrderID end) as FirstOrderId,
min(case when rn = 1 then OrderDate end) as FirstOrderDate,
min(case when rn = 1 then SubTotal end) as FirstSubTotal,
min(case when rn = 1 then Country end) as FirstCountry,
min(case when nx is null then OrderID end) as LastOrderId,
min(case when nx is null then OrderDate end) as LastOrderDate,
min(case when nx is null then SubTotal end) as LastSubTotal,
min(case when nx is null then Country end) as LastCountry,
count(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then 1 end) as ThreeMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then 1 end) as SixMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then 1 end) as TwelveMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o.CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o.CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o.OrderTypeID in (1,4,8,11)
and o.OrderDate >= #timeAgo
) o
group by o.CustomerID
) o on o.CustomerID = c.CustomerID
outer apply (
select top 1
od.ItemCode,
od.ItemDescription
from OrderDetails od
order by od.BusinessVolume desc
where od.OrderID = o.FirstOrderId
) fod
outer apply (
select top 1
od.ItemCode,
od.ItemDescription
from OrderDetails od
order by od.BusinessVolume desc
where od.OrderID = o.LastOrderId
) lod
left join PriceTypes fopt on fopt.PriceTypeID = o.FirstPriceTypeID
left join PriceTypes lopt on lopt.PriceTypeID = o.LastPriceTypeID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3);
I'm also going to give you a row-numbering version, as judging by your execution plan, it may actually be better. You need to try both
select
c.CustomerID,
c.FirstName + ' ' + c.LastName as Name,
cs.CustomerStatusDescription as Status,
ct.CustomerTypeDescription as Type,
pv.Volume80 as G3,
o.FirstOrderID,
o.FirstOrderDate,
o.FirstSubTotal,
o.FirstCountry,
o.FirstItemCode,
o.FirstItemDescription,
o.FirstPriceTypeDescription,
o.LastOrderID,
o.LastOrderDate,
o.LastSubTotal,
o.LastCountry,
o.LastItemCode,
o.LastItemDescription,
o.LastPriceTypeDescription
from Customers c
left join CustomerTypes ct on ct.CustomerTypeID = c.CustomerTypeID
left join CustomerStatuses cs on cs.CustomerStatusID = c.CustomerStatusID
left join PeriodVolumes pv on pv.CustomerID = c.CustomerID
and pv.PeriodTypeID = 2
and pv.PeriodID = (
select top 1 PeriodID
from Periods p
where p.PeriodTypeID = 2
and p.StartDate <= #now
and p.EndDate >= #now
)
left join (
select
o.CustomerID,
min(case when rn = 1 then o.OrderID end) as FirstOrderId,
min(case when rn = 1 then o.OrderDate end) as FirstOrderDate,
min(case when rn = 1 then o.SubTotal end) as FirstSubTotal,
min(case when rn = 1 then o.Country end) as FirstCountry,
min(case when rn = 1 then od.ItemCode end) as FirstItemCode,
min(case when rn = 1 then od.ItemDescription end) as FirstItemDescription,
min(case when rn = 1 then opt.PriceTypeDescription end) as FirstPriceTypeDescription,
min(case when nx is null then o.OrderID end) as LastOrderId,
min(case when nx is null then o.OrderDate end) as LastOrderDate,
min(case when nx is null then o.SubTotal end) as LastSubTotal,
min(case when nx is null then o.Country end) as LastCountry,
min(case when nx is null then od.ItemCode end) as LastItemCode,
min(case when nx is null then od.ItemDescription end) as LastItemDescription,
min(case when nx is null then opt.PriceTypeDescription end) as LastPriceTypeDescription,
count(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then 1 end) as ThreeMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -3, GETDATE()) then BusinessVolumeTotal end) as ThreeMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then 1 end) as SixMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -6, GETDATE()) then BusinessVolumeTotal end) as SixMonthTotal,
count(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then 1 end) as TwelveMonthCount,
sum(case when o.OrderDate >= DATEADD(month, -12, GETDATE()) then BusinessVolumeTotal end) as TwelveMonthTotal
from (
select *,
ROW_NUMBER() over (partition by o.CustomerID order by OrderDate) as rn,
LEAD(OrderID) over (partition by o.CustomerID order by OrderDate) as nx
from Orders o
where o.OrderStatusID >= 7
and o.OrderTypeID in (1,4,8,11)
and o.OrderDate >= #timeAgo
) o
left join PriceTypes opt on opt.PriceTypeID = o.PriceTypeID
join (
select *,
ROW_NUMBER() over (partition by od.OrderID order by od.BusinessVolume desc) as rn
from OrderDetails od
) od on od.OrderID = o.OrderId
where rn = 1 or nx is null
) o on o.CustomerID = c.CustomerID
where c.CustomerStatusID in (1,2)
and c.CustomerTypeID in (2,3);
Good indexing is essential to good performance. I would expect roughly the following indexes on your tables, either clustered or non-clustered (clustered indexed INCLUDE every other column automatically), you can obviously add other INCLUDE columns if needed:
Customers (CustomerID) INCLUDE (FirstName, LastName)
CustomerTypes (CustomerTypeID) INCLUDE (CustomerTypeDescription)
CustomerStatuses (CustomerStatusID) INCLUDE (CustomerTypeDescription)
PeriodVolumes (CustomerID) INCLUDE (Volume80)
Periods (PeriodTypeID, StartDate, PeriodID) INCLUDE (EndDate) -- can swap Start and End
Orders (CustomerID, OrderDate) INCLUDE (OrderStatusID, SubTotal, Country, BusinessVolumeTotal)
OrderDetails (OrderID, BusinessVolume) INCLUDE (ItemCode ItemDescription)
PriceTypes (PriceTypeID) INCLUDE (PriceTypeDescription)
You should think carefully about INNER vs LEFT joins, because the optimizer can more easily move around an INNER join.
Note also, that DISTINCT is not a function, it is calculated over an entire set of columns. Generally, one can assume that if a DISTINCT is in the query then the joins have not been thought through properly.
I'm getting error "Windowed functions can only appear in the SELECT or ORDER BY clauses." Can you please help me?
Iknow there are already some solutions to this problem but i don't know how to change it for my case. I am a beginner in SQL.
EDIT: I need to update column [BREAG_GUEST_SEQ] with correct sequence at UPDATE case. Every [BREAG_BGL_ID] have own sequence..
UPDATE G1
SET
G1.[BREAG_BGL_ID] = CASE WHEN G1.[BREAG_BGL_ID] <> G2.GROUP_ID THEN G2.GROUP_ID ELSE G1.[BREAG_BGL_ID] END
,G1.[BREAG_GUEST_SEQ] = CASE WHEN G1.[BREAG_BGL_ID] <> G2.GROUP_ID THEN ROW_NUMBER() OVER(ORDER BY G1.[BREAG_BRE_ID] DESC) + (SELECT ISNULL(MAX(BREAG_GUEST_SEQ), 0) FROM BOS_RESADDGUEST
WHERE DATEPART(YEAR, BREAG_DATEFROM) = DATEPART(YEAR, GETDATE()) AND BREAG_BGL_ID = G2.GROUP_ID)
ELSE G1.[BREAG_GUEST_SEQ] END
FROM
BOS_RESADDGUEST G1
INNER JOIN (
SELECT
[BRE_ID]
,MAX([BRE_DATEFROM]) AS DATEFROM
,MAX([BRE_DATETO]) AS DATETO
,MAX(BGL_ID) AS GROUP_ID
FROM
BOS_RESERVATION
LEFT JOIN BOS_UNIT_LIST ON BUL_ID = BRE_UNIT_ID
LEFT JOIN BOS_UNITTYPE_LIST ON BUL_UNITTYPE_ID = BUT_ID
LEFT JOIN BOS_GROUP_LIST ON BGL_ID = BUT_GROUP_ID
WHERE
BRE_ID = #DIALOG_BRE_ID
GROUP BY [BRE_ID]
) AS G2
ON G2.[BRE_ID] = G1.[BREAG_BRE_ID]
This isnt tested nor do you provide expected results or data structure. However, the issue is quite clear (as SMor has pointed out in the comments). This isnt the best code and the indentation is awful (i want it to be readable with scrolling left or right here on SO).
You most likely have to edit what Im joining on in the update statement at the end, because without further information from you i have to guess what the join predicate ought to be. Im sure you can figure it out from this point forward.
; WITH cte AS
(
SELECT
[BREAG_BRE_ID]
, CASE
WHEN x.[BREAG_BGL_ID] <> x.GROUP_ID THEN x.GROUP_ID
ELSE x.[BREAG_BGL_ID]
END As NewBREAGBGLID
, CASE
WHEN x.[BREAG_BGL_ID] <> x.GROUP_ID THEN ROW_NUMBER()
OVER (ORDER BY x.[BREAG_BRE_ID] DESC) +
(
SELECT ISNULL(MAX(BREAG_GUEST_SEQ), 0)
FROM BOS_RESADDGUEST
WHERE DATEPART(YEAR, BREAG_DATEFROM) = DATEPART(YEAR, GETDATE())
AND BREAG_BGL_ID = x.GROUP_ID
)
ELSE x.[BREAG_GUEST_SEQ]
END AS NewBREAGGUESTSEQ
FROM
(
SELECT g2.*, g1.[BREAG_BGL_ID], g1.[BREAG_BRE_ID], g1.[BREAG_GUEST_SEQ]
FROM
BOS_RESADDGUEST G1
INNER JOIN
(
SELECT
[BRE_ID]
,MAX([BRE_DATEFROM]) AS DATEFROM
,MAX([BRE_DATETO]) AS DATETO
,MAX(BGL_ID) AS GROUP_ID
FROM
BOS_RESERVATION
LEFT JOIN BOS_UNIT_LIST ON BUL_ID = BRE_UNIT_ID
LEFT JOIN BOS_UNITTYPE_LIST ON BUL_UNITTYPE_ID = BUT_ID
LEFT JOIN BOS_GROUP_LIST ON BGL_ID = BUT_GROUP_ID
WHERE
BRE_ID = #DIALOG_BRE_ID
GROUP BY [BRE_ID]
) AS G2
ON G2.[BRE_ID] = G1.[BREAG_BRE_ID]
) AS x
)
UPDATE
[BREAG_BGL_ID] = c.NewBREAGBGLID
, [BREAG_GUEST_SEQ] = c.NewBREAGGUESTSEQ
FROM BOS_RESADDGUEST br
INNER JOIN cte c ON br.[BREAG_BRE_ID] =c.[BREAG_BRE_ID]
You need to move the ROW_NUMBER into a derived table or CTE. You can update this directly, no need to rejoin.
You can also change the subquery to a windowed MAX
Note also that date comparisons should not use functions against the column, as this can affect performance
UPDATE G1
SET
G1.[BREAG_BGL_ID] = CASE WHEN G1.[BREAG_BGL_ID] <> G2.GROUP_ID THEN G2.GROUP_ID ELSE G1.[BREAG_BGL_ID] END
,G1.[BREAG_GUEST_SEQ] = CASE WHEN G1.[BREAG_BGL_ID] <> G2.GROUP_ID THEN G1.rn + G1.MaxSeq ELSE G1.[BREAG_GUEST_SEQ] END
FROM (
SELECT *,
ROW_NUMBER() OVER(ORDER BY G1.[BREAG_BRE_ID] DESC) AS rn,
ISNULL(MAX(CASE WHEN WHERE G1.BREAG_DATEFROM >= DATEFROMPARTS(YEAR(GETDATE()), 1, 1) THEN G1.BREAG_GUEST_SEQ END)
OVER (PARTITION BY G1.BREAG_BGL_ID), 0) AS MaxSeq
FROM
BOS_RESADDGUEST G1
) AS G1
INNER JOIN (
SELECT
[BRE_ID]
,MAX([BRE_DATEFROM]) AS DATEFROM
,MAX([BRE_DATETO]) AS DATETO
,MAX(BGL_ID) AS GROUP_ID
FROM
BOS_RESERVATION
LEFT JOIN BOS_UNIT_LIST ON BUL_ID = BRE_UNIT_ID
LEFT JOIN BOS_UNITTYPE_LIST ON BUL_UNITTYPE_ID = BUT_ID
LEFT JOIN BOS_GROUP_LIST ON BGL_ID = BUT_GROUP_ID
WHERE
BRE_ID = #DIALOG_BRE_ID
GROUP BY [BRE_ID]
) AS G2
ON G2.[BRE_ID] = G1.[BREAG_BRE_ID];
I have a table with customers that I join with a fact table with sales, based on invoices.
What I need from my report is to get in first part the biggest value of sales based on the incoming order type (1,2,3,C,D) for a customer for last year. And in the second part to get the same but for current year. What I get as result from my current query is all incoming order types with the customer revenue made for each of them. I tried with outer apply as subquery to get only the top 1 value ordered by revenue descending, but in the result I get the same - For all order types the customer revenue. Please help! I hope my explanation isn't understood only by me (happens a lot..)
use dwh01;
WITH OrderTypeUsedLY AS
(
SELECT
c.CustomerKey
,c.BranchId
,c.CustomerId
,c.CustomerName
,ISNULL(SUM(y.[Sale_Revenue]), 0) as [Sale_Revenue_LY]
,ISNULL(SUM(y.[Sale_GrossMarginTotal]), 0) as [Sale_GrossMarginTotal_LY]
,y.IncomingOrderTypeId as IncomingOrderType_LY
FROM live.DimCustomer c
left join (SELECT s.CustomerKey,iot.IncomingOrderTypeid, s.[Sale_Revenue], s.[Sale_GrossMarginTotal] FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d
on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType iot on iot.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where s.ReportCurrencyId = 'LC'
and D.Year = YEAR(GETDATE())-1 --- Last Year
) y on c.CustomerKey = y.CustomerKey
where c.CustomerKey = '157053'
group by c.CustomerKey, c.CustomerId, c.CustomerName, c.BranchId, y.IncomingOrderTypeId
),
--*********************************************************************************************************************************--
OrderTypeCY as(
SELECT
c.CustomerKey
,c.BranchId
,c.SalesRepKey
,c.CustomerId
,c.CustomerName
,ISNULL(SUM(y.[Sale_Revenue]), 0) as [Sale_Revenue_CY]
,ISNULL(SUM(y.[Sale_GrossMarginTotal]), 0) as [Sale_GrossMarginTotal_CY]
,y.IncomingOrderTypeId as IncomingOrderType_CY
FROM live.DimCustomer c
left join (SELECT s.CustomerKey,iot.IncomingOrderTypeid, s.[Sale_Revenue], s.[Sale_GrossMarginTotal] FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d
on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType iot on iot.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where s.ReportCurrencyId = 'LC'
and D.Year = YEAR(GETDATE()) --- Current Year
) y on c.CustomerKey = y.CustomerKey
where c.CustomerKey = '157053'
group by c.CustomerKey, c.CustomerId, c.CustomerName, c.BranchId, y.IncomingOrderTypeId, c.SalesRepKey
)
--*********************************************************************************************************************************--
SELECT
otly.BranchId,
rep.SalesRepId,
rep.SalesRepName,
otly.CustomerId,
otly.CustomerName,
otly.Sale_Revenue_LY,
otly.Sale_GrossMarginTotal_LY,
IncomingOrderType_LY,
otcy.Sale_Revenue_CY,
otcy.Sale_GrossMarginTotal_CY,
IncomingOrderType_CY
from OrderTypeUsedLY otly
left join OrderTypeCY otcy
on otly.CustomerKey = otcy.CustomerKey
join live.DimCustomer cus on cus.CustomerKey = otcy.CustomerKey
join live.DimSalesRep rep on rep.SalesRepKey = otcy.SalesRepKey
order by otcy.Sale_Revenue_CY desc, otly.Sale_Revenue_LY desc
,rep.SalesRepId
And here is the outer apply I tried:
outer apply (
SELECT top 1
iot.IncomingOrderTypeId,
Sale_Revenue
FROM [dwh01].[live].DimIncomingOrderType iot
where iot.IncomingOrderTypeKey = y.IncomingOrderTypeKey
order by Sale_Revenue desc) x
In the first select ( with OrderTypeUsed_LY ) I get this:
And I get the same in the second select, but with the values for current year.
The purpose of the report is to see the difference in the incoming order type most used (most profit made with it) for a customer last year and to see if he continues to use it this year, or uses another incoming order type this year.
Again I'm sorry for the bad explanation, I'm trying my best (I understand myself very well)
Here is the expected result:
Expected Result
I marked in red the last year part and in green the current year part.
If you change the subquery to:
SELECT
iot.IncomingOrderTypeKey,
MAX(Sale_Revenue)
FROM [dwh01].[live].DimIncomingOrderType iot
GROUP BY iot.IncomingOrderTypeKey
then you can JOIN (not APPLY) directly on IncomingOrderTypeKey AND Sale_Revenue.
Try this:
USE dwh01;
DECLARE #CustomerKey varchar(6) = '157053'
, #ReportCurrencyId varchar(2) = 'LC'
, #CurrentYear int = YEAR(GETDATE())
, #TargetYear int = YEAR(GETDATE())-1
;
WITH FactsTable AS
(
SELECT
s.CustomerKey
, i.IncomingOrderTypeid
, [Sale_Revenue] = ISNULL(s.[Sale_Revenue] , 0)
, [Sale_GrossMarginTotal] = ISNULL(s.[Sale_GrossMarginTotal], 0)
, d.[Year]
FROM [dwh01].[live].[FactSales] s
inner join live.DimDate d on d.DateId = s.PostingDateKey
inner join live.DimIncomingOrderType i on i.IncomingOrderTypeKey = s.IncomingOrderTypeKey
where
s.CustomerKey = #CustomerKey
and s.ReportCurrencyId = #ReportCurrencyId
)
, OrderTypeTable
(
SELECT
c.CustomerKey
, c.BranchId
, c.CustomerId
, c.CustomerName
, c.SalesRepKey
, IncomingOrderType_LY = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.IncomingOrderTypeId ELSE 0 END)
, [Sale_Revenue_LY] = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.[Sale_Revenue] ELSE 0 END)
, [Sale_GrossMarginTotal_LY] = SUM(CASE WHEN y.[Year] = #TargetYear THEN y.[Sale_GrossMarginTotal] ELSE 0 END)
, IncomingOrderType_LY = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.IncomingOrderTypeId ELSE 0 END)
, [Sale_Revenue_CY] = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.[Sale_Revenue] ELSE 0 END)
, [Sale_GrossMarginTotal_CY] = SUM(CASE WHEN y.[Year] = #CurrentYear THEN y.[Sale_GrossMarginTotal] ELSE 0 END)
FROM live.DimCustomer c
left join FactsTable y on y.CustomerKey = c.CustomerKey
group by
c.CustomerKey
, c.BranchId
, c.CustomerId
, c.CustomerName
, y.IncomingOrderTypeId
, c.SalesRepKey
)
SELECT
O.BranchId
, R.SalesRepId
, R.SalesRepName
, O.CustomerId
, O.CustomerName
, O.Sale_Revenue_LY
, O.Sale_GrossMarginTotal_LY
, O.IncomingOrderType_LY
, O.Sale_Revenue_CY
, O..Sale_GrossMarginTotal_CY
, O.IncomingOrderType_CY
from OrderTypeTable O
join live.DimSalesRep R on R.SalesRepKey = O.SalesRepKey
order by
O.Sale_Revenue_CY desc
, O.Sale_Revenue_LY desc
, R.SalesRepId
The solution is with using row_number() function in the inner query of the first CTE:
(
select *, row_number() over (partition by x0.CustomerKey order by x0.Sale_Revenue desc) as rn
from
(
select fs.CustomerKey, iot.IncomingOrderTypeKey,
sum(fs.Sale_Revenue) as Sale_Revenue
FROM [dwh01].[live].DimIncomingOrderType iot
join live.FactSales fs
on iot.IncomingOrderTypeKey = fs.IncomingOrderTypeKey
join live.DimDate d
on d.DateId = fs.PostingDateKey
where d.[year] = #CurrentYear
GROUP BY fs.CustomerKey, iot.IncomingOrderTypeKey
) as x0
) as x1 on x1.CustomerKey = s.CustomerKey
The row_number() function gets only the first row from the result for each customer, and that is with the biggest sale revenue ( order by sale_revenue desc).
I would like some help to optimize this query to be faster.
This query produces a view that latter will be showed in a table in a website.
This query is slow, and I am trying to make it faster.
The only thing I tried till now is to reduce the amount of columns I retrieve for the table.
This is the query:
SELECT TOP (100) PERCENT Id, MAX(BusinessTitle) AS BusinessTitle, MAX(ClientName) AS ClientName, MAX(ClientType) AS Type, MAX(CreatedWhen) AS CreatedWhen, MAX(CASE WHEN C.[CreatedBy] IS NULL
THEN 'Client' ELSE 'Admin' END) AS CreatedBy, CAST(MAX(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END) AS BIT) AS IsDisabled, MAX(ReferenceSource) AS ReferenceSource, MAX(OtherReferenceSource)
AS OtherReferenceSource, MAX(Address) AS Address,
(SELECT MAX(T.FirstName + ' ' + T.LastName) AS Expr1
FROM dbo.ApplicationUsers AS A INNER JOIN
dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS ClientAdmin,
(SELECT MAX(A.Email) AS Expr1
FROM dbo.ApplicationUsers AS A INNER JOIN
dbo.Therapists AS T ON A.UserName = MAX(C.ClientName) AND A.Id = T.ApplicationUserId) AS Email,
(SELECT MAX(Name) AS Expr1
FROM dbo.Cities AS CY
WHERE (Id = MAX(C.CityId))) AS City,
(SELECT COUNT(*) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id)) AS TotalPatientCount,
(SELECT COUNT(*) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (IsDisabled = 0)) AS ActivePatientCount,
(SELECT MAX(CreatedWhen) AS Expr1
FROM dbo.Patients AS P
WHERE (ClientId = C.Id)) AS LastPatientAddition,
(SELECT COUNT(*) AS Expr1
FROM dbo.Treatments AS T
WHERE (ClientId = C.Id)) AS TotalTreatmentCount,
(SELECT MAX(CreatedWhen) AS Expr1
FROM dbo.Treatments AS T
WHERE (ClientId = C.Id)) AS LastTreatmentAddition,
(SELECT COUNT(*) AS Expr1
FROM dbo.Therapists AS T
WHERE (ClientId = C.Id)) AS TotalTherapistCount,
(SELECT COUNT(*) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
WHERE (A.IsDeleted = 0) AND (A.IsDisabled = 0)) AS ActiveTherapistCount,
(SELECT MAX(A.CreatedWhen) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id) AS LastTherapistAddition,
(SELECT MAX(A.LastLoginDate) AS Expr1
FROM dbo.Therapists AS T INNER JOIN
dbo.ApplicationUsers AS A ON T.ClientId = C.Id AND T.ApplicationUserId = A.Id
WHERE (A.LastLoginDate IS NOT NULL)) AS TherapistLastLoginDate, CAST((CASE WHEN
((SELECT COUNT(S.[Id])
FROM [dbo].[ClientSubscriptions] AS S
WHERE ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
(S.[SubscriptionEnd] > GETDATE())))) > 0) THEN 1 ELSE 0 END) AS BIT) AS HasActiveSubscription,
(SELECT MAX(SubscriptionEnd) AS Expr1
FROM dbo.ClientSubscriptions AS S
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
SubscriptionEnd > GETDATE())) AS LastValidSubscriptionEnd, CAST((CASE WHEN
((SELECT COUNT(S.[Id])
FROM [dbo].[ClientSubscriptions] AS S
WHERE ((S.[ClientId] = C.[Id]) AND (S.[IsDeleted] = 0) AND ((S.[SubscriptionEnd] IS NULL) OR
(S.[SubscriptionEnd] > GETDATE())) AND (S.[Id] <>
(SELECT MIN(S2.[Id])
FROM [dbo].[ClientSubscriptions] AS S2
WHERE ((S2.[ClientId] = C.[Id]) AND (S2.[IsDeleted] = 0)))))) > 0) THEN 1 ELSE 0 END) AS BIT) AS IsPayingCustomer, COALESCE
((SELECT MAX(MonthlyPrice) AS Expr1
FROM dbo.ClientSubscriptions AS S
WHERE (ClientId = C.Id) AND (IsDeleted = 0) AND (SubscriptionEnd IS NULL OR
SubscriptionEnd > GETDATE()) AND (MonthlyPrice > 0)), 0.00) AS ActiveSubscriptionMonthlyPrice, MAX(ClientStatus) AS Status, MAX(Phone1) AS Phone, MAX(Phone2) AS Phone2,
(SELECT Code
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponCode,
(SELECT IssuedTo
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponIssuedTo,
(SELECT ClientDiscount
FROM dbo.DiscountCoupons AS DC
WHERE (Code =
(SELECT TOP (1) DiscountCouponCode
FROM dbo.ClientPayments AS CP
WHERE (ClientId = C.Id)
ORDER BY Id))) AS DiscountCouponClientDiscount, COALESCE
((SELECT COUNT(Id) AS Expr1
FROM dbo.ClientFiles AS F
WHERE (ClientId = C.Id)), 0) AS TotalFilesCount, COALESCE
((SELECT SUM(FileSize) AS Expr1
FROM dbo.ClientFiles AS F
WHERE (ClientId = C.Id)), 0) / 1048576.0 AS TotalFilesSize, CAST(MAX(CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded, MAX(CrmStatus) AS CrmStatus, MAX(CrmUnuseReason)
AS CrmUnuseReason,
(SELECT COUNT(1) AS Expr1
FROM dbo.Tipulog_Crm_Calls_new AS CC
WHERE (Cust_id = C.Id)) AS CrmCallCount
FROM dbo.Clients AS C
WHERE (IsDeleted = 0)
GROUP BY Id
I will add a second answer, which is the complete sql. This has of course not been tested as we have no access to your data, but I think you should be able to debug it yourself. There are many pointers in this code that should show you how to go.
The basic thing is to take out all of the correlated queries and put them as subqueries. The only reason to do this is all the Max/Min you use - I would look at those as if they are not necessary depending on your data then you should take them out and join to the tables directly. All the subqueries are left joins - again make them normal joins if you can depending on your data.
Also took out the outside group by Id, as 99% sure this is not necessary, as is the Top 100% bit.
SELECT BusinessTitle, ClientName, ClientType AS Type, CreatedWhen,
CASE WHEN C.[CreatedBy] IS NULL THEN 'Client' ELSE 'Admin' END) AS CreatedBy,
CAST(CASE WHEN C.IsDisabled = 1 THEN 1 ELSE 0 END AS BIT) AS IsDisabled,
ReferenceSource, OtherReferenceSource, Address,
ApplicationByName.FullName AS ClientAdmin,
ApplicationByName.Email AS Email,
Cities.Name AS City,
Patients.TotalPatientCount,
Patients.ActivePatientCount,
Patients.LastPatientAddition,
Treatments.TotalTreatmentCount,
Treatments.LastTreatmentAddition,
Therapists.TotalTherapistCount,
Therapists.ActiveTherapistCount,
Therapists.LastTherapistAddition,
Therapists.TherapistLastLoginDate
CAST(CASE WHEN Subscriptions.SubscriptionCount>0 then 1 else 0 end as BIT) as HasActiveSubscription,
Subscriptions.LastValidSubscriptionEnd
CAST(Subscriptions.IsPayingCustomer AS BIT) AS IsPayingCustomer,
COALESCE(ActiveSubscriptionMonthlyPrice,0) as ActiveSubscriptionMonthlyPrice
ClientStatus AS Status, Phone1 AS Phone, Phone2 AS Phone2,
ClientPayments.DiscountCouponCode,
DiscountCoupons.IssuedTo AS DiscountCouponIssuedTo,
DiscountCoupons.ClientDiscount AS DiscountCouponClientDiscount,
COALESCE(ClientFiles.TotalFilesCount,0) AS TotalFilesCount,
COALESCE(ClientFiles.TotalFilesSize,0) AS TotalFilesSize,
CAST((CASE WHEN C.CrmEnded = 1 THEN 1 ELSE 0 END) AS BIT) AS CrmEnded,
CrmStatus, CrmUnuseReason,
Crm_Calls.CrmCallCount
FROM dbo.Clients AS C
left join (
select A.UserName,
max(T.FirstName + ' ' + T.LastName) as FullName,
max(A.Email) as Email
from dbo.ApplicationUsers A
join dbo.Therapists T on T.ApplicationUserId=A.Id
group by A.Username
) ApplicationByName on ApplicationByName.UserName=C.ClientName
join dbo.Cities on Cities.ID=c.CityID
left join (
SELECT ClientId,
COUNT(*) AS TotalPatientCount,
sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
MAX(CreatedWhen) AS LastPatientAddition
FROM dbo.Patients
GROUP BY ClientId
) Patients on Patients.ClientId = C.Id
left join (
SELECT ClientId,
COUNT(*) AS TotalTreatmentCount,
MAX(CreatedWhen) AS LastTreatmentAddition
FROM dbo.Treatments
GROUP BY ClientId
) Treatments on Treatments.ClientID = C.Id
left join (
select T.ClientId,
count(distinct T.Id) as TotalTherapistCount,
sum(case when A.IsDeleted = 0 AND A.IsDisabled = 0 then 1 else 0 end) as ActiveTherapistCount,
max(A.CreatedWhen) as LastTherapistAddition,
max(A.LastLoginDate) as TherapistLastLoginDate
from Therapists T
left join dbo.ApplicationUsers A on A.Id=T.ApplicationUserId
group by T.ClientId
) Therapists on Therapists.ClientID = C.Id
left join (
SELECT S.ClientId,
count(*) as SubscriptionCount,
MAX(SubscriptionEnd) as LastValidSubscriptionEnd,
MAX(case when MinSub.Id!=S.ID then 1 else 0 end as IsPayingCustomer,
max(case when MonthlyPrice>0 then 0 end) as ActiveSubscriptionMonthlyPrice
FROM dbo.ClientSubscriptions S
join (
select ClientId, min(Id) as Id
from dbo.ClientSubscriptions
where IsDeleted=0
group by ClientId
) MinSub on MinSub.ClientId=ClientSubscriptions.ClientId
where IsDeleted=0 and (SubscriptionEnd is null or SubscriptionEnd>getdate())
group by ClientId
) Subscriptions on Subscriptions.ClientId=C.Id
left join (
select ClientId,
DiscountCouponCode,
row_number() over(partition by ClientId, order by Id) rn
from dbo.ClientPayments
) ClientPayments on ClientPayments.ClientId=C.ID and rn=1
left join dbo.DiscountCoupons on DiscountCoupons.Code=ClientPayments.DiscountCouponCode
left join (
select ClientId,
count(*) as TotalFilesCount,
sum(FileSize)/1048576.0 as TotalFilesSize
from dbo.ClientFiles
group by ClientId
) ClientFiles on ClientFiles.ClientId=Client.Id
left join (
SELECT Cust_id, COUNT(1) AS CrmCallCount
FROM dbo.Tipulog_Crm_Calls_new
group by Cust_id
) Crm_Calls on Crm_Calls.Cust_id=C.Id
WHERE C.IsDeleted = 0
This is a very partial answer, but you asked how to refer to a table once instead of multiple times in multiple subqueries.
This is an example of how you would replace all those subqueries to the Patients & Treatments tables, and also the cities table. You really need to learn about joins.
FROM dbo.Clients AS C
join dbo.Cities on Cities.ID=c.CityID
left join (
SELECT ClientId,
COUNT(*) AS TotalPatientCount,
sum(case when IsDeleted = 0 AND IsDisabled = 0 then 1 else 0 end) AS ActivePatientCount,
MAX(CreatedWhen) AS LastPatientAddition
FROM dbo.Patients
GROUP BY ClientId
) Patients on Patients.ClientId = C.Id
left join (
SELECT ClientId,
COUNT(*) AS TotalTreatmentCount,
MAX(CreatedWhen) AS LastTreatmentAddition
FROM dbo.Treatments
GROUP BY ClientId
) Treatments on Treatments.ClientID = C.Id
Then your column list replaces the subqueries to Patients and City like this:
Cities.Name AS City,
Patients.TotalPatientCount,
Patients.ActivePatientCount,
Patients.LastPatientAddition,
Treatments.TotalTreatmentCount,
Treatments.LastTreatmentAddition,
That should at least give you an idea.
I have the following query in postgres that I now need to run on SQL Server. Obviously I have already changed the trunc per round and basic things, but mainly I have a problem in the principle select distinct on (c.cod_socio) tbl. * Since SQL Server does not recognize that syntax.
select distinct on (c.cod_socio)
tbl.*, h.cod_oficina, h.cod_transaccion, h.num_transaccion,
h.num_sec, h.fec_movimiento
from
sgf_det_mov_his h
inner join
sgf_cuenta c on c.cod_producto = h.cod_producto and c.cod_cuenta = h.cod_cuenta
inner join
sgf_tran t on t.cod_transaccion = h.cod_transaccion and t.cod_oficina = h.cod_oficina and t.cod_tipo_transaccion in ('DA', 'DP','NC')
inner join
(select
sgf_cuenta.cod_socio,
sum(trunc(sgf_det_mov_his.val_efectivo, 0) + trunc(sgf_det_mov_his.val_cheques, 0)) as total
from
sgf_det_mov_his, sgf_cuenta, sgf_tran
where
sgf_cuenta.cod_producto = sgf_det_mov_his.cod_producto
and sgf_cuenta.cod_cuenta = sgf_det_mov_his.cod_cuenta
and sgf_det_mov_his.sts_mov = 'A'
and sgf_tran.cod_transaccion = sgf_det_mov_his.cod_transaccion
and sgf_tran.cod_oficina = sgf_det_mov_his.cod_oficina
and sgf_cuenta.cod_producto <> 2
and sgf_tran.cod_tipo_transaccion in ('DA', 'DP','NC')
and isnull(sgf_tran.cod_uaf, 0) > 0
and isnull(sgf_tran.cod_uaf, 0) not in (71)
and sgf_cuenta.cod_cuenta not in (select cod_cuenta
from sgf_credito
where sgf_credito.cod_producto = sgf_cuenta.cod_producto
and sgf_credito.cod_cuenta = sgf_cuenta.cod_cuenta
and sts_operacion in ('A'))
and date(sgf_det_mov_his.fec_movimiento) between '2015-01-01' and '2019-01-01'
group by
sgf_cuenta.cod_socio
having
sum(trunc(sgf_det_mov_his.val_efectivo,0) + trunc(sgf_det_mov_his.val_cheques,0)) >= 5000) tbl on tbl.cod_socio = c.cod_socio
where
date(h.fec_movimiento) between '2015-01-01' and '2019-01-01'
order by
c.cod_socio, h.fec_movimiento desc
distinct on (...) simply retains the "first row" which may be emulated using row_number() over(...) and a following where clause predicate that limits to one row per partition.. Note that distinct on relies on the order by clause to decide the "first row", so you need the equivalent conditions in the over clause. Also note if you after greater query compatibility between the two databases you could use the same row_number approach in PostgreSQL.
SELECT
*
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY c.cod_socio ORDER BY h.fec_movimiento DESC) AS rn
, c.cod_socio
, tbl.*
, h.cod_oficina
, h.cod_transaccion
, h.num_transaccion
, h.num_sec
, h.fec_movimiento
FROM sgf_det_mov_his h
INNER JOIN sgf_cuenta c ON c.cod_producto = h.cod_producto
AND c.cod_cuenta = h.cod_cuenta
INNER JOIN sgf_tran t ON t.cod_transaccion = h.cod_transaccion
AND t.cod_oficina = h.cod_oficina
AND t.cod_tipo_transaccion IN ('DA', 'DP', 'NC')
INNER JOIN (
SELECT
sgf_cuenta.cod_socio
, SUM(trunc(sgf_det_mov_his.val_efectivo, 0) + trunc(sgf_det_mov_his.val_cheques, 0)) AS total
FROM sgf_det_mov_his
, sgf_cuenta
, sgf_tran
WHERE sgf_cuenta.cod_producto = sgf_det_mov_his.cod_producto
AND sgf_cuenta.cod_cuenta = sgf_det_mov_his.cod_cuenta
AND sgf_det_mov_his.sts_mov = 'A'
AND sgf_tran.cod_transaccion = sgf_det_mov_his.cod_transaccion
AND sgf_tran.cod_oficina = sgf_det_mov_his.cod_oficina
AND sgf_cuenta.cod_producto <> 2
AND sgf_tran.cod_tipo_transaccion IN ('DA', 'DP', 'NC')
AND ISNULL(sgf_tran.cod_uaf, 0) > 0
AND ISNULL(sgf_tran.cod_uaf, 0) NOT IN (71)
AND sgf_cuenta.cod_cuenta NOT IN (
SELECT
cod_cuenta
FROM sgf_credito
WHERE sgf_credito.cod_producto = sgf_cuenta.cod_producto
AND sgf_credito.cod_cuenta = sgf_cuenta.cod_cuenta
AND sts_operacion IN ('A')
)
AND DATE(sgf_det_mov_his.fec_movimiento) BETWEEN '2015-01-01' AND '2019-01-01'
GROUP BY
sgf_cuenta.cod_socio
HAVING SUM(trunc(sgf_det_mov_his.val_efectivo, 0) + trunc(sgf_det_mov_his.val_cheques, 0)) >= 5000
) tbl ON tbl.cod_socio = c.cod_socio
WHERE DATE(h.fec_movimiento) BETWEEN '2015-01-01' AND '2019-01-01'
) AS d
WHERE d.rn = 1
ORDER BY
d.cod_socio
, d.fec_movimiento DESC