Calculate the amount payable to each user - mysql

I want to calculate the amount payable to each user.
This may be in the negative. Briefly:
MustPay = AmountTaken - AmountPaid
I could not write the sql query.
SELECT users.Name, users.Surname,
SUM(takenfrom.AmountTaken) - SUM(paid.AmountPaid) AS MustPay
FROM users
LEFT JOIN takenfrom ON takenfrom.UserId = users.UserId
LEFT JOIN paid ON paid.UserId = users.UserId
GROUP BY users.UserId
Tables:
FIRST TABLE USERS
| UserId | Name | Surname |
| 1 | foo | boo |
| 2 | f | b |
SECOND TABLE TAKENFROM
| TakenFromId | UserId | AmountTaken|
| 1 | 1 | 100 |
| 2 | 2 | 200 |
THIRD TABLE PAID
| PaidId | UserId | AmountPaid|
| 1 | 2 | 50 |
| 2 | 2 | 50 |
RESULT TABLE
| Name | Surname| MustPay |
| foo | boo | 100 |
| f | b | 100 |

You don't need a LEFT JOIN on TakenFrom as every user is going to have an amount billed to them, whether or not they've paid is what Paid is for. You only need a LEFT JOIN on Paid, as user may have paid already or they may have not.
Since not every user is going to have an AmountPaid, so you need to use an IFNULL() to check that. SUM() returns NULL if it's given a NULL.
Also, unless there are multiple rows for each user in takenfrom, then you don't need a SUM() for AmountTaken.
SELECT users.Name, users.Surname,
SUM(takenfrom.AmountTaken) - IFNULL(SUM(paid.AmountPaid), 0) AS MustPay
FROM users
JOIN takenfrom ON takenfrom.UserId = users.UserId
LEFT JOIN paid ON paid.UserId = users.UserId
GROUP BY users.UserId
DEMO: http://sqlfiddle.com/#!9/e69b3b/1
UPDATE: If both paid and takenfrom have multiple rows (for a UserId), then you'll get duplicate rows from the JOINs. To fix this, you can use subqueries instead of JOIN:
SELECT Name, Surname,IFNULL((
SELECT SUM(AmountTaken) FROM takenfrom WHERE UserID = users.UserID
), 0) - IFNULL((
SELECT SUM(AmountPaid) FROM paid WHERE UserID = users.UserID
), 0) AS MustPay
FROM users
DEMO: http://sqlfiddle.com/#!9/3960b5/26

Related

COUNT all values in a column with JOIN

I am joining three tables and need to return two separate counts, one showing the total number of unique users who have purchased an item, and the other showing the total number of unique users who haven't purchased an item. These are cropped for brevity, but here are the relevant tables:
user table
+----------+------+------+-----+
| username | colb | colc | etc |
+----------+------+------+-----+
| user1 | * | * | * |
| user2 | * | * | * |
| user3 | * | * | * |
+----------+------+------+-----+
purchase table
+------------+---------+----------+------+
| purchaseID | storeID | username | cost |
+------------+---------+----------+------+
| 1 | 1 | user1 | * |
| 2 | 1 | user2 | * |
| 3 | 5 | user2 | * |
| 4 | 3 | user1 | * |
+------------+---------+----------+------+
store table
+---------+-----------+-----+
| storeID | storeName | etc |
+---------+-----------+-----+
| 1 | store1 | * |
| 2 | store2 | * |
| 3 | store3 | * |
+---------+-----------+-----+
I am currently using this query to get the unique users who have purchased an item from a store:
SELECT
store.storeID storeID,
store.storeName storeName,
COUNT(DISTINCT CASE WHEN purchase.username IS NOT NULL
THEN purchase.purchaseID END) AS purchases
[Query to retrieve total unique users who have not purchased an item]
FROM store
LEFT JOIN purchase
ON store.storeID = purchase.storeID
LEFT JOIN user
ON purchase.username = user.username
GROUP BY 1, 2
I have tried a few different ways, none of which have worked. The issue I've identified is when the LEFT JOIN happens it only returns the matching results for usernames, thus the COUNT won't include the other users in the user table. I have not had any luck finding a way to fix this, so I'm hoping someone on here can lend me a hand. The results I'm hoping to see should be something like this:
+---------+-----------+-----------+--------------+
| storeID | storeName | purchases | nonPurchases |
+---------+-----------+-----------+--------------+
| 1 | store1 | 2 | 1 |
| 2 | store2 | 0 | 3 |
| 3 | store3 | 1 | 2 |
+---------+-----------+-----------+--------------+
that is actually quite simple.
First you count all user and subtract te count of distinct purchasers
SELECT
store.storeID storeID,
store.storeName storeName,
COUNT(DISTINCT CASE WHEN purchase.username IS NOT NULL
THEN purchase.purchaseID END) AS purchases,
(SELECT COUNT(*) FROM User) - COUNT(DISTINCT CASE WHEN purchase.username IS NOT NULL
THEN purchase.purchaseID END) AS NON_purchases
FROM store
LEFT JOIN purchase
ON store.storeID = purchase.storeID
LEFT JOIN user
ON purchase.username = user.username
GROUP BY 1, 2
Here is a clean solution.
Please note that the aggregation is done before the join.
with
purchases as
(
select storeID
,count(distinct username) as purchase
from purchase
group by storeID
),
users as
(
select count(*) as total_users
from user
)
select storeID
,storeName
,coalesce(purchase, 0) as purchase
,total_users - coalesce(purchase, 0) as nonPurchases
from store
left join purchases using (storeID)
cross join users
storeID
storeName
purchase
nonPurchases
1
store1
2
1
2
store2
0
3
3
store3
1
2
Fiddle
I'll go with a slightly different approach.
Generate a combination of store and user using CROSS JOIN, make it as a subquery then use that to LEFT JOIN with purchase table. In SELECT, change COUNT(DISTINCT ..) to SUM(..). Something like this:
SELECT us.storeID,
us.storeName,
SUM(CASE WHEN p.username IS NOT NULL
THEN 1 ELSE 0 END) AS purchases,
SUM(CASE WHEN p.username IS NULL
THEN 1 ELSE 0 END) AS nonPurchases
FROM (SELECT storeID, storeName, username FROM user u CROSS JOIN store s) us
LEFT JOIN (SELECT DISTINCT storeid, username FROM purchase) p
ON us.storeID = p.storeID
AND us.username=p.username
GROUP BY 1, 2;
Thanks to David pointing out in the comment that my previous suggestion is not exactly counting unique users. So I made a quick modification to make sure that it does what OP wanted in the first place. Therefore I did a SELECT DISTINCT ... on purchase table then make it as a subquery for the LEFT JOIN. The other parts of the original suggestion remains.
Updated fiddle

Displaying Latest Record and Join Multiple table

how do I join multiple tables and displaying each users sold item, display the latest record who sold the items
I need output like this
Sold by:
"jon" item "#1" "book" with a price of "1000"
tried :
SELECT uid , users.name AS uname, transact.transaction_id AS transacted INNER JOIN users on transaction_table.c_id=c_table.c_id
User table
--------------------------
| uid | name | timezone |
--------------------------
| 1 | jon | +1 gmt |
| 2 | mix | +2 gmt |
| 3 | vic | +1 gmt |
--------------------------
transaction table
-------------------------------
| transaction_id | uid | c_id |
-------------------------------
| dafsf22sdfssgs | 2 | 1 |
| 23425asda3afaa | 1 | 1 |
-------------------------------
C-table
------------------------
| c_id | c_name | price |
------------------------
| 1 | book | 1000 |
| 2 | comic | 100 |
| 3 | notes | 10 |
-------------------------
If you want to group by item name and get the total
select u.name,count(*) as count, c.c_name, c.price*count(*) as totalPrice from user u
inner join transaction t on u.uid=t.uid
inner join ctable c on c.c_id=t.c_id
group by c.c_name
If you want to query all the transactions
select u.name, c.c_name, c.price from user u
inner join transaction t on u.uid=t.uid
inner join ctable c on c.c_id=t.c_id
If you just want to return the last transaction info
select u.name, c.c_name, c.price from user u
inner join transaction t on u.uid=t.uid
inner join ctable c on c.c_id=t.c_id
order by t.transaction_id desc limit 1
And one more thing. It is a much much more better practice if your field names are consistent.

How to count rows in nested tables with one SQL query?

I have three tables. Each User can have multiple Subscriptions and each Subscription can have multiple Payments.
Me goal is to count all Payments for a single User using one SQL query. Is it possible to do and how?
In the case below, The result for a User with id 1 should be 2 (because the User has two Payments)
Users
+----+------+
| Id | Name |
+----+------+
| 1 | John |
+----+------+
Subscriptions
+----+--------+-----------+
| Id | userId | data |
+----+--------+-----------+
| 1 | 1 | some data |
+----+--------+-----------+
| 2 | 1 | some data |
+----+--------+-----------+
Payments
+----+----------------+--------+
| Id | subscriptionId | amount |
+----+----------------+--------+
| 1 | 1 | 30 |
+----+----------------+--------+
| 2 | 2 | 50 |
+----+----------------+--------+
try like below by using join and aggregation
SELECT u.id, u.Name, COUNT(p.id) AS numberofpayment
FROM users u
Left JOIN Subscriptions s ON u.Id=s.userId
Left JOIN Payments p ON s.id=p.subscriptionId
GROUP BY u.id, u.Name
You can try to do something like this:
SELECT COUNT(p.Id) AS PaymentCount
FROM Users u
LEFT JOIN Subscriptions s ON u.Id=s.userId
LEFT JOIN Payments p ON s.id=p.subscriptionId
WHERE u.Id = #yourUserID
Pay attention on COUNT(p.Id) - it means count of existing payments.
PS: this answer for #Kickstart.

MySQL GroupBy with null/zero results

I'm currently writing a ticket system that has three tables
one for users:
users
+----+-----------+----------+
| ID | FirstName | LastName |
+----+-----------+----------+
| 1 | First | User |
| 2 | Second | User |
| 3 | Third | User |
| 4 | Fourth | User |
| 5 | Fifth | User |
+----+-----------+----------+
one for tickets:
ticket
+----+---------------+
| ID | TicketSubject |
+----+---------------+
| 1 | Ticket #1 |
| 2 | Ticket #2 |
| 3 | Ticket #3 |
| 4 | Ticket #4 |
+----+---------------+
and one to assign users to tickets to action (can be more than one user per ticket):
ticket_assigned
+----+----------+--------+
| ID | TicketID | UserID |
+----+----------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 5 |
| 5 | 3 | 3 |
+----+----------+--------+
I'm trying to create a summary to show each user, and how many tickets they have assigned to them, example:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fourth | 0 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Note that the last entry is "unassigned", this is the number of records in the ticket table that DONT appear in the ticket_assigned table (thus being, unassigned). Also further note that user "Fourth" is zero, in that that user has no records in the ticket_assigned table.
Here is the current MySQL query I am using:
SELECT
CASE
WHEN users.FirstName IS NULL
THEN 'Unassigned'
ELSE users.FirstName
END as 'UserName',
COUNT(*) as 'TicketCount'
FROM tickets
LEFT OUTER JOIN ticket_assigned ON tickets.ticket_id = ticket_assigned.ticket_id
LEFT OUTER JOIN users ON ticket_assigned.user_id = users.user_id
GROUP BY ticket_assigned.user_id
ORDER BY UserName;
Problem with this is that it's not showing any of the users that don't feature in the ticket_assigned table, I'm essentially getting this:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Is anyone able to assist and tell me how I can modify my query to include users that have no records in the ticket_assigned table? Thanks in advance!
Use a LEFT JOIN with a subquery to aggregate tickets:
SELECT t1.FirstName,
COALESCE(t2.ticket_count, 0) AS num_tickets
FROM users t1
LEFT JOIN
(
SELECT UserID, COUNT(*) AS ticket_count
FROM ticket_assigned
GROUP BY UserID
) t2
ON t1.ID = t2.UserID
UNION ALL
SELECT 'Unassigned', COUNT(*)
FROM tickets t
WHERE NOT EXISTS (SELECT 1 FROM tickets_assigned ta
WHERE ta.ticketId = t.id)
In MySQL, I think you need a left join and union all:
select u.id, u.firstname, count(ta.userId) as num_tickets
from users u left join
tickets_assigned ta
on ta.userId = u.id
group by u.id, u.firstname
union all
select NULL, 'Unassigned', count(*)
from tickets t
where not exists (select 1
from tickets_assigned
where ta.ticketId = t.id
);
I included the u.id in the aggregations. I'm uncomfortable just aggregating (and reporting) by first name, because different people frequently have the same first name, even in a relatively small group.
SELECT
u2.Firstname, IFNULL(tmp.count, 0) AS count
FROM users u2
LEFT JOIN (
SELECT u.id, u.Firstname, COUNT(1) as count
FROM ticket_assigned ta
LEFT JOIN ticket t ON t.id = ta.ticketID
LEFT JOIN users u ON u.id = ta.userID
GROUP BY u.id
) tmp ON tmp.id = u2.id
UNION
SELECT
'Unassigned', count(1) AS count
FROM ticket
WHERE id NOT IN (SELECT ticketid FROM ticket_assigned)

How to select lowest id from multiple id's on joined table

I have small trouble creating a query. I have two tables:
user_data
+----+---------+--------+
| id | mail | etc... |
+----+---------+--------+
| 1 | 1#m.com | ... |
| 2 | 2#m.com | ... |
| 3 | 3#m.com | ... |
+----+---------+--------+
 
contracts
+----+---------+--------+
| id | user_id | etc... |
+----+---------+--------+
| 1 | 1 | ... |
| 2 | 2 | ... |
| 3 | 1 | ... |
| 4 | 1 | ... |
| 5 | 3 | ... |
+----+---------+--------+
As you can see, the first table contains data about users and the secound one about their contracts. There will be always only one entry about a user, but a user can have multiple contracts. Now I need to find out
all users, theirs first contract id ( with the lowest id in contracts table ) and their email, if it's in the were parameters.
So far I have such query:
SELECT
u.id as user_id,
c.id as first_contract_id,
u.mail as email
FROM
user_data u
JOIN
contracts c ON u.id = c.user_id
WHERE
u.mail
IN (
'1#me.com',
'2#me.com',
'3#me.com'
);
Now I have no idea how I can select only the lowest contract ID from these results. Help apreciated.
SELECT
u.id as user_id,
min(c.id) as first_contract_id,
u.Mail as email
FROM
user_data u
JOIN
contracts c ON u.id = c.user_id
WHERE
u.mail IN ('1#me.com', '2#me.com', '3#me.com')
GROUP BY u.id
If you group by the user you can get the lowest contract by using min.
(And MySQL has no problem with selecting column that are not in a group)
select
u.id as user_id,
c.id as first_contract_id,
u.Mail as email
from users as u inner join
(
select min(id) as id,user_id from contracts
group by user_id
) as c
on u.id = c.user_id
WHERE
u.mail
IN (
'1#me.com',
'2#me.com',
'3#me.com'
);