Gnarly Linq query - c#

Have the following table structure
I need the count of transcriptions by statuses where the records do not have a workflow folder. This does the trick:
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
xp=grouped.Key,
xp1= grouped.Count(),
}
Now I need to add the number of records where the Dueon date is in the past as in it is past the due by date.Something like
EntityFunctions.DiffHours(p.DueOn,DateTime.Today)>0
How do I include this in the resultset without firing 2 SQL queries? I am happy to get it as a third column with the same value in every row. Also is there anyway to get the percentage into the mix as in:
Status | Count | % |
------------------------------
Status1 | 20 | 20%
Status2 | 30 | 30%
Status3 | 30 | 30%
Overdue |20 | 20%
I have added Overdue as a row but perfectly happy to get it as a column with the same values.
Edited Content
Well this is the best I could come up with. Its not a single query but there is only one SQL trip. The result is:
Status | Count
----------------
Status1 | 20
Status2 | 30
Status3 | 30
Overdue |20
var q1= from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
status= (string)grouped.Key,
count= grouped.Count()
};
var q2 =(
from p in Transcriptions select new {status = "Overdue",
count = (from x in Transcriptions
where x.DueOn.Value < DateTime.Now.AddHours(-24)
group x by x.TranscriptionID into
grouped select 1).Count() }).Distinct();
q1.Union(q2)
It is a Union clause with the % calculation to be done once the results are returned. The weird thing is that I couldn't figure out any clean way to represent the following SQL in a LINQ statement which has resulted in the rather messy LINQ in the var q2.
SELECT COUNT(*) , 'test' FROM [Transcription]

You can add a condition to Count:
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
xp=grouped.Key,
xp1= grouped.Count(),
xp2= grouped
.Count(p => EntityFunctions.DiffHours(p.DueOn, DateTime.Today) > 0)
}
By the way, with entity framework you can also use p.DueOn < DateTime.Today.

#Gert Arnold
from p in Transcriptions
where p.WorkflowfolderID == null
group p by p.TranscriptionStatus.Description into grouped
select new
{
status= (string)grouped.Key,
count= grouped.Count(),
overdue= grouped.Count(p => p.DueOn < EntityFunctions.AddHours(DateTime.Today, -24)),
}
The above query does work as I wanted it to . It produces the outcome in the format
Status| Count | Overdue
----------------------
status1|2|0
status2|1|1
The only downside is the generated SQL is running 2 queries BOTH with inner joins . My original idea with the Union may be a better idea performance wise but you answered my query and for that I am grateful.
Can this query be represented in some other cleaner manner than my above attempt -
SELECT COUNT(*) , 'test' FROM [Transcription]

Related

Linq - Using a left join on to a table which may not have records

Let's say I have 3 tables - 1 header and 2 detail:
Header Table
id | label
1 | foo
2 | bar
Detail 1 Table
id | date | value
1 | 2015-01-01 | 5
Detail 2 Table
id | date | value
1 | 2015-01-01 | 7
2 | 2016-02-02 | 10
I want to make a linq query that joins all three, but does not eliminate data due to one detail table not having a record where the other one does. The result should look like:
Resulting Table
id | label | date | value1 | value2
1 | foo | 2015-01-01 | 5 | 7
2 | bar | 2016-02-02 | <null> | 10
So, a null for value1, instead of the entire row being removed.
If I were writing SQL, I could write
select
h.id,
h.label,
coalesce(d1.date, d2.date) as date,
d1.value as value1,
d2.value as value2
from
header h
left join detail1 d1
on d1.id = h.id
left join detail2 d2
on d2.id = h.id
and (
d2.date = d1.date
or d1.date is null
)
Is it possible to write this using Linq? I'm using the "on new equals new " syntax, and I cannot figure out how to preserve the detail2 record when there is no matching detail1 record.
Edit: I feel like the linked answer only answers the left join portion of my question. I know I can left join in linq, but the detail2 table is joining on to both header (not a problem) and detail1. If detail1 doesn't have a record for a date in detail2, the detail2 record will not appear in the result. Using "select new{} equals new{}" doesn't allow me to use the detail2 object before the equals, so I can't write
from
h in header.AsEnumerable()
join d1.AsEnumerable().DefaultIfEmpty()
on p.Id equals d1.Id
join d2.AsEnumerable().DefaultIfEmpty()
on new {
Id = h["Id"],
Date = d1["Date"] ?? d2["Date"], // Doesn't work, can't use d2 here.
} // d1 may not have a record, so there may not be a match
equals new {
Id = d2["Id"],
Date = d2["Date"],
}
select new {
// etc...
}
To implement a join with arbitrary conditions, you need to use another from clause with a where to handle your condition. I am not sure if used with Linq to SQL what type of SQL will be produced, you may be better off with my FullOuterJoin/LeftOuterJoin IQueryable extensions.
var ans = from h in header
join d1 in detail1 on h.id equals d1.id into hd1j
from hd1 in hd1j.DefaultIfEmpty()
from d2 in detail2 where h.id == d2.id && (hd1?.date == null || hd1.date == d2?.date)
select new { h.id, h.label, date = hd1?.date ?? d2?.date, value1 = hd1?.value, value2 = d2?.value };
For my Enumerable testing, I put in the conditional operators. You should remove them if testing against IQueryable (e.g. Linq to SQL).

Implementing the All functionality in SQL

I'm trying to write an SQL query where all of a certain group meets a condition.
Update
The Simplifed Table Structure would look like this
ID, TitleID, BlockFromSale
---------------------------
1 | 1 | true
2 | 1 | true
3 | 1 | true
4 | 2 | false
5 | 2 | true
this table would only return the the items TitleID 1.
In actuality I only need the title ID and not the whole item but either will satisfy the conditions.
How Could I write This Linq Query in SQL?
var Query = Data.Items.GroupBy(t => t.TitleID).Where(i => i.All(b => b.BlockFromSale == true));
I try to look at the Sql Query but it just instantly casts it to an object.
Basically I just Need a query that Grabs out all TitleIDs who for each item BlockFromSale is set to true so for the example from the table above it would only return TitleID, 1
It is possible to see the generated SQL of a LINQ query by using the Log property of the query. How to: Display Generated SQL shows an example of this. Basically, the web site shows this example:
db.Log = Console.Out;
IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
foreach(Customer custObj in custQuery)
{
Console.WriteLine(custObj.CustomerID);
}
If you want to check predicate p for all rows of a set you can write
NOT EXISTS (... WHERE NOT(p))
Because All(p) == !Any(!p) in pseudo-syntax.
I would guess that ORMs do it this way, too.
There isn't an easy way to do what your asking but a simple but some what slow way would be to use a subQuery
SELECT DISTINCT i.TitleID
FROM Items i
WHERE i.TitleID not in
(SELECT DISTINCT TitleID
FROM items it
WHERE it.BlockFromSale = 0)
so it will remove the titleids who have a false.

Linq collection with not like?

I have a dataGridView with multiple server roles. I'd like to get a collection of server names with roles "not like" webSrvr or client. For example.. DGV:
Servers | Server 1 | Server 2 | Server 3 | Server 4 | Server 5
Role | Database | Proxy | WebSrvr | Client | DC
Is there an easy linq statement to pull the Server 1, 2 and 5 column headers (names)? The reason I'd want to use "not like" or the equivalent is because the roles can have additional values at the end of it. Thoughts?
You could do pretty much what you want ...
var your_name = from str in your_list_source_here
where (str == some_condition)
select ( new { create your object here } )
you could just have the columns you want in the create your object here
here's an example from Linqpad , to illustrate how you can use the new object ...
var words =
from word in "The quick brown fox jumps over the lazy dog".Split()
orderby word.ToUpper()
select word;
var duplicates =
from word in words
group word.ToUpper() by word.ToUpper() into g
where g.Count() > 1
select new { g.Key, Count = g.Count() };
His objects will have the letter and the amount of time it repeats...

How do I get a value adjacent to a Max(value) using Linq?

I have a table:
Group | BasalArea | SpeciesName
1 | 3.6 | Palustris
1 | 45.0 | MSO
2 | 4.2 | Oak
2 | 2.0 | MSO
...
From this table, I would like to get the species name with the highest basal area grouped by the Group field, which would look like this:
Group | BasalArea | SpeciesName
1 | 45.0 | MSO
2 | 4.2 | Oak
Using SQL, I can get the highest basal area:
SELECT Group, Max(BasalArea)
FROM TABLE
GROUP BY Group
I can't figure out how to also get the species name without doing some looping. Is this possible? What are the strategies for handling ties?
This is simpler in LINQ2SQL than in SQL:
var res = source.MyTable
.GroupBy(item => item.Group)
.Select(g => g.OrderByDescending(item => item.BasalArea).First())
.ToList();
This will return the list of items with largest values of BasalArea in its Group, together with SpeciesName.
In SQL you would need to join back to the original table, like this:
SELECT * FROM TABLE b
JOIN (
SELECT Group, Max(BasalArea) as BasalArea
FROM TABLE
GROUP BY Group
) t on t.Group = b.Group AND t.BasalArea = b.BasalArea
Try this:
var froup = categories.GroupBy(g => new {g.CategoryType})
.Select(g => g.OrderByDescending(i => i.CategoryID).First())
.ToArray();
What sasblinkenlight said would be the LINQ. Out of curiosity, here is a potential SQL solution.
SELECT grouped.Group, raw.SpeciesName, grouped.MaBasalArea
FROM (
SELECT Group, MAX(BasalArea) as MaxBasalArea
FROM TABLE
GROUP BY Group
) grouped
INNER JOIN TABLE raw ON grouped.MaxBasalArea = raw.BasalArea AND grouped.Group = raw.Group

List of unique strings in database table using Linq?

I have a "Tickets" table with somewhat following structure (removed unnecessary columns)
int | string | int |
ID | Window | Count |
------------------------
0 | Internet | 10 |
1 | Phone | 20 |
2 | Fax | 15 |
3 | Fax | 10 |
4 | Internet | 5 |
. | . | . |
. | . | . |
And I have mapped this table to a class "Ticket". So I can get all records like this:
var tickets = from t in db.Tickets
select t;
Now I need to get the list of unique window names in the table. For above table, list would look something like:
Internet
Phone
Fax
Is there anyway to create this list without fetching all records and iterating over them?
I am using SQL Server 2008 express edition.
EDIT:
Thanks for the answers guys it solved the above problem. Just being greedy but is there any way to also get the total of count for each window. For example:
Internet = 15
Phone = 25
Fax = 20
How about:
var tickets = db.Tickets.Select(t => t.Window).Distinct();
I prefer to only use query expressions when I'm doing more than one operation, but if you like them the equivalent is:
var tickets = (from t in db.Tickets
select t.Window).Distinct();
To get the counts, you need to group:
var tickets = from t in db.Tickets
group t by t.Window into grouped
select new { Window=grouped.Key,
Total=grouped.Sum(x => x.Count) };
foreach (var entry in tickets)
{
Console.WriteLine("{0}: {1}", entry.Window, entry.Total);
}
Note that this should all end up being performed at the database side - examine the SQL query to check this.
var query2 = from ticket in db.tickets
group window by ticket.Window into result
select new
{
Name = result.Window,
Sum = result.Sum(i => i.Count)
};
The query will be evaluated inside the store.
var windows = db.Tickets.Select(ticket => ticket.Window).Distinct();
Linq Samples Part 11 by Bill Wagner should help you. Just call the Distinct() function on your Linq result. It's as simple as that.
var tickets = (from t in db.Tickets
select t).Distinct();
[EDIT]
Concering the numbers of the occurences, see this example as a hint.
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 5 };
var numberGroups =
from n in numbers
group n by 5 into g
select g;
g.Count(); // occurences
You can use the .Distinct() operator - it'll make a SELECT DISTINCT to the database, giving exactly what you ask for.

Categories