SQL Divide Column Value By Another Column - mysql

First off, please excuse my formatting. I'm new here and this is my first posting.
I would like to take the value of a column (costs) split by invoice. For example, my table currently looks like this, where worker is the worker who worked on the job, inv is the invoice number, costs is the total costs for the invoice, and amount is the amount of work each worker did on that invoice. I would like to, at the end of this, be able to sum the worker amounts and cost amounts to come up with the invoice total:
Worker | Inv | Costs | Amount
----A--- |---1 | 12 -----| 50
----B--- |---1 | 12 -----| 10
----C--- |---1 | 12 -----| 40
----A----|---2 | 1 ------| 10
----B----|---2 | 1 ------| 10
and I would like it to look like this:
Worker | Inv | Costs | Amount
----A--- |---1 | 4 -------| 50
----B--- |---1 | 4 -------| 10
----C--- |---1 | 4 -------| 40
----A----|---2 | .5 ------| 10
----B----|---2 | .5 ------| 10
The end result after I throw this into a pivot table in Excel would show that Invoice1 is for a total of $112 and the total for Invoice2 is $21

This one sums the cost and amount for each invoice:
SELECT inv, SUM(Cost+Amount)
FROM MyTable
GROUP BY inv
The result is $112 for Invoice 1 and $21 for Invoice 2

select inv,count(1) as workercount
from tbl
group by inv
This query will give the count of the number of invoices, workercount will contain the number you want to divide by. We will use it as a subquery
select worker, inv, costs/invcount.workercount as costs, amount
from tbl
inner join
(select inv,count(1) as workercount
from tbl
group by inv) invcount
on tbl.inv = invcount.inv
pivot results from there. You can calc the total invoice amount via SQL instead of the excel spreadsheet you want here.

Related

MySQL: Count of Maximum Occurrences of Distinct Value

Im having a problem with an aggregate function in mysql.
As an example I have this table layout. It gets filled with data every x minutes.
Company | Employee | Room | Temperature
---------------------------------------
A | Mike | 301 | 20
A | Mike | 301 | 30
A | Mike | 301 | 30
A | Mike | 402 | 40
A | Lisa | 402 | 10
Now in my query I'm grouping Company + Employee into one result and I'm looking for the count of the maximum occurrences of the Room value while still aggregating other values like temperature.
SELECT
Company,
Employee,
??? as Room,
AVG(Temperature) as Temperature
FROM
example_table
GROUP BY
Company,
Employee
In this example the room 301 appears 3 times for Mike which should output 3 in the aggregate function. Since the data is on a set interval it is basically the maximum length of a stay in a room for this employee. I'm looking for a result like this
Company | Employee | Room | Temperature
---------------------------------------
A | Mike | 3 | 30
A | Lisa | 1 | 10
I feel like I'm missing something but so far I have found nothing which worked in a query for me. I can group_concant the rooms and solve this with 2 lines of code in php, but the actual data is gigabytes which I don't want to send to a script. Performance of the database query doesn't matter. MySQL 8 is available.
edit: I've changed the example to make sure COUNT(DISTINCT Room) doesn't accidentally give the correct result, because it's not what I'm looking for.
SELECT Company, Employee
, MAX(roomOccurrence) AS Room
, AVG(roomTemp * roomOccurrence) AS Temperature
FROM ( SELECT Company, Employee, Room
, COUNT(*) AS roomOccurrence, AVG(Temperature) AS roomTemp
FROM example_table
GROUP BY Company, Employee, Room
) AS subQ
GROUP BY Company, Employee
;
Note the outer temperature average weights the temperature average from the inner query.
Alternatively, you could SUM the temps in the subquery...and then divide the SUM of the SUM by the SUM of the room COUNT; but's it should be the same either way. I would at best expect minor performance differences, and not sure if either way would be consistently faster.

select query performs wrong results on parallel execution

Following is my scenario
I have tables named
Products
id | name | count | Price
-------------------------
1 | meat | 1 | 10
Users
id | name | balance
-----------------
1 | Tim | 10
2 | Joe | 10
Work flow
select products if count >= 1,
reduce user's balance and count = count - 1
if no_balance or count < 1 throw error
Let's say if both users placing an order for 1 product at exact same time, products table count updates to -1, means query executes for both users.
Products
id | name | count | Price
-------------------------
1 | meat | -1 | 10
During placeing of an order,I have used the below query to select matching products
Select * from products where count >= 1 and price >= 10
Also, if users place orders with even little time difference, the expecting output gathered.
Is there any solution to this ?
You should consider use lock for each row, for example.
Select * from products where count >= 1 and price >= 10 FOR UPDATE.
But in your scenario, I advice you use Redis to do that.
How to design a second kill system for online shop

How to write a sql show the quantity result based on filter and merge two different column if no filtering option?

Table
|---------------------|------------------|------------------|
| ID | qty_ordered | qty_received |
|---------------------|------------------|------------------|
| 10 | 30 | |
|---------------------|------------------|------------------|
| 10 | 30 | 20 |
|---------------------|------------------|------------------|
I have a filter to let user choose whether to show qty_ordered or qty_received. When user select filter for qty_ordered then result will be 30 else if select qty_received then result will be 20 else will return 50.
SQL
select sum(qty_ordered) as order, sum(qty_received) as received from table...
Currently i will select both, and using if-else statement in my html to show the result of quantity. If filtering by qty_ordered and qty_received will be fine, but problem occurred when need to display all quantity. Mean, i need to merge ordered quantity and received quantity with same id as one row.
|---------------------|------------------|
| ID | quantity |
|---------------------|------------------|
| 10 | 50 |
|---------------------|------------------|
ps: Ordered quantity will only be sum up if there is no received quantity.
You can try aggregating by the ID and using SUM to obtain single values for the quantity ordered and received:
SELECT
ID,
SUM(qty_ordered) AS qty_ordered,
SUM(qty_received) AS qty_received,
SUM(COALESCE(qty_received, qty_ordered)) AS qty_total
FROM yourTable
GROUP BY ID;
ID qty_ordered qty_received qty_total
1 10 60 20 50
I use a COALESCE trick in the expression for the total sum. The logic is that if the quantity received is present, we count it, even if the quantity ordered might also be present. If the quantity received be absent, only then do we count the quantity ordered.
Demo

Select dates between date range and sum over a column

I have the following tables:
Let's assume the first table represents a booking made by a customer for a hotel.
+------------+-----------+-----------+--------------+----------------+
| booking_id | client_id | room_type | arrival_date | departure_date |
+------------+-----------+-----------+--------------+----------------+
| 1 | 1 | 1 | 2016-05-30 | 2016-06-03 |
+------------+-----------+-----------+--------------+----------------+
The second table contains the prices for different types of room. The prices vary for different periods of time.
+---------------+-----------+------------+------------+-------+
| room_price_id | room_type | start_date | end_date | price |
+---------------+-----------+------------+------------+-------+
| 1 | 1 | 2016-03-01 | 2016-05-31 | 150 |
| 2 | 1 | 2016-06-01 | 2016-08-31 | 200 |
+---------------+-----------+------------+------------+-------+
My question is: how can I calculate the total cost for a booking if the price is not the same during the whole period of the booking? (in my case the total cost would be 700)
I read the following links: MySQL: Select all dates between date range and get table data matching dates and Select data from date range between two dates, but I don't get it on how to solve my problem since the entries in the table with the prices contain only date ranges and not explicit dates as in the first link.
The trick with your question is in formulating the join condition between the booking and prices tables. Once this is out of the way, a simple aggregation gets you the result you want. The join condition should be true whenever any point in a given price range falls within the range of the arrival or departure date.
Then, to count days we use the greater of the arrival date or the start date, and the lesser of the departure date or the end date.
SELECT
b.booking_id,
SUM((DATEDIFF(LEAST(b.departure_date, p.end_date),
GREATEST(b.arrival_date, p.start_date)) + 1) * p.price) AS total
FROM booking b
INNER JOIN prices p
ON b.room_type = p.room_type AND
(p.start_date BETWEEN b.arrival_date AND b.departure_date OR
p.end_date BETWEEN b.arrival_date AND b.departure_date)
GROUP BY b.booking_id;
The output for your sample data is 900. This is correct because:
2 days x 150 = 300
3 days x 200 = 600
total = 900
Demo here:
SQLFiddle
And here is another demo which highlights the case where a stay overlaps an entire price range.

Is this select subquery avoidable?

I have two tables (Invoices and taxes) in mysql:
Invoices:
- id
- account_id
- issued_at
- total
- gross_amount
- country
Taxes:
- id
- invoice_id
- tax_name
- tax_rate
- taxable_amount
- tax_amount
I'm trying to retrive a report like this
rep_month | country | total_amount | tax_name | tax_rate(%) | taxable_amount | tax_amount
--------------------------------------------------------------------------------------
2017-01-01 | ES | 1000 | TAX1 | 21 | 700 | 147
2017-01-01 | ES | 1000 | TAX2 | -15 | 700 | 105
2016-12-01 | FR | 100 | TAX4 | 20 | 30 | 6
2016-12-01 | FR | 100 | B2B | 0 | 70 | 0
2017-01-01 | GB | 2500 | TAX3 | 20 | 1000 | 200
The idea behind this is that an invoice has a has_many relation with taxes. So an invoice can have or not taxes. The report should show the total amount collected (total_amount) for a given country (regardess if it includes taxes)
and indicate which part of that total amount is taxable (taxable_amount) for an specific tax.
My current approach is this one:
SELECT
DATE_FORMAT(invoices.issued_at, '%Y-%m-01') AS rep_month,
invoices.country AS country
( SELECT sum(docs.gross_amount)
FROM invoices AS docs
WHERE docs.country = invoices.country
AND DATE_FORMAT(docs.issue_date, '%Y-%m-01') = rep_month
) AS total_amount,
taxes.tax_name AS tax_name,
taxes.tax_rate AS tax_rate,
SUM(taxes.taxable_amount) AS taxable_amount,
SUM(taxes.tax_amount) AS tax_amount
FROM invoices
JOIN taxes ON invoices.id = taxes.document_id
AND documents.issue_date BETWEEN '2016-01-01' AND '2017-12-31'
GROUP BY account_id, rep_month, country, tax_name, tax_rate
ORDER BY country desc
Well, this works but for a real dataset (thousands of records) it's really slow as the select subquery for retrieving the total_amount is being run for each row of the report.
I cannot make a LEFT JOIN taxes with a direct SUM(gross_amount) as the GROUP BY groups by tax name and rate and I need to show the total collected per country regardless if the amount was taxed or not. Is there a faster alternative to this?
I do not know the exact use case of using this query but the issue is the way with which you're trying to structure the DB, you're trying to get the entire data in one go.
Ideally, you should run the query you have and store in a different table (summary table) and then query directly from the summary table whenever you want. And if you have a new entry in the Invoices table then you can use the query to run either on every entry or periodically update the summary table via a cronjob.