c#, build sql to select a user given an age range - c#

I have a talent table holding all my users with a column holding their birthdays. what would be the best way Talent within a specified age range. Here's what I have, but is seems to be off by a couple days. is there a better way?
// BUILD SQL FROM FORM DATA
sqlString += "SELECT * from Talent";
if (minAge != 0 || maxAge != 120)
{
// The age criteria has been change, filter by age.
// select all talents that have birthdays between the following 2 dates.
DateTime startDate = (DateTime.Now - new TimeSpan((maxAge * 365), 0, 0, 0)); // maxAge * 365 = totalDays
DateTime endDate = (DateTime.Now - new TimeSpan((minAge * 365), 0, 0, 0));
sqlString += " WHERE Birthdate BETWEEN '" + startDate.ToString() + "' AND '" + endDate.ToString() + "'";
}

Assuming you're using SQL Server...
using (var connection = new SqlConnection(connString))
using (var command = connection.CreateCommand()) {
string tsql = #"
select *
from Talent
where DATEDIFF(YEAR, BirthDay, GETDATE()) BETWEEN #minAge AND #maxAge";
command.CommandText = tsql;
command.CommandType = CommandType.Text;
int minAge = 1;
int maxAge = 120;
SqlParameter minAgeParam = command.CreateParameter();
minAgeParam.Direction = ParameterDirection.Input;
minAgeParam.DbType = SqlDbType.TinyInt;
minAgeParam.ParameterName = "#minAge";
minAgeParam.Value = minAge;
SqlParameter maxAgeParam = command.CreateParameter();
maxAgeParam.Direction = ParameterDirection.Input;
maxAgeParam.DbType = SqlDbType.TinyInt;
maxAgeParam.ParameterName = "#maxAge";
maxAgeParam.Value = maxAge;
// Just unsure here whether I must add the parameters to the command,
// or if they are already part of it since I used the
// SqlCommand.CreateParameter() method.
// Been too long since I haven't done any ADO.NET
command.Parameters.Add(minAgeParam);
command.Parameters.Add(maxAgeParam);
connection.Open();
SqlDataReader reader = null;
try {
reader = command.ExecuteReader();
// Process your records here...
} finally {
connection.Close()
command.Dispose();
connection.Dispose();
if (reader != null) {
reader.Dispose();
}
}
}
Where #minAge and #maxAge are your age parameters.
You may also tell the DATEDIFF TSQL function to consider the difference in days, in month, in hours, in minutes, in seconds, etc. Hence, you will have to convert your parameters value accordingly.

Personally, I've found using startDate.ToString("yy-MM-dd 00:00:00.000") and endDate.ToString("yy-MM-dd 23:59:59.000") works best (note the ,000 on the end date range. For some reason, in my experience, sql is off (probably due to some kind of rounding error) when it comes to ranges.
As an aside, you can use static methods from the TimeSpan object for time calculations. e.g. TimeSpan.FromDays(...)

The problem might be related to DateTime.Now, which considers time as well as date. Try replacing it with a DateTime.Today.

Why don't you use DateTime.AddYears method.
DateTime startDate = DateTime.Now.AddYears(-maxAge);
insated of
DateTime startDate = (DateTime.Now - new TimeSpan((maxAge * 365), 0, 0, 0));
Another thing is: Please don't use + operator between strings to build a sql query, use StringBuilder instead.

For what it's worth - your original solution is off by a few days because it is using 365 instead of accounting for leap years.

Related

Get COUNT of data at the current hour of system date

I have a table of data in Access
DateTimeLog has date/time datatype while the other rest are string.
I need to get the total number of row of data at the last hour of system date with specified ModelLog. But I could not think of a correct structure of the query using MS Access as I am very new to MS Access.
Below shows how I insert the data:
DateTime now = DateTime.Now;
var today = now.ToString("g");
DateTime d = DateTime.Parse(today);
const string sql = #"INSERT INTO timer(DateTimeLog, ShiftLog, CTLog, WorkcellLog, ModelLog, StationLog)VALUES(#d, #shift, #ct, #wc, #wm, #ws)";
OleDbCommand cmd = new OleDbCommand(sql, connection);
cmd.Parameters.Add("#d", OleDbType.DBTimeStamp).Value = d;
cmd.Parameters.Add("#shift", OleDbType.VarChar).Value = shiftlb.Text;
cmd.Parameters.Add("#ct", OleDbType.VarChar).Value = timerlb.Text;
cmd.Parameters.Add("#wc", OleDbType.VarChar).Value = wclb.Text;
cmd.Parameters.Add("#wm", OleDbType.VarChar).Value = mlb.Text;
cmd.Parameters.Add("#ws", OleDbType.VarChar).Value = slb.Text;
try
{}
catch(exception e)
{}
Ok, so you clearly know how to run a database query with parameters, you just need to run this query as ExecuteScalar, casting the result to an int
SELECT COUNT(*) FROM timer WHERE dateTimeLog >= #d
And then set your d parameter to an hour ago
cmd.Parameters.Add("#d", OleDbType.Date).Value = DateTime.Now.AddHours(-1);
Side note I would recommend not round tripping via string just to cut the seconds off a time. Instead consider:
var d = DateTime.Now;
d = d.AddTicks( -(d.Ticks % (60*TimeSpan.TicksPerSecond)));
Or maybe
var d = DateTime.Now;
d = d.AddMilliseconds(-d.Milliseconds).AddSeconds(-d.Seconds);
I found out another way of getting the number of data within the same hour. Create two new columns, HourID and TodayDate both represent current hour in 24hours format and today's date respectively. Insert the data accordingly.
Getting count of data:
const string sql = #"SELECT COUNT(*) FROM timer WHERE HourID = #h AND TodayDate = #td AND ModelLog = #m";
OleDbCommand cmd = new OleDbCommand(sql, connection);
cmd.Parameters.Add("#h", OleDbType.VarWChar).Value = DateTime.Now.ToString("%H");
cmd.Parameters.Add("#td", OleDbType.VarWChar).Value = DateTime.Now.ToShortDateString();
cmd.Parameters.Add("#m", OleDbType.VarWChar).Value = mlb.Text;

Comparing SQL Server DATETIME with DateTime.NOW in C# / ASP.NET

How do I compare a SQL Server DATETIME with the DateTime.Now value? As you can see I assigned it to a Session and tried comparing it with DateTime.Now.
string timestamp = #"SELECT sr.*, ud.* FROM SuspensionRecord sr, UserData ud WHERE sr.User_ID=#User_ID AND ud.User_ID=#User_ID";
using (SqlCommand cmd2 = new SqlCommand(timestamp, con))
{
cmd2.Parameters.AddWithValue("#User_ID", Session["UserID"].ToString());
using (SqlDataReader dr = cmd2.ExecuteReader())
{
if (dr.HasRows)
{
while (dr.Read())
{
Session["suspensiondate"] = dr["End_Date_Suspension"].ToString();
}
if (Convert.ToDateTime(Session["supensiondate"]) >= DateTime.Now.Date)
{
lblMessage.Text = "The account's status is suspended.";
lblMessage.Visible = true;
}
}
}
}
You should pass in the date and do the comparison in the query instead of in c#. That is one less step. If you do want to do it in c# then use the appropriate types, do not convert the DateTime to a string and then convert it back again.
There is no need for the join (2nd table) in your query
You do not have to use a DataReader for this, you can use ExecuteScalar which returns 1 value instead.
Use Add so you can specify the correct schema types with SqlDbType and not AddWithValue
string timestamp = #"SELECT 1 FROM SuspensionRecord sr WHERE sr.User_ID = #User_ID AND supensiondate > #now";
using (SqlCommand cmd2 = new SqlCommand(timestamp, con))
{
cmd2.Parameters.Add("#User_ID", SqlDbType.Int).Value = Session["UserID"]; // do not convert to string
cmd2.Parameters.Add("#now", SqlDbType.DateTime).Value = DateTime.Now.Date;
var result = cmd2.ExecuteScalar();
if(result != null) // if null then there were no records so account is not suspended
{
lblMessage.Text = "The account's status is suspended.";
lblMessage.Visible = true;
}
}
First, your SQL is terrible.
You are returning way too much data, and you are using an implicit join (when explicit joins are a part of ANSI-SQL for almost 30 years now!)
Second, Can we stop using AddWithValue() already?
Instead of all this code you can do the entire test on SQL and return a single value:
string sql =
#"SELECT CASE WHEN EXISTS
(
SELECT 1
FROM SuspensionRecord
WHERE User_ID = #User_ID
AND End_Date_Suspension >= CAST(GETDATE() AS DATE)
) THEN 1 ELSE 0 END";
Then you can use ExecuteScalar instead of ExecuteReader, and you don't need to loop through all the irrelevant data:
using (SqlCommand cmd2 = new SqlCommand(timestamp, con))
{
cmd2.Parameters.Add("#User_ID", SqlDbType.Int).Value = Session["UserID"];
if ((int)cmd2.ExecuteScalar() == 1)
{
lblMessage.Text = "The account's status is suspended.";
lblMessage.Visible = true;
}
}

Sql DataReader Has Rows but Returns With Empty data

I am using a query to select data from sql database. It has rows but data raader.Read() function returns false and rows are empty as I have checked in debugging
Code that i have been using is
public void getSale()
{
DB db = new DB();
try
{
db.cmd.CommandText = "select * from Sale where date is not null and (date between '"+StartDate+"' and '"+EndDate+"') order by date";
db.cmd.Connection = db.con;
db.con.Open();
if(db.con.State == System.Data.ConnectionState.Open)
{
db.dataReader = db.cmd.ExecuteReader();
if(db.dataReader.HasRows)
{
while(db.dataReader.Read())
{
SaleModel sm = new SaleModel();
sm.SaleId = long.Parse(db.dataReader["Id"].ToString());
sm.UserName = db.dataReader["UserName"].ToString();
sm.ItemsQuantity = int.Parse(db.dataReader["ItemsQuantity"].ToString());
sm.TotalAmount = double.Parse(db.dataReader["TotalAmount"].ToString());
sm.SubTotal = double.Parse(db.dataReader["SubTotal"].ToString());
sm.Discount = double.Parse(db.dataReader["Discount"].ToString());
sm.Completed = bool.Parse(db.dataReader["Completed"].ToString());
sm.Date = DateTime.Parse(db.dataReader["Date"].ToString());
sm.CustomerPhone = long.Parse(db.dataReader["CustomerPhone"].ToString());
SalesList.Add(sm);
}
db.con.Close();
}
}
}
catch(Exception ex)
{
MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);
}
}
And When I tested this query on Query editor in Visual studio rows were returned
If Anyone can help?
Why you concatenate strings to build your sql query? NEVER do that. It is a source for sql-injection and can cause issues like this. Instead use parameterized queries.
Also don't use SqlConnection wrappers like your DB class. That can cause several other issues. Instead create, open, close and dispose them where you need them, best by using the using-statament. The connection-pooling will manage the rest for you.
public List<SaleModel> GetSale(DateTime startDate, DateTime endDate)
{
string sql = #"select * from Sale
where date is not null
and date between #StartDate and #EndDate
order by date";
var salesList = new List<SaleModel>();
try
{
using (var con = new SqlConnection("insert your connection string"))
using (var cmd = new SqlCommand(sql, con))
{
cmd.Parameters.Add("#StartDate", SqlDbType.DateTime).Value = startDate;
cmd.Parameters.Add("#EndDate", SqlDbType.DateTime).Value = endDate;
con.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
SaleModel sm = new SaleModel();
sm.SaleId = long.Parse(reader["Id"].ToString());
sm.UserName = reader["UserName"].ToString();
sm.ItemsQuantity = int.Parse(reader["ItemsQuantity"].ToString());
sm.TotalAmount = double.Parse(reader["TotalAmount"].ToString());
sm.SubTotal = double.Parse(reader["SubTotal"].ToString());
sm.Discount = double.Parse(reader["Discount"].ToString());
sm.Completed = bool.Parse(reader["Completed"].ToString());
sm.Date = DateTime.Parse(reader["Date"].ToString());
sm.CustomerPhone = long.Parse(reader["CustomerPhone"].ToString());
salesList.Add(sm);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Exception", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);
}
return salesList;
}
I'm pretty sure that this works(f.e. can be a localization issue).
Side-note: a method GetSale should return a List<SaleModel> but not fill one. You should also pass the parameters as DateTime to the method. I've changed it in my code sample.
This is a much better way to structure you code, and there's a good chance it will fix your issue, too:
//accept the date values as parameter arguments, return the result.
// Do NOT mess about with variables at the global or class scope.
public IEnumerable<SalesModel> getSale(DateTime StartDate, DateTime EndDate)
{
string sql = "select * from Sale where date is not null and (date between #StartDate and #EndDate) order by date";
//DON'T abstract SqlCommand/SqlConnection. DO abstract your connection string.
//Also, don't bother with the try/catch at this level. You can't really do anything with it here, so worry about the exception in calling code.
using (var cn = new SqlConnection(DB.ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("#StartDate", SqlDbType.DateTime).Value = StartDate
cmd.Parameters.Add("#EndDate", SqlDbType.DateTime).Value = EndDate
cn.Open();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while(rdr.Read())
{
var sm = new SaleModel();
//If you have good schema design, these values are **already** in the correct type.
// The old code forces an expensive conversion to string, following by an expensive parse back to the type it already had.
// We can do MUCH better.
sm.SaleId = (long)rdr["Id"];
//but it is okay for types that are *already* strings
sm.UserName = rdr["UserName"].ToString();
sm.ItemsQuantity = (int)rdr["ItemsQuantity"];
sm.TotalAmount = (double)rdr["TotalAmount"]);
sm.SubTotal = (double)rdr["SubTotal"];
sm.Discount = (double)rdr["Discount"];
sm.Completed = (bool)rdr["Completed"];
sm.Date = (DateTime)rdr["Date"];
sm.CustomerPhone = (long).rdr["CustomerPhone"];
yield return sm;
}
}
}
}
Here it is again without all the extra comments. The point here is this is still less code than the original that used string concatenation, and it took less than 10 minutes to write. Good code doesn't necessarily take longer.
public IEnumerable<SalesModel> getSale(DateTime StartDate, DateTime EndDate)
{
string sql = "select * from Sale where date is not null and (date between #StartDate and #EndDate) order by date";
using (var cn = new SqlConnection(DB.ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.Add("#StartDate", SqlDbType.DateTime).Value = StartDate
cmd.Parameters.Add("#EndDate", SqlDbType.DateTime).Value = EndDate
cn.Open();
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while(rdr.Read())
{
var sm = new SaleModel();
sm.SaleId = (long)rdr["Id"];
sm.UserName = rdr["UserName"].ToString();
sm.ItemsQuantity = (int)rdr["ItemsQuantity"];
sm.TotalAmount = (double)rdr["TotalAmount"]);
sm.SubTotal = (double)rdr["SubTotal"];
sm.Discount = (double)rdr["Discount"];
sm.Completed = (bool)rdr["Completed"];
sm.Date = (DateTime)rdr["Date"];
sm.CustomerPhone = (long).rdr["CustomerPhone"];
yield return sm;
}
}
}
}
Note that I return an IEnumerable rather than a List. If you really need a List (tip: you probably don't, and sticking with IEnumerable is faster), you can just call ToList() on the result.
don't see any issue except that you are using date which is a reserve word and not your actual column name. Change your query to be
db.cmd.CommandText = "select * from Sale where [date] is not null and ([date] between '"+StartDate+"' and '"+EndDate+"') order by [date]";

SQL SELECT query not retrieving rows from a simple Access database

The DayDate column in the database is of type DateTime & I'm passing a string formulated using a DateTimePicker in a form. The reader.HasRows always returns false!! I don't know what I'm doing wrong. Any help would be appreciated. The code that I used is below.
if (!this.con.IsConnected())
{
this.con.Connect();
}
this.cmd = new OleDbCommand("SELECT DayNo FROM [Calendar] WHERE DayDate = " + date + "", this.con.conObj());
this.reader = cmd.ExecuteReader();
this.reader.Read();
int dayNo;
if (this.reader.HasRows)
{
dayNo = int.Parse(reader[0].ToString());
}
else
{
throw new InfoException("The system could not locate the date in the system");
}
The problem is your comparing a Date value to a DateTime so in essence you could possibly be comparing values like this:
DayDate = "2011-12-18 14:22:54"
Date = "2011-12-18 00:00:00"
You need to truncate the time part from your DB dates, try something like this:
"SELECT DayNo FROM [Calendar] WHERE dateadd(dd, 0, datediff(dd, 0, DayDate)) = " + date
Or if using SQL Server 2008 you can do:
"SELECT DayNo FROM [Calendar] WHERE cast(DayDate As Date) = " + date

How can I do that by using single query in C#?

I want to make passive them if a date is earlier than today from the recorded dates within database with update command.How can I do that by using single query in C#?
Colums: Tarih(nvarchar) , Durum(nvarchar)
For example;
if tarih<Datetime.Today.Date is set Durum='PASİF'
Code:
string sorgu = "select BitTarihi from Abonelikler";
SqlConnection gg = new SqlConnection(constr);
SqlCommand gg2 = new SqlCommand(sorgu,gg);
SqlDataReader gg3;
gg.Open();
gg3= gg2.ExecuteReader();
while (gg3.Read())
{
DateTime b1 = new DateTime();
DateTime b2 = new DateTime();
b1 = Convert.ToDateTime(gg3.GetString(0));
b2 = DateTime.Today.Date;
TimeSpan fark = new TimeSpan();
fark = b2 - b1;
if (fark.TotalDays > 0)
{
SqlConnection vv = new SqlConnection(constr);
SqlCommand vv2 = new SqlCommand("update Abonelikler set Durum='PASİF' where BitTarihi='" + b1.ToShortDateString() + "'", vv);
vv.Open();
vv2.ExecuteNonQuery();
vv.Close();
vv2.Dispose();
}
}
gg.Close();
gg2.Dispose();
Look at DATEDIFF function.
UPDATE Abonelikler SET Durum='PASIF'
WHERE DATEDIFF(DD, CONVERT(datetime, BitTarihi), GETDATE())>0
Also, please do not use concatenation of an SQL statement, use parameters. It's more safe and also sometimes concatenation of statement with parameters hampers server-side statement optimization.
Instead of updating the records why don't you decide the state at query level? Since you check this everyday there will be many unneeded updates.
SELECT BitTarihi, CASE WHEN DATEDIFF(DAY, BitTarihi, GetDate()) = 0 THEN 'AKTIF' ELSE 'PASIF' END DURUM FROM Abonelikler
This way you won't have to update your records.

Categories