Using a Join with Grouped Data Rows - mysql

I have two tables, invoices and deposits.
They look a bit like this:
INVOICES
id | paymentType | grossTotal | dateTime
1 | Cash | 1000 | UNIX TIME
2 | Card | 1350 | UNIX TIME
3 | Card | 1250 | UNIX TIME
4 | Card | 750 | UNIX TIME
DEPOSITS
id | paymentType | invNo | dateTime | amount
1 | Cash | 1 | UNIX TIME | 150
2 | Card | 2 | UNIX TIME | 350
The deposits are always past dates, and the invoice dates will be, for example today, so I want to determine the balance paid today on an invoice, ie, invoices.grossTotal - deposits.amount, and list by payment Type.
So, in the table example above, there is £850 would have been paid on Invoice 1 and £1000 on invoice 2, this is simple to achieve with one or two rows of each, but when grouping payment types and deposit invoices I am stuck...
SELECT
invoices.id,
sum(grossTotal)-IFNULL(depositsCheck.previouslyPaid,0) as todayTotal,
depositsCheck.previouslyPaid, sum(grossTotal) as grossTotal
FROM `invoices`
LEFT JOIN (SELECT SUM(amount) as previouslyPaid, invNo
FROM deposits
GROUP BY invNo) depositsCheck ON depositsCheck.invNo=invoices.id
GROUP BY invoices.paymentType ORDER BY id DESC
The SQL Query above, will work for the item paid for with CASH, but not for the Card payments, because, grouping invoices.paymentType means that the id column from the invoices table is no longer correct, so the JOIN fails if this row has an id to which no deposit relates.
How can I run a query as above, but ensuring that I can join the deposits table on any instance of an invoice, grouped by payment type that matches the grouped column id records?
I am using mySql, so please post joins that MySql can do! :D

The problem is that when you use GROUP BY, you can only SELECT aggregates and the columns you have grouped on.
invoices.id is a column you tried to select, but did not group. I think you probably want to add this column to the GROUP BY clause.
SELECT
invoices.id,
sum(grossTotal)-IFNULL(depositsCheck.previouslyPaid,0) as todayTotal,
depositsCheck.previouslyPaid, sum(grossTotal) as grossTotal
FROM `invoices`
LEFT JOIN (SELECT SUM(amount) as previouslyPaid, invNo
FROM deposits
GROUP BY invNo) depositsCheck ON depositsCheck.invNo=invoices.id
GROUP BY invoices.paymentType, invoices.id ORDER BY id DESC
For the example tables you gave, it will probably give:
id | paymentType | grossTotal | dateTime | previouslyPaid
1 | Cash | 1000 | UNIX TIME | 150
2 | Card | 1350 | UNIX TIME | 350
3 | Card | 1250 | UNIX TIME | 0
4 | Card | 750 | UNIX TIME | 0
But in general, you will have something like:
id | paymentType | grossTotal | dateTime | previouslyPaid
1 | Cash | 1000 | UNIX TIME | 150
1 | Card | 1000 | UNIX TIME | 300
2 | Cash | 1350 | UNIX TIME | 350
2 | Card | 1350 | UNIX TIME | 350
Where you can see above, for invoice 1, 150 was paid in cash, and 300 was paid by card.

Related

MySQL: Create running sum for all dates in range?

I have a table Service, that stores records of sales and referral types that led to the sale. I need to know the total number of sales that resulted from a given referral type over a range of dates. The relevant data in Service looks like the following:
+------+-------+------------+
| uuid | sr_id | s_saledate |
+------+-------+------------+
| | 1 | 2020-01-01 |
| | 1 | 2020-01-01 |
| | 2 | 2021-01-01 |
| | 2 | 2021-01-01 |
| | 1 | 2021-01-01 |
+------+-------+------------+
I want to count the number of sales for each referral type (sr_id) in a given date range.
If my date range is 2020-01-01 thru 2021-01-01, my output should be something like:
+------+-------+------------+----------------------+
| uuid | sr_id | date | num_sales_as_of_date
+------+-------+-----------------------------------+
| | 1 | 2020-01-01 | 2 |
| | 1 | 2020-01-02 | 2 |
| | 1 | 2020-01-03 | 2 |
........................................................ < many rows for days in range
1 2021-01-01 | 3
| | 2 | 2020-01-01 | 0 |
| | 2 | 2020-01-02 | 0 |
........................................................ < many rows for days in range
| | 2 | 2020-01-01 | 2 |
+------+-------+-----------------------------------+
There should be a row for each referral type on each date in the range.
Right now my query looks like:
SELECT s.sr_id,
s.s_saledate AS date,
Count(s.uuid)
OVER (
partition BY s.sr_id
ORDER BY s.s_saledate) AS num_sales_as_of_date
FROM Service s
How do I get the running sum for each referral type on days that had no Service with that particular referral type id?
*** EDIT FOR CLARIFICATION***
For example, in the first table I give there is no Service row in the Service table with sr_id = '1' AND s_saledate === "2020-01-02". There were two rows from prior days where sr_id = '1'. (2020-01-01). My output row for "2020-01-02" is:
sr_id date num_sales_as_of_date
1 | 2020-01-02 | 2 |
You need to left join your Services table from a table with all the dates in the range and a table with all the referral types, so that you get a row with every combination of date and referral type:
WITH RECURSIVE dates AS (
SELECT date('2020-01-01') AS date
UNION ALL
SELECT dates.date + INTERVAL 1 DAY
FROM dates
WHERE dates.date <= '2020-01-05'
)
SELECT ServiceReferral.sr_id,
dates.date,
Count(s.uuid)
OVER (
partition BY ServiceReferral.sr_id
ORDER BY dates.date) AS num_sales_as_of_date
FROM dates
CROSS JOIN ServiceReferral
LEFT JOIN Service s ON s.s_saledate=dates.date AND s.sr_id=ServiceReferral.sr_id
fiddle
If you do this a lot, it may be more convenient to create an actual table dates with all the dates from 0000-01-01 to 9999-12-31 and use that instead (selecting dates in the desired range in the where clause).

Finding Sum of amount per weekday per month MySQL

I have a task to find the total amount of money paid during weekdays vs weekends.
So first I have to separate the weekdays from the weekends then find the toal amount of money paid on each, per month.
The tables I am using have the following structure
Game_users table
game_id | user_id |bill_id| created_at | deleted_at
1234 | 244 | 456 |2016-10-08 14:47:11 | null
Bills Table
bill_id | user_id |amount | created_at | deleted_at
1234 | 244 | 100 |2016-10-08 14:47:11 | null
So, I have to sum(amount) and then separate them into weekdays and weekends per month
So group by month(created_at)
I need a query to achieve this expected result
Month | count(weekdays) | Total Amount Paid
1 | 34 | 500.0

mysql - Calculating profit of sales between dates

I have a table that looks like this:
+--------+---------------------+-------+--------+-----------+
| PartNo | Date | Inv | Retail | Wholesale |
+--------+---------------------+-------+--------+-----------+
| 1 | 2018-05-12 00:00:00 | 15 | $100 | $90 |
| 2 | 2018-05-12 00:00:00 | 20 | $200 | $150 |
| 3 | 2018-05-12 00:00:00 | 25 | $300 | $200 |
| 1 | 2018-05-13 00:00:00 | 10 | $95 | $90 |
| 2 | 2018-05-14 00:00:00 | 15 | $200 | $150 |
| 3 | 2018-05-14 00:00:00 | 20 | $300 | $200 |
+--------+---------------------+-------+--------+-----------+
And I want it to look like this with a Mysql query:
+--------+------+--------+
| PartNo | Sold | Profit |
+--------+------+--------+
| 1 | 5 | $25 |
| 2 | 5 | $250 |
| 3 | 5 | $500 |
+--------+------+--------+
I need to group by PartNo while calculating the difference between totals and profits over a date range.
The unit profit has to be calculated by subtracting the wholesale from retail on the last day (or record) of the date range.
I feel like this should be easy but the differences over the date ranges are confusing me and handling records within the date range that don't start or end exactly on the date range input are losing me.
Any help would be super appreciated.
Thank you.
You can look up the situation at the start and at the end of the period If no start situation is found, assume no stock. If no end situation is found, that means no sales during the period.
For example for the period starting 2018-05-13 and ending 2018-05-14:
select parts.PartNo
, coalesce(FirstSale.Total, 0) - coalesce(LastSale.Total, FirstSale.Total, 0) as Sold
, (coalesce(FirstSale.Total, 0) - coalesce(LastSale.Total, FirstSale.Total, 0)) *
coalesce(LastSale.Retail - LastSale.Wholesale, 0) as Profit
from (
select PartNo
, max(case when Date < '2018-05-13' then Date end) as FirstEntry
, max(case when Date <= '2018-05-14' then Date end) as LastEntry
from Sales
group by
PartNo
) parts
left join
Sales FirstSale
on FirstSale.PartNo = parts.PartNo
and FirstSale.Date = parts.FirstEntry
left join
Sales LastSale
on LastSale.PartNo = parts.PartNo
and LastSale.Date = parts.LastEntry
Example at SQL Fiddle.
SELECT c.partno as partno,MAX(c.inv)-MIN(c.inv) as sold,SUM(CASE WHEN c.date = c.last_date THEN profit else 0 END)*(MAX(c.inv)-MIN(c.inv)) as profit
FROM (SELECT partno,date,inv,retail-wholesale as profit,MAX(date) OVER (partition by partno) AS last_date FROM test1)c
GROUP BY c.partno
ORDER BY c.partno;
Using the window function, first append a new column to track the max date for each partno. So the inner query inside FROM will produce rows like these with one column added to the the original dataset,
| 1 | 2018-05-12 00:00:00 | 15 | $100 | $90 | **2018-05-13 00:00:00** |
The highlighted field is the one added to the dataset which is the last date in the date range for that part number!
Now from this result, we can pull out profit by checking for the row in which date column is equal to the new column we appended, which is essentially calculating the profit for the last date by subtracting wholesale from retail and multiplying with items sold.
PS : The logic for items sold is grouping by partno and subtracting MIN(Inv) from MAX(Inv)
Link to SQL Fiddle

Aggregate calculation query on single SQL table

In my database I have a table, payers_payments, consisting of some foreign keys (payer_id, payment_id) and some pivot fields (amount, pays).
| id | payer_id | payment_id | amount | pays |
|----|----------|------------|--------|------|
| 1 | 1 | 1 | 20 | 0 |
| 2 | 2 | 1 | 23 | 1 |
| 4 | 1 | 2 | 14 | 1 |
| 5 | 2 | 2 | 17 | 1 |
| 6 | 1 | 3 | 10 | 1 |
| 7 | 2 | 3 | 0 | 0 |
Each row represents a contribution a payer made towards a payment. The pays field specifies whether that payer should be included in the payment.
The total cost of a payment is the sum of amount for a given payment_id. So if I wanted to see how much payment 2 cost I would run:
SELECT SUM(amount) AS sumOfPayment
FROM payers_payments
WHERE payment_id=2;
Likewise the total amount a given payer (say, payer 1) has contributed is:
SELECT SUM(amount) AS sumOfPayment
FROM payers_payments
WHERE payer_id=1;
Now, what I want to do is use the concept of fair share. Fair share is the total cost of the payment divided by the number of payers who have pay=1 for that payment. The simplest way I can express this is with a sub-query:
SELECT SUM(payers_payments.amount) / (SELECT count(*)
FROM payers_payments
WHERE pays
AND payers_payments.payment_id = 3
) AS FairShare
FROM payers_payments
WHERE payment_id=3
GROUP BY
payers_payments.payment_id;
For a given payer and payment their fair share is defined as:
SELECT IF(pays, FairShare, 0) as payerFairShare
FROM payers_payments
WHERE payer_id = 1
AND payment_id=3; --FairShare is the query as above
My question is that I want a query to get the total fair share for each payer based on the fair share of each payment and whether or not they are included in the payment. (If pays=false then their fair share is 0)
Based on the above data this is the kind of result I'm after for the 2 payers:
| payer_id | total_paid | fair_share |
|----------|------------|------------|
| 1 | 44 | 25.5 |
| 2 | 40 | 58.5 |
Is there a way to achieve this in a single query or must I do some looping of result sets?
I am agnostic on RDMS but something MySQL-like is good.
I would start by writing a query that works what a single share of a payment is. That is, per payment_id, the sum of all the amounts, divided by the number of people it needs to pay. That result can then be joined back to the original data.
SELECT
payers_payments.payer_id,
SUM(payers_payments.amount ) AS total_paid,
SUM(payers_payments.pays * payments.single_share) AS fair_share
FROM
payers_payments
INNER JOIN
(
SELECT
payment_id,
SUM(amount) / SUM(pays) AS single_share
FROM
payers_payments
GROUP BY
payment_id
)
AS payments
ON payers_payments.payment_id = payments.payment_id
GROUP BY
payers_payments.payer_id
It will be of benefit to have indexes on both (payment_id) and (payer_id).
It will be of benefit to have the amount field in a DECIMAL data-type, though you need to consider what you want to do with rounding. (A total payment of 10.00 needs to be divided three ways, 3.33 each and then what do you want to happen to the spare 0.01?)

constructing a query for the following table

can anyone generate a query for me.
Lets say i have a table sales(saleID, date_of_sales, customerID, itemID, saleprice)
date_of_sales is the datetime field which stores the time of the sale.
customerID is self exlpaining tells to whom item was sold.
itemID is ID of the item sold.
saleprice is the price that the item was sold.
I want to construct a query which will give out the detail of the last purchase by each customers. this could be done by using date_of_sales.
Example table
saleID | date_of_sales | customerID | itemID | saleprice
101 | 2008-01-01 | C2000 | I200 | 650 |
102 | 2010-01-01 | C2000 | I333 | 200 |
103 | 2007-01-01 | C3333 | I111 | 800 |
104 | 2009-12-12 | C3333 | I222 | 100 |
this is the example data table, there are only two customer for simplicity.
customer C2000 did his last purchase
on 2010-01-01
customer C3333 did his last purchase
on 2009-12-12
I want to get a result like this
customerID | date_of_sales | itemID | saleprice
C2000 | 2010-01-01 | I333 | 200 |
C3333 | 2009-12-12 | I222 | 100 |
This might be what you are looking for...
SELECT *
FROM sales
WHERE sales.date_of_sales = (SELECT MAX(date_of_sales)
FROM sales s2
WHERE s2.customerID = sales.customerID);
There is a slight problem with it; if there were two sales on the same day to the same customer, you'll get two rows (unless your date-of-sales column includes the time as well). I think the same applies to the answer above, though.
Additionally, if you DO want to get results based on only a SINGLE entry of the maximum date, I would use the query by #Sachin Shanbhag above, but add a maximum sales ID value too... Since that would be implied as sequential, whichever was entered last would probably be the most recent.
SELECT S.* FROM
sales S
INNER JOIN
( SELECT
customerID,
MAX(date_of_sales) dos,
MAX(SalesID) maxSale
FROM
sales
GROUP BY customerID
) S2 ON S.customerID = S2.customerID
AND S.date_of_sales = S2.dos
AND S.SalesID = S2.maxSale