Output transactions that exceed certain amount within 3 days - c#

In a month, seperate by AccountNo, all transaction that exceeds 10,000 amount within 3 days must be outputted.
This is a sample table:
AccountNo-----Date------------Amount
1-------------5/1/17----------8000
1-------------5/3/17----------1000
1-------------5/4/17----------1000
1-------------5/6/17----------1000
2-------------5/7/17----------3000
2-------------5/10/17---------2000
2-------------5/13/17---------2000
2-------------5/13/17---------3000
3-------------5/14/17---------3000
3-------------5/15/17---------3000
3-------------5/16/17---------9000
4-------------5/17/17---------1000
5-------------5/18/17---------1000
5-------------5/19/17---------1000
5-------------5/20/17---------1000
The result must be:
AccountNo-----Date------------Amount
1-------------5/1/17----------8000
1-------------5/3/17----------1000
1-------------5/4/17----------1000
2-------------5/7/17----------3000
2-------------5/10/17---------2000
2-------------5/13/17---------2000
2-------------5/13/17---------3000
3-------------5/14/17---------3000
3-------------5/15/17---------3000
3-------------5/16/17---------9000
There is a code given to me, but it's not completely working yet.
Select A.*
From YourTable A
Cross Apply (
Select RT1 = sum(case when [Date] <= B2.TstDate then [Amount] else 0 end)
,RT2 = sum(case when [Date] >= B2.TstDate then [Amount] else 0 end)
From YourTable B1
Cross Join (Select TstDate=A.[Date]) B2
Where [Date] between DateAdd(DAY,-2,A.[Date]) and DateAdd(DAY,2,A.[Date])
and Year([Date])=Year(TstDate)
and Month([Date])=Month(TstDate)
) B
Where RT1>=10000 or RT2>=10000

Common table expression: TransInfo
TransInfo is a table with account, year, month, day, amount
The main query:
The [Tmiddle] query adds a row_number based on amount per day per month per accountno
The [Tmiddle] is joined again with TransInfo to limit the results to top 3 of a month AS [Touter] where the top 3 is >= 10.000
The outer most query combines the result with a table again to gain the complete transaction information again
In short:
Filter data on top 3 amounts per day per month per year per account
Check if sum of amount >= 10.000
Show results
Query:
WITH [TransInfo] ([AccountNo], [Year], [Month], [Day], [Amount], [Rownumber])
AS
(
SELECT [AccountNo]
,[Year]
,[Month]
,[Day]
,[Amount]
,ROW_NUMBER() OVER
(
PARTITION BY [AccountNo],
[Year],
[Month]
ORDER BY [Amount] DESC
) AS [Rownumber]
FROM
(
SELECT [AccountNo]
,DATEPART(YEAR, [Date]) AS [Year]
,DATEPART(MONTH, [Date]) AS [Month]
,DATEPART(DAY, [Date]) AS [Day]
,SUM([Amount]) AS [Amount]
FROM [Test].[dbo].[Data]
GROUP BY [AccountNo],
DATEPART(MONTH, [Date]),
DATEPART(YEAR, [Date]),
DATEPART(DAY, [Date])
) AS [Tinner]
)
SELECT [Data].[AccountNo]
,[Data].[Date]
,[Data].[Amount]
FROM [Test].[dbo].[Data]
INNER JOIN
(
SELECT [TransInfo].[AccountNo]
,[TransInfo].[Year]
,[TransInfo].[Month]
,[TransInfo].[Day]
,[TransInfo].[Amount]
FROM [TransInfo]
INNER JOIN
(
SELECT [AccountNo]
,[Year]
,[Month]
FROM [TransInfo]
WHERE [TransInfo].[Rownumber] <= 3
GROUP BY [TransInfo].[AccountNo],
[TransInfo].[Year],
[TransInfo].[Month]
HAVING SUM ([TransInfo].[Amount]) >= 10000
) AS [Tmiddle]
ON [Tmiddle].[AccountNo] = [TransInfo].[AccountNo]
AND [Tmiddle].[Year] = [TransInfo].[Year]
AND [Tmiddle].[Month] = [TransInfo].[Month]
WHERE [TransInfo].[Rownumber] <= 3
) AS [Touter]
ON [Data].[AccountNo] = [TOuter].[AccountNo]
AND DATEPART(YEAR, [Data].[Date]) = [TOuter].[Year]
AND DATEPART(MONTH, [Data].[Date]) = [TOuter].[Month]
AND DATEPART(DAY, [Data].[Date]) = [TOuter].[Day]
Result: Left is query result, right is complete table

Does this work as expected? You may need to change the 2's to 3's depending on what you mean by 'within 3 days'.
SELECT DISTINCT C.*
FROM YourTable C
INNER JOIN (
SELECT a.AccountNo, a.Date
FROM YourTable a
INNER JOIN YourTable b
ON a.AccountNo = b.AccountNo
AND DATEADD(DAY, 2, a.Date) <= b.Date AND b.Date >= a.Date
GROUP BY a.AccountNo, a.Date
HAVING SUM(b.Amount) > 10000
) d
ON C.AccountNo = d.AccountNo
AND DATEADD(DAY, 2, D.Date) <= C.Date AND C.Date >= D.Date

Related

How to use limit the results returned from SQL Server using ROW_NUMBER()

I have database as below
CREATE DATABASE Test2;
CREATE TABLE table1
(
name nvarchar(50),
year int,
total1 int,
total2 int
);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('a', 2020, 25,3);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('b', 2018, 33,4);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('c', 2020, 10,3);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('b', 2018, 7,2);
INSERT INTO table1 (name, year, total1,total2)
VALUES ('a', 2020, 20,6);
I want to limit the results returned from SQL Server (take 2nd row and 3rd row) with this code
select
*
from
(select
year, name,
sum(total1) as "sum_Total1",
sum(total2) as "sum_Total2",
round((cast(isnull(sum(total2), 0) as float)) / (cast(sum(total1) as float)), 3) as "sum_Total2/sum_Total1",
row_number() over (order by round((cast(isnull(sum(total2), 0) as float)) / (cast(sum(total1) as float)), 3) asc) as no
from
Table_1
group by
name, year
order by
round((cast(isnull(sum(total2), 0) as float)) * 100 / (cast(sum(total1) as float)), 3) asc) a
where
a.no > 1 and a.no < 3
SQL Server return an error:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
Actually since you have already used the Order By in this line ROW_NUMBER() over(ORDER BY ROUND you don't need to use it again in your inner query after grouping. So all that you need to do is removing the unnecessary order by after your group by keyword.
Also FYI, I can see that you've queried from Table_1 while your table name is table1, so you need to fix it as well.
there are two issues:
You are not using the same table which you created.
to get the 2nd and 3rd row, you'll need to change the condition a.no < 3 to a.no <= 3
there is no use of order by clause as we have no.
Finally:
SELECT *
FROM
(
SELECT year,
name,
SUM(total1) AS "sum_Total1",
SUM(total2) AS "sum_Total2",
ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) / (CAST(SUM(total1) AS FLOAT)), 3) AS "sum_Total2/sum_Total1",
ROW_NUMBER() OVER(
ORDER BY ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) / (CAST(SUM(total1) AS FLOAT)), 3) ASC) AS no
FROM Table1
GROUP BY name,
year
--ORDER BY ROUND((CAST(ISNULL(SUM(total2), 0) AS FLOAT)) * 100 / (CAST(SUM(total1) AS FLOAT)), 3) ASC
) a
WHERE a.no > 1
AND a.no <= 3;
Just move the order by outside your subquery:
select *
from
(
select year ,name,
sum(total1) as "sum_Total1",
SUM(total2) as "sum_Total2",
ROUND((CAST(ISNULL(sum(total2),0) as float))/
(CAST(sum(total1) as float)),3) as "sum_Total2/sum_Total1",
ROW_NUMBER() over (ORDER BY
ROUND((CAST(ISNULL(sum(total2),0) as float))/ (CAST(sum(total1) as float)),3) ASC ) as no
from Table1
group by name, year
) a
where a.no > 1 and a.no < 4
order by no;

How to Convert Sub Query to Joins for Fast Result?

I want to convert Sub Queries into joins to improve performance.
The following sub-queries take to long to load.
SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
(SELECT TOP 1 b.Level
from Microframe.dbo.TrackMessages b
where b.IMEI = a.IMEI
AND b.Timestamp >= #Start
order by b.Timestamp ) AS Level,
(select top 1 b.Timestamp
from Microframe.dbo.TrackMessages b
where b.IMEI = a.IMEI
AND b.Timestamp >= #Start
order by b.Timestamp ) AS TimeStamp,
(SELECT top 1 b.Temp
from Microframe.dbo.TrackMessages b
where b.IMEI = a.IMEI
AND b.Timestamp >= #Start
order by b.Timestamp ) AS Temp
FROM GatexServerDB.dbo.device as a
JOIN GatexReportsDB.dbo.tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})
You can move the subquery to the FROM clause and use CROSS APPLY. Since you seem to be dealing with IoT data though, you should investigate T-SQL's ranking, windowing and analytic functions. Performance will depend heavily on the table's indexes.
Given these tables :
create table #TrackMessages (
Message_ID bigint primary key,
imei nvarchar(50) ,
[timestamp] datetime2,
Level int,
temp numeric(5,2)
);
create table #device (
imei nvarchar(50) primary key,
owner_id int
);
create table #tbl_static_tank_info (
tank_id int not null primary key,
tank_name nvarchar(20),
fuel_type nvarchar(20),
capacity numeric(9,2),
owner_id int,
client_id int
)
And indexes :
create nonclustered index IX_MSG_IMEI_Time on #TrackMessages (imei,timestamp) include(level,temp) ;
create INDEX IX_Device_OwnerID on #device (Owner_ID)
create INDEX IX_Tank_Client on #tbl_static_tank_info (Client_ID);
create INDEX IX_Tank_Owner on #tbl_static_tank_info (Owner_ID);
The TOP 1 query would look like this :
SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
Level,
TimeStamp,
Temp
FROM #device as a
inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
cross apply (SELECT top 1 imei,Temp,Level,timestamp
from #TrackMessages b
where b.IMEI = a.imei
AND b.Timestamp >= #start
order by b.Timestamp ) msg
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)
If there is a 1-M relation between tanks, devices and messages, the FIRST_VALUE analytic function can be used to return the first record ber device, without using a subquery :
SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
first_value(Temp) over (partition by b.imei order by timestamp) as temp,
first_value(Level) over (partition by b.imei order by timestamp) as level,
min(timestamp) over (partition by b.imei) as timestamp
from #TrackMessages b
inner join #device as a on b.IMEI = a.imei
inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)
Performance will depend heavily on the indexes, the table statistics and whether the index and OVER order matches.
This query can be modified to return both the first and last value per device using LAST_VALUE :
SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
first_value(Temp) over (partition by b.imei order by timestamp) as StartTemp,
first_value(Level) over (partition by b.imei order by timestamp) as StartLevel,
min(timestamp) over (partition by b.imei) as StartTime,
last_value(Temp) over (partition by b.imei order by timestamp) as EndTemp,
lastt_value(Level) over (partition by b.imei order by timestamp) as EndLevel,
max(timestamp) over (partition by b.imei) as EndTime
from #TrackMessages b
inner join #device as a on b.IMEI = a.imei
inner JOIN #tbl_static_tank_info as c ON c.tank_id = a.owner_id
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN (1,5,7)
the server would have to sort the measurements both by ascending timestamp order (that's what the IX_MSG_IMEI_Time index already does) and descending order.
Here's a solution with CROSS APPLY which is like a function you can declare on the go and use it as a joining clause. You can change CROSS APPLY to OUTER APPLY if the returning set might not exist, in this case if there might not be any record on TrackMessages for a particular IMEI (will return NULL values).
SELECT
c.tank_name,
c.fuel_type,
c.capacity,
c.tank_id,
T.Level,
T.Timestamp,
T.Temp
FROM GatexServerDB.dbo.device as a
JOIN GatexReportsDB.dbo.tbl_static_tank_info as c ON c.tank_id = a.owner_id
CROSS APPLY (
SELECT TOP 1 -- Retrieve only the first record
-- And return as many columns as you need
b.Level,
b.Timestamp,
b.Temp
FROM
Microframe.dbo.TrackMessages AS b
WHERE
a.IMEI = b.IMEI AND -- With matching IMEI
b.Timestamp >= #Start
ORDER BY
b.Timestamp) T -- Ordered by Timestamp
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})
However I believe the key point here would be indexes on your tables. If you are already sure that the problem is the subquery, then make sure that TrackMessages has the following index:
CREATE NONCLUSTERED INDEX NCI_TrackMessages_IMEI_TimeStamp ON Microframe.dbo.TrackMessages (IMEI, Timestamp)
Indexes have pros and cons, make sure to check them out before creating or dropping one.
Not having the structures, my guees for the solution is:
WITH CTE AS
(SELECT B.IMEI,
b.Level,
b.Timetamp,
b.Temp,
ROW_NUMBER() OVER (PARTITION BY b.IMEI ORDER BY Timestamp) AS Row
FROM Microframe.dbo.TrackMessages b
WHERE b.Timestamp >= #Start
)
SELECT c.tank_name, c.fuel_type, c.capacity, c.tank_id,
CTE.Level, CTE.Timestamp, CTE.Temp
FROM GatexServerDB.dbo.device as a
INNER JOIN GatexReportsDB.dbo.tbl_static_tank_info as c ON c.tank_id = a.owner_id
INNER JOIN CTE ON CTE.IMEI = a.IMEI
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})
AND CTE.Row = 1;
I cannot test it but it should be very close to the solution. Please, confirm if it works.
You can compare and go with either of the below solutions
The JOIN way where ordering is done via row-number windowing function
SELECT * FROM
(
SELECT
c.tank_name,
c.fuel_type,
c.capacity,
c.tank_id,
Level=b.Level,
TimeStamp=b.Timestamp,
Temp=b.Temp,
r=Row_number() over ( order by b.timestamp)
FROM GatexServerDB.dbo.device as a
JOIN GatexReportsDB.dbo.tbl_static_tank_info as c
ON c.tank_id = a.owner_id
JOIN Microframe.dbo.TrackMessages as b
ON b.IMEI = a.IMEI AND b.Timestamp >= #Start
WHERE c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})
)T
where r=1
or the CROSS APPLY way like below
SELECT * FROM
(
SELECT
c.tank_name, c.fuel_type, c.capacity, c.tank_id
FROM GatexServerDB.dbo.device as a
JOIN GatexReportsDB.dbo.tbl_static_tank_info as c
ON c.tank_id = a.owner_id
AND c.client_id = 65
AND a.IMEI IS NOT NULL
AND c.tank_id IN ({Tanks})
) A
CROSS APPLY
(
SELECT
TOP 1
b.Level, b.Timestamp,b.Temp
FROM Microframe.dbo.TrackMessages b
WHERE b.IMEI = a.IMEI
AND b.Timestamp >= #Start
ORDER BY b.Timestamp
)D

SQL - Get an average price of the last 7 days

I have a table that looks like this:
timestamp | commodity | amount | price |
I am trying to get the weighted average of a commodity ,for each day from the last 7 days(In order to create a graph, c# program), I managed to create a query to get the weighted average:
SELECT SUM(price * amount),
SUM(amount)
FROM [table_name]
WHERE (commodity = #commodity)
AND (timestamp >= #fromDate
AND timestamp <= #toDate);
So, I send this query 7 times, each time adding (-1) to the fromDate and toDate, to get the averages of last 7 days.
I would like to ask if there is a way to do this in 1 query, and if there is, if it can be implemented to get the same information of the last month.
you can use convert to date and do code as below:
SELECT convert(date, timestamp) as [Dates],
SUM(price * amount),
SUM(amount)
FROM [table_name]
WHERE (commodity = #commodity)
AND (timestamp >= #fromDate
AND timestamp <= #toDate)
GROUP BY convert(date, timestamp)
you have to group by date
SELECT SUM(price* amount),SUM(amount) FROM [table_name] WHERE (commodity = #commodity) AND (cast(timestamp as date)>= #fromDate AND cast(timestamp as date)<= #toDate)
Group By cast(timestamp as date)
You should to group your data by date:
SELECT SUM(price * amount),SUM(amount)
FROM [table_name]
WHERE (commodity = #commodity)
AND (timestamp >= #fromDate
AND timestamp <= #toDate)
GROUP BY CAST(timestamp as DATE);
To generate dates, you can use as below:
;with Datescte as (
Select top (datediff(day, #fromDate, #toDate) +1) Dt = Dateadd(day, Row_Number() over (order by (Select null))-1, #fromDate)
from master..spt_values s1, master..spt_values s2
), cte2 as (
SELECT convert(date, timestamp) as [Dates],
SUM(price * amount) as [Sum_Price_Amount],
SUM(amount) as [Sum_Amount]
FROM [table_name]
WHERE (commodity = #commodity)
AND (timestamp >= #fromDate
AND timestamp <= #toDate)
GROUP BY convert(date, timestamp)
)
Select Dt as [Dates] , Sum_Price_Amount, Sum_Amount
FROM Datescte d
LEFT JOIN cte2 d2
ON d.Dt = d2.Dates

SQL Server - T-SQL UNION ALL + PIVOT

Is there a way that I can PIVOT the following SQL Query so I can retain the 3 different column names in each individual query as 1 row. At the moment it gives me the results as 3 rows under 1 column name from the first query.
SELECT COUNT(sdo.Id) AS [TotalNew] FROM [SubscriberDebitOrder] sdo WHERE sdo.[CampaignId]=#p0 AND sdo.[CreatedOn] >=
(
SELECT CAST(MAX(sdo.[CreatedOn]) AS DATE) FROM [SubscriberDebitOrder] sdo WHERE sdo.[CampaignId]=#p0
)
UNION ALL
SELECT COUNT(sdo.Id) AS [TotalNewSuccess] FROM [SubscriberDebitOrder] sdo
INNER JOIN [Collections] c ON c.[DebitOrderId]=sdo.[Id]
WHERE sdo.[CampaignId]=#p0 AND sdo.[CreatedOn] >=
(
SELECT CAST(MAX(sdo.[CreatedOn]) AS DATE) FROM [SubscriberDebitOrder] sdo WHERE sdo.[CampaignId]=#p0
) AND c.TxnStatus='S'
UNION ALL
SELECT COUNT(sdo.Id) AS [TotalNewFailed] FROM [SubscriberDebitOrder] sdo
INNER JOIN [Collections] c ON c.[DebitOrderId]=sdo.[Id]
WHERE sdo.[CampaignId]=#p0 AND sdo.[CreatedOn] >=
(
SELECT CAST(MAX(sdo.[CreatedOn]) AS DATE) FROM [SubscriberDebitOrder] sdo WHERE sdo.[CampaignId]=#p0
) AND c.TxnStatus='U'
Thanks in advance.
You can simplify the query to be the one below.
SELECT
COUNT(*) AS [TotalNew]
,sum(case when c.TxnStatus='S' then 1 else 0 end) as [TotalNewSuccess]
,sum(case when c.TxnStatus='U' then 1 else 0 end) as [TotalNewFailed]
FROM [SubscriberDebitOrder] sdo
INNER JOIN [Collections] c ON c.[DebitOrderId]=sdo.[Id]
WHERE sdo.[CampaignId]=#p0
AND sdo.[CreatedOn] >=
(
SELECT CAST(MAX([CreatedOn]) AS DATE)
FROM [SubscriberDebitOrder]
WHERE [CampaignId] = #p0
)

Entity Framework SUM CASE not optimized

I'm trying to write a simple SQL query in LinQ, and no matter how hard I try, I always get a complex query.
Here is the SQL I am trying to achieve (this is not what I'm getting):
SELECT
ClearingAccounts.ID,
SUM(CASE WHEN Payments.StatusID = 1 THEN Payments.TotalAmount ELSE 0 END) AS Sum1,
SUM(CASE WHEN DirectDebits.StatusID = 2 THEN DirectDebits.TotalAmount ELSE 0 END) AS Sum2,
SUM(CASE WHEN Payments.StatusID = 2 THEN Payments.TotalAmount ELSE 0 END) AS Sum3,
SUM(CASE WHEN DirectDebits.StatusID = 1 THEN DirectDebits.TotalAmount ELSE 0 END) AS Sum4
FROM ClearingAccounts
LEFT JOIN Payments ON Payments.ClearingAccountID = ClearingAccounts.ID
LEFT JOIN DirectDebits ON DirectDebits.ClearingAccountID = ClearingAccounts.ID
GROUP BY ClearingAccounts.ID
Here is the code:
from clearingAccount in clearingAccounts
let payments = clearingAccount.Payments
let directDebits = clearingAccount.DirectDebits
select new
{
ID = clearingAccount.ID,
Sum1 = payments.Sum(p => p.StatusID == 1 ? p.TotalAmount : 0),
Sum2 = directDebits.Sum(p => p.StatusID == 2 ? p.TotalAmount : 0),
Sum3 = payments.Sum(p => p.StatusID == 2 ? p.TotalAmount : 0),
Sum4 = directDebits.Sum(p => p.StatusID == 1 ? p.TotalAmount : 0),
}
The generated query gets the data from the respective table for each sum, so four times. I'm not sure if it's even possible to optimize this?
EDIT Here the is generated query:
SELECT
[Project5].[ID] AS [ID],
[Project5].[C1] AS [C1],
[Project5].[C2] AS [C2],
[Project5].[C3] AS [C3],
[Project5].[C4] AS [C4]
FROM ( SELECT
[Project4].[ID] AS [ID],
[Project4].[C1] AS [C1],
[Project4].[C2] AS [C2],
[Project4].[C3] AS [C3],
(SELECT
SUM([Filter5].[A1]) AS [A1]
FROM ( SELECT
CASE WHEN (1 = [Extent5].[StatusID]) THEN [Extent5].[TotalAmount] ELSE cast(0 as decimal(18)) END AS [A1]
FROM [dbo].[DirectDebits] AS [Extent5]
WHERE [Project4].[ID] = [Extent5].[ClearingAccountID]
) AS [Filter5]) AS [C4]
FROM ( SELECT
[Project3].[ID] AS [ID],
[Project3].[C1] AS [C1],
[Project3].[C2] AS [C2],
(SELECT
SUM([Filter4].[A1]) AS [A1]
FROM ( SELECT
CASE WHEN (2 = [Extent4].[StatusID]) THEN [Extent4].[TotalAmount] ELSE cast(0 as decimal(18)) END AS [A1]
FROM [dbo].[Payments] AS [Extent4]
WHERE [Project3].[ID] = [Extent4].[ClearingAccountID]
) AS [Filter4]) AS [C3]
FROM ( SELECT
[Project2].[ID] AS [ID],
[Project2].[C1] AS [C1],
(SELECT
SUM([Filter3].[A1]) AS [A1]
FROM ( SELECT
CASE WHEN (2 = [Extent3].[StatusID]) THEN [Extent3].[TotalAmount] ELSE cast(0 as decimal(18)) END AS [A1]
FROM [dbo].[DirectDebits] AS [Extent3]
WHERE [Project2].[ID] = [Extent3].[ClearingAccountID]
) AS [Filter3]) AS [C2]
FROM ( SELECT
[Project1].[ID] AS [ID],
(SELECT
SUM([Filter2].[A1]) AS [A1]
FROM ( SELECT
CASE WHEN (1 = [Extent2].[StatusID]) THEN [Extent2].[TotalAmount] ELSE cast(0 as decimal(18)) END AS [A1]
FROM [dbo].[Payments] AS [Extent2]
WHERE [Project1].[ID] = [Extent2].[ClearingAccountID]
) AS [Filter2]) AS [C1]
FROM ( SELECT
[Extent1].[ID] AS [ID]
FROM [dbo].[ClearingAccounts] AS [Extent1]
WHERE ([Extent1].[CustomerID] = 3) AND ([Extent1].[Deleted] <> 1)
) AS [Project1]
) AS [Project2]
) AS [Project3]
) AS [Project4]
) AS [Project5]
Edit
Note that as per #usr's comment, that your original Sql Query is broken. By LEFT OUTER joining on two independent tables, and then grouping on the common join key, as soon as one of the DirectDebits or Payments tables returns more than one row, you will erroneously duplicate the TotalAmount value in the 'other' SUMmed colums (and vice versa). e.g. If a given ClearingAccount has 3 DirectDebits and 4 Payments, you will get a total of 12 rows (whereas you should be summing 3 and 4 rows independently for the two tables). A better Sql Query would be:
WITH ctePayments AS
(
SELECT
ClearingAccounts.ID,
-- Note the ELSE 0 projection isn't required as nulls are eliminated from aggregates
SUM(CASE WHEN Payments.StatusID = 1 THEN Payments.TotalAmount END) AS Sum1,
SUM(CASE WHEN Payments.StatusID = 2 THEN Payments.TotalAmount END) AS Sum3
FROM ClearingAccounts
INNER JOIN Payments ON Payments.ClearingAccountID = ClearingAccounts.ID
GROUP BY ClearingAccounts.ID
),
cteDirectDebits AS
(
SELECT
ClearingAccounts.ID,
SUM(CASE WHEN DirectDebits.StatusID = 2 THEN DirectDebits.TotalAmount END) AS Sum2,
SUM(CASE WHEN DirectDebits.StatusID = 1 THEN DirectDebits.TotalAmount END) AS Sum4
FROM ClearingAccounts
INNER JOIN DirectDebits ON DirectDebits.ClearingAccountID = ClearingAccounts.ID
GROUP BY ClearingAccounts.ID
)
SELECT ca.ID, COALESCE(p.Sum1, 0) AS Sum1, COALESCE(d.Sum2, 0) AS Sum2,
COALESCE(p.Sum3, 0) AS Sum3, COALESCE(d.Sum4, 0) AS Sum4
FROM
ClearingAccounts ca
LEFT OUTER JOIN ctePayments p
ON ca.ID = p.ID
LEFT OUTER JOIN cteDirectDebits d
ON ca.ID = d.ID;
-- GROUP BY not required, since we have already guaranteed at most one row
-- per joined table in the CTE's, assuming ClearingAccounts.ID is unique;
You'll want to fix and test this with test cases before you even contemplate conversion to LINQ.
Old Answer(s)
The Sql construct:
SELECT SUM(CASE WHEN ... THEN 1 ELSE 0 END) AS Something
when applied in a SELECT list, is a common hack 'alternative' to pivot data from the 'greater' select into columns which meet the projection criteria (and hence the zero if not matched) . It isn't really a sum at all, its a 'matched' count.
With regards to optimizing the Sql generated, another alternative would be to materialize the data after joining and grouping (and of course, if there is a predicate WHERE clause, apply that in Sql too via IQueryable), and then do the conditional summation in memory:
var result2 = Db.ClearingAccounts
.Include(c => c.Payments)
.Include(c => c.DirectDebits)
.GroupBy(c => c.Id)
.ToList() // or any other means to force materialization here.
.ToDictionary(
grp => grp.Key,
grp => new
{
PaymentsByStatus = grp.SelectMany(x => x.Payments)
.GroupBy(p => p.StatusId),
DirectDebitByStatus = grp.SelectMany(x => x.Payments)
.GroupBy(p => p.StatusId),
})
.Select(ca => new
{
ID = ca.Key,
Sum1 = ca.Value.PaymentsByStatus.Where(pbs => pbs.Key == 1)
.Select(pbs => pbs.Select(x => x.TotalAmount).Sum()),
Sum2 = ca.Value.DirectDebitByStatus.Where(pbs => pbs.Key == 2)
.Select(ddbs => ddbs.Select(x => x.TotalAmount).Sum()),
Sum3 = ca.Value.PaymentsByStatus.Where(pbs => pbs.Key == 2)
.Select(pbs => pbs.Select(x => x.TotalAmount).Sum()),
Sum4 = ca.Value.DirectDebitByStatus.Where(pbs => pbs.Key == 1)
.Select(ddbs => ddbs.Select(x => x.TotalAmount).Sum())
});
However, personally, I would leave this pivot projection directly in Sql, and then use something like SqlQuery to then deserialize the result back from Sql
directly into the final Entity type.
1)
Add AsNoTracking in EF to avoid tracking changes.
Check that you have indexes on the columns you are using for the JOINs. Especially the column that you are using to group by. Profile the query and optimize it. EF has also overhead over a stored procedure.
or
2) If you cannot find a way to make it as fast as you need, create a stored procedure and call it from EF. Even the same query will be faster.

Categories

Resources