Mysql select sum from two table using join - mysql

I want to get the value of balance from two tables policies and payments. MySQL code below:
SELECT Sum(policy.premium) AS
`total`
,
(SELECT Sum(payments.amount)
FROM payments
WHERE ( payments.date_paid BETWEEN '2019-03-01' AND '2019-03-31' )) AS
`paid`
FROM `policy`
LEFT JOIN payments
ON policy.code = payments.code
WHERE ( policy.st BETWEEN '2019-03-01' AND '2019-03-31' )
AND policy.trn_type = 0
paid column returns give null, and how can I get the difference between total and paid.

You can calculate the 2 values like this:
select t.total, coalesce(t.paid, 0) paid, (t.total - coalesce(t.paid, 0)) diff from (
select
(select sum(premium) from policy where st between '2019-03-01' and '2019-03-31' and policy.trn_type=0) total,
(select sum(amount) from payments where date_paid between '2019-03-01' and '2019-03-31') paid
) t

Paid column is just this part:
SELECT SUM(payments.amount)
FROM payments
WHERE payments.date_paid BETWEEN '2019-03-01' AND '2019-03-31'
If it returns null, check first what you have in your data

Related

Slow query with join - works fast as individual queries

I have a sales table with a sales column TCV, customerKey column which holds customerKey stored in customer table. Each row has an order_date and there are other columns irrelevant to this query.
I have to find sales for current period and another period grouped by customers for comparison. So I have this below query.
SELECT
*,
SUM(TCV) AS sales,
current_sales - SUM(TCV) AS difference
FROM
`sales`
LEFT JOIN
(SELECT
customerKey AS customerID,
SUM(TCV) AS current_sales
FROM
`sales`
WHERE `order_date` BETWEEN '2020-08-01'
AND '2020-08-31'
GROUP BY `sales`.`customerKey`) AS `current_sales`
ON `customerKey` = `customerID`
WHERE `order_date` BETWEEN '2020-09-01'
AND '2020-09-31'
GROUP BY `sales`.`customerKey`
I have this query and it runs very slowly, takes about 30 secs, but if I run the query without the join the result comes back in a second.
What could be the problem, is it structured wrong?
Rewrite it without join, it will perform better:
select s.*
, current_sales - sales as difference
from
( SELECT customerKey
, sum(CASE WHEN order_date BETWEEN '2020-08-01' AND '2020-08-31' then TCV else 0 end) current_sales
, sum(CASE WHEN order_date BETWEEN '2020-09-01' AND '2020-09-30' then TCV else 0 end) sales
FROM sales
WHERE order_date BETWEEN '2020-08-01' AND '2020-09-30'
GROUP
BY sales.customerKey
) s

SQL query involving partial group with condition?

Below is a SQL query problem for which I am not able to understand correct approach:
DB tables:
Employee: emp_id, emp_name
Credit: credit_id, emp_id, credit_date, credit_amount
debit: debit_id, emp_id, debit_date, debit_amount
Here, each person can have multiple incomes and expenses.
Query requirement: At the end of each day, each employee will have some asset('credit till now' - 'debit till now'). We need to find top five employees in terms of maximum asset and the date on which they had this maximum asset.
I have tried the below query but seems like I am missing something:
select Credit.emp_id, Credit.date, (Credit.income_amount - Debit.credit_amount) from
(select emp_id, sum(amount) as credit_amount
from credit) Credit
LEFT JOIN LATERAL (
select emp_id, sum(amount) as debit_amount
from debits
where debits.emp_id = Credit.emp_id and Credit.date >= debits.date
group by debits.emp_id
) Debit
ON true
Here I'm breaking the query to make it more readable.
First of all, we need to get the total amount on a day-level for both credit and debit both, so that we can join the credit and debit table on the day level with the same emp_id.
with
credit as(
select emp_id,credit_date date,sum(credit_amount) as amount
from credit
group by 1,2),
debit as(
select emp_id,debit_date,sum(debit_amount) as amount
from expenses
group by 1,2),
Now we need to full outer join the "credit" and "debit" subqueries
payments as (
select distinct
case when c.emp_id is null then d.person_id else c.emp_id end as emp_id ,
case when c.emp_id is null then d.date else c.date end as date,
case when c.emp_id is null then 0 else i.amount end as credit ,
case when d.emp_id is null then 0 else d.amount end as debit
from credit c
full outer join debit d on d.emp_id=c.emp_id and d.date=c.date
),
Now we will take day-wise cumulative sum for credit, debit and total balance as shown below.
total_balance as(
SELECT emp_id, date,
sum(credit) OVER (PARTITION BY emp_id ORDER BY date asc) AS total_credit,
sum(debit) OVER (PARTITION BY emp_id ORDER BY date asc) AS total_debit,
(sum(income) OVER (PARTITION BY person_id ORDER BY date asc) -
sum(expense) OVER (PARTITION BY person_id ORDER BY date asc)) as total_balance
FROM group_payment
ORDER BY person_id, date),
Now we need to use the rank() function to assign rank based on total balance (desc) for an emp_id (ie. rank=1 will be assigned to the largest total balance on a day for a particular emp_id). The query is shown below.
ranks as (select emp_id,date,total_balance,
rank() over (partition by emp_id order by total_balance desc) as rank
from total_balance ),
Now pick the rows having rank=1 (ie. MAX of total_balance on a day for an emp_id and the date on which it was MAX).
Order it by total_balance descending and pick the top 5 rows
emp_order as (select emp_id,date,total_balance
from ranks
where rank=1
order by 3 desc
limit 5)
Now pick the name from the employee table.
select emp_id,name, date, total_balance as balance
from emp_order eo
join Employee e on e.emp_id = eo.emp_id
order by 4 desc
Group by and sum allows you to get the total credit for each person into 1 record. You can do a similar thing in a subquery to subtract the debit.
Select top 5 emp_id, credit_date, (sum(credit_amount) -
(select sum(debit_amount) from debit d
where c.emp_id = d.emp_id and c.credit_date = d.debit_date)
) as total
from Credit c group by emp_id, credit_date order by total

How to calculate percent?

Could you help me to calculate percent of users, which made payments?
I've got two tables:
activity
user_id login_time
201 01.01.2017
202 01.01.2017
255 04.01.2017
255 05.01.2017
256 05.01.2017
260 15.03.2017
2
payments
user_id payment_date
200 01.01.2017
202 01.01.2017
255 05.01.2017
I try to use this query, but it calculates wrong percent:
SELECT activity.login_time, (select COUNT(distinct payments.user_id)
from payments where payments.payment_time between '2017-01-01' and
'2017-01-05') / COUNT(distinct activity.user_id) * 100
AS percent
FROM payments INNER JOIN activity ON
activity.user_id = payments.user_id and activity.login_time between
'2017-01-01' and '2017-01-05'
GROUP BY activity.login_time;
I need a result
01.01.2017 100 %
02.01.2017 0%
03.01.2017 0%
04.01.2017 0%
05.01.2017 - 50%
If you want the ratio of users who have made payments to those with activity, just summarize each table individually:
select p.cnt / a.cnt
from (select count(distinct user_id) as cnt from activity a) a cross join
(select count(distinct user_id) as cnt from payment) p;
EDIT:
You need a table with all dates in the range. That is the biggest problem.
Then I would recommend:
SELECT d.dte,
( ( SELECT COUNT(DISTINCT p.user_id)
FROM payments p
WHERE p.payment_date >= d.dte and p.payment_date < d.dte + INTERVAL 1 DAY
) /
NULLIF( (SELECT COUNT(DISTINCT a.user_id)
FROM activity a
WHERE a.login_time >= d.dte and p.login_time < d.dte + INTERVAL 1 DAY
), 0
) as ratio
FROM (SELECT date('2017-01-01') dte UNION ALL
SELECT date('2017-01-02') dte UNION ALL
SELECT date('2017-01-03') dte UNION ALL
SELECT date('2017-01-04') dte UNION ALL
SELECT date('2017-01-05') dte
) d;
Notes:
This returns NULL on days where there is no activity. That makes more sense to me than 0.
This uses logic on the dates that works for both dates and date/time values.
The logic for dates can make use of an index, which can be important for this type of query.
I don't recommend using LEFT JOINs. That will multiply the data which can make the query expensive.
First you need a table with all days in the range. Since the range is small you can build an ad hoc derived table using UNION ALL. Then left join the payments and activities. Group by the day and calculate the percentage using the count()s.
SELECT x.day,
concat(CASE count(DISTINCT a.user_id)
WHEN 0 THEN
1
ELSE
count(DISTINCT p.user_id)
/
count(DISTINCT a.user_id)
END
*
100,
'%')
FROM (SELECT cast('2017-01-01' AS date) day
UNION ALL
SELECT cast('2017-01-02' AS date) day
UNION ALL
SELECT cast('2017-01-03' AS date) day
UNION ALL
SELECT cast('2017-01-04' AS date) day
UNION ALL
SELECT cast('2017-01-05' AS date) day) x
LEFT JOIN payments p
ON p.payment_date = x.day
LEFT JOIN activity a
ON a.login_time = x.day
GROUP BY x.day;

Get the most popular product per day, MySQL for Magento

I need to get a table that contains the most popular product sold per day. All data is stored in Magento, and I use MySQL to write the query. The only table I need is Sales_flat_order_item table.
The final table should have 3 columns: Date, Product SKU, and number of units sold of the most popular product that day - MaxQty.
I came up with the query that works for me, but I would like to know how it can be improved since I use the same subquery twice in my code:
1 Select Date, Product Id, Sku, and Quantity from sales_flat_order_item - Subquery1
2 Select Date and Maximum Quantity from Subquery1 - Subquery2
3 Join them together knowing that dates should be the same, and Quantity from Subquery1 should be equal to Maximum Quantity from Subquery2
SELECT DATE( sq2.created_at ) AS CreatedAt, sq0.sku AS SKU, sq2.MaxQty
FROM (
SELECT created_at, product_id, sku, SUM( qty_ordered ) AS qty
FROM `sales_flat_order_item`
GROUP BY DATE( created_at ) , product_id
) AS sq0
JOIN (
SELECT sq.created_at, MAX( sq.qty ) AS MaxQty
FROM (
SELECT created_at, product_id, SUM( qty_ordered ) AS qty
FROM `sales_flat_order_item`
GROUP BY DATE( created_at ) , product_id
) AS sq
GROUP BY DATE( sq.created_at )
) AS sq2 ON DATE( sq2.created_at ) = DATE( sq0.created_at )
AND sq2.MaxQty = sq0.qty
GROUP BY DATE( CreatedAt )
I believe this should do what you want.
I added a WHERE clause to run it only for this month, in case you have a huge database so it should not take much time.
SELECT day, sku, MAX(qty_total) AS qty FROM (
SELECT DATE(created_at) AS day, sku, SUM(qty_ordered) AS qty_total
FROM `sales_flat_order_item`
WHERE created_at > '2015-07%'
GROUP BY sku, day
ORDER BY qty_total DESC
) AS item_count
GROUP BY day

MYSQL inline view query (top customer)

I try to make a query, so that I can see who is the top customer in a month (every month since begin till now).
Now I have the tables:
orders (orderID, orderdate, customerID, Netamount, tax, totalamount)
orderline (orderlineID, orderID, prodID, quantity, orderdate)
customer (firstname lastname zip creditcardtype etc.)
I think the other tables aren't necessarily here.
Of course there are customers who never bought a thing and customers who already bought plenty of times.
Now I used this query:
SELECT customerid, Sum(netamount)
FROM orders
GROUP BY customerid limit 1000000;
Now I see all customers who already bought sth. with the total amount they paid.
With the query
SELECT YEAR ( Orderdate ) Year ,
MONTHNAME ( Orderdate ) Month ,
COUNT(*) TotOrd ,
FROM orders
GROUP BY YEAR ( Orderdate ),
MONTH ( Orderdate );
I get a table where each row shows me the Year Month Total order (placed in that month).
Still I want just to see the Top Customer of a month.
I searched a lot in the internet still couldn't find that what I want (maybe I just googled wrong). I know that I need at least one inline view still no idea how to realize it.
Hope someone can help me out here.
You need to join back to the data to get the top customer. So, first calculate the maximum amount in each month, then join back to get the customer with that amount:
select my.year, my.month, myc.customerid, myc.totord
from (select year, month, max(totord) as maxtotord
from (SELECT YEAR ( Orderdate ) Year, MONTHNAME ( Orderdate ) Month, customerid, COUNT(*) TotOrd ,
FROM orders
GROUP BY YEAR ( Orderdate ), MONTH ( Orderdate ), customerid
) myc
group by year, month
) my join
(SELECT YEAR ( Orderdate ) Year, MONTHNAME ( Orderdate ) Month, customerid, COUNT(*) TotOrd ,
FROM orders
GROUP BY YEAR ( Orderdate ), MONTH ( Orderdate ), customerid, count(*) as totord
) myc
on my.year = myc.year and my.month = myc.month and my.maxtotord = myc.totord
Note that this is untested, so there might be a syntax error.
Also, this returns multiple customers if there are multiple customers with the max value.
Finally, this is much easier in almost any other database, because most databases now support the row_number() function.
It's a group-wise max problem but unfortunately MySQL doesn't support window functions or CTE so this can be messy.
SELECT s1.year,s1.month,s1.customerid,s1.totord FROM
(SELECT YEAR ( Orderdate ) Year ,
MONTHNAME ( Orderdate ) Month ,
customerid,
COUNT(*) TotOrd
FROM orders
GROUP BY YEAR ( Orderdate ),
MONTH ( Orderdate ),customerid) as s1
LEFT JOIN
(SELECT YEAR ( Orderdate ) Year ,
MONTHNAME ( Orderdate ) Month ,
customerid,
COUNT(*) TotOrd
FROM orders
GROUP BY YEAR ( Orderdate ),
MONTH ( Orderdate ),customerid) as s2
ON
s1.year=s2.year AND s1.month=s2.month AND s2.TorOrd>s1.TotOrd AND s1.customerid>s2.customerid
WHERE s2.customerid IS NULL;
In case of doubles it will return the customer with lower id.