I have a application that I save this in the database:
FromLetter ToLetter
AAA AAZ
ABC MNL
what I need is to search like this AAC and returns record 1 and FBC and return record 2.
Is the same functionality if instead of letter I save dates. I need to do the same query.
I am using SQL Server and Entity Framework, any Idea how to do this?
Should be pretty straight forward. Here is a Linq to Entities solution, ignoring case:
Entity Framework/Linq solution strings:
string yourValue = somevalue;
var result = (from r in db.ExampleTable
where String.Compare(yourValue, r.FromLetter, true) == 1
&& String.Compare(yourValue, r.ToLetter, true) == -1
select r).First();
Dates:
DateTime yourValue = somevalue;
var result = (from r in db.ExampleTable
where yourValue >= r.FromDate
&& yourValue <= r.ToDate
select r).First();
I think it would be much easier to represent the FromLetter and ToLetter attributes using an integer. Especially if the length of the string is always just 3 - you can simply encode the number as:
(((letter1 - 'A') * 26 + (letter2 - 'A')) * 26) + (letter3 - 'A')
This will give you a number between 0 and 26^3 that represents the tripple and can be easily converted back to the string (using modulo and division as when converting numbers between numeric bases). This number fits into Int32 comfortably (up to 6 letters).
Searching for a string within a specified range would then be a simple search for an integer within a numeric range (which is easy to do and efficient).
Genius solution given by.... bunglestink
I wasted plenty of time in researching implementation of "between" clause for string in EF. This is helpful.
Related
I have a for loop that loops a list of transactions, which all contains amount. If the amount is correct, I want that transaction to be included in a new list.
So in code:
decimal searchAmount = 33.03;
foreach (var order in listOforders)
{
if(order.amount == searchAmount)
{
addOrderToList()
}
}
The currency used doesn't use more than two decimals, so that's okay. These three scenarios, should all add the order to the list.
order.Amount = 33.03
search.Amount = 33
order.Amount = 33.03
search.Amount = 33.03
order.Amount = 33.99
search.Amount = 33.9
Note:
This is a search. When the customer comes back, and says "I have a problem with the product I purchased, and it's not purchased on a registered customer", searching for the amount on the customers bank receipt is a great function. This is a retail brick and mortar store scenario, so some customers choose to not register themselves.
If you want to discard the fractional part completely, using a combination of LINQ and Math.Truncate
var orders = listOfOrders.Where(o => Math.Truncate(o.amount) == Math.Truncate(searchAmount))
.ToList();
Math.Truncate returns the integral part of a given decimal, Where selects only appropriate orders (do read up on LINQ's deferred execution if you don't know it) and ToList materializes the query into a list.
EDIT: given your edit, this is probably what you're looking for:
var orders
= listOfOrders.Where(
o => Math.Truncate(o.amount) == Math.Truncate(searchAmount)
&& o.amount.ToString(CultureInfo.InvariantCulture)
.StartsWith(searchAmount.ToString(CultureInfo.InvariantCulture)))
.ToList();
This first verifies if the integral part of the numbers match and then uses string comparison to check if the actual amount starts with what was inputted (by your lazy user).
Use your if condition like this. Round to 2 decimal places and compare.
if((Math.Round(order.amount,2) - Math.Round(searchAmount,2)) <= 0.9M)
{
addOrderToList();
}
What if you use Math.Truncate(number)? Just like:
if(Math.Truncate(order.amount) == Math.Truncate(searchAmount))
If am right you need to do define some maximum difference constant and use it something like that
decimal maxDiff = 0.03;
decimal searchAmount = 33.03;
var result = listOfOrders.Where(o => Math.Abs(o.Amount - searchAmount) <= maxDiff);
You need to use Math.Floor method to match all numbers i.e absolute value
decimal searchAmount = 33.03;
var temp = Math.Floor(searchAmount);
foreach (var order in listOforders)
{
if(Math.Floor(order.amount) == temp)
{
addOrderToList()
}
}
There is no need for any call to Math as a cast to int will do the same. Your code could be changed to;
int searchAmount = 33;
listOforders.Where(o => (int)o.Amount == searchAmount)
.ForEach(o => addOrderToList());
I have a scenario that I have string value in c# and in SQL server the data type is decimal and requirement is that i have to filter value like we do in string which includes (startswith,endswith,contains etc) filters. I am trying like this :
customers = customers.Where(x => Convert.ToString(x.CrmCustomerNumber).Contains(value));
but it's give me error because you can't use Convert.tostring in IQuerable. I know that I can do that
customers.ToList().Where(x => Convert.ToString(x.CrmCustomerNumber).Contains(value));
but I am doing Customer.ToList() at the end after applying all filters. is there any solution of my problem?
If the numbers are of a known size, for instance if they are all six digits, then you could just query for a value range.
Example: I'm looking for all six-digit numbers that start with 123. That's the same as saying "WHERE Value BETWEEN 123000 AND 123999". So you could query .Where(x => x >= 123000 && x <= 123999).
IF the numbers aren't all a consistent size, but at least have some practical limit, you could extend this to say .Where(x => x == 123 || (x >= 1230 && x <= 1239) || (x >= 12300 && x <= 12399) || (x >=123000 && x <= 123999). etc.
With a little math, you could make this work for any number. (x >= n * 10 && x <= ((n * 10) + 9))
EndsWith can be done using modulo math.
Contains... well... you're stuck with a table scan. In that case, you might seriously consider adding a second, perhaps computed column to the table, one that stores the same value as a string, and then indexing that column. Then, use the new column to do the searches.
Trying to update a table in ASP.net MVC with a Linq query. It works fine in terms of actually doing the update, but I can't figure out how to mathematically update it. For example, the Printer Credit Fund was set to "50", added 12 to it then sets it "5012" as opposed to "62". The following is the code that is doing the update:
tblOption fund = (
from n in db.tblOptions
where n.ID == 1
select n).First();
fund.PrinterCreditFund = fund.PrinterCreditFund + tblPrinterCredit.Money;
We're using Entity Framework if that makes any sort of a difference.
Any tips towards the right direction of doing a mathematical update rather than appending the value would be hugely appreciated.
Many thanks
That's it. Convert fund.PrinterCreditFund and tblPrinterCredit.Money to using Convert.ToInt32() and also make sure fund.PrinterCreditFund is also int type
tblOption fund = (
from n in db.tblOptions
where n.ID == 1
select n).First();
fund.PrinterCreditFund = Convert.ToInt32( fund.PrinterCreditFund) + Convert.ToInt32( tblPrinterCredit.Money);
I guess that either Money is a string. Then the + operator concats them instead of summing the values. So both must be a numeric type, for example int:
fund.PrinterCreditFund = fund.PrinterCreditFund + int.Parse(tblPrinterCredit.Money);
Of course it would be better to not store a number as string in the first place.
I have two tables in database. Ticket and TicketNumbers. I would like to write Linq to count the number of tickets that have numbers matching those passed into this function. Since we don't know how many numbers must be matched, the Linq has to be dynamic ... to my understanding.
public int CountPartialMatchingTicket(IList<int> numbers)
{
// where's the code? =_=;
}
Say for example there are 3 Tickets in the database now and I want to count up all those that have the numbers 3 and 4.
(1) 1 2 3 4
(2) 1 3 4 6 7
(3) 1 2 3
In this case the function should return 2, since ticket (1) and (2) have the matching numbers.
In another case if asked to match 1, 2, 3, then again we should be returned 2.
Here's what those two tables in the database look like:
Ticket:
TicketId, Name, AddDateTime
TicketNumbers:
Id, TicketId, Number
I've never used Dynamic Linq before, so just in case this is what I have at the top of my cs file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using LottoGen.Models;
using System.Linq.Dynamic;
First things first though, I don't even know how I should write the Linq line for a fixed amount of numbers. I suppose the SQL would be like this:
SELECT TicketId, COUNT(0) AS Expr1
FROM TicketNumber
WHERE (Number = 3) OR (Number = 4)
GROUP BY TicketId
However this isn't what I want either. The above query would get me Tickets that have either a 3 or a 4 - but I just want the tickets that have BOTH numbers. And I guess it has to be nested somehow to return a single count. If I had to use my imagination for completing the function then, it would be something like this:
public int CountPartialMatchingTicket(IList<int> numbers)
{
string query = "";
foreach(int number in numbers) {
query += "Number = " + number.ToString() + " AND ";
}
// I know.. there is a trailing AND.. lazy
int count = DbContext.TicketNumbers.Where(query).Count();
return count;
}
Oh wait a minute. There's no Dynamic Linq there... The above is looking like something I would do in PHP and that query statement obviously does not do anything useful. What am I doing? :(
At the end of the day, I want to output a little table to the webpage looking like this.
Ticket Matching Tickets
-----------------------------------
3 4 2
Trinity, help!
public int CountPartialMatchingTicket(IList<int> numbers)
{
var arr = numbers.ToArray();
int count = DbContext.Tickets
.Count(tk=>arr.All(n=> tk.TicketNumbers.Any(tn=>tn.Number== n));
return count;
}
UPDATE: "If you don't mind, how would I limit this query to just the tickets made on a particular AddDateTime (the whole day)?"
The part inside the Count() method call is the WHERE condition, so just extend that:
DateTime targetDate = ......;
DateTime tooLate = targetDate.AddDay(1);
int count = DbContext.Tickets
.Count(tk=>
targetDate < tk.AddDateTime && tk.AddDateTime < tooLate
&& arr.All(n=> tk.TicketNumbers.Any(tn=>tn.Number== n));
This is similar to James Curran's answer, but a little simpler, and it should produce a simpler WHERE IN-based query:
// count all tickets...
return DbContext.Tickets.Count(
// where any of their ticket numbers
tk => tk.TicketNumbers.Any(
// are contained in our list of numbers
tn => numbers.Contains(tn.Number)))
I have an array with a huge amounts of IDs I would like to select out from the DB.
The usual approach would be to do select blabla from xxx where yyy IN (ids) OPTION (RECOMPILE).
(The option recompile is needed, because SQL server is not intelligent enough to see that putting this query in its query cache is a huge waste of memory)
However, SQL Server is horrible at this type of query when the amount of IDs are high, the parser that it uses to simply too slow.
Let me give an example:
SELECT * FROM table WHERE id IN (288525, 288528, 288529,<about 5000 ids>, 403043, 403044) OPTION (RECOMPILE)
Time to execute: ~1100 msec (This returns appx 200 rows in my example)
Versus:
SELECT * FROM table WHERE id BETWEEN 288525 AND 403044 OPTION (RECOMPILE)
Time to execute: ~80 msec (This returns appx 50000 rows in my example)
So even though I get 250 times more data back, it executes 14 times faster...
So I built this function to take my list of ids and build something that will return a reasonable compromise between the two (something that doesn't return 250 times as much data, yet still gives the benefit of parsing the query faster)
private const int MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH = 5;
public static string MassIdSelectionStringBuilder(
List<int> keys, ref int startindex, string colname)
{
const int maxlength = 63000;
if (keys.Count - startindex == 1)
{
string idstring = String.Format("{0} = {1}", colname, keys[startindex]);
startindex++;
return idstring;
}
StringBuilder sb = new StringBuilder(maxlength + 1000);
List<int> individualkeys = new List<int>(256);
int min = keys[startindex++];
int max = min;
sb.Append("(");
const string betweenAnd = "{0} BETWEEN {1} AND {2}\n";
for (; startindex < keys.Count && sb.Length + individualkeys.Count * 8 < maxlength; startindex++)
{
int key = keys[startindex];
if (key > max+MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH)
{
if (min == max)
individualkeys.Add(min);
else
{
if(sb.Length > 2)
sb.Append(" OR ");
sb.AppendFormat(betweenAnd, colname, min, max);
}
min = max = key;
}
else
{
max = key;
}
}
if (min == max)
individualkeys.Add(min);
else
{
if (sb.Length > 2)
sb.Append(" OR ");
sb.AppendFormat(betweenAnd, colname, min, max);
}
if (individualkeys.Count > 0)
{
if (sb.Length > 2)
sb.Append(" OR ");
string[] individualkeysstr = new string[individualkeys.Count];
for (int i = 0; i < individualkeys.Count; i++)
individualkeysstr[i] = individualkeys[i].ToString();
sb.AppendFormat("{0} IN ({1})", colname, String.Join(",",individualkeysstr));
}
sb.Append(")");
return sb.ToString();
}
It is then used like this:
List<int> keys; //Sort and make unique
...
for (int i = 0; i < keys.Count;)
{
string idstring = MassIdSelectionStringBuilder(keys, ref i, "id");
string sqlstring = string.Format("SELECT * FROM table WHERE {0} OPTION (RECOMPILE)", idstring);
However, my question is...
Does anyone know of a better/faster/smarter way to do this?
In my experience the fastest way was to pack numbers in binary format into an image. I was sending up to 100K IDs, which works just fine:
Mimicking a table variable parameter with an image
Yet is was a while ago. The following articles by Erland Sommarskog are up to date:
Arrays and Lists in SQL Server
If the list of Ids were in another table that was indexed, this would execute a whole lot faster using a simple INNER JOIN
if that isn't possible then try creating a TABLE variable like so
DECLARE #tTable TABLE
(
#Id int
)
store the ids in the table variable first, then INNER JOIN to your table xxx, i have had limited success with this method, but its worth the try
You're using (key > max+MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH) as the check to determine whether to do a range fetch instead of an individual fetch. It appears that's not the best way to do that.
let's consider the 4 ID sequences {2, 7}, {2,8}, {1,2,7}, and {1,2,8}.
They translate into
ID BETWEEN 2 AND 7
ID ID in (2, 8)
ID BETWEEN 1 AND 7
ID BETWEEN 1 AND 2 OR ID in (8)
The decision to fetch and filter the IDs 3-6 now depends only on the difference between 2 and 7/8. However, it does not take into account whether 2 is already part of a range or a individual ID.
I think the proper criterium is how many individual IDs you save. Converting two individuals into a range removes has a net benefit of 2 * Cost(Individual) - Cost(range) whereas extending a range has a net benefit of Cost(individual) - Cost(range extension).
Adding recompile not a good idea. Precompiling means sql does not save your query results but it saves the execution plan. Thereby trying to make the query faster. If you add recompile then it will have the overhead of compiling the query always. Try creating a stored procedure and saving the query and calling it from there. As stored procedures are always precompiled.
Another dirty idea similar to Neils,
Have a indexed view which holds the IDs alone based on your business condition
And you can join the view with your actual table and get the desired result.
The efficient way to do this is to:
Create a temporary table to hold the IDs
Call a SQL stored procedure with a string parameter holding all the comma-separated IDs
The SQL stored procedure uses a loop with CHARINDEX() to find each comma, then SUBSTRING to extract the string between two commas and CONVERT to make it an int, and use INSERT INTO #Temporary VALUES ... to insert it into the temporary table
INNER JOIN the temporary table or use it in an IN (SELECT ID from #Temporary) subquery
Every one of these steps is extremely fast because a single string is passed, no compilation is done during the loop, and no substrings are created except the actual id values.
No recompilation is done at all when this is executed as long as the large string is passed as a parameter.
Note that in the loop you must tracking the prior and current comma in two separate values
Off the cuff here - does incorporating a derived table help performance at all? I am not set up to test this fully, just wonder if this would optimize to use between and then filter the unneeded rows out:
Select * from
( SELECT *
FROM dbo.table
WHERE ID between <lowerbound> and <upperbound>) as range
where ID in (
1206,
1207,
1208,
1209,
1210,
1211,
1212,
1213,
1214,
1215,
1216,
1217,
1218,
1219,
1220,
1221,
1222,
1223,
1224,
1225,
1226,
1227,
1228,
<...>,
1230,
1231
)