insert select statement with changes to fields and logic thinking - php

I'm working on some php code having to do with creating recurring invoices. I need some help thinking through the logic. I think ultimately I will end up with a type of Insert/Select statement. The php code that is ultimately generated here will run in a nightly cron.
First, let me show you some of my table items for a frame of reference and explain a bit about what they do. The table is called Invoice and there are other tables associated with it, but I think if I can nail now this one table's logic, I can apply it to the others.
ID = This is the primary key that auto increments and also happens to be the invoice number.
recurring = This is whether the invoice is one time or recurring. O=One time, M=Monthly etc
recurred = This will be set to N on insert and is the logic I'm using to see if the next recurring invoice that will be created in the table should be based off this invoice or not. When a new recurring invoice is inserted into the table, this will be changed to Y so its not processed again by the cron job.
date = This is the date that the invoice was inserted/created. For example, if date the invoice was created is the 15th of this month, the next invoice created will be the 15th of next month. Essentially M represents monthly. However, I haven't figured out yet what to do about a created date of the 31st but the next month only has 30 days, could use some help here.
So here is my logic. First, I run a query to find all rows that are not O and recurred = N.
$result = mysql_query("SELECT * FROM invoice WHERE recurring != 'O' and recurred
= 'N'") or die(mysql_error());
Next, I work on each kind of recurring M for monthly, Q for quarterly, etc..
if recurring = M
if todays date day = 'date' day
copy line item into a new row with the following changes: new ID, todays date, and mark the recurred to N for the new insert (will mark the old one Y)
Obviously that is not actual code, just what I'm thinking about.
So my questions are 1.) How to create a insert select statement to deal with changed variables and 2.) how to deal with dates where if the invoice was created on the 31st of one month and the next month only has 30 days (or 28).

take only rows that need to be re-inserted:
SELECT *, DATEDIFF(`date`, NOW()) as diff FROM invoice WHERE recuring != 'O'
AND recured = 'N' AND diff >= 30;
iterate through that list and use a new date for each row by calculating it with:
$newDate = date("Y-m-d", strtotime($oldDate+" +1 month"));
This should solve your 30th day of month problem.
Now insert newly created data.
Also, good advice to use enum for recuring field, and boolean for recured.

I have made somewhat similar application. In my case I had 2 tables: actual invoices and recurring invoices. Recurring table had one field called NextDate - the date when next invoice should be created. One time invoice is added directly into invoices table, otherwise the cron job scans the recurring table and checks for a NextDate value. If it is in past then new invoice is generated into invoices table and NextDate is updated (normally incremented by month).
In my case incrementing by month changes month value only by 1. For example in MySQL
DATE_ADD('2008-01-31', INTERVAL 1 MONTH) --> 2008-02-29

Related

Laravel get every date value for every user

I try to get value for every date even there is no transaction:
$transaction = Transaction::whereBetween(DB::raw('DATE(created_at)'), ['2015-12-01', '2016-12-31'])
->where('vendor_id', $row)
->groupBy('vendor_id', DB::raw('DATE(created_at)'))
->selectRaw('FLOOR(SUM(amount)) as total, DATE(created_at) as date')
->orderBy('created_at')->get();
return $transaction;
The result I get:
[{"total":"81739","date":"2015-12-22"},{"total":"82912","date":"2015-12-26"},{"total":"55912","date":"2015-12-27"},{"total":"55599","date":"2015-12-30"},{"total":"77626","date":"2015-12-31"}];
How can I get value for every date if it's no value insert it with 0?.
Thanks.
Update: added new solution (without joining)
The idea is very simple: if you've no transactions today, today's date is not in your transaction table.
The solution is simple too: you've to store every date like so:
Run every day at 00:00:00 a cronjob that inserts an empty transaction. So you get every date in your transaction table without joining to another table.
Be care: only if there's a 'real' transaction for a date, the 'fake' (empty) transaction should be deleted.
Alternative 1:
Create a table with dates (with id and date), for example from 2015-01-01 to 2025-12-31
Select your dates and left join to the transaction table
Example query:
SELECT * FROM dates d LEFT JOIN transactions t ON t.date_id = d.id
Instead of storing the date in your transaction table, your store the foreign key to the dates table.
Alternative 2:
Create a table for dates (with id and date)
Run every 00:00:00 a cronjob that insert the current date
Select your dates and left join to the transaction table
Alternative 3:
Generate with php's DateInterval a date range
Link the records from your transaction table to every right date of the range

PHP, MySQL Recurring Slot in Calendar using Daily, Weekly, Monthly, Yearly

I have this problem on how can I populate the slots in database if I need to look in another table and if there is a recurring so I need to repeat that slot without even adding it, just showing the slot again in another date.
For example, I have this calendar.
Calendar
As you can see in February 14 there's 1 Time Slots. And that slot has a recurrence Daily separate in another table. How can I show that time slot in another date also like February 15,16,17..29.
mysql_query("SELECT client_contact_slots.*, client_contacts.*, client_recurring_slots.*
FROM client_contact_slots
JOIN client_contacts ON client_contacts.client_contactid = client_contact_slots.client_contactid
LEFT JOIN client_recurring_slots ON client_recurring_slots.clientcontactslotsid = client_contact_slots.slotid
WHERE client_contact_slots.client_contactid in ($ci) and client_contact_slots.date in ($dstring)
ORDER BY client_contact_slots.slotstart ASC");
The recurrence is in table client_recurring_slots and with column recurring that will only have value Daily, Weekly and Monthly, Yearly.
Can somebody help me?
Thank you.

Maintain contiguous index values after deleting rows from a table

I've a schedule-building application under development that lets the user add activities to a day within a schedule.
I've a schedule table which holds the headline data:
schedule name, length of the schedule in days, id
and an activities table which holds activity id, schedule id and day offset.
The day offset refers to the 'nth' day of the schedule. I don't need to store specific dates - those are added later when a schedule is added to someone's calendar.
There isn't necessarily an activity for all of the days within a schedule, or indeed any of them.
Question is - I want to be able to delete days from a schedule (and insert n days too for that matter).
For deletion, the user can highlight multiple days within the schedule they're editing.
So on selecting delete I'll have an array of date offsets to remove from the activities table. That bit is trivial.
My problem is - if I delete day 5, say, from the schedule, day 6 becomes the new day 5; day 7 the new day 6 etc. so I need to update all the affected activity records in the table.
The only way I can think of doing this is ordering the days to be deleted in ascending order, in turn deleting each one then searching for and decrementing all records with a dayoffset value higher than the deleted offset. Then re-running this query for every day that's being removed.
It feels very inefficient but I can't think of anything better. Any ideas?
UPDATE `schedule` SET `day` = `day` - number_of_deleted_days WHERE `day` > first_day_deleted;
Order doesn't really depend and you can optimize it to work with continuous ranges, but in my knowledge there is not really anything better

How do I use price data in one table for a calculation that is stored in another table?

I'm still leanring PHP/MySQL but have learned quite a bit thanks to codies on StackOverflow. I'm trying to setup a sort of room reservations system using two tables:
SETUP:
Room price table: Has, prices for a type room a client may want to rent as well as the dates (day of week) they wish to use it. Pricing varies based on day of the week and per room.
I've setup a different table for each room type as each room type carries different pricing for each day of the week. So, There is an Alpha room table, Bravo room, etc. Within Alpha table are headers for the days of the week with pricing pre-entered into the rows.
Client info table: Has the name, address, date of room use, etc data for the specific client.
EXAMPLE:
Alpha-room price table:
Sun = $100; Mon = $200; Tue=$300 and so on.
Bravo-room price table:
Sun = $100; Mon = $200; Tue=$300 and so on.
Client data table:
ClientName; date-of-room-use; address; day_subtotal; grand_total.
QUESTION:
I'm trying to find PHP code that will:
look at the date of room use in the client data table,
look up the associated cost for that date in the specific room pricing table,
record that unit cost in the day subtotal of the client data table
and sum a grand total in the grand total row of the client data
table (assuming the room may be used more than one day by the
customer).
I know there's something to do with join but I'm finding it difficult to grasp the concept and, if someone can demonstrate using this example, I think I will have a better understanding of how to work this sort of transaction.
Thank you ALL in advance for your suggestions or alternatvie approaches.
First, you should separate your database slightly, you should have four tables. rooms, prices, clients and bookings. Setup somewhat like this...
rooms should have the following fields: id, name and description.
prices should have the following fields: id, price, room_id and day.
clients should have the following fields: id and whatever else you want to store on the user, such as first and last names, phone number or whatever.
bookings should have the following fields: id, client_id, room_id, started_at and ended_at. Preferably the started_at and ended_at fields will be an int, filled with PHP's time() method.
You can add any extra fields you want/need to the tables.
With the tables separated out like this, you will be able to properly query the database. So to answer your questions...
look at the date of room in use...
You can now query to see if a room is in use on a specific date by doing the following...
<?php
$selectedDate= mktime(); // Create a UNIX timestamp based on the day the user selected.
$query = "SELECT r.name, r.description
FROM rooms r, bookings b
WHERE b.room_id = r.id
AND b.started_at < $selectedDate
OR b.ended_at > $selectedDate";
$result = $pdo->query($query);
?>
Look up cost for that date
<?php
$selectedDate = mktime() // Create a UNIX timestamp based on the day the user selected
$dayOfWeek = Date('N', $selectedDate); // This will give the numerical day of the week.
$query = "SELECT price
FROM prices
WHERE room_id = $roomId
AND day = $dayOfWeek";
$result = $pdo->query($query);
?>
Record that unit cost in the client table
Doing this is just silly on account of already having the information in another table. Never recreate the same information in a database. If you are, you have built your database incorrectly.
Grand total in the customer table
Again, silly... don't recreate data...
Though, to find that information out, you would first need to do a query on the bookings table, and see the start and end date for which the user will be occupying the room.
Do a calculation on how long the client will be in the room for, (ended_at - started_at) / 86400, (86400 is the number of seconds in a day) that will give the number of days the client is in the room for.
Now that you know which days, and how long the client will be in the room, you can dynamically create a sql call to select the days of the week you need, remember Date('N', $timeStamp) will give you the numerical day of the week for a given timestamp.
Then it is just a matter of doing simple addition.
I have given you the basics here, You can modify the query from answer one to show you if a room is available to be booked within the time frame the user asked for.
I hope that covers everything you asked about...

Is it good or bad practise to alter start dates in a database to the next occurrence of an event?

I am trying to create an event calendar which whilst initially quite small could turn out to be quite large. To that end, when trying to future proof it as much as possible, all events that occur in the past will be deleted from the database. However, is it bad practise to alter the start date of recurring events once they have happened to indicate when the next event will start? This makes it easier to perform search queries because theoretically no events will start more than say a week in the past, depending on how often the database is updated.
Is there a better way to do this?
My current intention is to have a table listing the event details along with a column for whether it is a yearly, monthly, weekly or daily recurrence. When somebody then searches for events between 2 dates, I simply look at each row and check if (EVENT START <= SEARCH FINISH && EVENT FINISH >= SEARCH START). This then gets all the possible events, and the recurring ones then need to be checked to see if they occur during the time period given. This is where I come a little unstuck, as to how to achieve this specifically. My thoughts are as follows:
Yearly: if EVENT START + 1 YEAR <= SEARCH FINISH || EVENT FINISH + 1 Year >= SEARCH START; repeat for +2 YEARS etc until EVENT START + NO YEARS > SEARCH FINISH.
Monthly: As above but + 1 month each time.
Weekly: As above but EVENT START and EVENT FINISH will be plus 7 DAYS BETWEEN RECURRENCE each iteration until EVENT START + 7 DAYS REPEATED > SEARCH FINISH.
Daily: As above but NO OF DAYS DIFFERENCE instead of 7 days for a week. This could be used to specify things like every 14 days (fortnight), every 10 days. Even every week could use this method.
However, when I think about the query that would have to be built to achieve this, I cannot help think that it will be very cumbersome and probably slow. Is there a better way to achieve the results I want? I have still not found a way to do things like occurs on the first Monday of a month or the last Friday of a month, or the second Saturday of April each year. Are these latter options even possible?
-- Edit: added below:
It might help a bit if I explain a bit more about what I am creating. That way guidance can be given with respect to that.
I am creating a website which allows organisations to add events, whether they are a one-off or recurring (daily, weekly, monthly, first Tuesday of a month etc.). The user of the site will then be able to search for events within a chosen distance (arbitrary 10, 25, 50, 100miles, all of country) on a set date or between 2 given dates which could be from 1 day apart up to a couple of years apart (obviously events that far into the future will be minimal or non-existant depending on the dates used).
The EVENTS table itself currently holds a lot of information about the event, such as location, cost, age group etc. Would it be better to have this in a separate table which is looked up once it has been determined if the event is within the specified search parameters? Clearly not all of this information is needed until the detailed page view, maybe just a name, location, cost and brief description.
I appreciate there are many ways to skin a cat but I am unsure how to skin this one. The biggest thing I am struggling with is how to structure my data so that a query will know if the recursion is within the specified date. Also, given that the mathematics to calculate distance between 2 lat/longs is relatively complex, I need to be able to build this calculation into my query, otherwise I will be doing the calculation in PHP anyway. Granted, there will be less results to process this way, but it still needs to be done.
Any further advice is greatly appreciated.
Creating events for each recurrence is unnecessary. It is much better to store the details that define how the event recurs. This question has been answered many times on SO.
One way to do this is to use a structure like this -
tblEvent
--------
id
name
description
date
tblEventRecurring
-----------------
event_id
date_part
end_date
Then you could use a query like this to retrieve events -
SELECT *
FROM `tblEvent`
LEFT JOIN `tblEventRecurring`
ON `tblEvent`.`id` = `tblEventRecurring`.`event_id`
WHERE (`tblEvent`.`date` = CURRENT_DATE AND `tblEventRecurring`.`event_id` IS NULL)
OR (
CURRENT_DATE BETWEEN `tblEvent`.`date` AND `tblEventRecurring`.`end_date`
AND (
(`tblEventRecurring`.`date_part` = 'D') OR
(`tblEventRecurring`.`date_part` = 'W' AND DAYOFWEEK(`tblEvent`.`date`) = DAYOFWEEK(CURRENT_DATE)) OR
(`tblEventRecurring`.`date_part` = 'M' AND DAYOFMONTH(`tblEvent`.`date`) = DAYOFMONTH(CURRENT_DATE))
)
)
UPDATE Added the following example of returning events for a given date range.
When returning dates for a given date range you can join the above query to a table representing the date range -
SET #start_date = '2012-03-26';
SET #end_date = '2012-04-01';
SELECT *
FROM (
SELECT #start_date + INTERVAL num DAY AS `date`
FROM dummy
WHERE num < (DATEDIFF(#end_date, #start_date) + 1)
) AS `date_list`
INNER JOIN (
SELECT `tblEvent`.`id`, `tblEvent`.`date`, `tblEvent`.`name`, `tblEventRecurring`.`date_part`, `tblEventRecurring`.`end_date`
FROM `tblEvent`
LEFT JOIN `tblEventRecurring`
ON `tblEvent`.`id` = `tblEventRecurring`.`event_id`
WHERE `tblEvent`.`date` BETWEEN #start_date AND #end_date
OR (`tblEvent`.`date` < #end_date AND `tblEventRecurring`.`end_date` > #start_date)
) AS `events`
ON `events`.`date` = `date_list`.`date`
OR (
`date_list`.`date` BETWEEN `events`.`date` AND `events`.`end_date`
AND (
(`events`.`date_part` = 'D') OR
(`events`.`date_part` = 'W' AND DAYOFWEEK(`events`.`date`) = DAYOFWEEK(`date_list`.`date`)) OR
(`events`.`date_part` = 'M' AND DAYOFMONTH(`events`.`date`) = DAYOFMONTH(`date_list`.`date`))
)
)
WHERE `date_list`.`date` BETWEEN #start_date AND #end_date
ORDER BY `date_list`.`date`;
You can replace the SQL variables with PHP vars if you would prefer. To display days without any events you can change the INNER JOIN between the two derived tables, date_list and events, to a LEFT JOIN.
The table dummy consists of a single column with numbers from 0 to whatever you anticipate needing. This example creates the dummy table with enough data to cover one month. You could easily populate it using an INSERT... SELECT... on the AI PK of another table -
CREATE TABLE `dummy` (
`num` SMALLINT UNSIGNED NOT NULL PRIMARY KEY
);
INSERT INTO `dummy` VALUES
(00), (01), (02), (03), (04), (05), (06), (07), (08), (09),
(10), (11), (12), (13), (14), (15), (16), (17), (18), (19),
(20), (21), (22), (23), (24), (25), (26), (27), (28), (29),
(30), (31);
Break it up
Have one table for vents that haven't happened yet with a reccurring event ID. So you can just poke one offs in there with recurring veent id of null. Get rid /archive past ones etc.
Have another for the data about recurring events.
When an event marked as recurring happens, go back to recurring table, check to see if it's enabled (you might want to add a range to them ie do this every wek for three months), and if all is okay, add a new record for the next time it occurs.
One way to do it anyway, and it gets rid of the problem of using event start for two different things which is why your code is getting complicated.
If you want future jobs from this. ie everything needed to do in the next month.
The it would be a union query. One to get all teh "current jobs", unioned with one to get all the jobs that will recur in the next month.
Can't stress this enough, get the data design right the code "just happens". If you data is messed up as in one field "start date" serving two different needs, then every time you go near it, you have to deal with that dual use. Forget it once and you get anything from a painful mess to a disaster.
Adding a Recurring_Start_Date column would be better than your current plan, wouldn't it. You wouldn't be asking this question, beacseu your data would fit your needs.
I assume you'll be searching through events much more frequently than you will be creating new ones. During event creation, I would create records for each occurrence of the event up to so reasonable amount of time (maybe for the next year or two).
It would also make things like "The third thursday of each month" a little easier. If you tried to do any of the calculations in a query it would be difficult and probably slow.

Categories