Arithmetic overflow problem - c#

i have a table in my database as freespace... it has a column called freesize with its datatype as int.
i have three valuse in it :
1065189988
1073741818
1073741819
now i try to get the total free space but i get an error.
private void GetFreeSpace()
{
DataTable dt = new DataTable();
SqlConnection connection = new SqlConnection();
connection.ConnectionString = ConfigurationManager.ConnectionStrings["SumooHAgentDBConnectionString"].ConnectionString;
connection.Open();
SqlCommand sqlCmd = new SqlCommand("select sum(freesize)* 0.000000000931322575 as freespace from freespace", connection);
SqlDataAdapter sqlDa = new SqlDataAdapter(sqlCmd);
sqlDa.Fill(dt);
connection.Close();
if (dt.Rows.Count > 0)
{
double freeSpace = Convert.ToDouble(dt.Rows[0]["freespace"].ToString());
}
}
Arithmetic overflow error converting expression to data type int.
Line 123: SqlCommand sqlCmd = new SqlCommand("select sum(freesize)* 0.000000000931322575 as freespace from freespace", connection);
Line 124: SqlDataAdapter sqlDa = new SqlDataAdapter(sqlCmd);
Line 125: **sqlDa.Fill(dt);** here i get error
Line 126: connection.Close();
Line 127: if (dt.Rows.Count > 0)
Any suggestions of how to get the value in freespace without changing the datatype?
Thanks
I know it works good with bigint, but I want int.

So you're summing 3 large integers, then multiplying by a tiny double, but still want the output to be an int?
you could retrieve each row as an int, then do your math in c#
or you could cast the int to a bigint or double before the sum, so you don't overflow there.

You've got two choices as far as I can tell: use Li0liQ's approach, something on the order of
SELECT SUM(freesize * 0.000000931322575) ...
which may suck because of accuracy concerns; or, sucking it up and using a bigint. That raises the question: why are you so concerned with using an int rather than a bigint? If all you have is a hammer, then go nuts. But I'd at least consider using bigint if there isn't a compelling reason not to.

The sum of the 3 values alone are larger than the data type size for an Int in SQL which is defined as the range:
-2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647)
As such you could do the following:
select cast(sum(cast(freesize as bigint))* 0.000000000931322575 as int) as freespace from freespace
Which will support the math and get you back the result as an INT but this cast will just truncate the value and return 2 while as a double it would be 2.99 so you would also want to have SQL round the number appropriately.

Multiply by C < 1, and then sum.

Multiply first, then SUM?
select cast(sum(freesize* 0.000000000931322575) as int)...
However, as Adam Gritt pointed out, do you want 2 or 3 as your answer: round or truncate? So, expanding on is answer... To round correctly as deal with truncation, add 0.5 before the cast back to int
select cast(sum(freesize* 0.000000000931322575) + 0.5 as int)...

Related

MySQL 8: Should I be able to write a valid IEEE 754 floating point number and read the exact value back?

The program below writes a floating point number to the database and then reads it back.
The hexadecimal represenation of the floating point value written is:
a379eb4c
The value read out of the database is:
bd79eb4c
The value written looks like a valid IEEE 754 floating point value (see here).The MySQL docs mention IEEE 754 here with some remarks of the internal storage
format being machine dependent. This program uses MySQL 8.0.16 with the 64-bit ODBC driver on Windows 10 and my assumption therefore is that IEEE 754 is used throughout.
The complete output of the program is:
written (hex): a379eb4c
read (hex): bd79eb4c
written (dec): 123456792
read (dec): 123457000
How can the difference be explained?
Am I missing a setting somewhere?
Program (C#, Visual Studio 2019, .Net Core with System.Data.Odbc package):
using System;
namespace MySqlOdbcFloatTest
{
class Program
{
static void Main(string[] args)
{
var connectionString = "DSN=MySql64";
using (var connection = new System.Data.Odbc.OdbcConnection(connectionString))
{
connection.Open();
// create table
using (var cmd = new System.Data.Odbc.OdbcCommand("create table TestFloatTable (SomeFloat float)", connection))
{
cmd.ExecuteNonQuery();
}
// insert float
float floatToWrite = 123456789.0f;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "insert TestFloatTable (SomeFloat) values (?)";
var p = new System.Data.Odbc.OdbcParameter();
p.OdbcType = System.Data.Odbc.OdbcType.Real;
p.Value = floatToWrite;
cmd.Parameters.Add(p);
cmd.ExecuteNonQuery();
}
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
// write hex values
Console.Write("written (hex): ");
var floatWrittenBytes = BitConverter.GetBytes(floatToWrite);
foreach (var b in floatWrittenBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
Console.Write(" read (hex): ");
var floatReadBytes = BitConverter.GetBytes(floatRead);
foreach (var b in floatReadBytes)
{
Console.Write(string.Format("{0:x2}", b));
}
Console.WriteLine();
// write decimal values
Console.Write("written (dec): ");
Console.WriteLine(floatToWrite.ToString("F0"));
Console.Write(" read (dec): ");
Console.WriteLine(floatRead.ToString("F0"));
}
}
}
}
You are encountering MySQL bug 87794. The float is stored with the full precision, but is not returned to the client with full precision.
MySQL uses the FLT_DIG constant (which equals to 6 with IEEE 754 encoding)
to print float-type numbers. FLT_DIG is the number of decimal digits
that can be converted to a float-type binary number and back without
loss of precision for any input number. That doesn't mean there are no
numbers with more significant digits that can be represented precisely
in the binary format (and your case is an example of such a number), but
the constant ensures that property for all inputs.
There are a couple of workarounds you could use.
Binary Protocol
By default, MySQL uses a "text protocol" that sends numbers over the wire using ASCII digits. This is where FLT_DIG has an effect. By contrast, the binary protocol sends 32-bit IEEE 754 floats.
I don't know if the ODBC connector can expose the binary protocol, but MySqlConnector does for prepared statements.
using (var connection = new MySqlConnection("...;IgnorePrepare=false"))
{
// read float back
float floatRead;
using (var cmd = new MySqlCommand())
{
cmd.Connection = connection;
cmd.CommandText = "select SomeFloat from TestFloatTable";
// ADD THIS LINE
cmd.Prepare();
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)reader.GetValue(0); // GetValue returns a float object
}
}
Coerce to double precision
If you perform a calculation on the result, then MySQL will coerce it to double-precision. This will be formatted correctly on the wire and your client will read the correct result. Note that for MySqlConnector, the resulting value will now be typed as a double; quite possibly the ODBC connector works the same way:
// read float back
float floatRead;
using (var cmd = new System.Data.Odbc.OdbcCommand())
{
cmd.Connection = connection;
// ADD "+0" TO THE SELECT STATEMENT
cmd.CommandText = "select SomeFloat+0 from TestFloatTable";
var reader = cmd.ExecuteReader();
reader.Read();
floatRead = (float)(double)reader.GetValue(0); // GetValue returns a double object
// ALTERNATIVELY, just use GetFloat
floatRead = reader.GetFloat(0);
}

Estimate the size of a dataset returned from SQL

We've got a system that seems to be consuming a lot of data, it uses Dapper for database queries and Seq for logging. I was wondering if other than with SQL Profiler whether there was a way to add logging to Dapper to log the size of the dataset returned in MB -so we can flag large datasets for review?
This question has been asked a while ago but I was wondering whether there was now a way of doing it without wireshark and ideally without iterating over the rows/cells?
I would configure Provider Statistics for SQL Server for the connection in the base repository class. You can add a config setting to switch it on and easily save this information off to a log file or where ever you want.
Example code from MSDN
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace CS_Stats_Console_GetValue
{
class Program
{
static void Main(string[] args)
{
string connectionString = GetConnectionString();
using (SqlConnection awConnection =
new SqlConnection(connectionString))
{
// StatisticsEnabled is False by default.
// It must be set to True to start the
// statistic collection process.
awConnection.StatisticsEnabled = true;
string productSQL = "SELECT * FROM Production.Product";
SqlDataAdapter productAdapter =
new SqlDataAdapter(productSQL, awConnection);
DataSet awDataSet = new DataSet();
awConnection.Open();
productAdapter.Fill(awDataSet, "ProductTable");
// Retrieve the current statistics as
// a collection of values at this point
// and time.
IDictionary currentStatistics =
awConnection.RetrieveStatistics();
Console.WriteLine("Total Counters: " +
currentStatistics.Count.ToString());
Console.WriteLine();
// Retrieve a few individual values
// related to the previous command.
long bytesReceived =
(long) currentStatistics["BytesReceived"];
long bytesSent =
(long) currentStatistics["BytesSent"];
long selectCount =
(long) currentStatistics["SelectCount"];
long selectRows =
(long) currentStatistics["SelectRows"];
Console.WriteLine("BytesReceived: " +
bytesReceived.ToString());
Console.WriteLine("BytesSent: " +
bytesSent.ToString());
Console.WriteLine("SelectCount: " +
selectCount.ToString());
Console.WriteLine("SelectRows: " +
selectRows.ToString());
Console.WriteLine();
Console.WriteLine("Press any key to continue");
Console.ReadLine();
}
}
private static string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrive it from a configuration file.
return "Data Source=localhost;Integrated Security=SSPI;" +
"Initial Catalog=AdventureWorks";
}
}
}
Not really a complete answer, but might help you out.
sys.dm_exec_query_stats and sys.dm_exec_connections might help you trace large result sets. For example:
SELECT * FROM sys.dm_exec_query_stats
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
(Units are pages of 8k).
This sort of gives you what you're using wireshark for at the moment (kinda :)
SELECT * FROM sys.dm_exec_connections
CROSS APPLY sys.dm_exec_sql_text(most_recent_sql_handle)
ORDER BY num_writes DESC
https://msdn.microsoft.com/en-us/library/ms189741.aspx
https://msdn.microsoft.com/en-AU/library/ms181509.aspx
You can estimate the size required by a row summing up each column type size, then multiply by the number of rows. It should be accurate if you don't have TEXT / VARCHAR in your query:
int rowSize = 0;
foreach(DataColumn dc in Dataset1.Tables[0].Columns) {
rowSize += sizeof(dc.DataType);
}
int dataSize = rowSize * Dataset1.Tables[0].Rows.Count;
In case you need a more accurate figure, sum up the size of each individual value using Marshal.SizeOf:
int dataSize = 0;
foreach(DataRow dr in Dataset1.Tables[0].Rows)
{
int rowSize = 0;
for (int i = 0; i < Dataset1.Tables[0].Columns.Count; i++)
{
rowSize += System.Runtime.InteropServices.Marshal.SizeOf(dr[i]);
}
dataSize += rowSize;
}
Ideas for performance gain if high accuracy is not a concern:
Compute the size of just a sample. Let's say, instead of iterating through all rows, pick 1 in every 100, then multiply your result by 100 in the end.
Use [Marshal.SizeOf]((https://msdn.microsoft.com/en-us/library/y3ybkfb3.aspx) to compute the size of each DataRow dr instead of iterating through all it's values. It will give you a higher number since a DataRow object has additional properties, but that's something you can tweak by subtracting the size of an empty DataRow.
Know the average size of a single row beforehand, by it's columns, and just multiply by the number of rows.
We got around this limitation by just capping/limiting the query size. This prevents us from having to worry about size and doing double queries. The PR we used that you could also use is https://github.com/DapperLib/Dapper/pull/1758/files

SQLCommand.Parameters.Add - How to give decimal value size?

How would you specify this:
Decimal(18,2)
In this:
SqlComm.Parameters.Add("#myValue", SqlDbType.Decimal, 0, "myValue");
Currently I have defined precision = 2 from the design side properties. I'm just curious as to how to accomplish this from the code. Thanks
There's not an overload of Add that lets you set the decimal precision inline, so you either need to create a SQlParameter object and add it to the collection:
SqlParameter param = new SqlParameter("#myValue", SqlDbType.Decimal);
param.SourceColumn = "myValue";
param.Precision = 18;
param.Scale = 2;
SqlComm.Parameters.Add(param);
or keep a reference to the parameter after adding it:
SqlParameter param = SqlComm.Parameters.Add("#myValue", SqlDbType.Decimal, 0, "myValue");
param.Precision = 18;
param.Scale = 2;
or using the parameter constructor:
SqlComm.Parameters.Add(new SqlParameter(
parameterName = "#myValue",
dbType = SqlDbType.Decimal,
precision = 18,
scale = 2,
sourceColumn = "myValue"));
var cmd = new SqlCommand()
SetDecimalParameter(cmd.Parameters.Add("#paramName", SqlDbType.Decimal), 18, 2).Value = 12.34;
SqlParameter SetDecimalParameter(SqlParameter parameter, byte precision, byte scale) {
parameter.Precision = precision;
parameter.Scale = scale;
return parameter;
}
My answer is not directly connected with the OP's question, but I've seen a lot of people asking "why set the precision since it is taken from the value".
It has to do with the way SQL Server works when comparing the decimal parameter with the text column. Imagine you have column named strNumberColumn witch is of nvarchar type. If you define a #var Decimal(1,0) = '1', comparison on the condition where strNumberColumn >= #var will work only as long, as the longest entry in that column is between "0" and "9". If any of the entries go beyond, for example "10" or "123" you will get an OverflowException while converting string to decimal. What is important, that conversion is made "behind the scenes".
Please, do not bring arguments like "if that column should contain numbers it should not be made nvarchar" - I totally agree, but that is beyond the scope of the problem (sometimes you work with a legacy system and you have no influence over the column datatype). The above example shows a real life scenario when defining precision is required in order for the query to run successfully despite having a smaller precision amount assigned to the variable (like #var Decimal(12,2) = '1.0').

How to convert string system collection to int?

I have a windows form chart that retrieve the x value in mysql database and plot it on a bar graph. This is the the first time I'm doing this so I don't really have an idea why I am getting this error.
public void loadChart()
{
string conn = "server=localhost;user=root;password='';database=cashieringdb;";
string cmdstring = "SELECT sum FROM salessum";
MySqlDataAdapter adapter = new MySqlDataAdapter(cmdstring, conn);
DataTable dt = new DataTable();
adapter.Fill(dt);
chart1.DataSource = dt;
this.chart1.Palette = ChartColorPalette.SeaGreen;
this.chart1.Titles.Add("Daily Record");
int[] pointsArray = a.mysqlSelect("select sum from salessum"); // error in this line
string[] seriesArray = { "Mon", "Tue", "Wed", "Th", "Fri" };
for (int i = 0; i < seriesArray.Length; i++)
{
Series series =this.chart1.Series.Add(seriesArray[i]);
series.Points.Add(pointsArray[i]);
}
Here's the error shows: No matter how much time searching for solution on the internet, but still I ca'nt get this working. Or is there any easy way to retrieve data from database and plot it on the windows form chart?
Error 2 Cannot implicitly convert type 'System.Collections.Generic.List<string>[]' to 'int[]'
EDIT
I change my code to this:
List<string>[] pointsArray = a.mysqlSelect("select sum from salessum"); //no error
Error in this line:
series.Points.Add(pointsArray[i]);
This error occurs because you are converting a List to int[]. You can not implicitly do this, you have to do this explicitly. you can do this easily using Linq.
I assume that a.mysqlSelect("select sum from salessum"); is a method call. and out put a list of strings(each string representing a number)
Something Like this:
int[] pointsArray = a.mysqlSelect("select sum from salessum").Select(i => int.Parse(i)).ToArray();
This link will be helpful to you as well.
Edited:
Take a look at the LINK I have added earlier and get an idea on what you are going to accomplish. :)
in this code,
series.Points.Add(pointsArray[i]);
Points.Add method do not expect a string it expects a int. What you have tried to do earlier is correct. So point Array has to be a int[].
I mean this code:
int[] pointsArray = a.mysqlSelect("select sum from salessum");
I think I have found the exact same thing that you want to accomplish. Please take a look at this LINK. This will be helpful to you.

Using SqlDBType.Decimal in Prepared Statement C#

am using a Prepared Statement in C#.
SqlCommand inscommand = new SqlCommand(supInsert, connection);
inscommand.Parameters.Add("#ordQty", SqlDbType.Decimal,18);
inscommand.Prepare();
u = inscommand.ExecuteNonQuery();
The above code throws below Exception:
SqlCommand.Prepare method requires parameters of type 'Decimal' have an explicitly set Precision and Scale.
EDIT: How to avoid this Exception
The following would set a Decimal with Precision 18 and Scale 8 (Decimal (18,8))
SqlCommand insertCommand= new SqlCommand(supInsert, connection);
insertCommand.Parameters.Add("#ordQty", SqlDbType.Decimal,18);
insertCommand.Parameters["#ordQty"].Precision = 18;
insertCommand.Parameters["#ordQty"].Scale = 8;
insertCommand.Prepare();
u = insertCommand.ExecuteNonQuery();
As the exception pointed out, you have to explicitly set the SqlParameter.Precision and SqlParameter.Scale properties in order to use the decimal type as a parameter.
Let's say your SQL field is of type decimal(18,8). The way to do this inline is to use brace-initialization for your SqlParameter while adding it to the SqlParameterCollection, as follows:
cmd.Parameters.Add(new SqlParameter("#ordGty", SqlDbType.Decimal) {
Precision = 18, Scale = 8 });
You can also do
cmd.Parameters.Add(new SqlParameter("#ordGty", SqlDbType.Decimal) {
Precision = 18, Scale = 8}).Value = 0.4m; // or whatever
to add the value, if you need one. You could even do
cmd.Parameters.Add(new SqlParameter("#ordGty", SqlDbType.Decimal) {
Precision = 18, Scale = 8, Value = 0.4m /* or whatever */});
if you prefer. Brace initialization is really powerful.
Side note: I realize that this is an old question, but I think this form is much more readable than creating the object while adding it to the list and then setting the scale and precision. For Posterity! (since this is a high-listing google search result)
try this:
SqlParameter parameter = new SqlParameter("#ordQty", SqlDbType.Decimal);
parameter.Precision = 18;
parameter.Scale = 0;
parameter.Value = YOURVALUEHERE;
inscommand.Parameters.Add(parameter);
You will have to explicitly define precision and scale for this parameter.
SqlParameter ordQty = cmd.Parameters.Add("#ordQty", SqlDbType.Decimal);
ordQty.Precision = x; //Replace x with what you expect in Sql Sp
ordQty.Scale = y; //Replace y with what you expect in Sql Sp
ordQty.Value = 18; //Set value here
inscommand.Parameters.Add(ordQty);

Categories

Resources