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.
Related
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.
I have a SQL Server table containing data where each row has a name and a DateTimeOffset field.
Now I want to delete all data from the table where the DateTimeOffset field contains a value >= than a DateTime (UTC) object I have in my C# application so that I can insert new data afterwards without having duplicates in the table. Here is an example for how I am trying to do this in C#:
DateTime timestamp = data.Timestamp.UtcTime; //28.10.2020 15:00:00 UTC
using (SqlCommand command = new SqlCommand($"DELETE FROM <tablename> WHERE Name = #p_name AND Time >= #p_timestamp", conn))
{
command.Parameters.Add(new SqlParameter("p_name", SqlDbType.NVarChar) { Value = "ExampleName" });
command.Parameters.Add(new SqlParameter("p_timestamp", SqlDbType.DateTimeOffset) { Value = timestamp });
command.ExecuteNonQuery();
}
After executing this statement I can still find rows in the table with the Time field "2020-10-28 15:00:00.0000000 +01:00". Why? I already tried using SqlDbType.DateTime or SqlDbType.DateTime2 instead of SqlDbType.DateTimeOffset but that did not fix the problem. Converting the timestamp to a DateTimeOffset object also made no difference. The timestamp I use for this delete statement is the exact same timestamp that is later used when inserting the data so what could be the problem here?
For a query on a table in PostgreSQL I am able to fetch data correctly.
var query = "Select Id,name from employee
where
joiningTime BETWEEN '{startDateTime:yyyy-MM-dd HH:mm:ss}' AND '{endDateTime:yyyy-MM-dd HH:mm:ss}'"
The data are returned correctly but since this approach is prone for SQL Injection, I want to change this to parameterized way
var query = "Select Id,name from employee
where
joiningTime BETWEEN '#startDateTime' AND '#endDateTime'"
var result = dbConnection.Query<Result>(query, new {startDateTime, endDateTime });
How can the format be passed still with parameters?
DateTimes don't have a format, they're like number (eg like 1000 can be formatted as 1000.0 or 1x10^3 etc but it's still just a thousand).
You just write the query like:
SELECT * FROM t WHERE dateCol BETWEEN #fromDate AND #toDate
Note: you don't put ' around parameter names!
And in the dapper call you put datetime typed parameters:
DateTime x = DateTime.Now.AddDays(-1);
DateTime y = DateTime.Now;
dbConnection.Query<Result>(query, new { fromDate = x, toDate = y});
If, in your database, you've made your datetime columns varchar and filled them with strings that's the first thing you should fix (make them a proper date type)..
But even if you did dothis, the advice wouldn't change:
DateTime x = DateTime.Now.AddDays(-1);
DateTime y = DateTime.Now;
dbConnection.Query<Result>(
"SELECT * FROM t WHERE dateCol BETWEEN #fromDate AND #toDate",
new {
fromDate = x.ToString("yyyy-MM-dd HH:mm:ss"),
toDate = y.ToString("yyyy-MM-dd HH:mm:ss")
}
);
You're still writing parametyers into your SQL, you're now putting formatted strings into the parameter values to match the formatted strings in the DB table. Don't do this if your table holds DATE/TIME/TIMESTAMP type columns - this is only for if you've arrange the questionable(foolish)_ situation of storing your dates as strings and are unwilling to change it (you should)
I have a column named compare_time(datatype: DateTime) in database. Its value is inserted as 3/8/2017 12:09:08 AM. Now in c# I want to write a query to compare if this column value is equal to Singapore's current date time.(Only need to compare the date.). Current Singapore date time is get as 08-03-2017 PM 03:35:11.
TimeZone time2 = TimeZone.CurrentTimeZone;
DateTime test = time2.ToUniversalTime(DateTime.Now);
var singapore = TimeZoneInfo.FindSystemTimeZoneById("Singapore Standard Time");
var singaporetime = TimeZoneInfo.ConvertTimeFromUtc(test, singapore);
DateTime dt = Convert.ToDateTime(singaporetime); //08-03-2017 PM 03:35:11.
SqlDataAdapter adapter = new SqlDataAdapter("SELECT * FROM time_details where compare_time='"+dt+"' ", con1);
Please help to correct the where clause.
The first step is to not compare the dates using automatic string conversion but use a parameter.
Still this is not enough because your DateTime variable contains also the time part as well the database column. So you need to isolate the date part both on the database data and in the variable
DateTime dt = Convert.ToDateTime(singaporetime); //08-03-2017 PM 03:35:11.
SqlDataAdapter adapter = new SqlDataAdapter(#"SELECT * FROM time_details
where Convert('Date',compare_time) = #date", con1);
adapter.SelectCommand.Parameters.Add("#date", SqlDbType.DateTime).Value = dt.Date;
....
In this way you don't let the compiler decide which is the right string 'format' that is correct for Sql Server to understand your query.
Instead you pass the DateTime variable as a Date (using the Today property which doesn't have a meaningful time part) to the database engine that now has all the info to make the correct comparison.
Notice that this approach could not be the most efficient one. That CONVERT inside the WHERE clause could wreak havoc with your indexes. Probably you could use a different approach
string cmdText = #"SELECT * FROM time_details
where compare_time >= #init AND
compare_time < #end";
SqlDataAdapter adapter = new SqlDataAdapter(cmdText, con1);
adapter.SelectCommand.Parameters.Add("#init", SqlDbType.DateTime).Value = dt.Date;
adapter.SelectCommand.Parameters.Add("#end", SqlDbType.DateTime).Value = dt.Date.AddDays(1);
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.