Extract a month from a date - php

I'm trying to build a query with propel Criteria to get all Foo's in a given month.
For example I want all Foo's in March. In regular SQL I would build a query like this:
SELECT * FROM FooPeer WHERE MONTH(startDate) = 3
Any Idea how I can implement the "MySQL Month-function within a Criteria Object" ?
$c = new Criteria();
$c -> add(FooEvent::START_DATE, 3, Criteria::EQUAL); //where do I have to put the Month function ?
return self::doSelect($c);

Alright, a Custom Criteria did the job!
$month = 3; //march
$criteria->add(FooPeer::START_DATE, 'MONTH('.FooPeer::START_DATE.')='. $month, Criteria::CUSTOM);

This question was partly answered there: How to use MySQL functions in Propel
Using Criteria::CUSTOM or writing custom SQL seem to be the only solutions.

Related

SQL fill in empty months if data isn't present in Laravel 8 query

I'm using the query builder in my Laravel 8 project to create a monthly sum of all of the deleted users in my application, I'm then outputting two items to use as part of a graph, total and date.
This works well, but, if a month didn't have any data then it would skip straight onto the next month, e.g:
2021-01
2021-04
2021-05
How can I modify the query to add all of the months, from a given start date, up until "now" and effectively add blank values for those months that don't have data?
My current query is:
$data = User::selectRaw('DATE_FORMAT(created_at, "%Y-%m") as date, COUNT(*) as total')
->groupByRaw('DATE_FORMAT(created_at, "%Y-%m")')
->withTrashed()
->whereNotNull('deleted_at')
->get();
And I'm thinking of calculating the start by doing something like this:
$user = User::orderBy('created_at', 'asc')->first();
$start = $user->created_at;
$data = User::selectRaw('DATE_FORMAT(created_at, "%Y-%m") as date, COUNT(*) as total')
->groupByRaw('DATE_FORMAT(created_at, "%Y-%m")')
->withTrashed()
->whereNotNull('deleted_at')
->get();
$end = Carbon::now()->endOfMonth();
Not sure how to get it into the query though
The problem here is that groups originate from the rows, not the other way around. A group will not exist unless a row exists to be included within the group. You only see "missing" months because, in your mind, there are months between January and April.
I'd recommend doing it in post-processing, because any clever attempts to create phantom rows so that groups appear will inevitably be more complicated and more frustrating to maintain.
It may feel clunky, but looping through months from Carbon and adding values to your query result will work fine. Plus, you don't need to rely on $start from your first user result, you can set it yourself.
$start = Carbon::today()->subYear(); // Use any start date, even include it from user input (like a datepicker).
$end = Carbon::today(); // Use any end date, though it won't be useful any later than today.
$loop = $start->copy();
while ($loop->lessThanOrEqualTo($end)) {
$exists = $data->first(function($item) use ($end) {
return $item->date == $end->format('Y-m');
});
if (!$exists) {
$row = new stdClass();
$row->date = $loop->copy()->format('Y-m');
$row->total = 0;
$data->push($row);
}
$loop->addMonth(); // This keeps the loop going.
}
This accomplishes what you want and doesn't get into any N+1 issues.
Edit: Added example below in re-usable function.
function fillEmptyMonths(Collection $data, Carbon $start, Carbon $end): Collection
{
$loop = $start->copy();
// Loop using diff in months rather than running comparison over and over.
for ($months = 0; $months <= $start->diffInMonths($end); $months++) {
if ($data->where('date', '=', $loop->format('Y-m'))->isEmpty()) {
$row = new stdClass();
$row->date = $loop->copy()->format('Y-m');
$row->total = 0;
$data->push($row);
}
$loop->addMonth();
}
return $data;
}
You could also expand this to take another parameter that defines the increment (and pass it "month", "day", "year", etc.). But if you are only using month, this should work.

Time Calculations with MySQL

I'm writing a time logging programme for a client who is a piano tuner, and I've written the following PHP code to give a record a status of 'to do':
$last_tuned = '2017-01-05';
$tuning_period = 3;
$month_last_tuned = date('Y-m', strtotime(date('Y-m-d', strtotime($last_tuned))));
$next_tuning = date('Y-m', strtotime($month_last_tuned.(' +'.$tuning_period.' months')));
if (time() > strtotime($next_tuning.' -1 months')) {
if (time() > strtotime($next_tuning)) {
return 'late';
} else {
return 'upcoming';
}
}
As you can see, the $last_tuned variable is of the date(YYYY-MM-DD) format. This is then converted to a (YYYY-MM) format.
Once convered, an additional number of months, identical to $tuning_period is then added to the $month_last_tuned variable giving us a month and year value for when we need to add a new record.
If the current time (found with time()) is greater than the $next_tuning variable - 1 month, it returns that the task is upcoming. If it's after the $next_tuning variable, it returns that the task is late.
I now have to write a MySQL query to list the items that would return as upcoming or late.
How would I write this in MySQL? I'm not very good with MySQL functions, and some help would be much appreciated.
My attempt at the logic is:
SELECT * FROM records
// The next lines are to get the most recent month_last_tuned value and add the tuning_period variable
WHERE
NOW() > (SELECT tuning_date FROM tunings ORDER BY tuning_date ASC LIMIT 1)
+
(SELECT tuning_period FROM records WHERE records.id = INITIAL CUSTOMER ID)
I know that that is completely wrong. The logic is pretty much there though.
My database schema is as follows:
I expect the rows returned from the query to be on-par with the 'late' or 'upcoming' values in the PHP Code above. This means that the rows returned will be within 1 months of their next tuning date (calculated from last tuning plus tuning period).
Thanks!
You'd probably be better off with using the DateTime object instead of manipulating date strings.
$last_tuned = '2017-01-05';
$tuning_period = 3; // months
$dt_last_tuned = DateTimeImmutable::createFromFormat('Y-m-d',$last_tuned);
$dt_next_tuning = $dt_last_tuned->add(new DateInterval('P3M'));
$dt_now = new DateTimeImmutable();
$dt_tuning_upcoming = $dt_next_tuning->sub(new DateInterval('P1M'));
if( $dt_now > $dt_next_tuning) {
return 'late';
}
if( $dt_now > $dt_tuning_upcoming) {
return 'upcoming';
}
You can also use these DateTime objects in your MySQL queries, by building the query and passing through something like $dt_next_tuning->format('Y-m-d H:i:s'); as needed.
Given your table structure, however, it may be easier to just get all the relevant records and process them. It's a little difficult to tell exactly how the pieces fit together, but generally speaking MySQL shouldn't be used for "processing" stuff.

Mysql query on date function

I have table "movie" which consists of a field "date of release"
how do I query that, which movie hit's the theatre every friday?
$r = false;
$movie = false;
$r = query("SELECT id FROM movie WHERE dateOfRelease='friday'");
$movieInfo = query("SELECT * FROM movie WHERE id='{$r[\'id\']}'");
Made some assumptions because your question has very little information, hopefully this will help
You can find out this week's friday like this:
$datefriday=date('Y-m-d', strtotime('next friday'));
Assuming you have a date() type field in your database, you can use a query like:
SELECT column1,column2,column3,column4 from movie where date_of_release = $datefriday
I have no idea if you use mysqli, pdo or even mysql. So I cant let the query run in php for you. The query has to look like this though. If you want the friday of the week after that, you can use:
$date_next_friday=date('Y-m-d', strtotime($datefriday.'+ 7 days'));
This should help you.

how to implement add expression for date in drupal 7

When i try to use timestamp in time format in expression .I got error
For example
$query->addExpression('date("H:i:s","dept_timestamp")','time_val');
I think the problem is that you've put a PHP function into addExpression. It's feeding your expression straight into the database, so you'll want to use a date-formatting function that your db will understand. Assuming you're using MySQL as a backend, you'd want something like this:
$query->addExpression('from_unixtime("dept_timestamp", "%h:%i:%s")', 'time_val');
Here's a full example that, for no good reason, joins the node table and the field_data_body table:
$query = db_select('node', 'n')
->fields('n', array('nid', 'title'));
$query->join('field_data_body', 'b', 'n.nid = b.entity_id');
$query->fields('b', array('body_value'));
$query->addExpression("from_unixtime(created, '%h:%i:%s')", 'my_timestamp');
$result = $query->execute();

SQL query result to string

I'm using SQL in Yii framework.
I need to show the person's latest active week (it's number and date).So I wrote following code:
public function latestWeek()
{
$datalogin=//the login is working fine
$sql ="SELECT w.number,MAX(w.start_date)
FROM tbl_person_week t, tbl_week w
WHERE t.person_id=$this->id AND t.week_id=w.id";
$query = mysqli_query($datalogin, $sql);
return $query;
}
Now , I checked this query on the server and it works fine (almost) but first thing: I need to convert it into string , because yii's CgridView can't read it , and I couldn't find a working solution for this.
Second: on the server , it gave me the max date indeed , but not it's correct number , but the first number available. How can I fix this as well?
Queries like that should never be used in objective framework. If yu want to execute your own query, you should do it this way:
$sql = "your sql code";
$array = Yii::app()->db->createCommand($sql)->queryAll();
As result you will get multidimensional array with selected columns and rows
If you want to use it in grid view, you should do it this way:
$count = Yii::app()->db->createCommand($sql)->queryScalar();
$dataProvider = new CSqlDataProvider($sql, array('totalItemCount'=>$count));
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'grid-id',
'dataProvider'=> $dataProvider,
));
You can also use connection other than Yii::app()->db. Check CDbConnection class in docs.
edit: if you wanna use queries like mysql_fetch_assoc, check out also queryRow() method instead of queryAll()
Use Mysql_fetch _array
public function latestWeek()
{
$datalogin=//the login is working fine
$sql ="SELECT w.number,MAX(w.start_date)
FROM tbl_person_week t, tbl_week w
WHERE t.person_id=$this->id AND t.week_id=w.id";
$query = mysqli_query($datalogin, $sql);
while($row = mysqli_fetch_array($query)){
echo $row;
}
}
Assuming from your qu. that you want the week number and start date as one string, you have to concatenate the two columns in the sql.
You also need to specify that the week number is from the row with the maximum start date, which isn't as simple as you might first think.
I don't like injecting the person_id straight into SQL, it isn't awful in this case but is a bad habit to get into security-wise. There are binding methods available in the framework and I agree with Arek, that you should lean on the yii framework as much as possible.
To get the scalar string value, if you are insisting on using your own SQL.. I suggest the following:
$sql='
SELECT CONCAT('Week ',tw.number,' starting ',tw.start_date)
FROM tbl_week tw
JOIN (
SELECT MAX(twi.start_date) max_start_date
FROM tbl_week twi
JOIN tbl_person_week tpwi
ON tpwi.week_id = twi.id
AND tpwi.person_id = :person_id
) i
ON tw.start_date = i.max_start_date;
';
$command=Yii::app()->db->createCommand($sql);
$command->bindParam(":person_id", $this->id);
return $command->queryScalar();

Categories