How to get closest smallest number using linq C# - c#

I have a sql table like this,
SomeDouble SomeInt
1.00 121
1.50 124
2.00 200
2.50 321
and so on... up to 10,000 for SomeDouble
Now I can have a decimal number anywhere between 0.0 to 10,000.00 and I need to find the correct row for it. For example if number is 1.12 then I want it to return 121.
1.49 should return 121, 1.50 should return 124, 1.51 should return 124.
Trimmed version of what I am trying is,
var myValue = 1.12
var SomeInt = (from mainTable in table1
join table3 in table2 on mainTable.someId equals table3.someId
where table3.Column1 == Column1 && mainTable.SomeDouble >= myValue
select mainTable.SomeInt).FirstOrDefault();
but my output is 124. How can I change above to get me the closest smallest number then myValue ?

Because the SomeDouble values are integers and half-integers, you can round up myValue to the next multiple of 0.5:
var myValueToLookUp = Math.Ceiling(myValue * 2) / 2;
and then look up the value of SomeInt directly with mainTable.SomeDouble == myValueToLookUp to avoid any confusion or inefficiency with <= or >=.

In SQL, you can express the closest as:
select t.*
from t
order by abs(SomeDouble - 1.12)
fetch first 1 row only;
A more efficient method would narrow it down to two rows first:
select t.*
from ((select t.*
from t
where t <= 1.12
order by SomeDouble desc
fetch first 1 row only
) union all
((select t.*
from t
where t > 1.12
order by SomeDouble asc
fetch first 1 row only
)
) t
order by (SomeDouble - 1.12)
fetch first 1 row only;

Using Linq queries:
var above = (from mainTable in table1
join table3 in table2 on mainTable.someId equals table3.someId
where table3.Column1 == Column1 && mainTable.SomeDouble >= myValue
orderby mainTable.SomeDouble
select new {SomeInt = mainTable.SomeInt, SomeDouble = mainTable.SomeDouble}).FirstOrDefault();
var below = (from mainTable in table1
join table3 in table2 on mainTable.someId equals table3.someId
where table3.Column1 == Column1 && mainTable.SomeDouble < myValue
orderby mainTable.SomeDouble descending
select new {SomeInt = mainTable.SomeInt, SomeDouble = mainTable.SomeDouble}).FirstOrDefault();
int SomeInt;
if (above == null)
SomeInt = below.SomeInt;
else if (below == null)
SomeInt = above.SomeInt;
else if (Math.Abs(below.SomeDouble - myValue) <= Math.Abs(above.SomeDouble - myValue))
SomeInt = below.SomeInt;
else
SomeInt = above.SomeInt;

Here's the linq extension method to order the records by absolute difference of SomeDouble, then by SomeInt to get the smallest first for 2 or more matches, and then we get the first one. It looks like both columns exist on main table, so I'm guessing we can limit that first then join whatever you want to it.
mainTable.OrderBy(x => Math.Abs(x.SomeDouble - myValue)).ThenBy(x => x.SomeInt).First()

If you can do it in SQL, then
SELECT COALESCE(MAX(SomeInt), 0)
FROM DoubleToInt
WHERE SomeDouble <= 1.12

Related

Simplifying LINQ query in C#

Table1
Table1ID Name Graduation Version Hobbies
1 A Degree 1 B
2 A Degree 2 C
3 A Degree 3 D
Table2
Table2ID Table1ID Name Graduation Version Address Surname Date
1 1 A Degree 1 A A 08-10-2019
2 2 A Degree 2 A A 08-10-2019
3 3 A Degree 3 A A
//I want to check if any version greater than highest version exists in Table1 .where Date column is not null in Table2
Suppose for the combination of Name and Degree , the highest version is 2 in Table2 since Date is null for Table2, I want to check if any record greater than 2 exists in Table1, if yes add it to a new List
Here is what I am doing.
List<Table2> groupByTable2 = //Operations on Table2 and get highest Version record from db
List<Table1> check = new List<Table1>();
List<Table1> check2 = await _table1.GetAll().ToListAsync();
Foreach(var a in groupByTable2)
{
List<Table1> check4 = check2.Where(x => x.Name == a.Name && x.Graduation == a.Graduation).ToList();
If(check4.Any(x=>x.Version > a.Version))
{
check.Add(check2.Where(x=>x.Table1ID == a.Table1ID).First());
}
}
Now my check contains a record where ID is 3. But is there any simpler way to achieve this in simpler way with readability and performance?
I hope I understood what you are trying to achieve. You could try the following.
var result = table2.Where(x=>x.Date!=null)
.GroupBy(x=> new {x.Name, x.Graduation})
.SelectMany(x=> x.OrderByDescending(c=>c.Version).Take(1))
.Join(table1,t2=>t2.Table1ID,t1=>t1.Table1ID,(t2,t1)=>t1)
.ToList();
result.AddRange(table1.Where(x=> result.Any(c=>c.Name.Equals(x.Name)
&& c.Graduation.Equals(x.Graduation)
&& c.Version < x.Version)));
The idea is to first use GroupBy and Join to get the List of Items with highest Version number in Table1 that has a valid date in Table2. Then, use List.AddRange to add remaining higher versions from Table1.

Column value be max value of another column where another value is the same

I need to update a column in the table... Let's call it MaxUsers, with the amount of different rows for the same User. So we have 4 columns:
Column 0: Unique ID
Column 1: User (Unique User ID, is the same for all subuser)
Column 2: SubUser Nr (this is 0 if it is the main user)
Column 3: MaxUsers (Maximum Subuser on this User)
I need column 3 (MaxUsers) to be the maximum number of Column 2 (SubUser Nr) where Row 1 is the same (User) is the same
I can do this in Linq on C# or even in python, but I am a bit of a newbie on SQL and doing over there would use many more queries than I think is needed, and I am pretty sure my method is not the most efficient.
If I was to do this in C# I would do it like this assuming the SQL database is queryable in LINQ:
// Grab all main users to an array
var MainUsers = SQLDatabase.Where(x => x.SubUser == 0).toArray();
// Go through them one by one
for(int i = 0;i < MainUsers.length();i++)
{
// Find all users in this user ID
var Group = SQLDatabase.Where(x => x.User = MainUsers[i].User).toArray();
// Go through each subuser
for(int o = 0; o < Group.Length();o++){
// If the subuser nr is bigger than the maxusers change it to that
if(Group[o].SubUserNr > Group[0]MaxUsers){
Group[0]MaxUsers = Group[o].SubUserNr;
}
}
// Sets the subusers to max value too
for(int o = 0; o < Group.Length();o++){
Group[o].MaxUsers = Group[0]MaxUsers
}
}
SQLDatabase.SaveChanges();
The final result would look like this
Row :UID / User / SubUser Nr/ MaxUsers
Row 0:0 / 10 / 0 / 3
Row 1:1 / 10 / 1 / 3
Row 2:2 / 10 / 2 / 3
Row 3:3 / 10 / 3 / 3
Row 4:4 / 15 / 0 / 4
Row 5:5 / 15 / 1 / 4
Row 6:6 / 15 / 2 / 4
Row 7:7 / 15 / 3 / 4
Row 8:8 / 15 / 4 / 4
Row 9:9 / 17 / 0 / 1
Row 10:10 / 17 / 1 / 1
I think this should achieve what you want:
UPDATE Users u
JOIN (SELECT uid, MAX(SubUser) AS max
FROM Users
GROUP BY uid) AS t
ON u.uid = t.uid
SET u.MaxUsers = t.max
It should take the max "subuser" for each user id, and then update the main user table with those values
Join two subqueries:
select t1.UID
, t1.User
, t1.[SubUser Nr]
, t2.MaxUsers
from (
select t.UID
, t.User
, t.[SubUser Nr]
from tbl t
) t1
inner join (
select t.UID
, t.User
, max(t.[SubUser Nr]) as MaxUsers
from tbl t
group by t.UID
, t.User
) t2 on t2.UID = t1.UID
and t2.User = t1.User
Or you may be able to use a correlated subquery.
select t1.UID
, t1.User
, t1.[SubUser Nr]
, (select max(t2.[SubUser Nr]
from tbl t2
where t2.User = t1.User) as MaxUsers
from tbl t1
(Sorry, my experience is in SQL Server. The syntax may need to be adjusted for MySQL.)
I am not sure I understand your question correctly, but I believe ultimately you want to have an extra column that tells you the latest user record to use?
var MainUsers = SQLDatabase.Where(x => x.SubUser == 0);
foreach (var user in MainUsers)
{
user.MaxUsers = MainUsers.Where(x => x.UID == user.UID).Max(x => x.SubUser);
}
Or you do not necessarily need to have an extra column but select in SQL by this query:
select UID, User, Max(SubUser)
from t1
group by UID, User, SubUser

How to set an integer value to one if a record exist in database C# Sql Query

getName_as_Rows is an array which contains some names.
I want to set an int value to 1 if record found in data base.
for(int i = 0; i<100; i++)
{
using (var command = new SqlCommand("select some column from some table where column = #Value", con1))
{
command.Parameters.AddWithValue("#Value", getName_as_Rows[i]);
con1.Open();
command.ExecuteNonQuery();
}
}
I am looking for:
bool recordexist;
if the above record exist then bool = 1 else 0 with in the loop.
If have to do some other stuff if the record exist.
To avoid making N queries to the database, something that could be very expensive in terms of processing, network and so worth, I suggest you to Join only once using a trick I learned. First you need a function in your database that splits a string into a table.
CREATE FUNCTION [DelimitedSplit8K]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(#pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(#pString,t.N,1) = #pDelimiter OR t.N = 0)
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
Item = SUBSTRING(#pString,s.N1,ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000))
FROM cteStart s
GO
Second, concatenate your 100 variables into 1 string:
"Value1", "Value 2", "Value 3"....
In Sql Server you can just join the values with your table
SELECT somecolumn FROM sometable t
INNER JOIN [DelimitedSplit8K](#DelimitedString, ',') v ON v.Item = t.somecolumn
So you find 100 strings at a time with only 1 query.
Use var result = command.ExecuteScalar() and check if result != null
But a better option than to loop would be to say use a select statement like
SELECT COUNT(*) FROM TABLE WHERE COLUMNVAL >= 0 AND COLUMNVAL < 100,
and run ExecuteScalar on that, and if the value is > 0, then set your variable to 1.

Select from multiple tables using linq

I am trying to execute the following SQL statement using linq:
SELECT TTT.SomeField
FROM Table1 as T, Table2 as TT, Table3 as TTT
WHERE (T.NumberID = 100 OR T.NumberID = 101)
AND T.QuestionID = 200
AND T.ResponseID = TT.ResponseID
AND TT.AnswerID = TTT.AnswerID
Essentially getting one field from a third table based on primary/foreign key relationships to the other 2 tables. It is expected to have a single result every time.
var query = from t in db.Table1
join tt in db.Table2 on t.ResponseID equals tt.ResponseID
join ttt in db.Table3 on tt.AnswerID equals ttt.AnswerID
where (t.NumberID == 100 || t.NumberID == 101) && t.QuestionID == 200
select ttt.SomeField
If you always expect single result, you can wrap this in ().Single(), or, if there might be no results found, in ().SingleOrDefault().
If I understand you correct. You should read something about Joins I guess.
here

linq-to-sql Concat() throwing a System.IndexOutofRangeException

I'm struggling with an exception using linq-to-sql Concat()
I've got 2 tables.
The first table, ParsedMessages, has the following fields
* ParsedMessageID (int)
* MessageTypeID (int)
* TextMessage (varchar(max))
The second table, ParsedMessageLinks, has the following fields
* ParsedMessageID (int)
* AnotherID (int)
* NumberOfOccurences (int)
This is what I need to achieve using a single linq query but I'm not sure if it's possible or not.
Through a join, retrieves ParsedMessage records that links to a certain AnotherID. In example SQL and linq code, the AnotherID will have the value 0 just for the purpose of having an example.
For each ParsedMessage record, I also need the NumberOfOccurences (field of table #2)
Retrieve only the top(100) ParsedMessage records for each MessageTypeID. So for example, if there is 275 records in ParsedMessages that links to AnotherID==0 where the first 150 records have MessageTypeID == 0 and the remaining 125 records having MessageTypeID == 1, I want my query to end up returning 200 records, the top(100) with MessageTypeID == 0 and the top(100) with MessageTypeID == 1
After a lot of search, I've found that the plain SQL equivalent of I what I want to do is this. I knew that this exists first end, but I tried to find something else without Union all at first and fail to do so (my SQL knowledge is not that good) :
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 0 ORDER BY PM.ParsedMessageID DESC UNION ALL
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 1 ORDER BY PM.ParsedMessageID DESC UNION ALL
SELECT TOP(100) PM.*,
PML.NumberOfOccurences FROM
ParsedMessages PM INNER JOIN
ParsedMessageLinks PML ON
PM.ParsedMessageID =
PML.ParsedMessageID WHERE
PML.AnotherID = 0 AND PM.MessageTypeID
= 2 ORDER BY PM.ParsedMessageID DESC
So basically, the only way to retrieve the data I need is to do 3 sql queries in a single pass where only the PM.MessageTypeID is different for each query.
Now I wanted to achieve this using linq-to-sql. After googling, I've found that I could use the Linq Concat() method to reproduce a SQL Union All.
Here are some links pointing to what I thought would work :
http://blog.benhall.me.uk/2007/08/linq-to-sql-difference-between-concat.html
EF. How to union tables, sort rows, and get top entities?
I end up having this exception :
System.IndexOutOfRangeException : "Index was outside the bounds of the array."
Here's the faulty code :
IQueryable<MyObject> concatquery;
int[] allMessageTypeIDs = new int[] { 0, 1, 2 };
for (int mt = 0; mt < allMessageTypeIDs.Length; mt++)
{
if (mt == 0)
{
concatquery = (from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == allMessageTypeIDs[mt]
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
else
{
concatquery = concatquery.Concat(from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == allMessageTypeIDs[mt]
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
}
var results = concatquery.ToArray();
I've declared the int array allMessageTypeIDs, for simplicity. But remember that the values it holds may differ, so that's why I've added the for loop. Maybe it's "illegal" to use a Concat() in a loop that way, but I could not find any relevant information on this exception.
The class MyObject basically hold a int (NumberOfOccurences) and a ParsedMessage database object, nothing else.
Any suggestions on what could be wrong with my code that causes the exception?
Thanks
Francis
Never use the variable you're looping with in your Linq queries. It just doesn't work. You want to assign a new temporary variable to use instead.
IQueryable<MyObject> concatquery;
int[] allMessageTypeIDs = new int[] { 0, 1, 2 };
for (int mt = 0; mt < allMessageTypeIDs.Length; mt++)
{
var myItem = allMessageTypeIDs[mt]; // <-- HERE!
if (mt == 0)
{
concatquery = (from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == myItem
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
else
{
concatquery = concatquery.Concat(from pm in db.ParsedMessages
join pml in db.ParsedMessageLinks on pm.ParsedMessageID equals pml.ParsedMessageID
where pml.AnotherID == 0 && pm.MessageTypeID == myItem
orderby pm.ParsedMessageID descending
select new MyObject
{
NumberOfOccurences = pml.Occurrences,
ParsedMessage = pm
}).Take(100);
}
}
var results = concatquery.ToArray();

Categories

Resources