I have a custom priority array.
int[] priorities = { 866, 663, 855, 853, 854};
And I have this code to currently to sort through my list and pick the first based on the criteria.
var target = (from people in GetNearbyPeopleList()
where people.DistanceToTravel > 0 && people.ReachedDestination == false
orderby //not sure what to do here
select people).FirstOrDefault();
So I want to order by my custom priorities. Where people.currentlocation is ordered by my priorities array.
Do I do - ?
orderby priorities.Contains(people.currentlocation)
That's all I can think of, but it does not properly order by the order of my custom priorities. I want it to follow this order exactly:
int[] priorities = { 866, 663, 855, 853, 854};
So if location is 866, pick that one. I only want to select one and I want to select the first based on that priority list. If currentlocation == 866 does not exist, then pick 663 and so and so on.
If your values are fixed at compile-time... avoid the array and write:
orderby
people.currentLocation == 866 ? 1 :
people.currentLocation == 663 ? 2 :
people.currentLocation == 855 ? 3 :
people.currentLocation == 853 ? 4 :
people.currentLocation == 854 ? 5 :
6
If your values change at run-time, but the number of values has some fixed maximum, then write:
Person FindPriorityPerson(IQueryable<Person> query,
int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0,
int p5 = 0, int p6 = 0, int p7 = 0, int p8 = 0)
{
return query.OrderBy(person =>
person.currentLocation == p1 ? 1 :
person.currentLocation == p2 ? 2 :
person.currentLocation == p3 ? 3 :
person.currentLocation == p4 ? 4 :
person.currentLocation == p5 ? 5 :
person.currentLocation == p6 ? 6 :
person.currentLocation == p7 ? 7 :
person.currentLocation == p8 ? 8 :
9).FirstOrDefault();
}
I think this will do it. This gives you sorting based on array order.
Had to add logic so -1 (not found) is at the end
orderby Array.IndexOf(priorities, people.currentLocation) == -1 ? Integer.MaxValue : Array.IndexOf(priorities, people.currentLocation);
Ctznkan525's answer gave me the idea to use "Select with index" to sort your array in the order or your priorities.
So you want all people with priority is first in the list or Priorities. 2nd are the people with priority that is mentioned 2nd in the list, 3rd are those with priority 3rd in the list etc.
Did you notice that I mentioned the index to identify the order? So if I add the index to your priorities list, I can order by this index.
This works if GetNearByPeopleList returns an IEnumerable.
var indexedPriorities = priorities.Select( (priority, index) => new
{
Priority = priority,
OrderIndex = index,
});
var result = GetNearbyPeopleList()
.Where(...) // take only people you want
.Join(indexedPriorities, // join with indexedPriorities
people => people.CurrentLocation, // from each people take the CurrentLocation
indexedPrio => indexedPrio.Priority, // from each indexedPriority take the priority
(people, prio) => new // when they match, make a new object
{
Index = prio.Index, // containing the index of the matching priority
People = people, // and the matching data
})
.OrderBy(item => item.Index) // order by ascending index
.Select(item => item.People); // keep only the People
This code leaves out all people with CurrentLocation that are not in your Priority list. If you want them, you should Concat them where you want them, probably at the end.
Alas, linq-to-entities does not support "select with index". You could try to first add the index, make your list AsQueryable and then do the join:
var indexedPriorities = = priorities.Select( (priority, index) => new
{
Priority = priority,
OrderIndex = index,
})
.AsQueryable();
var result = GetNearbyPeopleList()
.Where(...)
.Join(indexedPriorities,
people => people.CurrentLocation, CurrentLocation
indexedPrio => indexedPrio.Priority,
...
Alas this doesn't work, because you can only transfer simple types AsQueryable to be used in a join.
However, if the data is remote, and you really need this, you could transfer your priorities and indexes as a decimal:
var indexedPriorities = = priorities.Select( (x, i) => (decimal)x + (decimal)i/1000M)
.AsQueryable();
The number before the decimal point is the priority, after the decimal point is the ordering index: (.001 comes before .002 etc
IQueryable<People> nearbyPeople = GetNearbyPeopleList()
.Where(...);
var result = nearbyPeople.Join(indexedPriorities,
people => people.CurrentLocation,
prio => (int)Math.Floor(prio),
(people, prio) => new
{
OrderIndex =(prio - Math.Truncate(prio),
People = people,
})
.OrderBy(item => item.OrderIndex)
.Select(item => item.People);
First I change the original priority list: { 866, 663, 855, 853, 854} into {866.000, 663.001, 855.002, 853.003, etc}. Assuming your priority list does not have 1000 elements.
Then I do a join with the Math.Floor of 866.000, 663.001 etc. When matching I keep the part after the decimal point as OrderIndex: .000, .001, .002 etc. (Not needed, but if desired: multiply by 1000)
After Ordering I get rid of the OrderIndex.
This can not be done this way.* Linq can not work with your own array the way you need. But there are several other ways to do that I can think of
Redesign you application so you have the priorities in table on the server.
Use table valued parameter (TVP). That is the right way to send your array to the server to be used in the query, it scales to thousands of items without issue. It needs server that supports that (like Microsoft SQL server) and the query has to be done outside the linq. Linq does not support that.
Use Contains and query all items, pick the first on the client. Contains can use your own array, however be aware it converts it into parameters, one value means one parameter. Do not use it if you have too many items (>100) in the array or the query would be slow and would crash for more than approx 2000 items (depends on server type).
Loop the array and query one item a time. You can combine it with Contains and query 10 items a time for example to speed it up.
*Amy B found a way, my bad. But beware of the limits. It uses not one, but two parameters per every value, making it even more constrained. And it would probably lead to table scan which may be much more expensive, depends on table size and array size. On 20M table it needs about 25000x more database resources than just querying all 5 rows with Contains. You should probably combine it with Contains filter to avoid the table scan, but that means three parameters per item...
Related
I have a list of data retrieved from SQL and stored in a class. I want to now aggregate the data using LINQ in C# rather than querying the database again on a different dataset.
Example data I have is above.
Date, Period, Price, Vol and I am trying to create a histogram using this data. I tried to use Linq code below but seem to be getting a 0 sum.
Period needs to be a where clause based on a variable
Volume needs to be aggregated for the price ranges
Price needs to be a bucket and grouped on this column
I dont want a range. Just a number for each bucket.
Example output I want is (not real data just as example):
Bucket SumVol
18000 50
18100 30
18200 20
Attempted the following LINQ query but my SUM seems to be be empty. I still need to add my where clause in, but for some reason the data is not aggregating.
var ranges = new[] { 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 };
var priceGroups = eod.GroupBy(x => ranges.FirstOrDefault(r => r > x.price))
.Select(g => new { Price = g.Key, Sum = g.Sum(s => s.vol)})
.ToList();
var grouped = ranges.Select(r => new
{
Price = r,
Sum = priceGroups.Where(g => g.Price > r || g.Price == 0).Sum(g => g.Sum)
});
First things first... There seems to be nothing wrong with your priceGroups list. I've run that on my end and, as far as I can understand your purpose, it seems to be grabbing the expected values from your dataset.
var ranges = new[] { 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000 };
var priceGroups = eod.GroupBy(x => ranges.FirstOrDefault(r => r > x.price))
.Select(g => new { Price = g.Key, Sum = g.Sum(s => s.vol) })
.ToList();
Now, I assume your intent with the grouped list was to obtain yet another anonymous type list, much like you did with your priceGroups list, which is also an anonymous type list... List<'a> in C#.
var grouped = ranges.Select(r => new
{
Price = r,
Sum = priceGroups.Where(g => g.Price > r || g.Price == 0).Sum(g => g.Sum)
});
For starters, your are missing the ToList() method call at the end of it. However, that's not the main issue here, as you could still work with an IEnumerable<'a> just as well for most purposes.
As I see it, the core problem is at your anonymous property Sum attribution. Why are your filtering for g.Price > r || g.Price == 0?
There is no element with Price equal to zero on your priceGroups list. Those are a subset of ranges, and there is no zero there. Then you are comparing every value in ranges against that subset in priceGroups, and consolidating the Sums of every element in priceGroups that have Price higher than the range being evaluated. In other words, the property Sum in your grouped list is a sum of sums.
Keep in mind that priceGroups is already an aggregated list. It seems to me you are trying to aggregate it again when you call the Sum() method after a Where() clause like you are doing. That doesn't make much sense.
What you want (I believe) for the Sum property in the grouped list is for it to be the same as the Sum property in the priceGroups list, if the range being evaluated matches the Price being evaluated. Furthermore, where there is no matches, you want your grouped list Sum to be zero, as that means the range being evaluated was not in the original dataset. You can achieve that with the following instead:
Sum = priceGroups.FirstOrDefault(g => g.Price == r)?.Sum ?? 0
You said your Sum was "empty" in your post, but that's not the behavior I saw on my end. Try the above and, if still not behaving as you would expect, share a small dataset for which you know the expected output with me and I can try to help you further.
Use LINQ instead to query the DB is great, mainly because you are saving process avoiding a new call to your DB. And in case you don't have a high update BD (that change the data very quickly) you can use the retrived data to calculate all using LINQ
I want to retrieve data from a database by a grouping query and then calculate their ratio. Take for instance positions in a warehouse where you retrieve stock values for 2 days and you want to know the change ratio.
E.g.:
var query = from o in dbContext.Orders
where (o.Date == firstDate) || (o.Date == secondDate)
group o by o.Date into g
select g;
How can i now (inside or outside of the query) caluclate the change ratio of the matching order items? (The ratio being defined as (newOrder.Stock / oldOrder.Stock) -1) I know how to do it by a simple somewhat verbose way, but i was hoping that there is a more elegant solution in linq.
Edit: An example of the data queried and the desired result.
ID Date InStock ItemID
1 15.01 5000 1
2 16.01 7000 1
3 15.01 9000 2
4 16.01 2000 2
This would now show an 40% increase for item 1 and an -78% decrease for item 2.
I already did achieve this by separating the groups into two lists and then checking each list for the corresponding items in the other list. This way you can easily calculate the ratios but you create some new variables and nested foreach loops which seem unnecessary. I'm simply searching for a more elegant solution.
var query = from o in orders
where (o.Date == firstDate || o.Date == secondDate)
group o by o.ItemID into g
select new
{
ItemID = g.Key,
DateStart = g.ElementAt(0).Date,
DateEnd = g.ElementAt(1).Date,
Ratio = g.ElementAt(1).InStock / (float)g.ElementAt(0).InStock
};
We use the fact that we know that each grouping will only contain two items (one for each date) to simply select the result of the division in a new anonymously typed item.
My example used int as the type for InStock, so feel free to remove the (float) cast if it's not needed.
I am looking to identify rows using linq where there is a date clash. I have (for this example) 5 columns
ID ref_id ref_Name Borrow_Date Return_Date
1 1343 Gate 13/09/2011 20/09/2011
2 1352 Door 20/09/2011 22/09/2011
3 1343 Gate 17/09/2011 21/09/2011
In this case my 'Gate' is clashing because someone wants to borrow it when someone else also wants to borrow it.
Is there anyway to identify the date range clashes using linq easily?
One way would be like this. It might be more performant variants out there though:
var collisions = myList.Where( d1 => !myList.Where( d => d != d1).All( d2 => d1.Return_Date <= d2.Borrow_Date|| d1.Borrow_Date >= d2.Return_Date));
This will return all rows that overlap with at least one other row. In the case above it will return all three of them, since the line with ID 3 overlaps both 1 and 2. If you change 1 to have Return_Date 17/09/2011, it will return only 2 and 3.
If you have a list of objects with properties as shown in your table, you can find out the books with the same title that have conflicting dates using something like this:
(Haven't tested this code, so there might be some typo bugs.)
var collisions = collection
.Join(collection, x => x.ref_Name, y => y.ref_Name,
(x, y) => new {
ID_x = x.ID,
ID_y = y.ID,
ref_id = x.ref_id,
ref_Name = x.ref_Name,
Borrow_Date_x = x.Borrow_Date,
Borrow_Date_y = y.Borrow_Date,
Return_Date_x = x.Return_Date,
Return_Date_y = y.Return_Date
}
)
.Where( z => (z.Return_Date_x > z.Borrow_Date_y && z.Borrow_Date_x < z.Return_Date_y))
.Where( z => z.ID_x != z.ID_y);
You will probably get duplicates of results. (i.e. ID 1 and 3, and ID 3 and 1)
Although it is certainly possible to identify these clashes in the database once they have occurred, would it not be a better to prevent the second person from borrowing an item when it is already scheduled to be borrowed. In this case this would be a simple matter of testing to ensure no existing rows with a ref_id of 1343 have a return date equal to or greater then the new requested borrow date.
I need to add a literal value to a query. My attempt
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
In the above example, I get an error:
"Local sequence cannot be used in LINQ to SQL implementation
of query operators except the Contains() operator."
If I am using Entity Framework 4 for example, what could I add to the Union statement to always include the "seed" ID?
I am trying to produce SQL code like the following:
select distinct ID
from product
union
select 0 as ID
So later I can join the list to itself so I can find all values where the next highest value is not present (finding the lowest available ID in the set).
Edit: Original Linq Query to find lowest available ID
var skuQuery = Context.Products
.Where(p => p.sku > skuSeedStart &&
p.sku < skuSeedEnd)
.Select(p => p.sku).Distinct();
var lowestSkuAvailableList =
(from p1 in skuQuery
from p2 in skuQuery.Where(a => a == p1 + 1).DefaultIfEmpty()
where p2 == 0 // zero is default for long where it would be null
select p1).ToList();
var Answer = (lowestSkuAvailableList.Count == 0
? skuSeedStart :
lowestSkuAvailableList.Min()) + 1;
This code creates two SKU sets offset by one, then selects the SKU where the next highest doesn't exist. Afterward, it selects the minimum of that (lowest SKU where next highest is available).
For this to work, the seed must be in the set joined together.
Your problem is that your query is being turned entirely into a LINQ-to-SQL query, when what you need is a LINQ-to-SQL query with local manipulation on top of it.
The solution is to tell the compiler that you want to use LINQ-to-Objects after processing the query (in other words, change the extension method resolution to look at IEnumerable<T>, not IQueryable<T>). The easiest way to do this is to tack AsEnumerable() onto the end of your query, like so:
var aa = new List<long>();
aa.Add(0);
var a = Products.Select(p => p.sku).Distinct().AsEnumerable().Union(aa);
a.ToList().Dump(); // LinqPad's way of showing the values
Up front: not answering exactly the question you asked, but solving your problem in a different way.
How about this:
var a = Products.Select(p => p.sku).Distinct().ToList();
a.Add(0);
a.Dump(); // LinqPad's way of showing the values
You should create database table for storing constant values and pass query from this table to Union operator.
For example, let's imagine table "Defaults" with fields "Name" and "Value" with only one record ("SKU", 0).
Then you can rewrite your expression like this:
var zero = context.Defaults.Where(_=>_.Name == "SKU").Select(_=>_.Value);
var result = context.Products.Select(p => p.sku).Distinct().Union(zero).ToList();
I'm using LINQ to SQL and I have a stored procedure which brings back a result set that looks like so:
Type Field1 Field2
5 1 1
6 2 0
21 0 0
I'm hoping to do a few things with this record set:
1) Have 3 groups of results, one that has values in both field1 and field2, one that has a value in field1 but not field2 and one that has zeros in field1 and field2.
2) I'm only interested in a subset of types. I have a list of type id's I'm looking for (say 5 and 21 for this example). Right now the type values are stored in an enumeration but I can move them to a better data type if appropriate.
I've gotten to where I can group each set but I'm unsure of how to limit the types so I only bring back those I'm interested in.
Here's what I have:
var result = from all in dataContext.sp(..variables...)
group all by all into list
let grp1 = (from a in list
where a.field1 != 0 && a.field2 != 0
select a)
let grp2 = (from b in list
where b.field1 == 0 && b.field2 != 0
select b)
let grp3 = (from c in list
where c.field1 == 0 && c.field2 == 0
select c)
select new { grp1, grp2, grp3 };
Any help is appreciated.
Do you actually want all the data for those groups? If so, you might as well do the grouping back in .NET - just filter in SQL:
// Assuming interestingTypes is an array or list of the interesting types
var query = db.Whatever.Where(entry => interestingTypes.Contains(entry.Type)
// I understand you're not interested in this group
&& !(entry.Field1==0 && entry.Field2==1));
var grouped = query.AsEnumerable()
.GroupBy(entry => new { entry.Field1, entry.Field2 });
An alternative to GroupBy is to use ToLookup:
var lookup = query.AsEnumerable()
.ToLookup(entry => new { entry.Field1, entry.Field2 });
Then:
var values00 = lookup[new { Field1=0, Field2=0 }];
(Then values00 will be an IEnumerable<T> of your entry type.)
If you're only interested in the types for each field group, you could change the lookup to:
var lookup = query.AsEnumerable()
.ToLookup(entry => new { entry.Field1, entry.Field2 },
entry => entry.Type);
You'd fetch values00 in the same way, but each entry would be the type rather than the whole record.
I dont think you will be able to do it in a single query (maybe, but not without it being ugly).
I would recommend storing the result of the stored proc, and just use 3 queries. Or modify the stored proc to return the 3 resultsets you are looking for.