Gettings grouped sums from related tables into linq query columns - sql-server

I would like to query a table (Accounts) and also as part of the query get totals (Total) from another table (AccountPositions). How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement... but the group by usage is confusing me.
select
(select sum(ap.Quantity * ap.LastPrice) from AccountPositions ap
where ap.AccountId = a.Accountid and ap.IsOpen = 0) as Total,
a.AccountId, a.Username, a.FirstName, a.LastName, a.AccountNumber
from Accounts a
where a.IsTrading = 1
Something like???
var query = (from a in _context.Accounts
join ap in _context.AccountPositions on ap.AccountId = a.AccountId
where a.IsTrading == true && and ap.IsOpen == true
select new {
Total = ap.Sum(r => r.Quantity * r.LastPrice),
AccountId = a.AccountId,
Username = a.Username,
FirstName = a.FirstName,
LastName = a.LastName,
AccountNumber = a.AccountNumber
});
Desired Result:
Total AccountId Username FirstName LastName AccountNumber
2500 496 brice Brian Rice U399445
160 508 jdoe John Doe U555322
Tables:
Accounts
AccountId Username FirstName LastName AccountNumber IsTrading
496 brice Brian Rice U399445 1
498 bmarley Bob Marley U443992 0
508 jdoe John Doe U555332 1
AccountPositions
AccountPositionId AccountId Quantity LastPrice IsOpen
23 496 10 200 1
24 496 15 48 0
25 508 8 20 1
26 498 18 35 1
27 496 5 100 1

How can I rewrite this sql query as a linq query? Is it as easy as adding a group by statement...
It's even easier than that, because the SQL query in question uses single aggregate returning correlated subquery in the select clause, so the translation to LINQ is literally one to one - just use the corresponding C# operators and remember that in LINQ select goes last. And aggregate methods like Sum are outside the LINQ query syntax:
var query =
from a in _context.Accounts
where a.IsTrading
select new
{
Total = (from ap in _context.AccountPositions
where ap.AccountId == a.AccountId && ap.IsOpen
select ap.Quantity * ap.LastPrice).Sum(),
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};
But LINQ allows you to mix query syntax with method syntax, so the Sum part can be written more naturally using the later like this:
Total = _context.AccountPositions
.Where(ap => ap.AccountId == a.AccountId && ap.IsOpen)
.Sum(ap => ap.Quantity * ap.LastPrice),
Finally, if you are using Entity Framework, the relationship between the Account and AccountPosition will be expressed by something like
public ICollection<AccountPosition> AccountPositions { get; set; }
navigation property inside Account class. Which allows to forget about the join (correlation) conditions like ap.AccountId == a.AccountId - they will be applied automatically, and concentrate just on your query logic (see Don’t use Linq’s Join. Navigate!), e.g.
Total = a.AccountPositions.Where(ap => ap.IsOpen).Sum(ap => ap.Quantity * ap.LastPrice),
Inside LINQ query, the EF collection navigation property represents the so called grouped join - an unique LINQ feature (at least doesn't have direct equivalent in SQL). In LINQ query syntax it's expressed as join + into clauses. Let say you need more than one aggregate. Grouped join will help you to achieve that w/o repeating the correlation part.
So with your model, the query using grouped join will start like this:
from a in _context.Accounts
join ap in _context.AccountPositions on a.AccountId equals ap.AccountId into accountPositions
From here you have 2 variables - a representing an Account and accountPositions representing the set of the AccountPosition related to that Account.
But you are interested only in open positions. In order to avoid repeating that condition, you can use another LINQ query syntax construct - the let clause. So the query continues with both filters:
where a.IsTrading
let openPositions = accountPositions.Where(ap => ap.IsOpen)
Now you have all the information to produce the final result, for instance:
select new
{
// Aggregates
TotalPrice = openPositions.Sum(ap => ap.Quantity * ap.LastPrice),
TotalQuantity = openPositions.Sum(ap => ap.Quantity),
OpenPositions = openPositions.Count(),
// ...
// Account info
a.AccountId,
a.Username,
a.FirstName,
a.LastName,
a.AccountNumber
};

Related

SQL query to show good records as well as null records

My query works perfectly well to find records with real values, however, I also need my query to show records with null values. So far my attempts at recreating this query to also show null values has resulted in losing at least 1 of my columns of results so now I'm looking for help.
This is my query so far:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[TransactionSummary] t
INNER JOIN [log].[dbo].[Customer] c
on t.CustNo = c.CustNo
and t.City = c.City
and t.subno = c.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
This is currently what my query results show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
This is what I want my query results to show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
Cust 4 City(d) 0 0 0
Cust 5 City(e) 0 0 0
I suggest you use RIGHT JOIN in the place of INNER JOIN. You should then retain the rows from Customer that don't have matching rows in TransactionSummary.
You may also want to refactor the query like this so you use LEFT JOIN. The next person to work on the query will thank you; LEFT JOIN operations are more common.
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on t.CustNo = c.CustNo
and t.City = c.City
jwabsolution, your issue stems from grabbing all transactions instead of all customers. My mind works in this way: you want to select all of the customers & find all transaction states. Therefore, you should be selecting from the customer table. Also, you shouldn't use the INNER JOIN or you will ignore any customers that don't have transactions. Instead, use left join the transactions table. In this manner, you will retrieve all customers (even those with no transactions). Here is a good visual for SQL joins: http://www.codeproject.com/KB/database/Visual_SQL_Joins/Visual_SQL_JOINS_orig.jpg
So your query should look like this:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on c.CustNo = t.CustNo
and c.City = t.City
and c.subno = t.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
Fixed it. Needed to use coalesce to get the values to show up properly.
Also added a "where" option if I want to query individual customers
SELECT sq.* ,sq.TransactionCountTotal - sq.CompleteTotal as [InProcTotal]
from
(
select
c.custname
,c.port
,sum(coalesce(t.transactioncount,0)) as TransactionCountTotal
,sum(
case when (
[format]in(23,25,38)
or[format]between 400 and 499
or[format]between 800 and 899)
then t.TransactionCount
else 0
end) as CompleteTotal
from log.dbo.customer c
left join log.dbo.TransactionSummary t
on c.custNo=t.custno
and c.subno=t.subno
and c.city=t.city
and t.transactiondate between '7/1/16' and '7/12/16'
/*where c.custname=''*/
group by c.custname,c.city
) sq

How to display only the MAX results from a query

I am new to writing MS SQL queries and I am trying to display only the record with the highest field named RecordVersion.
Below is the query that works but displays all records:
SELECT
PriceCalendars.PriceProgramID,
PriceCalendars.EffectiveDateTime,
PriceSchedules.Price,
PriceSchedules.PLU,
items.Descr,
PriceSchedules.LastUpdate,
PriceSchedules.LastUpdatedBy,
PriceSchedules.RecordVersion,
PriceSchedules.PriceScheduleUniqueID
FROM
PriceCalendars
INNER JOIN PriceSchedules ON PriceCalendars.PriceProgramID = PriceSchedules.PriceProgramID
INNER JOIN items ON PriceSchedules.PLU = items.PLU
WHERE
(PriceSchedules.PLU = 'SLS10100103')
AND (PriceCalendars.EffectiveDateTime = '2016-03-22')
Here are the query results:
PriceProgramID EffectiveDateTime Price PLU Descr LastUpdate LastUpdatedBy RecordVersion PriceScheduleUniqueID
1 2016-03-22 00:00:00.000 35.00 SLS10100103 Architecture Adult from NP POS 2015-01-22 07:53:15.000 GX70,83 9 569
1 2016-03-22 00:00:00.000 32.00 SLS10100103 Architecture Adult from NP POS 2014-02-25 16:22:46.000 GX70,83 5 86180
The first line of the results has RecordVersion being 9 and the second line results is 5, I only want the higher record displaying, the one that returned RecordVersion = 9.
Every time I try to use the MAX command I get errors or the group by and I have tried every example I could find on the web but nothing seems to work.
Using MS SQL 2012.
Thanks,
Ken
Try the following query which attempts to solve your problem by ordering the returned rows by RecordVersion DESC and then SELECTs just the first row.
SELECT TOP 1
PriceCalendars.PriceProgramID,
PriceCalendars.EffectiveDateTime,
PriceSchedules.Price,
PriceSchedules.PLU,
items.Descr,
PriceSchedules.LastUpdate,
PriceSchedules.LastUpdatedBy,
PriceSchedules.RecordVersion,
PriceSchedules.PriceScheduleUniqueID
FROM
PriceCalendars
INNER JOIN PriceSchedules ON PriceCalendars.PriceProgramID = PriceSchedules.PriceProgramID
INNER JOIN items ON PriceSchedules.PLU = items.PLU
WHERE
(PriceSchedules.PLU = 'SLS10100103')
AND (PriceCalendars.EffectiveDateTime = '2016-03-22')
ORDER BY
RecordVersion DESC
All group by columns should be in select ,that's the rule of group by.How group by works is for every distinct combination of group by columns,arrange remaining columns into groups,so that any aggregation can be applied,in your case I am not sure what group by columns are unique with out test date.here is one version which use row number which gives you the output desired
Remember ,order by last updated date is the one which decides rows order and assign numbers
WITH CTE
AS
(
SELECT PriceCalendars.PriceProgramID,
PriceCalendars.EffectiveDateTime,
PriceSchedules.Price,
PriceSchedules.PLU,
items.Descr,
PriceSchedules.LastUpdate,
PriceSchedules.LastUpdatedBy,
PriceSchedules.RecordVersion,
PriceSchedules.PriceScheduleUniqueID,
ROW_NUMBER() OVER (PARTITION BY PriceSchedules.RecordVersion ORDER BY PriceSchedules.LastUpdatedBy) AS RN
FROM
PriceCalendars
INNER JOIN PriceSchedules ON PriceCalendars.PriceProgramID = PriceSchedules.PriceProgramID
INNER JOIN items ON PriceSchedules.PLU = items.PLU
WHERE
(PriceSchedules.PLU = 'SLS10100103')
AND (PriceCalendars.EffectiveDateTime = '2016-03-22')
)
SELECT * FROM CTE WHERE RN=1

Need help implementing a Full Outer Join in MS Access

I'm having trouble getting a query to work properly in Access. I need a full outer join on dbo_cardpurchases and dbo_vendors so that all all vendors will appear in the query regardless of whether a purchase was made at that vendor. But Access doesn't support full outer joins. How else can I do this?
SELECT dbo_vendors.name,
Iif([fundingsourceid] = 10, [amount], "") AS Credit,
Iif(( [fundingsourceid] = 2 )
OR ( [fundingsourceid] = 3 ), [amount], "") AS EBT,
Iif([fundingsourceid] = 4, [amount], "") AS [Match],
dbo_cardpurchases.updateddate,
dbo_markets.marketid
FROM (((dbo_cardpurchases
LEFT JOIN dbo_vendors
ON dbo_cardpurchases.vendorid = dbo_vendors.vendorid)
LEFT JOIN dbo_cardfundings
ON dbo_cardpurchases.cardfundingid =
dbo_cardfundings.cardfundingid)
INNER JOIN dbo_marketevents
ON dbo_cardpurchases.marketeventid =
dbo_marketevents.marketeventid)
INNER JOIN dbo_markets
ON dbo_marketevents.marketid = dbo_markets.marketid
ORDER BY dbo_vendors.name;
As mentioned in the Wikipedia article on joins here, for sample tables
[employee]
LastName DepartmentID
---------- ------------
Heisenberg 33
Jones 33
Rafferty 31
Robinson 34
Smith 34
Williams NULL
and [department]
DepartmentID DepartmentName
------------ --------------
31 Sales
33 Engineering
34 Clerical
35 Marketing
the full outer join
SELECT *
FROM employee FULL OUTER JOIN department
ON employee.DepartmentID = department.DepartmentID;
can be emulated using a UNION ALL of three SELECT statements. So, in Access you could do
SELECT dbo_employee.LastName, dbo_employee.DepartmentID,
dbo_department.DepartmentName, dbo_department.DepartmentID
FROM dbo_employee
INNER JOIN dbo_department ON dbo_employee.DepartmentID = dbo_department.DepartmentID
UNION ALL
SELECT dbo_employee.LastName, dbo_employee.DepartmentID,
NULL, NULL
FROM dbo_employee
WHERE NOT EXISTS (
SELECT * FROM dbo_department
WHERE dbo_employee.DepartmentID = dbo_department.DepartmentID)
UNION ALL
SELECT NULL, NULL,
dbo_department.DepartmentName, dbo_department.DepartmentID
FROM dbo_department
WHERE NOT EXISTS (
SELECT * FROM dbo_employee
WHERE dbo_employee.DepartmentID = dbo_department.DepartmentID)
However, since you are using linked tables into SQL Server you can just use an Access pass-through query and perform a "real" FULL OUTER JOIN using T-SQL:
Pass-through queries always produce recordsets that are not updateable, but a native Access query against linked tables that uses UNION ALL is going to produce a recordset that is not updatable anyway, so why not take advantage of the speed and simplicity of just using SQL Server to run the query?

SQL query converted to LINQ

How do I write this SQL query using LINQ?
I tried a few ideas but can't figure it out :
SELECT id , Email
FROM Customer
WHERE Customer.Id IN
(SELECT CustPeople.PeopleID
FROM CustPeople WHERE MemID = 1)
Table Customer:
Id Email
--------------------------
1 johnDoe#aol.com
12 billGates#yahoo.com
11 charlieParker#aol.com
Table CustPeople:
Id MemID PeopleID
----------------------
1 1 11
2 1 12
3 4 163
Result :
11 charlieParker#aol.com
12 billGates#yahoo.com
Do I use joins? Is there some way of creating a sub query in linq?
var people = from c in _custRepository.Table
join p in _custPeopleRepository.Table on c.Id equals p.MemID
???
Contains call on a collection will be translated into IN clause:
var people = from c in _custRepository.Table
where _custPeopleRepository.Table
.Where(x => x.MemID == 1)
.Select(x => x.PeopleID)
.Contains(c.Id)
select new { c.Id, c.Email };

SQL Server : join only one table

Let's say that I have those four tables.
PEOPOLE
ID NAME SURNAME COMPANY UNIT GROUPS
--------------------------------------------
1 Michael Backer 1 1 1
2 Travis Morgan 2 2 2
3 George Marshall 3 3 3
COMPANY
ID NAME
------------
1 Coca Cola
2 Pepsi
3 Sprite
WORKUNIT
ID NAME
-------------
1 Finances
2 Marketing
3 Sales
GROUPS (both values can be null)
ID NAME FLOOR
-------------------------
1 Risks 5
2 NULL NULL
3 Secretariat NULL
Expected results
NAME SURNAME COMPANYNAME WORKUNIT GROUPS FLOOR
-----------------------------------------------
Michael Backer Coca Cola Finances Risks 5
Travis Morgan Pepsi Marketing NULL NULL
George Marshall Sprite Sales Secretariat NULL
So far I write this query with no success:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, groups, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
I am not familiar with combining join statements in more than one table, so please help me out because I'm stuck.
Just write like this:
SELECT
people.NAME,
people.SURNAME,
company.NAME,
workunit.NAME,
groups.NAME,
groups.FLOOR
FROM
people
INNER JOIN GROUP
ON people.GROUP=GROUPS.id
INNER JOIN company
ON people.company=company.id
INNER JOIN UNIT
ON people.UNIT =UNIT.id
WHERE
GROUPS.floor='something from textbox';
I verified your query in SQL Server. You need to modify it a little bit, at least, to see it executing, in this way:
SELECT
people.NAME, people.SURNAME, company.NAME,
workunit.NAME, groups.NAME, groups.FLOOR
FROM
company, workunit, people
LEFT JOIN
groups on people.GROUP = GROUPS.id
WHERE
company.id = people.company AND
workunit.id = people.unit AND
groups.id = people.group AND
groups.floor = 'something from textbox';
Anyway, your query doesn't work because, even if you want the results in the way you described using a LEFT JOIN, you are setting the condition:
AND groups.floor = 'something from textbox';
In this way, you won't be able to see the records where there isn't a match with the GROUPS table, because you're saying explicitly that you want the records where the value of the column floor must be equal to 'something from textbox'.
If you want also the non-matches you can replace the condition with:
OR groups.floor = 'something from textbox';
but you will have some duplicates and a group by statement won't work anyway.
Then, you have to check if you really want those expected results.
I didn't understand why you want also the non-matches, even if you are looking for the precise result, using groups.floor = 'something from textbox'. If you want to display the results in a GUI, the non-matches would be useless information for a user, in my opinion.
SELECT people.NAME, people.SURNAME, company.NAME, workunit.NAME, groups.NAME, groups.FLOOR
FROM company, workunit, groups LEFT JOIN people on people.GROUP=GROUPS.id
WHERE
company.id=people.company AND
workunit.id=people.unit AND
groups.id=people.group AND
groups.floor='something from textbox';

Resources