How to program a recurring billing/invoice system using PHP and MySQL - php

I have already programmed a basic invoicing system using PHP/MySQL which has a table structure as follows;
Invoice table; invoice_id, invoice_date, customer_id, etc
Invoice line table; invoice_line_id, invoice_id, quantity, price, description, etc
I need the system to automatically generate future invoices at set intervals (for example every 1 week or every 2 months, etc). I was thinking of creating a new table as follows;
Invoice schedule table; invoice_schedule_id, invoice_id, interval (e.g. 1), interval_unit (months), start date, next_processing_date
My idea was to then setup a cron job which would execute a PHP file once a day. The PHP file would then generate an invoice when the next_processing_date matched today's date and update the next_processing_date in the database. I'm comfortable on how to achieve this but what I'm stuck with is how to actually insert the new invoice into the table/database. Does MySQL have any type of 'copy row' feature as the new invoice would be identical to the original one except for the invoice_date having to be updated.

Cron sounds good. (Also it is worth to mention the MySQL Event Scheduler, but again I would go for a Cronjob)
A copy would be something like this SQLFIDDLE:
create table t ( id int, d date );
insert into t values( 0, CURDATE() );
insert into t values( 2, CURDATE() );
insert into t values( 1, CURDATE() );
insert into t ( select id+1,curdate() from t order by id desc limit 1 );
Above example is to copy the latest order as a copy, of course you could put a where clause where id=1 or what id your reference order is.

BigScar's reference of "How to copy a row and insert in same table with a autoincrement field in MySQL?" seems to solve your copy-insert problem.
However, since you are mostly doing a specific group of DB queries, instead of cronjobs, you may use MySQL events. If your MySQL version supports them (check in phpmyadmin: select a DB and look to the top menu bar, you can create them there without even have to know the syntax), it's a good practical alternative.

Related

Whats better; mysql scheduler or php script and crontab?

I have 1 mysql table where it has thousands of rows. I use this as a transaction history for my users. I also query this table on the same page for a sum of earnings per product. here are my two mysql calls;
$earnperproduct = mysqli_query($con,"SELECT product, SUM(amount) AS totalearn FROM wp_payout_history WHERE user=$userid GROUP BY product");
$result = mysqli_query($con,"SELECT * FROM wp_payout_history WHERE user=$userid ORDER BY date DESC");
my fear is that as the table grows, the $earnperproduct call will become too intensive and slow down page loading. Therefore instead of doing a sum command every time the page loads, i think it would be easier to update a summary (example wp_summary_table) whenever wp_payout_history is changed to replace past values with new SUM(AMOUNT)values per user and product; and thus query something like this;
$earnperproduct = mysqli_query($con,"SELECT * FROM wp_payout_summary_table WHERE user=$userid ORDER BY product DESC");
TL;DR
What is the best method to go about updating a table using the $earnedperproduct style call? would I be better using mysql event scheduler or a php script with a crontab? Is there any tutorials that can help me create either option for my needs?
Both the control mechanisms you mention use time as the trigger for an action. But your description says that you really want to trigger an action when the data changes. And in a relational database the best way to trigger an action when data is changed is with a ....trigger. Which makes your question a duplicate of this.
Arguably it may be more efficient to snapshot the transactions, then something like....
[INSERT INTO summary_table (user, total_amount, last_id) ]
SELECT
user, SUM(amount), MAX(id)
FROM (
SELECT a.user, a.total_amount AS amount, a.last_id
FROM summary_table a
WHERE a.user=$user_id
AND last_id=(SELECT MAX(b.last_id)
FROM summary_table b
WHERE b.user=$user_id)
UNION
SELECT h.user, h.amount, h.id
FROM wp_payout_history h
WHERE h.user=$user_id
AND h.id>(SELECT MAX(c.last_id)
FROM summary_table c
WHERE c.user=$user_id)
) ilv
GROUP BY user;
...then it doesn't really matter what you use to refresh the history - the query will always give you an up to date response. If you go down this route then add a dummy integer column in the summary table and add 0 as unaggregatedrows to the second SELECT and SUM(1) to the 4th SELECT to work out when it will be most efficient to update the summary table.

Adding database entries together

I am making a site which allows admins to basically add points for a user.
At this point in time, I have a table, where id_child is unique, and id_points changes. So a constant stream of id_points can come in, however, it will only show the latest id_points, not the total.
I am wondering how I could go about creating a PHP script that could add all of those together.
From the image, the idea is that I want all id_points values added together to give a total, and this is for the same id_child
Use SQL sum() funciton:
select sum(id_points) from table `table_name` where `id_child` = 1
Hope i understood right.
First if you want to show only the latest points added you have to create another table #__points where you will keep every new change of points.
You need 3 columns id as PRIMARY and AUTO_INCRENMENT , pts and user_id . user_id will be FK to id_child.
So when you want to add a new record :
INSERT INTO `#__points` (pts,user_id) VALUES ("$pts",$id)
When you want to select last inserted value for each admin :
SELECT * from `#__points` where user_id=$id ORDER BY id ASC LIMIT 1

SQL INSERT INTO SELECT and Return the SELECT data to Create Row View Counts

So I'm creating a system that will be pulling 50-150 records at a time from a table and display them to the user, and I'm trying to keep a view count for each record.
I figured the most efficient way would be to create a MEMORY table that I use an INSERT INTO to pull the IDs of the rows into and then have a cron function that runs regularly to aggregate the view ID counts and clears out the memory table, updating the original one with the latest view counts. This avoids constantly updating the table that'll likely be getting accessed the most, so I'm not locking 150 rows at a time with each query(or the whole table if I'm using MyISAM).
Basically, the method explained here.
However, I would of course like to do this at the same time as I pull the records information for viewing, and I'd like to avoid running a second, separate query just to get the same set of data for its counts.
Is there any way to SELECT a dataset, return that dataset, and simultaneously insert a single column from that dataset into another table?
It looks like PostgreSQL might have something similar to what I want with the RETURNING keyword, but I'm using MySQL.
First of all, I would not add a counter column to the Main table. I would create a separate Audit table that would hold ID of the item from the Main table plus at least timestamp when that ID was requested. In essence, Audit table would store a history of requests. In this approach you can easily generate much more interesting reports. You can always calculate grand totals per item and also you can calculate summaries by day, week, month, etc per item or across all items. Depending on the volume of data you can periodically delete Audit entries older than some threshold (a month, a year, etc).
Also, you can easily store more information in Audit table as needed, for example, user ID to calculate stats per user.
To populate Audit table "automatically" I would create a stored procedure. The client code would call this stored procedure instead of performing the original SELECT. Stored procedure would return exactly the same result as original SELECT does, but would also add necessary details to the Audit table transparently to the client code.
So, let's assume that Audit table looks like this:
CREATE TABLE AuditTable
(
ID int
IDENTITY -- SQL Server
SERIAL -- Postgres
AUTO_INCREMENT -- MySQL
NOT NULL,
ItemID int NOT NULL,
RequestDateTime datetime NOT NULL
)
and your main SELECT looks like this:
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
To perform both INSERT and SELECT in one statement in SQL Server I'd use OUTPUT clause, in Postgres - RETURNING clause, in MySQL - ??? I don't think it has anything like this. So, MySQL procedure would have several separate statements.
MySQL
At first do your SELECT and insert results into a temporary (possibly memory) table. Then copy item IDs from temporary table into Audit table. Then SELECT from temporary table to return result to the client.
CREATE TEMPORARY TABLE TempTable
(
ItemID int NOT NULL,
Col1 ...,
Col2 ...,
...
)
ENGINE = MEMORY
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
;
INSERT INTO AuditTable (ItemID, RequestDateTime)
SELECT ItemID, NOW()
FROM TempTable;
SELECT ItemID, Col1, Col2, ...
FROM TempTable
ORDER BY ...;
SQL Server (just to tease you. this single statement does both INSERT and SELECT)
MERGE INTO AuditTable
USING
(
SELECT ItemID, Col1, Col2, ...
FROM MainTable
WHERE <complex criteria>
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT
(ItemID, RequestDateTime)
VALUES
(Src.ItemID, GETDATE())
OUTPUT
Src.ItemID, Src.Col1, Src.Col2, ...
;
You can leave Audit table as it is, or you can set up cron to summarize it periodically. It really depends on the volume of data. In our system we store individual rows for a week, plus we summarize stats per hour and keep it for 6 weeks, plus we keep daily summary for 18 months. But, important part, all these summaries are separate Audit tables, we don't keep auditing information in the Main table, so we don't need to update it.
Joe Celko explained it very well in SQL Style Habits: Attack of the Skeuomorphs:
Now go to any SQL Forum text search the postings. You will find
thousands of postings with DDL that include columns named createdby,
createddate, modifiedby and modifieddate with that particular
meta data on the end of the row declaration. It is the old mag tape
header label written in a new language! Deja Vu!
The header records appeared only once on a tape. But these meta data
values appear over and over on every row in the table. One of the main
reasons for using databases (not just SQL) was to remove redundancy
from the data; this just adds more redundancy. But now think about
what happens to the audit trail when a row is deleted? What happens to
the audit trail when a row is updated? The trail is destroyed. The
audit data should be separated from the schema. Would you put the log
file on the same disk drive as the database? Would an accountant let
the same person approve and receive a payment?
You're kind of asking if MySQL supports a SELECT trigger. It doesn't. You'll need to do this as two queries, however you can stick those inside a stored procedure - then you can pass in the range you're fetching, have it both return the results AND do the INSERT into the other table.
Updated answer with skeleton example for stored procedure:
DELIMITER $$
CREATE PROCEDURE `FetchRows`(IN StartID INT, IN EndID INT)
BEGIN
UPDATE Blah SET ViewCount = ViewCount+1 WHERE id >= StartID AND id <= EndID;
# ^ Assumes counts are stored in the same table. If they're in a seperate table, do an INSERT INTO ... ON DUPLICATE KEY UPDATE ViewCount = ViewCount+1 instead.
SELECT * FROM Blah WHERE id >= StartID AND id <= EndID;
END$$
DELIMITER ;

Shift all data to one table to another table

I have two tables A and B both table are same, if I will insert today some values on table A and I want to all inserted table A data automatic insert on table B after 2 days. for example today is 15 June 2013 and all table A data shifted automatic on 17 June 2013.
Please give me any script.
Thanks in advance.
You'll need to add a timestamp field to your rows in Table A, then you can do this:
insert into b (select * from A where datediff(curdate(), postdate)>=2)
where postdate is your timestamp.
To automate this you can use the mySQL event scheduler (see CREATE EVENT for the syntax) if it's enabled on your server, or use a cron job as suggested elsewhere
insert into tableb select * from tablea;
Full documentation here
Maybe you're looking for something like:
CREATE TABLE tableB LIKE tableA;
As already said the SQL code for this is "insert into b (select * from A)"
to call this automatically you can write a small PHP-Cronjob, add this to the crontab of your server and let it execute every x min/hour/days (what you need)

restaurant table availability check for a particular time using sql

I am trying to build a restaurant table management application using yii framework currently i am stuck with retrieving availability of tables for a particular time
i have the following tables
below image shows the table structure i have
i am getting the avaiable table by using the following piece of code
$model = new Table;
$criteria = new CDbCriteria;
$criteria->condition = 'floor_id=' . $floorid;
$rows = $model->model()->with(array(
'bookingTables' => array(
'condition' => 'bookingTables.table_id IS NULL'
)))->findAll($criteria);
this works only when the table ids are not present in the booking_table table
suppose if say the booking_table table has table ids filled then above code will not work it will always return as not available (no clue how to use time to get available tables)
so how can i get the available tables for a particular time period any solution in yii or sql
Since you accept both SQL and YII, I give you a solution in SQL.
If you want to check availability at a given time, you need to know the reservation date (that includes the hour), so let's consider that we add the field 'reservation_date' in your 'booking_table' table, and let's consider a booking ends 2 hours after it has started.
This is what I'll do in SQL :
SELECT t.table_id
FROM TABLE AS t
LEFT OUTER JOIN booking_table AS b ON b.booking_table_id = t.table_id
WHERE b.booking_id IS NULL
OR DATE_ADD(NOW(), INTERVAL 2 HOUR) > b.reservation_date
This query lists all available tables at the time its executed. If you want to check availability at a specific time, replace 'NOW()' by whatever you need.

Categories