Display result on specified column based on computed result - php

I have 2 db tables: Customer's Information and balances
Customer : Customer_ID, Name
Transaction: Customer_ID, Transaction_Name, Transaction_Amount... (Other columns but not necessary for my question)
Sample expected query output:
Customer_ID | Debit_Balance | Credit_Balance
1 | 500.00|
2 | | 300.00
Explanation: Customer 1 has 500.00 remaining balance and Customer 2 has 300.00 extra amount paid (This is usually (-) negative in nature)
Question:
How to do this in single query alone? I just wanted to generate report (like the above sample output) to display the result amount if positive as Debit_Balance and Credit_Balance for those negative balances then will process the result using Php.
UPDATE:
I used to do this processing of transaction amounts : I Sum up all the Transaction_Amount based on their Transaction_Name.
i.e.
Balance = (All Transaction_Name 'SALES' ) Less (-) (All
Transaction_Name 'PAYMENT')
But this will forced me to create dummy table as repository table for my report and more php condition to compare and INSERT.

I would use something like below:
SELECT C.customer_id, C.Name,
IF(SUM(Transaction_Amount)>0, SUM(Transaction_Amount),'') as Debit_Balance,
IF(SUM(Transaction_Amount)<=0, SUM(Transaction_Amount),'') as Credit_Balance
From Customer As C
LEFT JOIN Transaction As T ON C.customer_id = T.customer_id
GROUP BY T.customer_id

You want a single row of output for each customer, with the balance in either one column or another depending on whether it's positive or negative. Try (if we're using standard SQL):
select
subqry.Customer_ID
, case
when subqry.Balance >= 0 then subqry.Balance
end as Debit_Balance
, case
when subqry.Balance < 0 then subqry.Balance * -1
end as Credit_Balance
from (
select
Customer_ID
, sum(
case Transaction_Name
when 'SALES' then Transaction_Amount
when 'PAYMENT' then Transaction_Amount * -1
else null
end
) as Balance
from Transaction
group by Customer_ID
) as subqry
No need to join with the Customer table so far because we're not using the Customer Name anywhere.

I can't come up with anything easier than this:
SELECT c.customer_id, pos.amount debit_balance, neg.amount credit_balance
FROM customer c
LEFT JOIN (SELECT SUM(t.transaction_amount) amount, t.customer_id
FROM transaction t
WHERE t.transaction_amount > 0
GROUP BY t.customer_id) pos ON pos.customer_id = c.customer_id
LEFT JOIN (SELECT SUM(t2.transaction_amount) amount, t2.customer_id
FROM transaction t2
WHERE t2.transaction_amount < 0
GROUP BY t2.customer_id) neg ON neg.customer_id = c.customer_id
The first subquery sums up all positive values for each customer, while the second subquery sums up all the negative values. Then you get all customers and combine the two corresponding values into one result.

Related

How to create new table for the output of DISTINCT COUNT to be distributed in rows, not in column?

My query displays the DISTINCT count of buyers with corresponding ticketserial#. I need to automatically calculate the SOLD and BALANCE column and save into the database either into the existing table (table1) with the rows that corresponds to the ticketserial. I've already exhausted my brain and did google many times but I just can't figure it out. So I tried another option by trying to create a new table into the database for the output of DISTINCT COUNT but I didn't find any sample query to follow, so that I could just use INNER JOIN for that new table with table1, with that the PRINTED, SOLD are in the same table, thus I can subtract these columns to obtain the values for the BALANCE column.
Existing table1 & table2 are records in the database via html form:
Table1
Ticket Serial Printed Copies SOLD(sold) Balance
TS#1234 50 ?(should be auto ?
TS#5678 80 ?(should be auto ?
(so on and so forth...)
Table2
Buyer Ticket Serial
Adam TS#1234
Kathy TS#1234
Sam TS#5678
(so on and so forth...)
The COUNT DISTINCT outputs the qty. of sold tickets:
<td> <?php print '<div align="center">'.$row['COUNT(ticketserial)'];?></td>
...
$query = "SELECT *, COUNT(ticketserial) FROM buyers WHERE ticketsold != 'blank' GROUP BY
ticketserial ";
It's COUNT output looks like this:
Ticket Serial------Distinct Count
TS#1234 7
TS#5678 25
(so on and so forth...)
I tried to update the SOLD column and BALANCE column by UPDATE or INSERT and foreach loop but only the first row in table was updated.
Table1
Ticket Serial Printed Copies Sold Balance
TS#1234 50 **7** 0
TS#5678 80 **0** 0
TS#8911 40 **0** 0
(so on and so forth...)
Note: The fieldname "sold" in table1 is not the same with the fieldname "ticketsold" in table2 as the former is quantity and the later is ticketserials.
Your question is a bit hard to follow. However this looks like a left join on a aggregate query:
select
t1.ticket_serial,
t1.printed_copies,
coalesce(t2.sold, 0) sold,
t1.printed_copies - coalesce(t2.sold, 0) balance
from table1 t1
left join (
select ticket_serial, count(*) sold
from table2
group by ticket_serial
) t2 on t2.ticket_serial = t1.ticket_serial
If you are looking to update the main table:
update table1 t1
left join (
select ticket_serial, count(*) sold
from table2
group by ticket_serial
) t2 on t2.ticket_serial = t1.ticket_serial
set
t1.sold = coalesce(t2.sold, 0),
t1.balance = t1.printed_copies - coalesce(t2.sold, 0)
I would not actually recommend storing the sold and balance in the main table - this is derived information that can be easily computed when needed, and would be tedious to maintain. If needed, you could create a view using the first above SQL statement, which will give you an always up-to-date perspective at your data.

Sum columns on different tables and multiply by value of a column on another table

I need to compute employees' monthly salaries based on meetings attended, deductions and bonuses given;
Employees have different pay per meeting based on their job position.
The solution is:
salary = (Pay_per_minute * meetings_attended) + bonuses - deductions ;
I have four tables:
Jobs: Id, title, pay_per_meeting
Employees: Id, Name, job_id
Bonuses: Id, amount, employee_id, date
Deductions: Id, amount, employee_id, date
Meetings: Id, employee_id, date
SELECT
COUNT(meetings.employee_id) as meetings_attended,
COUNT(deductions.amount) as debt,
COUNT(bonuses.amount) bonus,
(SELECT jobs.pay_per_attendance from jobs where jobs.id = (select job_id from employees where id=meetings.employee_id)) as pay,
((meetings_attended * pay) + bonus - debt) as salary
FROM meetings
JOIN deductions ON deductions.employee_id = meetings.employee_id
JOIN bonuses ON bonuses.employee_id = meetings.employee_id
WHERE meetings.employee_id = 1
GROUP BY MONTH(meetings.date), MONTH(deductions.date), MONTH(bonuses.date)
The above query returns many incorrect values whenever i remove the salary line but gives error of unknown column pay, meetings_attended, debt and bonus, am sure something is wrong with the grouping but i can't just see it.
You can't refer to column aliases in the same select list as they're defined, you need to refer to the underlying column. And a subquery can't access an aggregate calculated in the main query. You need to repeat the aggregate expression, or move everything into a subquery and do the calculation with it in an outer query.
Also, all your COUNT() expressions are going to return the same thing, since they're just counting rows (I assume none of the values can be NULL). You probably want COUNT(DISTINCT <column>) to get different counts, and you need to use a column that's unique, so they should be the primary key column, e.g. COUNT(DISTINCT deductions.id).
Another problem is that when you try to sum and count values when you have multiple joins, you end up with a result that's too high, because rows get duplicated in the cross product of all the tables. See Join tables with SUM issue in MYSQL. The solution is to calculate the sums from each table in subqueries.
SELECT m.month, m.meetings_attended, d.debt, b.bonus,
m.meetings_attended * j.pay_per_meeting + b.amount - d.amount AS salary
FROM (
SELECT MONTH(date) AS month, COUNT(*) AS meetings_attended
FROM meetings
WHERE employee_id = 1
GROUP BY month) AS m
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS bonus, SUM(amount) AS amount
FROM bonuses
WHERE employee_id = 1
GROUP BY month) AS b ON m.month = b.month
JOIN (
SELECT MONTH(date) AS month, COUNT(*) AS debt, SUM(amount) AS amount
FROM deductions
WHERE employee_id = 1
GROUP BY month) AS d ON m.month = d.month
CROSS JOIN employees AS e
JOIN jobs AS j ON j.id = e.job_id
WHERE e.employee_id = 1

Joins and correlated subquery

Cannot figure out query for situation where I want to display only customers with unverified order but do not include customers who already have at least one verified order. One customer can have more records in DB since for every order also new record in customers table is made so the only way how to track specific user is by customer_number.
My DB structure (simplified):
customers:
id | customer_number
orders:
id | customer_id | isVerified
I would probably need to combine join and correlated queries (to search records in customers table for every customer_number and check isVerified column for false) which in the end could be really slow especially for thousands of records.
I use Laravel so Eloquent ORM is available if this can make things easier.
(Second thought: Or maybe it would be faster and more efficient to rewrite that part to create only one user record for orders of specific user.)
Any ideas? Thank you
There are probably a few ways to do this, but you can achieve this result with a join, aggregation and conditional sum:
select a.customer_id,
sum( case when isVerified = 1 then 1 else 0 end ) as Num_Verified,
sum( case when isVerified = 0 then 1 else 0 end ) as Num_unVerified
from customers as a
left join
orders as b
on a.customer_id = b.customer_id
group by a.customer_id
having Num_Verified = 0
and Num_unVerified > 0
SQLfiddle here
You can achieve like this:
$customer_id = Customer::join('orders','customers.id','orders.cutomer_id')
->where('isVerified',true)
->select('orders.*')
->groupBy('customer_id')
->pluck('customer_id');
This will give customers with at least one verified order.
Now get customers with unverified orders as:
$customers = Customer::join('orders','customers.id','orders.customer_id')
->where('isVerified',false)
->whereNotIn('customer_id',$customer_id)
->select('customers.customer_number','orders.*')
->groupBy('customer_id')
->pluck('customer_id');
How about this one?
$customer_list = customers::where('customer_number',$customer_number)->with('orders',function($query){
$query->where('isVerified',0);
})->get();
One method is an aggregation query:
select c.customer_number
from customers c join
orders o
on c.customer_id = o.customer_id
group by c.customer_number
having sum(isVerified = 1) = 0 and
sum(isVerified = 0) > 0;
This structure assumes that isVerified is a number that takes on the values of 0 for false and 1 for true.

Speed up slow 3 table MYSQL query

I'm querying 3 tables in an eCommerce site.
orders Table:
id
order_number
name
etc...
order_lines table:
id
order_number
sku
quantity
etc.
products Table
id
sku
title
ship_by (INT)
etc.
order_number links orders table to order_lines table. SKU links order_lines table to products table.
Notice the ship_by column in the products table, this denotes which supplier will ship the item. The query needs to pull orders for a particular supplier. Orders may contain items sold by different suppliers.
This is the query I managed to cobble together:
SELECT
orders.`order_number` as `orderId`,
orders.`shipping` as `fPostageCost`,
FROM_UNIXTIME(orders.`time`) as `dReceievedDate`,
(
CASE orders.`dob`
WHEN 'COURIER 3 DAY' THEN 'UK_SellersStandardRate'
WHEN 'COURIER 24 HOUR' THEN 'UK_OtherCourier24'
ELSE orders.`dob`
END
)
...(plus a number more)
FROM orders
INNER JOIN order_lines
ON orders.order_number = order_lines.order_number
WHERE
(
(SELECT COUNT(order_lines.sku)
FROM order_lines, products
WHERE order_lines.sku = products.sku
AND products.ship_by = 1
AND order_lines.order_number = orders.order_number) > 0
)
AND ( orders.`printed` = 'N' OR orders.`printed` IS NULL )
AND orders.status = 'Awaiting Despatch'
AND ( orders.payment_status = 'Success' OR orders.payment_status = 'Paypal Paid' OR orders.payment_status = 'Manual Payment' )
GROUP BY orders.`order_number`
ORDER BY orders.order_number ASC
It is taking about 7 seconds to execute the query.
If I remove the line 'AND order_lines.order_number = orders.order_number' from the second SELECT query it is executed almost instantly, but without this line it doesn't work as I need it to.
Aside from adding the 'ship_by' column into the order_lines table (which I'd rather not do since I'd have to change a lot of php code), is there any way of modifying this query to speed it up?
The query is being run from outside of PHP so it has to be a pure mysql query.
Here is the EXPLAIN on the query:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY order_lines ALL NULL NULL NULL NULL 9627 Using temporary; Using filesort
1 PRIMARY orders eq_ref order_idx order_idx 17 .order_lines.order_number 1 Using where
2 DEPENDENT SUBQUERY order_lines ALL NULL NULL NULL NULL 9627 Using where
2 DEPENDENT SUBQUERY products ref sku_2,sku,sku_3 sku_2 63 order_lines.prod_code 11 Using where
Thanks
Your order_lines table needs an index on order_number at the very least. We would need to see the schema to best choose one. Perhaps a composite index would pick up speed in other areas.
But in choosing index changes, it must be carefully weighed against other queries in your system, and the impact on insert and update speeds.
The goal shouldn't be to make 10% of your app fast, at the expense of the 90.
To show very useful information, publish show create table tableName for relevant tablenames. (versus describing it free-hand, like my table has this and that). We need to see the schema for orders, order_lines, products.
The manual page on Create Index
Idk how large the table is on your inline view but if it is large then
it may help to use a union instead of having to query the view for every record.
Something like.
SELECT L1*
FROM
(SELECT
orders.`order_number` as `orderId`,
0 AS SKU_CNT,
orders.`shipping` as `fPostageCost`,
FROM_UNIXTIME(orders.`time`) as `dReceievedDate`,
(
CASE orders.`dob`
WHEN 'COURIER 3 DAY' THEN 'UK_SellersStandardRate'
WHEN 'COURIER 24 HOUR' THEN 'UK_OtherCourier24'
ELSE orders.`dob`
END
)
...(plus a number more)
FROM orders
INNER JOIN order_lines
ON orders.order_number = order_lines.order_number
WHERE ( orders.`printed` = 'N' OR orders.`printed` IS NULL )
AND orders.status = 'Awaiting Despatch'
AND ( orders.payment_status = 'Success' OR orders.payment_status = 'Paypal Paid' OR orders.payment_status = 'Manual Payment' )
UNION ALL
Select
0 AS ORDERID,
COUNT(OL.SKU) AS SKU_CNT,
0 as `fPostageCost`,
NULL as `dReceievedDate`,
...
FROM ORDER_LINES OL,
PRODUCTS PR
WHERE order_lines.sku = products.sku
AND products.ship_by = 1
AND order_lines.order_number = orders.order_number
GROUP BY ORDERID, ORDER_LINES_CNT, fPostageCost`, dReceievedDate`,
)L1
WHERE L1.SKU_CNT > 0
GROUP BY L1.`order_number`
ORDER BY L1.order_number ASC
You can replace the subquery with exists:
WHERE EXISTS (SELECT 1
FROM order_lines ol2 JOIN
products p2
ON ol2.sku = p.sku AND
p2.ship_by = 1
ol.order_number = o.order_number
) AND
. . .
But, your snippet of the query is not using order_lines in the outer query. I suspect you can just get rid of it with the aggregation:
FROM orders o
WHERE EXISTS (SELECT 1
FROM order_lines ol2 JOIN
products p2
ON ol2.sku = p2.sku AND
p2.ship_by = 1
ol.order_number = o.order_number
) AND
. . .
This will at least return all the orders information -- and the lack of aggregation may simplify other parts of the query. If some versions of the query are running fast, then indexes are probably set up reasonable.

mysql query need to be optimized

I have a query which give result like
id | productid | userid | coinsid
1 | 2 | 2 | 5
3 | 2 | 2 | 6
4 | 2 | 3 | 7
5 | 2 | 4 | 8
6 | 2 | 3 | 9
This is result for specific productid. Now i have to update the balance in user table by adding $1 to all the users in above result, but if userid is twice, i need to add $1 twice to the balance of that specific user. So in the above case $1 twice added to userid=2 balance and userid=3 balance.
The simple way is to count records for every distinct userid and run queries as many time as we have users in foreach loop. But i am looking for some optimize way. Please suggest any. Thanks
One approach:
UPDATE user_table u
JOIN ( SELECT q.userid
, SUM(1.00) AS deposit
FROM (
-- original OP query goes here
) q
GROUP BY q.userid
) r
ON r.userid = u.userid
SET u.balance = u.balance + r.deposit
We use the original OP query that returns the resultset displayed, and make that an inline view (aliased in the query above as q).
From that, we query a distinct list of userid, and the number of times that userid appears in the resultset. That gives us the username and a deposit amount (1 dollar for each time the userid appears) (some databases might want us to specify the value as 1.0 rather than 1, to make sure it was decimal. I think the SUM is more representative of what we are trying to accomplish.)
We join that inline view (r) to the user table, and add the deposit amount to the current balance, for that user (assuming the balance is stored as decimal dollars (1.00 = one dollar)
To testing, convert the UPDATE into a SELECT statement:
remove the "SET" clause
add an "ORDER BY" clause (optional) to make the results determinate
remove the "UPDATE" keyword and replace it
with:
SELECT r.userid
, r.deposit
, u.balance AS old_balance
, u.balance + r.deposit AS new_balance
, u.userid
FROM
Full select:
SELECT r.userid
, r.deposit
, u.balance AS old_balance
, u.balance + r.deposit AS new_balance
, u.userid
FROM user_table u
JOIN ( SELECT q.userid
, SUM(1.00) AS deposit
FROM (
-- original OP query goes here
) q
GROUP BY q.userid
) r
ON r.userid = u.userid
NOTE There is no WHERE clause, the JOIN predicates (in the ON clause) is what determines which rows are selected/affected in the user table.
Assuming you have no duplicate user ids in your balance table, maybe something like this would work:
update balance_table set balance_table.balance = (select count(*) from users_table where users_table.user_id = balance_table.user_id) * 1;
I haven't tried this query against a mysql database as I am more familiar with plsql, but wouldn't something like this work ?
The correlated subquery in the other answer will work, but an INNER JOIN will usually be more efficient. Try something like this; you'll of course need to supply the table and column names.
UPDATE myTable
INNER JOIN (
SELECT userid, count(*) AS AmountToAdd
FROM users
GROUP BY userid
) UserCounts ON myTable.userid = UserCounts.userid
SET balance = balance + UserCounts.AmountToAdd
select count(*), userid from yourTable group by userid
If I do understand your question.

Categories