Receiving ORA-0858 in Oracle DataReader but not in SQL Developer - c#

I have a query that was built and works using SQL Developer. When I use the same query in an Oracle DataReader object, I receive ORA-01858: a non-numeric character was found where a numeric was expected
If I remove the clause to check the CRTDDATE column, the query works in the DataReader.
query:
SELECT count(distinct(H.id)) AS Completed, T.Cat, 'Task' as Type
FROM HISTORY H
INNER JOIN Tasks T ON H.id = T.id
WHERE H.Step In ('1.41', '1.61', '6.41', '6.61')
AND T.Cat = :cat
and H.CRTDDATE >= :sdate and H.CRTDDATE <= :edate
GROUP BY T.Cat, 'Task'
Code:
using (OracleConnection conn = new OracleConnection(ConnectionString))
{
OracleCommand cmd = new OracleCommand(query, conn);
cmd.Parameters.Add("sdate", startDate);
cmd.Parameters.Add("edate", endDate);
cmd.Parameters.Add("cat", cat);
await conn.OpenAsync();
using (var dr = await cmd.ExecuteReaderAsync())
{
if (dr.HasRows)
{
while (await dr.ReadAsync())
{
var report = new IPTCompletedReport();
var count = dr.GetString(0);
report.Completed = 0;
report.IPT = dr.GetString(1);
report.Type = dr.GetString(2);
results.Add(report);
}
}
}
}
Values:
startDate = {1/1/2021 12:00:00 AM}
endDate = {8/17/2022 12:00:00 AM}
cat = "DRV"
The error occurs at this line: using (var dr = await cmd.ExecuteReaderAsync())
How can I change the query to allow the DataReader to accept it?
Should I use a DataAdapter instead?
I have several other queries and DataReaders in this file that are functioning properly. Most of them have where clauses featuring date checks.

I don't know C# nor DataReader, but - error you got (and found line that causes it):
and H.CRTDDATE >= :sdate and H.CRTDDATE <= :edate
means that Oracle - in DataReader - can't implicitly convert values you provided as :sdate and :edate into a valid DATE datatype value. Oracle SQL Developer, on the other hand, did it and query worked.
Let me illustrate the problem.
Test table with one column whose datatype is DATE; as sysdate function returns value of that datatype, insert works OK:
SQL> create table test (datum date);
Table created.
SQL> insert into test values (sysdate);
1 row created.
SQL> select * From test;
DATUM
----------
17.08.2022
Really, today is 17th of August 2022.
Let's set date format and date language:
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> alter session set nls_date_language = 'croatian';
Session altered.
If I pass strings (note that '17.08.2022' is a string; to us, humans, it represents date, but Oracle has to implicitly try to convert it to date datatype:
SQL> select * from test where datum between '17.08.2022' and '20.08.2022';
DATUM
----------
17.08.2022
Oracle succeeded! Nice! OK, but - what if I pass a string that - instead of numeric month value - contains month abbreviation, written in English (remember that I previously set date language to Croatian!):
SQL> select * from test where datum between '17-aug-2022' and '20-aug-2022';
select * from test where datum between '17-aug-2022' and '20-aug-2022'
*
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected
Aha ... here's the error, the same as you got. Oracle wasn't able to implicitly convert string '17-aug-2022' into date datatype value.
What if I pass Croatian month name (it is "kolovoz" for "August"):
SQL> select * from test where datum between '17-kol-2022' and '20-kol-2022';
DATUM
----------
17.08.2022
SQL>
Right, not it works again.
So, what should you do? Take control over it! One option is to use to_date function with appropriate format model. I'll again pass English month name, but this time it'll be OK:
SQL> select * from test where datum between to_date('17-aug-2022', 'dd-mon-yyyy', 'nls_date_language = english')
2 and to_date('20-aug-2022', 'dd-mon-yyyy', 'nls_date_language = english');
DATUM
----------
17.08.2022
SQL>
Or, if you don't want to use that, use date literal (which always consists of the date keyword followed by date value in yyyy-mm-dd format enclosed into single quotes):
SQL> select * from test where datum between date '2022-08-17' and date '2022-08-20';
DATUM
----------
17.08.2022
SQL>
If you can't do any of these, then make sure that you passed values which can be implicitly converted to date datatype value.
Unfortunately, as I said, I don't know C# so I can't suggest anything smart (related to C#), but I hope that now - that you know what caused the error - you'll be able to fix it.

Using the comment from #madreflection, I added cmd.BindByName = true and that fixed my problem.
Thanks to everyone who provided suggestions.

Related

DateTimeOffset date doesn't include records with end date

I would like to get the database records by a date range including the data with the end date range included. The TimeStamp column in the database is a type of dateTime2(7). The date and time is stored in UTC. But, I'm displaying the data based on the users time zone. To achieve this, I'm looking up the time zone and then converting it to BaseUtfOffset in C#. Ex. 3/18/2020 and 3/29/2020
var newOffSetDate = TimeZoneInfo.FindSystemTimeZoneById(timeZoneName);
DateTimeOffset d1 = new DateTimeOffset(Convert.ToDateTime(date1), newOffSetDate.BaseUtcOffset);
DateTimeOffset d2 = new DateTimeOffset(Convert.ToDateTime(date2), newOffSetDate.BaseUtcOffset);
I then pass the start and end dates in procedure (type of DateTimeOffset parameter) created here to do a simple select statement in SQL. The data returned is only including the records with start date.
create table MyTable
(
Id int Primary Key Identity(1,1),
[TimeStamp] datetime2(7) not null
)
insert into MyTable(TimeStamp) values('2020-03-29 19:40:46.8500000')
insert into MyTable(TimeStamp) values('2020-03-29 19:40:53.1000000')
insert into MyTable(TimeStamp) values('2020-03-18 17:15:48.2600000')
select * from MyTable
where
convert(datetimeoffset, convert(datetime2(7), timestamp, 1)) >= '3/18/2020 12:00:00 AM -04:00' and
convert(datetimeoffset, convert(datetime2(7), timestamp, 1)) <= '3/29/2020 12:00:00 AM -04:00'
For example, in the above scenario, I should get ALL the records, but I'm getting only one record.
2020-03-18 17:15:48.2600000
Is the format of DateTimeOffset produced in C# affecting results or am I missing something?
SQLFIDDLE
UPDATE:
Stored Procedure parameters:
,#StartDate DateTimeOffset = NULL
,#EndDate DateTimeOffset = NULL
In the example is used: start date 3/18/2019 and end date 3/29/2020. The C# method that converts the dates to DateTimeOffset produced the following output:
d1 = 3/18/2020 12:00:00 AM -04:00
d2 = 3/29/2020 12:00:00 AM -04:00
I believe you are making it more difficult than it needs to be.
In your code: Apply the TimeZoneInfo to the DateTime arguments supplied by the user. As the data is persisted as DateTime2 use parameter types that match in this case System.DateTime. Based on your question you want the upper value to be inclusive, with Dates the easiest thing to do is to add 1 to the Date value (or 24 Hours is also acceptable) and change the query to be less than.
See the code below, you can modify this to be a stored proc. instead.
public void Test(DateTime argStartDate, DateTime argEndDate)
{
var newOffSetDate = System.TimeZoneInfo.FindSystemTimeZoneById("");
DateTime startParam = new DateTimeOffset(argStartDate, newOffSetDate.GetUtcOffset(argStartDate)).UtcDateTime;
DateTime endParam = new DateTimeOffset(argEndDate, newOffSetDate.GetUtcOffset(argEndDate)).UtcDateTime;
endParam = endParam.AddDays(1);
const string query = "SELECT [column1], [column2], ... FROM [YourTable] WHERE [TimeStamp] >= #start AND [TimeStamp] < #end";
using (var con = new System.Data.SqlClient.SqlConnection(""))
using (var com = new System.Data.SqlClient.SqlCommand(query, con))
{
com.Parameters.Add("#start", SqlDbType.DateTime2).Value = startParam;
com.Parameters.Add("#end", SqlDbType.DateTime2).Value = endParam;
con.Open();
using (var reader = com.ExecuteReader())
{
while (reader.Read())
{
// do stuff
}
}
}
}
The reason your query is returning row is because that's correct. You have 3 times, which you say are UTC:
2020-03-29 19:40:46.8500000+00:00
2020-03-29 19:40:53.1000000+00:00
2020-03-18 17:15:48.2600000+00:00
Let's therefore convert them to -04:00 (which is EDT):
2020-03-29 15:40:46.8500000-04:00
2020-03-29 15:40:53.1000000-04:00
2020-03-18 13:15:48.2600000-04:00
You are then to see if the times are on or after 2020-03-18T00:00:00-04:00 and on or before 2020-03-29T00:00:00-04:00.
2020-03-18 13:15:48.2600000-04:00 is both after 2020-03-18T00:00:00-04:00 and before 2020-03-29T00:00:00-04:00, so that is display. ON the other hand both 2020-03-29 15:40:53.1000000-04:00 and 2020-03-29 15:40:46.8500000-04:00 are after 2020-03-29T00:00:00-04:00 and so are not displayed.
You are getting 1 row, because only 1 of those rows fulfil there WHERE.

How to properly convert query into DateTime in asp.net?

In my app, I am trying to grab a date from the database and compare it against local time. Right now I am getting an error when I am converting to date incorrectly.
I have tried:
Convert.ToDateTime()
DateTime.ParseExact()
My code:
string time = "Select top(1) DATE from SERVICE order by DATE desc";
SqlCommand command = new SqlCommand(time, connection);
connection.Open();
using (SqlDataReader timereader = timecommand.ExecuteReader())
{
while (timereader.Read())
{
if (DateTime.ParseExact(time, "yyyy-MM-dd HH:mm:ss", null).AddMinutes(10) > currenttime)
{
// code
}
}
connection.Close();
}
I am hoping when I retrieve this value from the database, I can convert it into a proper datetime value and compare against local time and run other code after that.
At the moment I am just getting this error:
The string was not recognized as a valid DateTime. There is an unknown word starting at index 0.
I'm probably just dumb and missing something obvious..
Your query selects a single value. Use ExecuteScalar and cast it to a DateTime (it's already a DateTime, but boxed inside an object):
string time = "Select top(1) DATE from SERVICE order by DATE desc";
SqlCommand command = new SqlCommand(time, connection);
connection.Open();
DateTime d = (DateTime)command.ExecuteScalar();
connection.Close();
After you do this, and before you embark on some long mission of this protracted way of getting data out of a database, converting it to objects for use in your app etc, take a look at least at an ORM called Dapper, if not Entity Framework. Dapper's basically what you're doing now, but it auto converts your queries to objects and back and saves a lot of tedious code. With Dapper it'd look more like:
using (var connection = new SqlConnection("connstr"))
{
var d = connection.QuerySingle<DateTime>("SELECT TOP 1 Date FROM ...");
}
Yes, it's not much of a saving over what you have now, right? But what if you have a list of Order that themselves have 20 properties:
using (var connection = new SqlConnection("connstr"))
{
var orders = connection.Query<Order>("SELECT * FROM order WHERE customerID = #custid", new {custid = 123}).ToList();
}
Orders is now a list of Order objects for customer 123, parameterized, injection safe, quick and a one liner to read and populate your orders; doing that in a datareader is going to take at least 25 lines of boring code
http://dapper-tutorial.net and spend 2 minutes reading; I'll lay bets you'll be glad you did
Just try to read the value as a proper, native DateTime type like this (assuming that the DATE column in SQL Server is in fact a DATETIME or similar datatype - not a string - hopefully!):
using (SqlDataReader timereader = timecommand.ExecuteReader())
{
while (timereader.Read())
{
// just read the DateTime value as such
DateTime dt = timereader.GetDateTime(0);
// then do whatever you need to do with this DateTime value .....
}
connection.Close();
}

How to check for duplicate records before inserting them to DB(2 Primary Keys)

I am having a problem with checking 2 values in my database at the same time at the same row, in my table i have 2 primary keys (Date and TagNumber), and before I am inserting any new data i want to check for duplicate records.
I need to check I am not inserting any new data with the same date and the same tagnumber.
For example: Current Record
Date: 25/03/2015
TagNumber:111
When new data is available I need to check that the Date and the TagNumber do not already exist on another record (as this would be a duplicate).
So if the new data is
Date:25/03/2015
TagNumber:111
This record would already exist and would skip inserting a new record. However if the new data was:
Date:27/03/2015
TagNumber:111
This would be a new record and would proceed to insert to data.
Code:
foreach (DataGridViewRow row in dataGridView1.Rows)
{
string constring = #"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\koni\Documents\Visual Studio 2015\Projects\t\Project\DB.mdf;Integrated Security=True";
using (SqlConnection con = new SqlConnection(constring))
{
using (SqlCommand sqlCommand = new SqlCommand("SELECT * from ResultsTable where TagNumber=#TagNumber AND Date=#Date", con))
{
con.Open();
string smdt1 = row.Cells["Exposure Date"].Value.ToString();
string format1 = "dd.MM.yyyy";
DateTime dt1 = DateTime.ParseExact(smdt1, format1, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
sqlCommand.Parameters.AddWithValue("#Date", dt1);
sqlCommand.Parameters.AddWithValue("#TagNumber", row.Cells["Device #"].Value);
}
}
}
and i have tried already ExecuteScalar() command and its not good - it worked only on 1 parameter....
Firstly, its not really clear whats in this table or your data types. Lets assume your data types are TagNumber: int and Date: datetime.
Next your problem is probably with the date field.
DateTime dt1 = DateTime.ParseExact(smdt1, format1, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
Will parse the date as you would expect. However this will also return the time. So in your query the param #Date will also add the time portion automatically to the result (place a breakpoint and have a look). Now as you supplied DateTimeStyles.AssumeUniversal the time is set to 00:00:00 UTC time which will be translated to the current timezone. (Being here in Australia puts that to 10:30:00).
sqlCommand.Parameters.AddWithValue("#Date", dt1.Date); //parsed date at midnight 00:00:00
Now IMHO using a stored procedure would be your best bet as you can use a single query to query and insert.
A sample procedure such as.
CREATE PROCEDURE InsertNewRecord
#TagNumber int,
#Date datetime
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT TOP 1 1 FROM ResultsTable WHERE [Date] = #Date AND TagNumber = #TagNumber)
INSERT INTO ResultsTable (TagNumber, [Date]) VALUES (#TagNumber, #Date)
END
GO
Next you can easily call this (note just using test data).
var tagNumber = "111";
var date = DateTime.ParseExact("28.01.2017", "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
using(var con = new SqlConnection(connectionString))
{
using(var cmd = new SqlCommand("EXEC InsertNewRecord #TagNumber, #Date", con))
{
cmd.Parameters.AddWithValue("#TagNumber", tagNumber);
cmd.Parameters.AddWithValue("#Date", date.Date);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
As you see from the stored procedure we are simply querying first (using the NOT EXISTS and selecting a true result limiting to a single row for performance. SELECT TOP 1 1 FROM .. returns a single row of 1 if both the tag number and date exist on a record.
Now you could also change your data type from datetime to date which eliminates the time portion of your #Date paramater. However this will require you to ensure your data is clean and the table would have to be rebuilt.
One final option is to cast your datetime fields to a date in your query and change the #Date paramter to a type of date then check if they are equal such as.
ALTER PROCEDURE InsertNewRecord
#TagNumber int,
#Date date
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT TOP 1 1 FROM ResultsTable WHERE cast([Date] as date) = #Date AND TagNumber = #TagNumber)
INSERT INTO ResultsTable (TagNumber, [Date]) VALUES (#TagNumber, #Date)
END
GO
For completness if for some reason you dont want to use a Stored Procedure the following will check if the record exists (note using the .Date property).
using (var con = new SqlConnection(connectionString))
{
bool exists = false;
using(var cmd = new SqlCommand("SELECT TOP 1 1 FROM ResultsTable WHERE TagNumber=#TagNumber AND [Date]=#Date", con))
{
cmd.Parameters.AddWithValue("#TagNumber", tagNumber);
cmd.Parameters.AddWithValue("#Date", date.Date);
con.Open();
var result = cmd.ExecuteScalar(); //returns object null if it doesnt exist
con.Close();
exists = result != null; //result will be one or null.
}
if (exists)
{
//INSERT RECORD
}
}
Either way I would say the issue is lying on the time portion of the data however without more information we can only guess.
This should be done on the SQL side. Pass your parameters to a stored proc that checks if a record already exists in the table and if so, either returns an error or discards the record. If it doesn't exist, insert it into the table. You can't do it on the client side as you wouldn't have the full table in memory.
As #Nico explained, creating a stored procedure is better way of doing it.

C# Select in SQL Server just using the Date with sql parameters

I am currently trying to do a Select in my SQL Server database using a parameter with Datetime type. But I need this parameter only has the format YYYY-MM-DD date because of the following query in SQL I'm using and it's working :
select
idCliente, DescCliente, DescAnalista , StatusChamado , DataAtendimento
from
Atendimento
where
cast ([DataAtendimento] as date) = '2016-04-27';
I read several posts indicating that I use DbType or .ToString but when running it is generating error alleging failure to convert the string to the date / time format.
This is how I use the SqlParameter:
DateTime date;
date = Data;
try
{
sqlClient.Command.Parameters.AddWithValue("#adate", date);
sqlClient.Command.CommandText = #" select idCliente, DescCliente, DescAnalista , StatusChamado , DataAtendimento from Atendimento where cast ([DataAtendimento] as date) = '#adate';";
I need a help from you guys , I'm not finding any means that can perform this select
You have to remove the '' in = '#adate'. With the '' in place, it effectively turns your query into:
select
idCliente, DescCliente, DescAnalista , StatusChamado , DataAtendimento
from Atendimento
where cast ([DataAtendimento] as date) = '#date';
This means that cast([DateAtendimento] as date) is compared against the string '#date', thus the conversion to date error.

Passing C# datetime value to Oracle DB Query

I have a SQL query that I am passing a C# variable into my Oracle DB.
I am having trouble passing a C# datetime variable, "PROCESS_DATE", into my query in my application. I do not get any records back. If I copy the query into my oracle developer tool, TOAD, it works fine and I get multiple records.
Here is the query I am using in my application:
String SelectAllSQL = "SELECT * FROM REALMS_AUDIT.R2_GROUP_QUERY_RPT WHERE PROCESS_DATE = :pPROCESS_DATE";
I also tried converting the datetime variable into a shortDateString() so it matches the database exactly I then used the TO_DATE function, which I have to use if I query dates directly in TOAD, without any luck. The shortDateString() changes my date into: 1/16/2016, which is what I need, but the OracleDataReader does not like it. Here it the query with the TO_DATE function:
String SelectAllSQL = "SELECT * FROM REALMS_AUDIT.R2_GROUP_QUERY_RPT WHERE PROCESS_DATE = TO_DATE(:pPROCESS_DATE, 'MM-DD-YYYY'";
:pROCESS_DATE is a datetime variable that is passed in.
There must be a breakdown between C# and Oracle in relation to handling a datetime variable. I am using Oracle DataReader to handle the processing of the query.
OracleDataReader dataReader = mDataAccess.SelectSqlRows ( oracleConnection, oracleCommand, sqlCommand, parameters );
while ( dataReader.Read ( ) )
{
groupEntityFacilityRptList.Add ( ReadRecord ( dataReader ) );
}
If I use the TO_DATE function, the application will not step into the while loop. If I use the original query, it does but returns no data.
The datetime variable PROCESSDATE looks like this:
1/16/2016 12:00:00 AM
I notice it has a timestamp on it, so I'm not sure if that is the problem or not.
The data in Oracle is like this:
1/16/2016
Unless I've totally misunderstood your issue, I think you might be making this harder than it needs to be. ODP.net handles all of that dirty work for you. If PROCESS_DATE is an actual DATE datatype in Oracle, then you just need to pass an actual C# DateTime variable to it and let ODP.net do the heavy lifting. There is no need to do conversion of any type, provided you are passing an actual date:
DateTime testDate = new DateTime(2015, 7, 16);
OracleCommand cmd = new OracleCommand(
"SELECT * FROM REALMS_AUDIT.R2_GROUP_QUERY_RPT WHERE PROCESS_DATE = :pPROCESS_DATE",
conn);
cmd.Parameters.Add(new OracleParameter("pPROCESS_DATE", OracleDbType.Date));
cmd.Parameters[0].Value = testDate;
OracleDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
object o = reader.IsDBNull(0) ? null : reader.GetValue(0);
}
reader.Close();
If your data in C# is not a date, I'd recommend making it one before even trying:
DateTime testDate;
if (DateTime.TryParse(testDateString, out testDate))
{
// run your query
}
As per my comment, please try below and see this resolves.
TRUNC(TO_DATE(:pPROCESS_DATE,'MM-DD-YYYY HH:MI:SS AM')) if pROCESS_DATE format is 1/16/2016 12:00:00 AM.
TRUNC(TO_DATE(:pPROCESS_DATE,'DD-MM-YYYY HH:MI:SS AM')) if pROCESS_DATE format is 16/1/2016 12:00:00 AM.
First, I learned that my code will not go into the code below unless I actually have records returned to me.
OracleDataReader dataReader = mDataAccess.SelectSqlRows ( oracleConnection, oracleCommand, sqlCommand, parameters );
while ( dataReader.Read ( ) )
{
groupEntityFacilityRptList.Add ( ReadRecord ( dataReader ) );
}
Second, to get ProcessDate to work, I needed to take the string that was coming from my View, convert it to a datetime, and then I formatted it back as a string. It may not be best practices but it worked.
public JsonResult GetGroupReportData ( String reportDate )
{
DateTime processDate = DateTime.Parse ( reportDate );
var monthlyReport = SelectAllGroupRprt (processDate.ToString("MM/dd/yyyy");
return new JsonResult ( )
{
Data = monthly,
MaxJsonLength = Int32.MaxValue
};
}

Categories

Resources