SQL for a Household Bill splitting db - php

I'm trying to build a mySQL database for a PHP page that will allow users to enter the cost of a bill and select who in the house it is split with. The problem I'm having is I can't figure out how to calculate the total owed between residents, taking account of what they have paid and what they need to pay someone else.
I want it to be able to handle variable numbers of users 'residents' so have designed the tables like so:
Users Table:
UserID | User
-------+---------
1 | Jon
2 | Boris
3 | Natalie
Bills Table:
BillID | BIll | Amount | Paid By (UserID F.Key)
-------+--------+----------+--------------
1 | Gas | £30.00 | 1 (Jon)
2 | Water | £30.00 | 1 (Jon)
3 | Tax | £60.00 | 2 (Boris)
4 | Phone | £10.00 | 2 (Boris)
So this table shows that the Phone Bill was £10.00 and Boris paid the Phone company for it.
Bills-Users Liabilities Junction Table:
UserID_fKey | BillID_fKey
------------+--------------
1 (Jon) | 1 (Gas)
2 (Boris) | 1 (Gas)
3 (Nat) | 1 (Gas)
1 (Jon) | 2 (Water)
2 (Boris) | 2 (Water)
3 (Nat) | 2 (Water)
1 (Jon) | 3 (Tax)
2 (Boris) | 3 (Tax)
1 (Jon) | 4 (Phone)
2 (Boris) | 4 (Phone)
The Bill-Users table is used to describe who is liable for each of the bills. So, for example Natalie never uses the phone so doesn't need to pay Boris anything for it. The phone bill therefore is split between Borris and Jon. Jon therefore owes Boris £5.
I think this is the best way to do it but I can't work out how to generate a table for each user when they login that shows what they owe one another. To do this (for example) I need to add up everything Jon has paid for, what everyone has paid for on behalf of Jon and work out the balance. Obviously this needs to scale so that there could be 3 or more users in the system.
This is the result I would like to get to:
(eg: Logged in as Jon)
User | Amount Owed
---------+-------------
Boris | -£15.00
Natalie | £20.00
Many Thanks in advance.

you need an owe field in your bill table in order to do bottom bit.
select individual user
SELECT Users.User, Bills.BIll, Bills.owe, Bills.Amount
FROM Users
LEFT JOIN Bills ON Users.UserID = Bills.UserID
WHERE Users.UserID =1
select all users
SELECT Users.User, Bills.BIll, Bills.owe, Bills.Amount
FROM Users
LEFT JOIN Bills ON Users.UserID = Bills.UserID
LIMIT 0 , 30
select all users sum of paid
SELECT Users.User, Bills.BIll, sum(Bills.owe), sum(Bills.Amount), sum(Bills.owe)- sum(Bills.Amount) as arrears
FROM Users
LEFT JOIN Bills ON Users.UserID = Bills.UserID
GROUP BY Users.UserID;
demo

In the end I achieved this by using a function. It probably wouldn't scale to 100's of users very well but still gives me the flexibility I wanted. I'm trying to start an SQLFiddle to demo it but having issues.
Set #CurrentUser = '1' (Jon)
Select User, funcAmountOwed(#currentuser,UserID) from Users
WHERE UserID != #CurrentUser;
Returns:
User | Amount Owed
---------+-------------
Boris | -£15.00
Natalie | £20.00
The function is something along the lines of:
funcAmountOwed(#CurrentUser, #CurrentDebtor);
SET #AmountPaidByCurrentUser = (
Select SUM(Amount) from Bills b
INNER JOIN BillsUsers bu ON b.BillID = bu.BillID_fkey
WHERE b.PaidBy = #CurrentUser and bu.UserId_fkey = #CurrentDebtor)
SET #AmountPaidByDebtor = (
Select SUM(Amount) from Bills b
INNER JOIN BillsUsers bu ON b.BillID = bu.BillID_fkey
WHERE b.PaidBy = #CurrentDebtor and bu.UserId_fkey = #CurrentUser)
RETURN #AmountPaidByCurrentUser - #AmountPaidByDebtor;
I had to nest another function in there to divide the Amount by the Number of people the Bill was split between too but as above I can't remember that from memory without SQLFiddle.

Related

MySQL View table for referrals?

I have a table (user) with the following info:
id | user_id | name | country | referral_id
----------------------------------------------------
1 | 10 | Jhon | India | 0
2 | 11 | Krish | America | 0
3 | 12 | Boss | Canada | 0
4 | 13 | Jack | India | 11
5 | 14 | Ronald | Japan | 10
6 | 15 | Jey | Germany | 10
And have other table (total_earning) with following info:
id | user_id | date | earning
--------------------------------------------
1 | 10 | 2015-10-25 | 4.4$
2 | 14 | 2015-10-25 | 2.2$
3 | 15 | 2015-10-27 | 3.0$
4 | 15 | 2015-10-25 | 1.5$
I Want to give Referral Payment (10% of that user earning) to users.
eg. User.10 has 2 referrals (User.14 and User.15), so i want give 10% from user.14 & user.15 earning.
How can I create a MySQL View table from this 2 table?
You need a two-part query. The first query is on a per-user basis of ANY possible earnings. This is the "JustMe" alias... what did EACH person earn directly.
From that, doing a LEFT-JOIN (as not all users had referrals from other) to the second query "Referrals" alias. This joins the earnings to the users table and grabs the referral Id from the USERS table as the grouping. You could have 10 people all be referral from user X, so you want ALL their earnings rolled-up into one for user X.
From that, then JOIN to the user table to pull the who the main person was with their earnings PLUS a column to show the total referral bonus they would receive.
select
U.Name,
U.Country,
JustMe.JustMyEarnings,
coalesce( Referrals.ReferralEarnings * .1, 0 ) as ReferralBonus
from
( select
te.user_id,
sum( te.earning ) justMyEarnings
from
total_earning te
group by
te.user_id ) JustMe
LEFT JOIN
( select
U2.referral_id,
sum( te2.earning ) as ReferralEarnings
from
total_earnings te2
join users U2
on te2.user_id = U2.user_id
AND U2.referral_id > 0
group by
U2.referral_id ) Referrals
on JustMe.user_id = Referrals.referral_id
JOIN Users U
on JustMe.user_id = U.user_id
AND, if you want this as a view, just
CREATE VIEW UserReferralView as
(entire select from above...)
BUT, if you want the results for a single user, then I would adjust as you DO NOT want the inner queries to query the entire table every time you want totals for one person. I would adjust the inner queries to something like
( select
te.user_id,
sum( te.earning ) justMyEarnings
from
total_earning te
where
te.user_id = THE_ONE_USERID_YOU_WANT
group by
te.user_id ) JustMe
LEFT JOIN
( select
U2.referral_id,
sum( te2.earning ) as ReferralEarnings
from
total_earnings te2
join users U2
on te2.user_id = U2.user_id
AND U2.referral_id > 0
AND te2.referral_id = THE_ONE_USERID_YOU_WANT
group by
U2.referral_id ) Referrals

Mysql - join 2 queries with limit

Im not very familiar with using 'join' in queries. I really tried solving this by my own, but it seems to be too hard.
I got 2 Tables:
Table 'users':
+-----------------+-----------------+
| member | online |
+-----------------+-----------------+
| mahran | 1 |
| peter | 1 |
| Jen | 1 |
| Steve | 0 |
+-----------------+-----------------+
Table 'tickets'
+-----------------+----------------+----------------+
| name | category | time |
+-----------------+----------------+----------------+
| mahran | silver | 1 |
| peter | blue | 1 |
| mahran | blue | 2 |
| peter | red | 3 |
| peter | green | 2 |
| Jen | silver | 1 |
+-----------------+----------------+----------------+
The chellange:
I need each member (users.member) who's online (users.online). The next thing is to get the category for each member (user.member = tickets.name) with the highest time (probably ORDER BY time DESC LIMIT 1).
So, for example:
Peter is online. Peters highest time is 3 at the position of category=red. So I want peter to show up in the result with his category 'red'. Mahran would show up with blue. Jen would get silver. And steve would be left out because he's not online.
I hope this was clear. In general I know how the queries would look like but theres no chance for me merging them together.
What needs to be merged:
SELECT member FROM users WHERE online = 1;
|
v for each member
SELECT category FROM tickets WHERE name=users.member ORDER BY time DESC.
So, any ideas how to solve this?
Here is a fiddle with a not working query: Click
You can do this easily with a correlated subquery:
select u.member,
(select t.category
from tickets t
where t.name = u.member
order by t.time desc
limit 1
) as MostRecentCategory
from users u
where u.online = 1;
This can make use of the following indexes: users(online, member) and ticket(member, time, category).
Here is the query you're looking for:
SELECT U.member
,T.category
FROM users U
INNER JOIN tickets T ON T.name = U.member
INNER JOIN (SELECT T2.name
,MAX(T2.time) AS [maxTime]
FROM tickets T2
GROUP BY T2.name) AS M ON M.name = T.name
AND M.maxTime = T.time
WHERE U.online = 1
The use of [name] to join the two tables is not a good practice, it's much better to use keys instead. But my query is just here to help you understanding the process of jointure.
Hope this will help you.
If i understand you correctly
SELECT DISTINCT users.member, tickets.category FROM tickets JOIN users ON users.member = tickets.name WHERE users.online = 1 ORDER BY tickets.time DESC
Can you make sql fiddle?
USE DISTINCT
stackoverflow.com/questions/11704012/mysql-distinct-join
try this
SELECT DISTINCT User.member,Ticket.category FROM users AS USER
INNER JOIN tickets AS Ticket ON (User.member = Ticket.name)
WHERE User.online = 1;
Sorry, but peter seems to be RED, It's time is 3. Don't you?
Depending on table definition, is not guaranteed to have one only result for each user.
For example, if peter has time 3 in two categories, you can get one different category depending of the SQL sorting method.
To be sure, tickets.Category and tickets.time must be in a unique key (both toghether, not a unike key for each field)
Assuming that, the Query could be this.
select t2.name, t2.category
from
tickets t2
INNER JOIN (Select
u.member, max(time)
from users u, tickets t
where
u.member = t.name
and u.online = 1
group by u.member
) as usermaxtime on t2.name = usermaxtime.member;

Counting in a complex query

I will explain my problem with an example:
Tables:
place - comment - activities_comment - activities
User can comment a place and select what kind of activities are being able to do in that place.
Activities are for example: run, swim, walk and climb.
So some users would vote more than 1 activity, so i can't put it right in the same table COMMENT, i need to create a pivote table ACTIVITIES_COMMENT, the problem goes here, i was able to show all the comments with their activities selected in each place..
But now i want to count the number of activities and order them by the most selected activity to the less selected by the users in the comments for each place.
How can I do that??
The best thing i can do is:
$place = Place::with('city','comments')
->where('id',$placeId)->first();
$place->activities = Activity::join('activities_comment', 'activity.id', '=', 'activities_comment.activity_id')
->join('comment', 'comment.id', '=', 'activities_comment.comment_id')
->select('comment.id','activities_comment.activity_id',
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 1) as run'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 2) as swim'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 3) as walk'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 4) as climb'),
)
->where('comment.place_id',$place->id)
->get();
The problem is that this query counts the most selected activities but in ALL places, i want to count only in each place.
EDIT:
Example rows:
place table:
id | name
----------
1 | Alaska
2 | Peru
3 | Argentina
comment table:
id | user_id | place_id | text
------------------------------------
1 | 1 | 1 | some text
2 | 3 | 1 | some text
3 | 2 | 2 | some text
activity table:
id | name
----------
1 | run
2 | swim
3 | walk
4 | climb
activity_comment table:
id | comment_id | activity_id
------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
4 | 3 | 2
When i get into Alaska comments, i would like to see the times that users selected an activity there, for alaska it will show:
run: 1 time
swim: 2 times
walk: 0 times
climb: 0 times
If i go to Peru comments:
run: 0 times
swim: 1 time
walk: 0 times
climb: 0 times
Thank you in advance.
First, get the tables all linked up via the joins showing how each table is related to the next by their respective keys. Then, its just a matter of each name (city/location), and name of the activity and getting a count grouped by the respective location and activity.
The order by clause will put all same locations first, then within each location will put the highest count activity secondary... if any have same counts, then the names will be alpha ordered
SELECT
p.name,
a.name as Activity,
COUNT(*) as NumComments
from
place p
join comment c
ON p.id = c.place_id
join activity_comment ac
ON c.id = ac.comment_id
join activity a
ON ac.activity_id = a.id
group by
p.name,
a.name
order by
p.name,
COUNT(*) desc,
a.name
Now, you will just need to plug in your WHERE clause (before the group by) for whatever location or activity you may be interested in.

php mysql Get the latest records based on multiple conditions and count them

I have one table - staff
id | staff Name | adress
-------------------------
1 | Mr.A | Any Address
2 | Mr. B | Any Address
2nd Table - employment_history
eid | staff_id | school_id | type | grade | date_of_appointmet
--------------------------------------------------------------------
1 | 1 | 1 |Promotion | 17 | 2012-12-12
2 | 1 | 2 |promotion | 18 | 2013-2-2
3 | 2 | 2 |appointment | 17 | 2013-3-3
and so on tables moves
Now the Question is that
i want to get the latest job of the person with his details from the staff table
how can i count how many of 17 grade staff works in school_id 1
(remembering that staff_id 1 (mr.a) now have been promoted to 18 and now works in school_id 2.)
select staff_id, max(date_of_appointment) as date_of_appointment
from employment_history
group by staff_id
This query will return the most recent staff record for each staff_id. Turn it into a subquery and join onto the employment history table
Select grade, count(1)
from
(select staff_id, max(date_of_appointment) as date_of_appointment
from employment_history
group by staff_id) a
inner join employment_history e on e.staff_id = a.staff_id and a.date_of_appointment = e.date_of_appointment
group by grade
This solution makes the assumption that staff_id + date_of_appointment is a unique key...if you have multiple rows where one staff_id has multiple employment history entries for one date, this won't work. You need some logic to make the 'most recent employment history entry' return a unique combination of data...if staff_id + max(date_of_appointment) is not unique, you'll need to come up with logic in the 'a' subquery that returns unique data.
What about something like
SELECT *
FROM employment_history eh1
WHERE eh1.date_of_employment = (
SELECT max(eh2.date_of_employment)
FROM staff s
JOIN employment_history eh2 ON s.id = eh2.staff_id
WHERE s.id = ?
)
Recplacing the ?, or using bind_param() as appropriate.

PHP Propel ORM MySQL - Left Join on Many to Many

I have two tables one called meeting and one called attendance, attendance is a many to many relational database in the following format:
Attendance:
user_id | meeting_id | invited
--------+------------+--------
1 | 5 | 1
2 | 5 | 0
3 | 4 | 0
3 | 5 | 1
3 | 6 | 0
Meetings are in the following format:
Meetings:
meeting_id | meeting_name | owner_id
-----------+--------------+----------
3 | Awesome | 2
4 | Boring | 2
5 | Cool | 5
9 | Sexy | 3
There can only be one meeting row per meeting, but unlimited attendance rows per meeting (limited to for every possible user for every meeting).
How in SQL and/or Propel do I create something that would list all meetings where the (provided) user_id is either the owner_id in meetings OR were the user_id and invited in the attendance database.
I am looking for a result (based on the above data) when searching for userid 3 of:
Result for userid3:
meeting_id | meeting_name | owner_id
-----------+--------------+----------
5 | Cool | 5 - Because userid 3 is attending meeting 5
9 | Sexy | 3 - Because userid 3 owns meeting 9
I currently have the following which doesn't work really, and produces multiple rows per meeting (because the meeting exists more than once in the attendance DB).
$criteria->addJoin(MeetingMeetingsPeer::ID, MeetingAttendancePeer::MEETING_ID, Criteria::LEFT_JOIN);
$criterion = $criteria->getNewCriterion(MeetingMeetingsPeer::OWNER_ID, Meeting::getUserId());
$criterion->addOr($criteria->getNewCriterion(MeetingAttendancePeer::USER_ID, Meeting::getUserId()));
$criteria->add($criterion);
return $criteria;
Which is something like the below in SQL:
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME, meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH, meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT FROM `meeting_meetings` LEFT JOIN meeting_attendance ON (meeting_meetings.ID=meeting_attendance.MEETING_ID) WHERE (meeting_meetings.OWNER_ID=1 OR meeting_attendance.USER_ID=1)
Thanks for your time,
This should get you all meetings that are owned by user_id 1, as well as all meetings that are attended by user_id 1.
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME,
meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH,
meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT
FROM `meeting_meetings`
WHERE `meeting_meetings`.`owner_id` = 1
UNION DISTINCT
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME,
meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH,
meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT
FROM `meeting_meetings`
JOIN `meeting_attendance` ON `meeting_meetings`.`meeting_id` = `meeting_attendance`.`meeting_id` AND `meeting_attendance`.`invited`
WHERE `meeting_attendance`.`user_id` = 1
It's a bit clunky. Personally, I would consider adding an owner flag to the meetings_attendance table.

Categories