Select row from DataTable based on condition - c#

I am trying to use Datatable Select but I am getting incorrect data
double marks = 5;
DataRow[] result = dsGrades.Tables[0].Select("Convert(MarksFrom, 'System.Decimal') >=" + marks + " And " + marks + "<= Convert(MarksTo, 'System.Decimal') ");
dsGrades contains below data,
when 'marks' contain '5.0', I am expecting row where MarksFrom = 5.0 and MarksTo = 5.9, as 5.0 falls in this range, but here it is returning 5 rows.
Whats wrong with datatable select? Any help is appreciated.

If would make sense to change your DataColumn types to double, however even with decimal you don't need a conversion inside the expression.
Note in your provided example, your constraint appears to be backwards. You're specifying that you want MarksFrom greater or equal to the passed in amount, which won't return a single row in the range you want.
This should return a single row for any mark passed in:
double marks = 5.0;
DataRow[] result = dsGrades.Tables[0].Select($"{marks} >= MarksFrom AND {marks} <= MarksTo");
Also since you're always only expecting a single match, you could change this to:
DataRow match = table.Select($"{marks} >= MarksFrom AND {marks} <= MarksTo").SingleOrDefault();
SingleOrDefault will throw an InvalidOperationException if more than one result is returned, which may be the desired outcome in this case.

You can do it like this:
double marks = 5.0;
decimal newMarks = Convert.ToDecimal(marks);
var result =
dsGrades.Tables[0]
.AsEnumerable()
.Where( dr => dr.Field<decimal>( "MarksFrom" ) >= newMarks
&& dr.Field<decimal>( "MarksTo" ) < newMarks + 1);
This could be the solution:
var result = dsGrades.Tables[0].Select("Convert(MarksFrom, 'System.Decimal') >=" + newMarks + " And Convert(MarksTo, 'System.Decimal') < " newMarks + 1);
From my comment on question explaining problem:
Getting all the rows where MarksFrom is above 5 will return the first 5 visible rows in table, checking the second condition for these 5 rows and 5.0 is less than or equal to MarksTo in each of the rows so this would evaluate true for these rows. Therefore grabbing 5 rows

Needs to do casting from datatable as below:
DataRow[] drGrdPercntl = dt_GrdPercntil.Select($"{SubjVal} >= Convert(MarksFrom, 'System.Decimal') AND {SubjVal} <= Convert(MarksTo, 'System.Decimal')");

Related

Get rows according to given value

I have mssql table as the given image. I want to know how can I return rows from the table tblSpace that the sum of "Available" rows is greater than or equal to given value using Linq. Also there is a order of filling from 1 to 5. Thanks
Table capture and data:> tblSpace
What I've tried so far
1). If I pass 9,000 as value it should return only Vienguard row (9,000 < 10,000)
2). If I pass 23,000 as value it should return both Vienguard and Halestorm rows (23,000 < 10,000+15,000)
3). if I pass 35,000 as value it should return Vienguard, Halestorm and Quarts rows (35,000 < 10,000+15,000+20,000)
Alright so I think you need something like this?
var sum = 0;
var result = tblSpace.OrderBy(x => x.Available).TakeWhile(x => (sum += x.Available) <= value).ToList();
EDIT
In case you want an extra value if it exceeds you add
var result = tblSpace.OrderBy(x => x.Available).TakeWhile(x => (sum += x.Available) - x.Available < value).ToList();

How to handle division by zero in DataTable.Compute()

I'm trying to build a module where customer can create custom formulas and calculate them on the data he has in SQL database.
The customer has access to basic arithmetic operators (+, -, *, /) and aggregate functions (MIN, MAX, AVG, SUM). In addition he is given a set of columns he can perform those operations on, for the sake of the example: (UnitsProduced, ProductionTime, DownTime etc.). With these tools he can construct formulas such as: SUM(UnitsProduced)/SUM(ProductionTime).
After checking that the formula is mathematically correct and contains only valid columns I fetch the data using Stored Procedure from SQL Server into DataTable in C# and then use DataTable.Compute() method on that formula.
The problem is when the aggregate function yields 0. I tried padding the provided formula with IIF condition so that SUM(UnitsProduced)/SUM(ProductionTime) becomes SUM(UnitsProduced)/IIF(SUM(ProductionTime)<>0,SUM(ProductionTime),NULL) and i would expect to get null as the result but it gives me the error:
Cannot evaluate. Expression 'System.Data.FunctionNode' is not an aggregate
After doing some research I found that I cannot use aggregate function in a conditional statement. I haven't tried LINQ yet and I don't know if it's going to work.
How can I solve this problem?
After some research, me and my colleagues came to an interesting solution.
Basically you need to find all the divisors of the given formula and create a filtering string that will filter out all the data where the divisor is 0. This code also handles aggregation on multiple columns because Comute can't do that.
The code that takes care of zero divisions and aggregation:
DataTable dt = db.GetDataFromStoredProcedure("GetCustomFormulasData", param);
//Avoid zero division
List<string> divisors = GetFormulaDivisors(formula);
string formulaFilter = "";
foreach (string divisor in divisors)
formulaFilter += $" and {divisor}<>0 ";
if(formulaFilter != "")
formulaFilter = formulaFilter.Remove(0, 5); //remove the 1st " and "
//Compute cant handle multiple columns in one aggregate, so we handle it here
List<string> aggregations = GetFormulaAggregations(formula);
for (int i = 0; i < aggregations.Count; i++)
{
string expr = aggregations[i].Substring(4, aggregations[i].Length - 5);//remove the agg func and last paren
DataColumn dc = new DataColumn
{
DataType = typeof(float),
ColumnName = $"Column{i}",
Expression = expr
};
dt.Columns.Add(dc);
Regex rgx = new Regex(Regex.Escape(expr));
string temp = rgx.Replace(aggregations[i], dc.ColumnName);
rgx = new Regex(Regex.Escape(aggregations[i]));
formula = rgx.Replace(formula, temp);
if (formulaFilter != "")
formulaFilter = rgx.Replace(formulaFilter, temp);
}
return float.Parse(dt.Compute(formula, formulaFilter).ToString());
Where GetDataFromStoredProcedure, GetFormulaDivisors, GetFormulaAggregations are inner methods that do as their name suggests.
So for the example of formula SUM(GoodUnitsProduced+RejectedUnits)/SUM(ProductionTime), the DataTable dt will get the Columns: (GoodUnitsProduced, RejectedUnits, ProductionTime) from stored procedure, List divisors will have SUM(ProductionTime) and List aggregations will have: SUM(GoodUnitsProduced+RejectedUnits) and SUM(ProductionTime).
Right before calling the Compute method the DataTable will have two additional columns, one for each aggregate in the formula and filter will have the divisors in it. So formula = SUM(Column0)/SUM(Column1) and formulaFilter = SUM(Column1)<>0 and the problem of zero division is solved.

Running multiple sql queries to get matrix results in .NET Core

I am trying to fetch results from database to generate some kind of matrix results to send back to front end. The point is that I have percentile value for X and Y axes which I divide into 10 parts to have 10x10 table. To get each value I calculate distinct user Ids, so it goes like 1-1, 1-2 ... 10-10.
This is my current code (unfinished though, just the idea what I have so far) which I want to improve because running 100 queries one after one doesn't seem like a nice solution. However I am a little bit stuck how to make the performance better and whether I should return results in dictionary with lenght=100, or multidimensional matrix array to make it a good practice code. Thanks anyone for the tips in advance, my code is below:
public async Task GenerateMatrix(List<double> x, string xAxis, List<double> y, string yAxis, Parameters parameters)
{
IDictionary<string, string> xDict = GenerateRanges(x, parameters.XAxis);
IDictionary<string, string> yDict = GenerateRanges(y, parameters.YAxis);
var innerJoin = GenerateInnerJoin(parameters);
var whereClauses = GenerateWhereClause(parameters);
var sql = $#"SELECT COUNT(DISTINCT [dbo].[{nameof(Table)}].[{nameof(Table.UserId)}]) FROM [dbo].[{nameof(Table)}] {innerJoin} ";
if (whereClauses.Any())
{
sql += " WHERE " + string.Join(" AND ", whereClauses);
}
for (int i = 0; i < x.Count; i++)
{
var queryToExecute = "";
for (int j = 0; j < y.Count; j++)
{
queryToExecute = sql + " AND " + xDict.Values.ElementAt(i) + " AND " + yDict.Values.ElementAt(j);
var userCount = await Store().QueryScalar<int>(queryToExecute);
}
}
return null;
}
private IDictionary<string, string> GenerateRanges(List<double> axis, string columnTitle)
{
IDictionary<string, string> d = new Dictionary<string, string>();
for (int i = 0; i < axis.Count; i++)
{
var rangeSql = $#" [dbo].[{nameof(Table)}].[{columnTitle}]";
if (i == 0)
{
d.Add(axis[i].ToString(), rangeSql + " < " + axis[i]);
}
else if (i == axis.Count - 1)
{
d.Add(axis[i] + "+", rangeSql + " > " + axis[i]);
}
else
{
d.Add(axis[i-1] + "-" + axis[i], rangeSql + " > " + axis[i-1] + " AND " + rangeSql + " < " + axis[i]);
}
}
return d;
}
sql looks like this:
SELECT
COUNT(DISTINCT [dbo].[Table].[UserId])
FROM [Table]
WHERE Table.[ClientId] = '2'
AND [dbo].[Table].[ProbabilityAlive] < 0.1
AND [dbo].[Table].[SpendAverage] < 24.86
so there's going to be 100 hundred of lines like this.
ProbabilityAlive and SpendAverage are column titles that come from front end, there could be any other column titles.
For these two columns I calculate percentile value, which then I divide into ten parts, one being X axis, another being Y axis. and then I use sql query from above to get value for each matrix value which becomes 100 queries since the matrix is 10x10.
As a result I want to get 100 integer values I am still trying to figure out whether it's best to put data in dictionary and then have key with range x-y and value as select result (e.g. "0-1", 5472"), or whether to put it in multidimensional array or something else. I have xDict that contains range as a key e.g. "0-1" and then sql sentence ProbabilityAlive > 0 AND ProbabilityALive <1 and then add same for Y axis from yDict. Then I have two lists x and y that contain 10 double values that are used for these ranges
It looks like you want to calculate the user counts for specific ranges of ProbabilityAlive and SpendAverage.
First, you need to generate the range combinations. The easy way to generate combinations in SQL is to join two tables or sets of values.
If you had two tables with the range values like these:
create table ProbabilityRanges
(
LowBound decimal(3,2), UpperBound(3,2)
)
create table SpendRanges
(
LowBound decimal(3,2), UpperBound(3,2)
)
You could use a cross-join to generate all the combinations:
SELECT
SpendRanges.LowBound as SLow,
SpendRanges.UpperBound as SUpper,
ProbabilityRangers.LowBound as PLow,
ProbabilityRanges.UpperBound as PUpper
FROM ProbabilityRanges CROSS JOIN SpeedRanges
You can use those combinations to filter and count the rows in another table that are within those bounds:
SELECT
SpendRanges.LowBound as SpendValue,
SpendRanges.LowBound as ProbabilityValue,
Count(DISTINCT UserID) as Count
FROM SomeTable CROSS JOIN ProbabilityRanges CROSS JOIN SpeedRanges
Where
SomeTable.ClientID=2
AND SomeTable.SpendAverage >=SpeedRanges.LowBound
AND SpendAverage < SpeedRanges.UpperBound
AND SomeTable.ProbabilityAlive >= ProbabilityRangers.LowBound
AND SomeTable.ProbabilityAlive < ProbabilityRanges.UpperBound
GROUP BY SpendRanges.LowBound,SpendRanges.LowBound
It's possible to create the bounds dynamically for a specific number of bins, eg using a Numbers table. You'll have to provide more information on what you actually want though
With just two dimensions, easiest to do & maintain, would be to make a TSQL Stored Procedure that outputs a Tuple (the default output) of what you want.
Pass in as parameters what you got as input from the Front-End.
What if you had that functionality with a web-service HTTP GET returning a JSON (or XML)?
Simulate it with TSQL instead.
Since you have direct access to the SQL Server, you call a Stored Procedure of type "get" that you pass parameters, and get a Tuple result.
Easy to write & test independently of your .Net core application. Will be very fast also.
type "get" = just a read-only SP, I name mine USP_GET_method_name.
I also use SPs for saving with SQL-side validations, so I call them USP_PUT_method_name.
Cheers

Error while performing compute SUM() in a column

I've tried to compute a column in my datagrid, i will be showing these in my code as per below. I Keep getting these error.
I've go through these links
1. How To Convert The DataTable Column type?
2. Error while taking SUM() of column in datatable
3. Invalid usage of aggregate function Sum() and Type: String
//This is the column i add into my datagrid
MITTRA.Columns.Add("Min_Tol");
MITTRA.Columns.Add("Max_Tol");
MITTRA.Columns.Add("Min_Weight");
MITTRA.Columns.Add("Max_Weight");
// The following codes is working finely
// if you notice MTTRQT column, it's a data queried from database
for (int i = 0; i <= MITTRA.Rows.Count - 1; i++)
{
string item = MITTRA.Rows[i]["MTITNO"].ToString();
Tolerancechecking = database_select4.LoadUser_Tolerance(item);
MITTRA.Rows[i]["Min_Tol"] = Tolerancechecking.Rows[0]["Min_Tol"].ToString();
MITTRA.Rows[i]["Max_Tol"] = Tolerancechecking.Rows[0]["Max_Tol"].ToString();
MITTRA.Rows[i]["Min_Weight"] = Convert.ToDecimal(MITTRA.Rows[i]["MTTRQT"]) - ((Convert.ToDecimal(MITTRA.Rows[i]["MTTRQT"]) * Convert.ToDecimal(MITTRA.Rows[i]["Min_Tol"]) / 10));
MITTRA.Rows[i]["Max_Weight"] = Convert.ToDecimal(MITTRA.Rows[i]["MTTRQT"]) + ((Convert.ToDecimal(MITTRA.Rows[i]["MTTRQT"]) * Convert.ToDecimal(MITTRA.Rows[i]["Max_Tol"]) / 10));
dataGrid2.Columns.Clear();
dataGrid2.ItemsSource = null;
dataGrid2.ItemsSource = Tolerancechecking.DefaultView;
}
//Working Sum computation
Decimal Sum = Convert.ToDecimal(MITTRA.Compute("SUM(MTTRQT)", string.Empty));
MaxTol.Text = Sum.ToString(); /*** This is working as i got my value on the text box ****/
Errors when trying sum computation for different column
Initial try
1. Decimal Sum = Convert.ToDecimal(MITTRA.Compute("SUM(Min_Weight)", string.Empty));
Errors occurred
Invalid usage of aggregate function Sum() and Type: String.
Second Attempts
2. Decimal Sum = Convert.ToDecimal(MITTRA.Compute("SUM(Convert(Min_Weight,'System.Decimal'))", string.Empty));
3. Decimal Sum = Convert.ToDecimal(MITTRA.Compute("SUM(Convert(Min_Weight,'System.Decimal'))", ""));
Errors occurred
Syntax error in aggregate argument: Expecting a single column argument with possible 'Child' qualifier.
How am i going to get the compute sum function to work?
Thank you #NoChance for the solutions. Hope this post can help others too.
/***** This is the part where i declare my datacolumn data type ****/
DataColumn Min_Weight = new DataColumn("Min_Weight");
Min_Weight.DataType = System.Type.GetType("System.Decimal");
MITTRA.Columns.Add(Min_Weight);
/**** This Works finally *****/
Decimal Sum = Convert.ToDecimal(MITTRA.Compute("SUM(Min_Weight)", string.Empty));
MaxTol.Text = Sum.ToString();

how to get values from text using .IndexOf and .Substring in c#?

I need to make a summation for several values out from string variable,
Here is my variable:
string strBillHeader = "Invoice Details
INVOICE_DATE,INVOICE_DESCRIPTION,VALUE,FROM_DATE,TO_DATE
01/11/2014,New Corpbundle 7,7,01/11/2014,30/11/2014
01/11/2014,New Corpbundle 7,-7,01/11/2014,30/11/2014
01/11/2014,New Corpbundle 7,7,01/11/2014,30/11/2014
01/11/2014,Missed Call ALERT with Offer,2,01/11/2014,30/11/2014"
I need to get out the VALUES which are (7,-7,7,2) in this case? and to get 9 as a result.
I tried to do it this way:
for (int x = 4; x <= countCommas - 3; x += 4)
{
int firstCommaIndex = strBillHeader.IndexOf(',', strBillHeader.IndexOf(',') + x);
int secondCommaIndex = strBillHeader.IndexOf(',', strBillHeader.IndexOf(',') + (x + 1));
string y = strBillHeader.Substring(firstCommaIndex + 1, 1);
chargeAmount = Convert.ToInt32(y);
//chargeAmount = Int32.Parse(strBillHeader.Substring(firstCommaIndex, secondCommaIndex - firstCommaIndex));
TotalChargeAmount += ChargeAmount;
//string AstrBillHeader = strBillHeader.Split(',')[0];
}
but it did not work since I keep getting 'V' in the y variable.
Any help will be appreciated
If those commas and newlines are always there, this should work:
var lines = strBillHeader.Split(Environment.NewLine).Skip(2);
int total = lines.Split(',').Sum(line => Convert.ToInt32(line[2]));
So, you split the invoice into lines and discard the first 2 ("Invoice Details" and "INVOICE_DATE,INVOICE_DESCRIPTION,VALUE,FROM_DATE,TO_DATE"). Then you split each line on the commas, and take the third value - the first is a date, the second is the "New Corpbundle 7" part, the third is your value. You parse that value as an int, and sum the whole lot.
You may find you need to filter out the lines properly, rather than just assuming you can skip the first two and use the rest, but this should get you started.

Categories

Resources