Get total cost for a customer call - mysql

I have 2 tables = customer and their call history
Now I want to charge them based on call duration and that too for a specific month say Jan 2015.
Here is the criteria to calculate the cost for calls -
A) For incoming calls, the charge is 1 unit per second. Example if the duration is 250 seconds then cost is 250
B) For outgoing calls, for the first 2mins, the cost is fixed at 500 units. for subsequent seconds the cost is 2 units per second.
Example if the outgoing duration is 5mins then cost is 500 units + 2*3*60 units = 860 units
Below are the tables:
customer table with columns id, name, phone
history table with columns id, incoming_phone, outgoing_phone, duration, dialed_on (YYYY-MM-DD)
I have come up with below queries for my conditions:
For incoming call cost:
select c.name, c.phone, h.duration as cost
from customer c join history h on c.phone = h.incoming_phone
When I run the above query I did not get any syntax errors.
For outgoing call cost:
select c.name, c.phone, CASE
WHEN h.duration > 120 THEN 500 + 2*(h.duration-120)
ELSE 2*(h.duration-120)
END; as cost
from customer c join history h on c.phone = h.outgoing_phone
When I run the above query I got syntax error like "ERROR 1109 (42S02) at line 1: Unknown table 'c' in field list"
I want to join these two queries and get the total cost and display the fields as name, phone, cost
I still need to add a condition for a specific month to restrict data for Jan 2015, but got stuck with the approach.

The error is due to the extra semicolon ; after END.
Sounds like your final query will be this:
SELECT c.name,
c.phone,
SUM(CASE WHEN h.direction = 'in' THEN h.duration END) as IncomingCost,
SUM(CASE WHEN h.direction = 'out' AND h.duration > 120 THEN 500 + 2*(h.duration-120)
WHEN h.direction = 'out' AND h.duration <= 120 THEN 500
END) as OutgoingCost,
SUM(CASE WHEN h.direction = 'in' THEN h.duration END +
CASE WHEN h.direction = 'out' AND h.duration > 120 THEN 500 + 2*(h.duration-120)
WHEN h.direction = 'out' AND h.duration <= 120 THEN 500
END) as TotalCost
FROM customer c
JOIN (SELECT 'out' as directon, duration, dialed_on, outgoing_phone as phone
FROM history
WHERE YEAR(dialed_on) = 1995
AND MONTH(dialed_on) = 1
UNION ALL
SELECT 'in' as direction, duration, dialed_on, incoming_phone as phone
FROM history
WHERE YEAR(dialed_on) = 1995
AND MONTH(dialed_on) = 1
) h ON c.phone = h.phone
GROUP BY c.name,
c.phone

Related

Sum on same column with two different conditions mysql

I have a table named Order with schema as
user_id, state amount
11 success 100
11 FAILED 10
11 FAILED 10
11 success 17
state can have two values (Success/Failed).
I want to fetch sum(amount) when state = "SUCCESS" - sum(amount) when state = "FAILED"
means difference total amount when success - total amount when failed.
I can solve this problem in 2 queries.
A = select id, sum(amount) when state = "SUCCESS"
B = select id, sum(amount) when state = "FAILED"
And solution will be A-B.
Is there any way I can achieve this in single sql query?
use case when
select user_id,sum(case when state = 'SUCCESS' then amount else 0 end)-sum(case when state = 'FAILED' then amount else 0 end)
from table group by user_id
Use conditional aggregation:
select id,
sum(case when state = 'SUCCESS' then amount else - amount end) as total
from t
where state in ('SUCCESS', 'FAILED')
group by id;
I assume that you want this sum per id and not overall in the table.
select sum(case when state = "SUCCESS" then amount else o end) -
sum(case when state = "FAILED" then amount else o end)
from tbl
group by userid

Alternatives to joins in mysql

I have a query as follows that retrieves the status for different stores in a table and displays it as different columns.
SELECT a.Store_ID,b.total as order_completed,c.total as order_cancelled,d.total as order_processed,e.total as order_failed FROM ORDER_HISTORY a
-> LEFT OUTER JOIN(select Store_ID,count(*) as total from ORDER_HISTORY where Status = 57 group by Store_ID)b on a.Store_ID = b.Store_ID
-> LEFT OUTER JOIN(select Store_ID,count(*) as total from ORDER_HISTORY where Status = 53 group by Store_ID)c on a.Store_ID = c.Store_ID
-> LEFT OUTER JOIN(select Store_ID,count(*) as total from ORDER_HISTORY where Status = 52 group by Store_ID)d on a.Store_ID = d.Store_ID
-> LEFT OUTER JOIN(select Store_ID,count(*) as total from ORDER_HISTORY where Status = 62 group by Store_ID)e on a.Store_ID = e.Store_ID
-> group by a.Store_ID;
Can anybody suggest an alternative to using joins as it affects the performance of db operations.
Create an index on ORDER_HISTORY over (Store_ID, Status), then this should be plenty fast.
SELECT
Store_ID,
status,
COUNT(*) as total
FROM
ORDER_HISTORY
GROUP BY
Store_ID,
status;
Then use your application to display the few resulting rows data in columns. Should not be hard to implement.
Another approach would be (same index as above):
SELECT
Store_ID,
SUM(CASE WHEN Status = 57 THEN 1 ELSE 0 END) AS order_completed,
SUM(CASE WHEN Status = 53 THEN 1 ELSE 0 END) AS order_cancelled,
SUM(CASE WHEN Status = 52 THEN 1 ELSE 0 END) AS order_processed,
SUM(CASE WHEN Status = 62 THEN 1 ELSE 0 END) AS order_processed
FROM
ORDER_HISTORY
GROUP BY
Store_ID;
Replace NULL values as appropriate.
Try using a trigger. Same as a stored procedure that executes when an event occurs within the database. maybe it will help you.

How to find results that don't contain results on the same day

I am trying to figure out how to get a set of results returned to me in SQL.
I have a column called "Status" that has either 'Passed' or 'Failed' as a value. Within the same table is a column for "Date_Entered"
I need a query to return all of the days that 'Failed' that do not have a 'Passed' for the same day.
Example:
01/01/2017 - Failed
01/01/2017 - Failed
01/01/2017 - Failed
01/01/2017 - Passed
01/02/2017 - Failed
01/02/2017 - Failed
01/03/2017 - Passed
In my result set, I only want to see both entries on 01/02/2017 since the 1st and the 3rd has a 'Passed' result.
Here is my most recent attempt:
select COUNT(*)
, CAST(h.Status AS VARCHAR) status
, CAST(h.Location AS VARCHAR) location
, CAST(h.Date_Entered AS DATE)
from de_Hgb h
where 1=1
and h.Date_Entered between '01/01/2017' and '01/31/2017 23:59:59'
and h.status not in (SELECT CAST(g.Status AS VARCHAR) status FROM de_Hgb g WHERE 1=1 and g.Status <> 'Passed' AND g.Date_Entered = h.Date_Entered)
GROUP BY h.date_entered, h.Status, h.location
However, this is returning the inverse of what I want; this is showing me the entries for the 1st and the 3rd, not the 2nd.
Here's one way - select all the failed dates, then left join against the same table on those dates with a 'Passed' status. Then you just need rows where that is NULL (i.e. there was no Passed status)...
SELECT DISTINCT failed.Date_Entered
FROM de_Hgb AS failed
LEFT JOIN de_Hgb AS passed
ON(passed.Date_Entered=failed.Date_Entered and passed.Status="Passed")
WHERE failed.Status="Failed" AND passed.Status IS NULL;
select cast(date_entered as date) as date_entered
from de_Hgb h
where date_entered between ...
group by cast(date_entered as date)
having
count(case when Status = 'Failed' then 1 end) > 0
and count(case when Status = 'Passed' then 1 end) = 0
A variations on that that are equivalent assuming those are the only two possible values:
having count(case when Status = 'Failed' then 1 end) = count(*)
having max(Status) = 'Failed'
It's probably safer to use the first choice in case you introduce new states later.

One to many join, displaying client list and it's client status based on other tables column

Hello I have 2 tables with the following format & data:
Drives table:
id timestamp user driver client vehicle departure destination distance type payment_type total expenses profit debt note
26 2014-01-21 14:56:00 146 10 17 27 Lewforos Athinwn 209 Aerolimenas Athinwn El. Venizelos 60 Diadromi Cash 50 40 10 0 Aida...
Clients table:
id user name surname region address vat tax_office phone fax mobile email registered note
1 146 Maria Papadopoulou Alimos Lisikratous 37 137890567 P.Falirou 2109859786 2109857683 6944536784 maria.pap#domain.com 2013-10-29 13:00:07 some test!
The connection between those 2 tables is drives.client = clients.id
1 client can have many drives.
At the moment I'm using the following query:
SELECT clients.id,
CONCAT(LEFT(clients.name, 1), '. ', clients.surname) AS client_name,
clients.region,
clients.address,
clients.phone,
clients.fax,
clients.mobile,
clients.email,
CASE
WHEN DATE(drives.timestamp) < DATE(NOW()) THEN
CASE
WHEN drives.debt = 0 THEN
CASE
WHEN drives.total > 0 THEN 'Completed'
WHEN drives.total = 0 THEN 'Pending'
END
WHEN drives.debt > 0 THEN 'Overdue'
END
WHEN DATE(drives.timestamp) >= DATE(NOW()) THEN 'Scheduled'
ELSE ''
END AS payment_status,
clients.registered,
DATE_FORMAT(clients.registered, '%d-%m-%Y %H:%i:%s') AS stamp
FROM clients
LEFT OUTER JOIN drives ON clients.id = drives.client
WHERE clients.user
What I'm trying to achieve is a list of all the clients printing each client once and only once and if he has a drive with status pending, overdue, or scheduled to show up as payment status or alternatively a separate column for each client to show how many overdue , pending and scheduled drives has
Desired results would be(row):
client.id client.name client.surname client.overdue client.pending client.scheduled
Your overall query needs a group by clause, if you want to see things at the client level. The following counts the number of "drives" in each of the categories. It is based on the logic in the query in your question:
SELECT c.id, CONCAT(LEFT(c.name, 1), '. ', c.surname) AS client_name,
c.region, c.address, c.phone, c.fax, c.mobile, c.email,
sum(CASE WHEN DATE(d.timestamp) < DATE(NOW()) and
d.debt = 0 and
d.total > 0
THEN 1 else 0
end) as Completed,
sum(CASE WHEN DATE(d.timestamp) < DATE(NOW()) and
d.debt = 0 and
d.total = 0
THEN 1 else 0
end) as Pending,
sum(CASE WHEN DATE(d.timestamp) < DATE(NOW()) and
d.debt > 0
THEN 1 else 0
end) as OverDue,
sum(CASE WHEN DATE(d.timestamp) >= DATE(NOW()) and
d.debt > 0
THEN 1 else 0
end) as Scheduled,
c.registered,
DATE_FORMAT(c.registered, '%d-%m-%Y %H:%i:%s') AS stamp
FROM clients c LEFT OUTER JOIN
drives d
ON c.id = d.client
GROUP BY c.id;

Mysql Query performance issue

We run our systems on a dedicated server with the following spec
Dual Xeon 2.8Ghz
8GB RAM
2 x 250GB Storage
1TB Monthly Data Transfer
All my code is in php and databases are mysql
Currently we have around 500k records in our database; this is likely to increase by at least 500k every year
I am facing an issue of a very high load time of one of our main pages.
I have noticed that on their local server the load time is 2 secs and on our server it is 18 seconds.Using same query and an exact copy of server database on the local server.
What steps would you take to find out why there is such a large difference in load times?
Each call that comes through our system is loaded into database. Currently there are 370k records
Each call is associated with a campaign, tariff and pricing model.
Our queries calculate:
a) Total number of calls per campaign,
b) Total call charges per campaign
The problem is that much of the data, (call charges) can changed in the past by updating tariffs, pricing, campaign start dates, end dates etc. This has led to some instability as the table includes the call charges
The alternative is to calculate call charges on the fly. So to calculate it in the code every time there is a query. However there are 400k records, and this is likely to increase by a million every year
What is the best way to do this?
Please see the query strings below:
a) Platform Campaign Lead Data Query
SELECT c.tariff, c.campaign_name, c.ad_status, c.ad_control, cd.company, cd.customer_name, pm.pricing_model_name, c.start_date, c.end_date,
(
SELECT p.payment_date
FROM payments p
WHERE p.campaign_id = c.campaign_id
ORDER BY p.payment_date DESC
LIMIT 1) AS LastPaidOn,
(
SELECT p.payment_amount
FROM payments p
WHERE p.payment_status = 'Complete' AND p.campaign_id = c.campaign_id
ORDER BY p.payment_date DESC
LIMIT 1) AS LastPaidAmount,
(CASE WHEN NOW() BETWEEN c.start_date AND c.end_date THEN 'Live' ELSE 'Dead' END) AS STATUS,
(CASE WHEN c.group_name = c.campaign_id THEN '' ELSE c.group_name END)
AS group_name_processed,
(
SELECT IFNULL(SUM(call_leads_purchased),0)
FROM payments
WHERE campaign_id = c.campaign_id AND payment_status = 'Complete') AS CallLeadsPurchased,
(
SELECT IFNULL(SUM(cd.call_leads_used),0)
FROM call_data cd
WHERE cd.campaign_id = c.campaign_id) AS CallLeadsUsed
FROM campaign_data c
JOIN customer_details cd ON cd.customer_id = c.customer_id
JOIN pricing_model pm ON pm.pricing_model_id = c.pricing_model_id
HAVING STATUS = 'Live'
ORDER BY c.campaign_name
b) Dashboard Queries
/For Calls Summary/
SELECT
(
SELECT SUM(call_leads_used)
FROM call_data cd
WHERE 1 = 1 AND cd.call_date >= '2010-03-06' AND campaign_id IN(6)) AS SummaryCallLeadsUsed,
(
SELECT COUNT(*)
FROM call_data cd
WHERE 1 = 1 AND cd.call_date >= '2010-03-06' AND campaign_id IN(6)) AS SummaryReceivedCalls,
(
SELECT COUNT(*)
FROM call_data cd
WHERE 1 = 1 AND cd.call_date >= '2010-03-06' AND campaign_id IN(6) AND cd.call_status = 'Unanswered') AS SummaryUnanswered,
(
SELECT SUM(call_leads_used)
FROM call_data cd
WHERE campaign_id IN(6)) AS TotalCallLeadsUsed,
(
SELECT SUM(call_leads_purchased)
FROM payments
WHERE campaign_id IN(6) AND payment_status = 'Complete') AS TotalLeadsPurchased
/For Pie Chart/
SELECT
'Free Calls' AS TYPE, COUNT(*) AS calls
FROM call_data cd,
campaign_data cad
WHERE cd.campaign_id = cad.campaign_id AND cd.call_date >= '2010-03-06' AND cd.is_processed = 1 AND cd.applied_charges = 0 AND cad.customer_id = 6
GROUP BY cad.customer_id UNION
SELECT
'Charged Calls' AS TYPE, COUNT(*) AS calls
FROM call_data cd,
campaign_data cad
WHERE cd.campaign_id = cad.campaign_id AND cd.call_date >= '2010-03-06' AND cd.is_processed = 1 AND cd.applied_charges > 0 AND cad.customer_id = 6
GROUP BY cad.customer_id
/*For Calls per day chart*/
SELECT COUNT(*) AS calls, DATE_FORMAT(call_date, '%Y-%m-%d') AS call_day
FROM call_data cd,
campaign_data cad
WHERE cd.campaign_id = cad.campaign_id AND cd.call_date >= '2010-03-06' AND cd.is_processed = 1 AND cad.customer_id = 6
GROUP BY call_day
ORDER BY call_day
/*For total pages */
SELECT COUNT(*) AS expr
FROM call_data cd,
campaign_data cad
WHERE cd.campaign_id = cad.campaign_id AND cd.is_processed = 1 AND cd.call_date >= '2010-03-06' AND cad.customer_id = 6
/*Calls data*/
SELECT
cad.campaign_name,
cd.call_date,
cd.caller_id,
cd.call_duration,
cd.call_status,
cd.call_duplication,
cd.applied_charges
FROM call_data cd,
campaign_data cad
WHERE cd.campaign_id = cad.campaign_id AND cd.is_processed = 1 AND cd.call_date >= '2010-03-06' AND cad.customer_id = 6
ORDER BY cd.call_date DESC
LIMIT 0, 20