I have a method that requires 3 string so:
public <List> MethodName(string date1, string date2, string number)
{
//
}
in my database date1 and date2 are stored as DateTime and number is stored as Int so how would I write a SQL query that will search based on these three parameters?
I have converted my date1 and date2 like this:
DateTime firstdate = Convert.ToDateTime(date1);
string fdate = firstdate.ToLongDateString();
DateTime seconddate = Convert.ToDateTime(date1);
string sdate = seconddate .ToLongDateString();
My SQL query is:
SELECT * From TableName
WHERE [Time] > #date1 AND
[Time] < #date2 AND [StaffCode] =#StaffCode;
command.Parameters.AddWithValue("#date1", fdate);
command.Parameters.AddWithValue("#date2", sdate );
command.Parameters.AddWithValue("#StaffCode", number);
conn.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{ get the data ...}
I am sure that my SQL query is wrong.
Since your database columns are already strongly typed, just ensure that your C# parameters are typed as System.DateTime and int (System.Int32) respectively before binding them to your query. Your sql is then simply:
SELECT col1, col2, col3
FROM TableName
WHERE [Time] > #date1 AND
[Time] < #date2 AND [StaffCode] =#StaffCode;
If you allow for inclusive dates, you can use BETWEEN, i.e.
WHERE [Time] BETWEEN #date1 AND #date2
AND [StaffCode] = #StaffCode
i.e. avoid the need to convert a Date to a string altogether. Wherever possible, try and keep a strong type system all the way through your code, both C# and SQL - this will save a lot of pain during conversion. e.g. if possible, see if you can change the signature to:
public List<MethodName>(DateTime date1, DateTime date2, int number)
{
// Bind the DateTimes, not a string!
command.Parameters.AddWithValue("#date1", date1);
command.Parameters.AddWithValue("#date2", date2);
command.Parameters.AddWithValue("#StaffCode", number);
Edit Don't use SELECT * - use SELECT Col1, Col2, ...
Conversion:
DateTime firstdate = Convert.ToDateTime(date1);
string fdate = firstdate.ToString("yyyy-MM-dd");
DateTime firstdate2 = Convert.ToDateTime(date2);
string fdate2 = firstdate2.ToString("yyyy-MM-dd");
SQL Query:
#"SELECT * From TestTable WHERE Time BETWEEN CONVERT(Date,#date1) AND CONVERT(Date,#date2) AND StaffCode=CONVERT(int,#StaffCode)"
command.Parameters.AddWithValue("#date1", fdate);
command.Parameters.AddWithValue("#date2", fdate2);
command.Parameters.AddWithValue("#StaffCode", number);
Related
I was playing with dates using a C# program.
I want to filter on any table that has a DateTime, DateTime2, DateTimeOffset columns.
I store the LastRefreshDate as DateTimeOffSet in UTC and I use it to filter data on those tables. I adjust the offset (using NodaTime) of the LastRefreshDate based on the timezone used to store the dates in those tables. Usually, it is given by the user.
So I created a test sample to explain the problem. Usually, the SQL queries are dynamic and the parameters as well. Here is the sample code:
[TestMethod]
public void Test()
{
using (SqlConnection connection = new SqlConnection("Server=myserver;Database=mydb;User ID=admin;Password=admin"))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT [TimeStamp] FROM [dbo].[DATA] WHERE [TimeStamp] >= #p0", connection))
{
string datestring = "2019-06-18 13:35:20.1133868 -04:00";
// Does not work
// DateTimeOffset p0 = DateTimeOffset.Parse(datestring, CultureInfo.InvariantCulture);
// Does work
DateTime p0 = DateTime.Parse(datestring, CultureInfo.InvariantCulture);
command.Parameters.AddWithValue("#p0", p0);
using (SqlDataReader reader = command.ExecuteReader())
{
var dataTable = new DataTable();
dataTable.Load(reader);
var result = dataTable.Rows.Count == 0;
}
}
}
}
I created 2 SQL fiddles that demonstrate the issue. By the way, I ran the SQL Server Profiler and the generated queries are similar to the queries in the fiddles.
DateTime fiddle: http://sqlfiddle.com/#!18/a06be/1
declare #p0 datetime = '2019-06-18 13:35:20'
SELECT
[TimeStamp]
FROM
[dbo].[DATA]
WHERE
([TimeStamp] >= #p0)
DateTimeOffSet fiddle: http://sqlfiddle.com/#!18/a06be/2
declare #p0 datetimeoffset(7) ='2019-06-18 13:35:20.1133868 -04:00'
SELECT [TimeStamp]
FROM
[dbo].[DATA]
WHERE
([TimeStamp] >= #p0 )
I did even more tests. By applying the cast directly, the SQL query works. It seems that SQL Server implicit conversion is not behaving in the same manner as an explicit cast. Here is the test case:
declare #p0 datetime
set #p0 = '2019-06-18 17:48:00.00'
declare #p1 datetimeoffset(7)
set #p1 = '2019-06-18 17:47:00.5385563 -04:00'
select 1
where #p0 > cast(#p1 as datetime) -- working
--where #p0 > #p1 -- not working
A few things:
In SQL Server, if you use CAST, or CONVERT without specifying a style, the default style is 0, which when converting a datetimeoffset to either a datetime or datetime2 simply takes the date and time value from the datetimeoffset without considering the offset. If you want to take the offset into account, then use CONVERT and pass 1 for the style:
DECLARE #p0 datetimeoffset = '2019-06-18 13:35:20.1133868 -04:00'
SELECT convert(datetime, #p0, 0) as 'A', convert(datetime, #p0, 1) as 'B'
-- A = 2019-06-18T13:35:20.113Z
-- B = 2019-06-18T17:35:20.113Z
When querying a datetime or datetime2 field using a datetimeoffset parameter, the offset is indeed taken into account in the implicit conversion (it is like B above).
On the C# side, be careful about DateTime.Parse. By default, it emits a local time based value when an offset is provided. If you check, you'll see p0.Kind == DateTimeKind.Local. You could pass DateTimeStyles.AdjustToUniversal, but a better idea is to parse as a DateTimeOffset like you showed in your "doesn't work" code. But then instead of passing the full DateTimeOffset, pass the UtcDateTime property:
DateTime p0 = DateTimeOffset.Parse(datestring, CultureInfo.InvariantCulture).UtcDateTime;
For both performance and stability reasons, you might consider using ParseExact or TryParseExact instead of Parse. Or, since you said you already are using Noda Time, you can use its text parsing features with an OffsetDateTimePattern. From there you'd either call .ToDateTimeOffset().UtcDateTime, or .ToInstant().ToDateTimeUtc().
Alternatively, you could just define your SQL database columns as datetimeoffset, then you can pass any DateTimeOffset parameter and it will be normalized to UTC when querying.
My table contains an integer value. I want to present multiple values sort of like:
select
MeetingId, StartDate, EndDate, RoomId, MeetingStatusId, Subject
from
Meeting
where
RoomId in (#roomids )
and StartDate >= #start and EndDate <= #end
and CreatedById = #user
But how do I construct the #roomids parameter in C# to be integers? I tried casting RoomId to a varchar, but that didn't work.
You can use SQL Server STRING_SPLIT function and use the parameter as varchar:
select
MeetingId, StartDate, EndDate, RoomId, MeetingStatusId, Subject
from
Meeting
where
RoomId in (SELECT cast(VALUE as int) FROM dbo.string_split(#roomids) )
and StartDate >= #start and EndDate <= #end
and CreatedById = #user
Reference: https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-2017
If you don't have the build in split string function due to your SQL Server version, here's an article on how to create one:
https://sqlperformance.com/2012/07/t-sql-queries/split-strings
A table-valued parameter is a good choice here. For starters you'd create a custom table type in SQL Server, like so:
create type dbo.IdentifierList as table (Identifier int not null);
Here's a simple C# function to create an instance of a query parameter having this type:
SqlParameter CreateIdentifierTableParameter(string name, IEnumerable<int> identifiers)
{
// Build a DataTable whose schema matches that of our custom table type.
var identifierTable = new DataTable(name);
identifierTable.Columns.Add("Identifier", typeof(long));
foreach (var identifier in identifiers)
identifierTable.Rows.Add(identifier);
return new SqlParameter
{
ParameterName = name, // The name of the parameter in the query to be run.
TypeName = "dbo.IdentifierList", // The name of our table type.
SqlDbType = SqlDbType.Structured, // Indicates a table-valued parameter.
Value = identifierTable, // The table created above.
};
}
Then you write your query with the #RoomIds parameter treated as if it were any other table in your database, call the function created above to build the table-valued parameter, and then add it to your SQL command just like you would any other SqlParameter. For instance:
void GetMeetings(IEnumerable<int> roomIdentifiers)
{
// A simplified version of your query to show just the relevant part:
const string sqlText = #"
select
M.*
from
Meeting M
where
exists (select 1 from #RoomIds R where M.RoomId = R.Identifier);";
using (var sqlCon = new SqlConnection("<your connection string here>"))
{
sqlCon.Open();
using (var sqlCmd = new SqlCommand(sqlText, sqlCon))
{
sqlCmd.Parameters.Add(CreateIdentifierTableParameter("RoomIds", roomIdentifiers));
// Execute sqlCmd here in whatever way is appropriate.
}
}
}
This seems like a lot of work at first, but once you've defined the SQL type and written some code to create instances of it, it's really easy to re-use wherever you need it.
Try This
string RooomList = "5,3,7";
string DateS = "01-01-2017";
string DateE = "12-31-2017";
string userT = "Rami";
string sqlText = string.Format(#"
select MeetingId, StartDate, EndDate, RoomId, MeetingStatusId, Subject
from Meeting
where
RoomId in ({0} )
and StartDate >= {1} and EndDate <= {2}
and CreatedById = {3} ", RooomList, DateS , DateE , userT);
I am trying to get date between two dates but i get error
selection query is
DateTime startdate = Convert.ToDateTime(metroLabel8.Text);
DateTime enddate = Convert.ToDateTime(metroLabel9.Text);
SqlCommand cmd = new SqlCommand("Select [LedId],[LedName] from [Ledger] where Date>='"+startdate+"' and Date<='"+enddate+"'", con);
error is
Please, do not hardcode, use parametrized queries instead
DateTime startdate = Convert.ToDateTime(metroLabel8.Text);
DateTime enddate = Convert.ToDateTime(metroLabel9.Text);
...
// Make sql readable
string sql =
#"Select [LedId],
[LedName]
from [Ledger]
where Date >= #prmStartDate and Date <= #prmEndDate";
// wrap IDisposable (SqlCommand) into using
using (SqlCommand cmd = new SqlCommand(sql, con)) {
cmd.Parameters.Add("#prmStartDate", SqlDbType.DateTime).Value = startDate;
cmd.Parameters.Add("#prmEndDate", SqlDbType.DateTime).Value = endDate;
...
}
Hardcoded queries are
Prone to Sql injection
Brittle (depends on, say, datetime formatting - the gap you fell into)
Slow: there's performance decrease since RDBMS has to parse the query each time it executes it
The best way to handle conversion is to let the provider handle that for you:
DateTime startdate = Convert.ToDateTime(metroLabel8.Text);
DateTime enddate = Convert.ToDateTime(metroLabel9.Text);
SqlCommand cmd = new SqlCommand("Select [LedId],[LedName] from [Ledger] where Date >= #startDate and Date <= #endDate", con);
SqlParameter startParameter = cmd.Parameters.Add("#startDate ",
System.Data.SqlDbType.DateTime);
SqlParameter endParameter = cmd.Parameters.Add("#endDate",
System.Data.SqlDbType.DateTime);
startParameter.Value = startdate;
endParameter.Value = enddate;
cmd.Parameters.Add(startParameter);
cmd.Parameters.Add(endParameter);
Don not concatenate strings when building up your SQL queries, this is prone to SQL injection and is considered a security issue in your code.
Without seeing your inputs, I would suggest you use parameterized SQL in your C# or create a stored procedure to accept DATETIME parameters. You shouldn't use hardcoded SQL queries - in short: they are prone to attack and not optmized in SQL.
A really easy way to do this would be to use the Dapper.NET object mapper.
In SQL, you could do:
CREATE PROCEDURE return_led_for_dates
#startdate DATETIME,
#enddate DATETIME
AS
BEGIN
SELECT
[LedId],
[LedName]
FROM
[Ledger]
WHERE
Date BETWEEN #Startdate AND #Enddate
END
And with Dapper, your C# could then be:
DateTime startdate = Convert.ToDateTime(metroLabel8.Text);
DateTime enddate = Convert.ToDateTime(metroLabel9.Text);
var LED = this.Connection.Query<LED>(
"return_led_for_dates",
new {
StartDate = startdate,
EndDate = enddate
},
commandType: CommandType.StoredProcedure);
You would need an LED class too:
public class LED
{
int LedId {get; set;},
string LedName {get; set;}
}
Finally, this assumes that there is no issue with your text field conversions, you should use DateTime.TryParse. You can then bullet proof your code, and ensure the field is corretly parsed. Like:
DateTime startDate;
DateTime endDate;
if (DateTime.TryParse(Convert.ToDateTime(metroLabel8.Text), out startDate) && DateTime.TryParse(Convert.ToDateTime(metroLabel9.Text), out endDate))
{
// Your data code.
}
Here is my code for passing table value pair to stored procedure.
DOJ field is DateTime and in SP, DOJ field is date. Both are compatible. Output is like dd/MM/yyyy.
If DOJ field is DateTime and in SP, DOJ field is DateTime2(3), o/p is dd/MM/yyyy hh:mm:ss
But I need o/p to be dd/MM/yyyy. How should i write the code ?
dt1.Columns.Add("DOJ", typeof(System.DateTime));
DataRow dr1 = dt1.NewRow();
dr1["DOJ"] = DateTime.ParseExact("02/03/2001", formats, us, DateTimeStyles.None);
// dr1["DOJ1"] = "12/13/2001"; if i use this one it works .
dt1.Rows.Add(dr1); // Get DOJ as - 3/2/2001 12:00:00 AM
ds1.Tables.Add(dt1);
Here is my stored procedure code -
-- CREATE TYPE StateTbls7 AS TABLE
( StateID VARCHAR(200)
, StateCode VARCHAR(200)
, StateName VARCHAR(200)
, DOJ date
)
ALTER PROCEDURE sp_Add_contact
(
#ds1 StateTbls7 readonly
)
AS
begin
declare #DOJ VARCHAR(200)
select #DOJ = d1.DOJ from #ds1 d1
select #DOJ as 'a1'
end
return
Remove 'formats' declaration
Instead, change like this in your c#,
dr1["DOJ"] = DateTime.ParseExact("02/03/2001", us, DateTimeStyles.None).ToString("d");
Hope it may help.Let me know the result.
Try to change you SQL query like this
....Code before this
select #StateID = d1.StateID
,#StateCode = d1.StateCode
,#DOJ = convert(varchar(15), d1.DOJ, 103) -- 103 is dd/MM/yyyy
from #ds1 d1
....Rest of the Code
here i cast the date time to text and removed the time
HOPE THIS HELP!
In my application I want to count the number of records with the same current year and month. So my logic is to compare the current Year and Month to the date_needed column in my table.
Here's how I did it:
using (MySqlConnection con = new MySqlConnection(serverstring))
{
con.Open();
string query = "SELECT * FROM tblOrder WHERE date_needed=#dateTimeNow";
using (MySqlCommand cmd = new MySqlCommand(query, con))
{
cmd.Parameters.AddWithValue("#dateTimeNow", DateTime.Now.ToString("yyyy-MM")); using (MySqlDataReader dr = cmd.ExecuteReader())
{
int count = 0;
while (dr.Read())
{
count++;
}
MessageBox.Show(count.ToString());
}
}
}
I know that it doesn't work because in my messagebox it shows zero instead of one record. What do you think is the problem?
You should supply a complete date (as a DateTime, without formatting it - text conversions are almost always a bad idea when sending parameter values to a database) and use the MySQL date/time functions to compare the values.
For example:
string query = #"SELECT * FROM tblOrder
WHERE MONTH(date_needed) = MONTH(#dateTimeNow)
AND YEAR(date_needed) = YEAR(#dateTimeNow)";
Alternatively, you could pass the month and year as separate parameters:
string query = #"SELECT * FROM tblOrder
WHERE MONTH(date_needed) = #month
AND YEAR(date_needed) = #year";
Or - possibly more performantly - you could give start and end points:
string query = #"SELECT * FROM tblOrder
WHERE date_needed >= #start AND date_needed < #end";
Here you'd set #start to the start of this month, and #end to the start of the next month. You could work those out as:
// Consider using UtcNow.Date instead. Basically, think about time zones.
DateTime today = DateTime.Today;
DateTime start = new DateTime(today.Year, today.Month, 1);
DateTime end = start.AddMonths(1);
If you wanna get Count of rows in sql use
But Need to pass Complete date value in #dateTimeNow
SELECT Count(*) FROM tblOrder WHERE MONTH(date_column)= MONTH(#dateTimeNow) and YEAR(date_column) = YEAR(#dateTimeNow)
If you want to do it all on the MySQL end, try this:
SELECT *
FROM tblOrder
WHERE EXTRACT(YEAR_MONTH FROM date_needed) = EXTRACT(YEAR_MONTH FROM CURRENT_DATE)
To make it fit your #dateTimeNow parameter, which is formatted as yyyy-MM (in .NET this means the year with century, followed by a dash, followed by the month as two digits), do this:
SELECT *
FROM tblOrder
WHERE #dateTimeNow = DATE_FORMAT(date_needed, '%Y-%m')