How to create model for Codeigniter? - php

I Have 4 tables on my db
Table "User"
id | user
-----------
55 | Jhon
56 | Krish
57 | Boss
Table "Payout"
id | Payout_Date
------------------
1 | 31.10.2015
2 | 24.10.2015
3 | 17.10.2015
Table "Earning"
Userid | Date | Earning
------------------------------------
55 | 31.10.2015 | 5$
56 | 31.10.2015 | 1$
57 | 31.10.2015 | 3$
55 | 30.10.2015 | 5$
56 | 30.10.2015 | 12$
57 | 30.10.2015 | 0$
55 | 29.10.2015 | 5$
56 | 29.10.2015 | 4$
When I add a new Payout date (Payout_Date),
I want to find who earn >10$ in their Last payment date between new payout out date, If not pay before that user, his Last Payment_Date is No(get his all earning).
Then add that results to table "Payment" with new Payout_Date to Payment_Date. Their Last Payment_Date is From_Date.
Table "Payment"
id | Userid | From_Date | Payment_Date | Earning | Status
------------------------------------------------------------------
1 | 55 | 24.10.2015 | 31.10.2015 | 12$ | 0
2 | 56 | 24.10.2015 | 31.10.2015 | 17$ | 0
How to write that on Codeigniter Controller and Model?

You need to break the problem down into pieces: find current payout date, find last time user paid, add earnings since then, filter if > $10 .
Here is a sample of what a view may look like (I did not actually run this so there could be syntax errors)
SELECT Earning.Userid, sum(Earning.Earning) earning_since_last
FROM
/* Find current payout Date */
(SELECT MAX(Payout_Date) current_payout_date FROM PAYOUT) as curr_payout,
/* find last time user paid */
(SELECT Userid, MAX(Payout_Date) last_payout_date
FROM PAYOUT
GROUP BY Userid) last_payout,
Earning
WHERE Earning.date <= curr_payout.current_payout_date
and last_payout.Userid = Earning.Userid
and earning.date > last_payout.last_payout_date
GROUP BY Earning.Userid
HAVING SUM(Earning.Earning) >= 10

Related

Eloquent get last record groupped by date

I have a table prices with where I store more times in a day more values referred to a customer like this:
Table prices:
| id | customer_id | value_1 | value_2 | value_3 | created_at |
-------------------------------------------------------------------------
| 1 | 10 | 12345 | 122 | 10 | 2021-08-11 10:12:40 |
| 2 | 10 | 22222 | 222 | 22 | 2021-08-11 23:56:20 |
| 3 | 12 | 44444 | 444 | 44 | 2021-08-12 08:12:10 |
| 4 | 10 | 55555 | 555 | 55 | 2021-08-13 14:11:20 |
| 5 | 10 | 66666 | 666 | 66 | 2021-08-13 15:15:30 |
| 6 | 10 | 77777 | 777 | 77 | 2021-08-13 16:12:50 |
I have some filters on that table to retrieve only records with date greater than X and/or lower than Y, sort records by value_1 or value_2, etc...
With that filters I have to take only 1 record for each day of a customer specified.
I'm able to get the record with the highest value_1 for example, by using sql function max() and group by date.
// Init query
$query = Price::query();
// Take the greatest value of value1
$query = $query->selectRaw(
'max(value_1) as highest_value_1, ' .
'date(created_at) as date'
);
// If defined, add a greater or equals
if ($from) $query->where("created_at", ">=", $from);
// If defined add a lower or equals
if ($to) $query->where("created_at", "<=", $to);
// Get results for current customer only, grupping by date and ordering it
$query = $query->where('customer_id', $id)->groupBy('date')
->orderBy('date', 'DESC');
// Fetch records
$records = $query->get();
But now I would like to have only the last record for each day of a customer specified.
I need an eloquent/sql solution because the date range to search may be large and the table has a lot of records.
How can I archive that?
Thanks
Not a complete solution (no customer filter nor laravel use), but I would use something like that in pure sql :
SELECT
CONCAT(EXTRACT(YEAR_MONTH FROM created_at),EXTRACT(DAY FROM created_at)) AS day,
MAX(created_at)
FROM mytable
GROUP BY day;
Of course, you may use other function to group by day (like string regexp or substr).

Calculate Available Balance from Wallet system excluding Expired Credits

In credits table I have
(id, user_id, process, amount, date_add, date_exp, date_redeemed, remark)
SELECT * FROM credits WHERE user_id = 2;
+----+---------+---------+--------+------------+------------+---------------+----------+
| id | user_id | process | amount | date_add | date_exp | date_redeemed | remark |
+----+---------+---------+--------+------------+------------+---------------+----------+
| 22 | 2 | Add | 200.00 | 2018-01-01 | 2019-01-01 | | Credit1 |
| 23 | 2 | Add | 200.00 | 2018-03-31 | 2019-03-31 | | Credit2 |
| 24 | 2 | Deduct | 200.00 | | | 2018-04-28 | Redeemed |
| 25 | 2 | Add | 200.00 | 2018-07-11 | 2018-10-11 | | Campaign |
| 26 | 2 | Deduct | 50.00 | | | 2018-08-30 | Redeemed |
| 27 | 2 | Add | 200.00 | 2018-10-01 | 2019-09-30 | | Credit3 |
| 28 | 2 | Deduct | 198.55 | | | 2018-10-20 | Redeemed |
+----+---------+---------+--------+------------+------------+---------------+----------+
The following query I wrote will only calculate the balance, but I don't know whether the credit is expired and used before expired.
SELECT
u.id,
email,
CONCAT(first_name, ' ', last_name) AS name,
type,
(CASE
WHEN (SUM(amount) IS NULL) THEN 0.00
ELSE CASE
WHEN
(SUM(CASE
WHEN process = 'Add' THEN amount
END) - SUM(CASE
WHEN process = 'Deduct' THEN amount
END)) IS NULL
THEN
SUM(CASE
WHEN process = 'Add' THEN amount
END)
ELSE SUM(CASE
WHEN process = 'Add' THEN amount
END) - SUM(CASE
WHEN process = 'Deduct' THEN amount
END)
END
END) AS balance
FROM
users u
LEFT JOIN
credits c ON u.id = c.user_id
GROUP BY u.id;
Or I am doing it in the wrong way? Maybe I should have done the calculation in my backend instead of SQL?
EDIT 1:
I want to calculate the balance of every user's e-wallet, but the credit will expire,
IF it was expired AND not redeemed then exclude from the balance
ELSE IF used before expired AND redeem amount < expire amount
THEN (balance - (expire amount - redeem amount))
ELSE IF used before expired AND redeem amount > expire amount
THEN the usable balance will be deducted as expire amount is not enough to deduct redeemed amount
EDIT 2:
The query above will output 351.45, my expected output is 201.45. Which will not calculate the redemption on 2018-08-30 as the redeem amount is lower than the expired amount
EDIT 3:
User table:
+----+------------+-----------+----------+----------------+----------+
| id | first_name | last_name | type | email | password |
+----+------------+-----------+----------+----------------+----------+
| 2 | Test | Oyster | Employee | test#gmail.com | NULL |
+----+------------+-----------+----------+----------------+----------+
My output:
+----+----------------+-------------+----------+---------+
| id | email | name | type | balance |
+----+----------------+-------------+----------+---------+
| 2 | test#gmail.com | Test Oyster | Employee | 351.45 |
+----+----------------+-------------+----------+---------+
Expected output:
total (200+200+200) 600
redeemed amount 448.55 (200+50+198.55)
Remaining balance is 151.45
+----+----------------+-------------+----------+---------+
| id | email | name | type | balance |
+----+----------------+-------------+----------+---------+
| 2 | test#gmail.com | Test Oyster | Employee | 151.45 |
+----+----------------+-------------+----------+---------+
There are basic structural problems with your current table. So I would propose some alterations to the table structure and subsequently application code. Table structures for Wallet systems can be very detailed; but I would suggest minimum possible changes here. I am not suggesting that it would be ideal way; but it should work. Initially, I will layout some of the problems with the current approach.
Problems:
What if there are multiple Credits available which are not yet expired ?
Out of these available Credits, some may actually have been utilized already, but not yet expired. How do we ignore them for available balance ?
Also, some may have been partially utilized. How do we account for partial utilization ?
There may be a scenario where a redemption amount spans across multiple unexpired credits. Some may get partially utilized; while some may get fully utilized.
General Practice:
We generally follow FIFO (First In First Out) approach, to give maximum benefit to the customer. So the older credits (which has higher chance of getting expired without utilization) gets utilized first.
In order to follow FIFO, we will have to effectively use a Looping technique in the query/application code every-time, in order to compute basic things, such as, "Available Wallet Balance", "Expired and Underutilized Credit", etc. Writing a query for this will be cumbersome and possibly inefficient at bigger scales
Solution:
We can add one more column amount_redeemed in your current table. It basically represent the amount which has already been redeemed against a specific credit.
ALTER TABLE credits ADD COLUMN amount_redeemed DECIMAL (8,2);
So, a filled table would look something like below:
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| id | user_id | process | amount | amount_redeemed | date_add | date_exp | date_redeemed | remark |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
| 22 | 2 | Add | 200.00 | 200.00 | 2018-01-01 | 2019-01-01 | | Credit1 |
| 23 | 2 | Add | 200.00 | 200.00 | 2018-03-31 | 2019-03-31 | | Credit2 |
| 24 | 2 | Deduct | 200.00 | | | | 2018-04-28 | Redeemed |
| 25 | 2 | Add | 200.00 | 0.00 | 2018-07-11 | 2018-10-11 | | Campaign |
| 26 | 2 | Deduct | 50.00 | | | | 2018-08-30 | Redeemed |
| 27 | 2 | Add | 200.00 | 48.55 | 2018-10-01 | 2019-09-30 | | Credit3 |
| 28 | 2 | Deduct | 198.55 | | | | 2018-10-20 | Redeemed |
+----+---------+---------+--------+-----------------+------------+---------------+---------------+----------+
Notice that the amount_redeemed against Credit of id = 25 is 0.00, using FIFO approach. It got a chance for redemption on 2018-10-20, but by that time, it has expired already (date_exp = 2018-10-11)
So, now once we have this setup, you can do the following things in your application code:
Populate amount_redeemed value in existing rows in the table:
This will be a one time activity. For this, formulating a single query would be difficult (that is why we are here in the first place). So I would suggest you to do it one time in your application code (eg: PHP), using Loops and FIFO approach. Look at Point 3 below, to get an idea of how to do it in application code.
Get Current Available Balance:
Query for this becomes trivial now, as we just need to calculate Sum of amount - amount_redeemed for all Add process, which are not yet expired.
SELECT SUM(amount - amount_redeemed) AS total_available_credit
FROM credits
WHERE process = 'Add' AND
date_exp > CURDATE() AND
user_id = 2
Update amount_redeemed at the time of redemption:
In this, you can firstly get all available Credits, which has amount available for redemption, and not yet expired.
SELECT id, (amount - amount_redeemed) AS available_credit
FROM credits
WHERE process = 'Add' AND
date_exp > CURDATE() AND
user_id = 2 AND
amount - amount_redeemed > 0
ORDER BY id
Now, we can loop over the above query results, and utilize the amount accordingly
// PHP code example
// amount to redeem
$amount_to_redeem = 100;
// Map storing amount_redeemed against id
$amount_redeemed_map = array();
foreach ($rows as $row) {
// Calculate the amount that can be used against a specific credit
// It will be the minimum of available credit and amount left to redeem
$amount_redeemed = min($row['available_credit'], $amount_to_redeem);
// Populate the map
$amount_redeemed_map[$row['id']] = $amount_redeemed;
// Adjust the amount_to_redeem
$amount_to_redeem -= $amount_redeemed;
// If no more amount_to_redeem, we can finish the loop
if ($amount_to_redeem == 0) {
break;
} elseif ($amount_to_redeem < 0) {
// This should never happen, still if it happens, throw error
throw new Exception ("Something wrong with logic!");
exit();
}
// if we are here, that means some more amount left to redeem
}
Now, you can use two Update queries. First one would update amount_redeemed value against all the Credit id(s). Second one would Insert the Deduct row using the sum of all individual amount_redeemed value.
SELECT `id`, `email`, `NAME`, `type`,
(
( SELECT SUM(amount) FROM credit_table AS ct1 WHERE u.id = ct1.id AND process = 'ADD' AND date_exp > CURDATE()) -
( SELECT SUM(amount) FROM credit_table AS ct2 WHERE u.id = ct2.id AND process = 'Deduct' )
) AS balance
FROM
`user_table` AS u
WHERE
id = 2;
Hope it works as you wish

$this->db->group_by() and adding the quantity

I have this in my receipt UI(Note: This is only from one user):
Item_Name | Quantity | Price | Total
Panty | 2 | 50 | 100
Bra | 1 | 35 | 35
Panty | 2 | 50 | 100
As you can see the user gets the Panty TWICE!
I want my UI to be looking like this:
Item_Name | Quantity | Price | Total
Panty | 4 | 50 | 100
Bra | 1 | 35 | 35
As you can see the Panty's that the user buys groups together, but in my query instead of Panty having 4 Quantity it still has 2
Here is my Code:
public function getClientReservation($name){
$this->db->select('*');
$this->db->from('tbl_item_availed');
$this->db->join('tbl_items','tbl_items.Item_ID = tbl_item_availed.Item_ID');
$this->db->join('user_account','user_account.Account_no = tbl_item_availed.Account_no');
$this->db->where('Username',$name);
$this->db->where('isReserve',1);
$this->db->where('Active',1);
$this->db->group_by('tbl_items.Item_Name');
$query = $this->db->get();
return $query->result();
}
Please help!
This is the table tbl_item_availed by the way:
Item_availed_id | Item_ID | Quantity | Account_no
1 | 1 | 2 | 5
2 | 2 | 1 | 5
3 | 1 | 2 | 5
table tbl_items:
Item_ID | Price | Item_Name
1 | 100 | Panty
2 | 35 | Bra
table user_account:
Account_no | Firstname | Lastname
1 | LeBron | James
2 | Dwyane | Wade
3 | Wilt | Chamberlain
4 | Michael | Jordan
5 | Charles | Barkley
Try this.
public function getClientReservation($name){
$this->db->select('tbl_items.`Item_Name`, SUM(tbl_item_availed.`Quantity`) AS quantity, tbl_items.`Price`, (tbl_items.`Price`*(SUM(tbl_item_availed.`Quantity`))) AS total');
$this->db->from('tbl_item_availed');
$this->db->join('tbl_items','tbl_items.Item_ID = tbl_item_availed.Item_ID');
$this->db->join('user_account','user_account.Account_no = tbl_item_availed.Account_no');
$this->db->where('Username',$name);
$this->db->where('isReserve',1);
$this->db->where('Active',1);
//$this->db->group_by('tbl_items.Item_Name');//do not use this line unless item name is unique
//group by item id
$this->db->group_by('tbl_items.Item_ID');
$query = $this->db->get();
return $query->result();
}
Also use GROUP BY for Item_ID (assuming it is unique), not Item_Name unless it is unique.
Side note:
Moreover, use Account_no instead of username in WHERE clause. If you want to modify it, just replace this line
$this->db->where('Username',$name);
with
$this->db->where('Account_no',$account_no);

How To Copy Database Records and Track Their New IDs

I have three tables: years, employees, positions. Suppose that I already have these data in those tables.
years:
----------------
| id | name |
----------------
| 1 | 2011 |
----------------
positions:
------------------------------
| id | name | year_id |
------------------------------
| 1 | Director | 1 |
| 2 | Manager | 1 |
------------------------------
employees:
---------------------------------------------------------
| id | name | position_id | year_id |
---------------------------------------------------------
| 1 | Employee A (Director) | 1 | 1 |
| 2 | Employee B (Manager) | 2 | 1 |
---------------------------------------------------------
========================================
The years table is a central point.
If I insert a new year record, I must also copy all positions and employees which are related to the previous year.
So if I insert year 2012 into the years table, the data is suppose to be like this:
years:
----------------
| id | name |
----------------
| 1 | 2011 |
| 2 | 2012 |
----------------
positions:
------------------------------
| id | name | year_id |
------------------------------
| 1 | Director | 1 |
| 2 | Manager | 1 |
| 3 | Director | 2 |
| 4 | Manager | 2 |
------------------------------
employees:
---------------------------------------------------------
| id | name | position_id | year_id |
---------------------------------------------------------
| 1 | Employee A (Director) | 1 | 1 |
| 2 | Employee B (Manager) | 2 | 1 |
| 3 | Employee A (Director) | 3 (?) | 2 |
| 4 | Employee B (Manager) | 4 (?) | 2 |
---------------------------------------------------------
NOTICE the question marks in the third and fourth row of employees table.
I use these queries to insert a new year and copy all related positions and employees:
// Insert new year record
INSERT INTO years (name) VALUES (2012);
// Get last inserted year ID
$inserted_year_id = .......... // skipped
// Copy positions
INSERT INTO positions (name, year_id) SELECT name, $inserted_year_id AS last_year_id FROM positions WHERE year_id = 1;
// Copy employees
INSERT INTO employees (name, position_id, year_id) SELECT name, position_id, $inserted_year_id AS last_year_id FROM employees WHERE year_id = 1;
The problem is at copying employees query. I can't find a method to get or track the new ID of positions.
Is there a simple method to do this?
Thank you very much.
Your data model is seriously flawed and probably needs a complete overhaul, but if you insist on copying the data like you describe, this should do the trick:
// Copy employees
INSERT INTO employees (name, position_id, year_id)
SELECT name, new_positions.id, $inserted_year_id AS last_year_id
FROM employees
JOIN positions AS old_positions ON old_positions.id = employees.position_id
AND old_positions.year_id = employees.year_id
JOIN positions AS new_positions ON new_positions.name = old_positions.name
AND new_positions.year_id = $inserted_year_id
WHERE employees.year_id = 1
I think you should read about database normalization. Copying the data leads to maintenance issues and erroneous reporting.
If you went with a different design like the following, then there would be nothing to insert, until an employee changes position, is terminated, or a position is discontinued. There are plenty of other ways to approach this, too, but you should minimize redundancy (i.e. have only one copy of each Employee), and then keep track of data that changes over time, separately. Also read about foreign keys before you try to implement something like this.
positions:
-- If you are keeping track of the years that each position is active,
-- using dates provides simplicity. Note: this design assumes that positions
-- are never reactivated after being deactivated.
------------------------------------------------
| id | name | DateActive | DateInactive |
------------------------------------------------
| 1 | Director | 01/01/2011 | |
| 2 | Manager | 01/01/2011 | |
------------------------------------------------
employees:
---------------------------------------------------------------
| id | name | DateHired | DateTerminated |
---------------------------------------------------------------
| 1 | Employee A | 01/01/2011 | |
| 2 | Employee B | 01/01/2011 | |
| 3 | Employee C | 01/01/2011 | 10/01/2012 |
---------------------------------------------------------------
EmployeePositionRelationships
--If you are keeping track of time that each employee held a position
-- Employee A has been a Director since 1/1/2011
-- Employee B was a Manager from 1/1/2011 to 10/6/2012. Then they became a Director
-- Employee B was a Manager from 1/1/2011 to 10/1/2012. Then they were terminated
--------------------------------------------------------
EmployeeId | PositionId | DateStarted | DateEnded |
--------------------------------------------------------
1 | 1 | 01/01/2011 | |
2 | 2 | 01/01/2011 | 10/6/2012 |
3 | 2 | 01/01/2011 | 10/1/2012 |
2 | 1 | 10/6/2012 | |
--------------------------------------------------------

How to bind string column data

I want to write a php script to compare rows from the database and then adds the values together if payment_id are matches. Based on payment_id:
Example:
+----+------------+-----------+--------+
| id | payment_id | cheque_id | amount |
+----+------------+-----------+--------+
| 1 | 1000 | MB101 | 20 |
| 2 | 1000 | MB102 | 20 |
| 3 | 1111 | MB113 | 20 |
+----+------------+-----------+--------+
Result required
+------+--------------+----+
| 1000 | MB101/MB102 | 40 |
| 1111 | MB113 | 20 |
+------+--------------+----+
Trying to merge cheque column as string. For 'Amount' column I know that should be use SUM.
Any suggestions is appreciated,
Thanks
use GROUP BY
select payment_id,group_concat(cheque_id SEPARATOR '/') as cheque_ids,
SUM(amount) as amount
FROM table name
GROUP BY payment_id
I found the solution
SELECT payment_id, group_concat(cheque_id SEPARATOR '/') AS cheque_ids,
SUM(amount) AS amount
FROM tbl_name
GROUP BY payment_id

Categories