I want to Retrieve customer names, total orders (how many time they order the products) and the total amount they're spent in the lifetime. Run a single query WITHOUT Join, group by, having operators. Show only customers who have at least one order.
Here is my database
Customer- CustomerID| CustomerName SalesOrder- SalesOrderID | CustomerID | SaleTotal
100000 | John 1001 | 100000 | 2000
200000 | Jane 1002 | 100000 | 3000
300000 | Sean 1003 | 200000 | 5000
When I query
SELECT CustomerName,count(*) AS Total_Orders,sum(SaleTotal) AS SaleTotal
FROM Customer C,SalesOrderHeader SH WHERE C.CustomerID=SH.CustomerID;
It show only one row.
The answer that I want is
CustomerName | Total_Orders | SaleTotal
John 2 5000
Jane 1 5000
I just new on mysql.
So does anyone here know how to do this?
If you are to do this without joins and group by, then the simplest approach is to use correlated subqueries:
select *
from (
select
c.customerName,
(
select count(*)
from salesOrder so
where so.customerID = c.customerID
) totalOrders,
(
select sum(salesTotal)
from salesOrder so
where so.customerID = c.customerID
) saleTotal
from customer c
) t
where totalOrders > 0
Note that this query is clearly suboptimal - because it scans the salesOrder table twice, while a single scan would suffice. A better way to write this would be:
select c.customerName, count(*) totalOrders, sum(salesTotal) saleTotal
from customer c
inner join saleOrder so on so.customerID = c.customerID
group by c.customerID, c.customerName
There is no need for a having clause here - the inner join filters out customers that have no order already.
Use aggregation . . . and proper join syntax:
SELECT CustomerName, COUNT(*) AS Total_Orders, SUM(SaleTotal) AS SaleTotal
FROM Customer C JOIN
SalesOrderHeader SH
ON C.CustomerID = SH.CustomerID
GROUP BY CustomerName;
Your query would fail in almost any database -- including newer versions of MySQL. You have mixed aggregated columns and unaggregated columns in the SELECT. The unaggregated ones should be in a GROUP BY.
Never use commas in the FROM clause. Always use proper, explicit, standard, readable JOIN syntax.
You have to use below query. You cannot achieve it without join and group by
SELECT CustomerName,count(*) AS Total_Orders,sum(SaleTotal) AS SaleTotal
FROM Customer C,SalesOrderHeader SH WHERE C.CustomerID=SH.CustomerID
group by;
Related
I am trying to find a way to add a country code to a database call record based on a phone number column. I have a table with countries and their dialling codes called countries. I can query all records and add the country code after but I need to be able to filter and paginate the results.
I am working with a system I don't have much control over so adding new columns to tables or rewriting large blocks of code isn't really an option. This is what I have to work with.
Countries Table.
id
name
dialling_code
1
Ireland
353
2
America
1
Call Record table.
id
startdatetime
enddatetime
route_id
phonenumber
duration_seconds
1
2014-12-18 18:51:12
2014-12-18 18:52:12
23
3538700000
60
2
2014-12-18 17:41:02
2014-12-18 17:43:02
43
18700000
120
Routes table.
id
number
enabled
23
1234567890
1
43
0987654321
1
I need to get sum values of duration, total unique phone numbers all grouped by route_id, route_number but now we need to group these results by country_id so we can group callers by country. I use the mysql query below to get sum values of duration, total unique phone numbers all grouped by route_id, route_number. This query was written by another developer a long time ago.
SELECT
phone_number,
route_number,
COUNT(callrecord_id) AS total_calls,
SUM(duration_sec) AS total_duration,
callrecord_join.route_id
FROM routes
RIGHT JOIN (
SELECT
DATE(a.startdatetime) AS call_date,
a.id AS callrecord_id,
a.route_id AS route_id,
a.phonenumber AS phone_number,
a.duration_seconds as duration_sec,
b.inboundnumber AS route_number,
FROM callrecord AS a
INNER JOIN routes AS b ON a.route_id = b.id
WHERE DATE_FORMAT(a.startdatetime, '%Y-%m-%d') >= '2014-12-18'
AND DATE_FORMAT(a.startdatetime, '%Y-%m-%d') <= '2014-12-18'
AND b.isenabled = 1
) AS callrecord_join ON routes.id = callrecord_join.route_id
GROUP BY route_id, route_number
LIMIT 10 offset 0;
I have everything up to adding a country_id in the right join table so I can group by the country_id.
I know I could loop through each country using php and get the results using a where clause, something like the below but I cannot paginate these results or filter them easily.
WHERE LEFT(a.phonenumber, strlen($dialling_code)) = $dialling_code
How can I use the countries table to add a column to the join table query with the country id so I can group by route_id, route_number and country_id? Something like the table below.
id
startdatetime
enddatetime
route_id
phonenumber
duration_seconds
country_id
1
2014-12-18 18:51:12
2014-12-18 18:52:12
23
3538700000
60
1
2
2014-12-18 17:41:02
2014-12-18 17:43:02
43
18700000
120
2
The RIGHT JOIN from routes to callrecord_join serves no purpose, as you already have the INNER JOIN between routes and callrecord in the sub-query, which is on the righthand side of the join.
You can use the join you have described -
JOIN countries c ON LEFT(a.phonenumber, LENGTH(c.dialling_code)) = c.dialling_code
but it will give the same result as:
JOIN countries c ON a.phonenumber LIKE CONCAT(c.dialling_code, '%')
which should be slightly less expensive.
You should test the join to countries to make sure none of your numbers in callrecord join to multiple countries. Some international dialling codes are ambiguous, so it depends on which list of dialling codes you are using.
SELECT a.*, COUNT(*), GROUP_CONCAT(c.dialling_code)
FROM callrecord a
JOIN country c ON a.phonenumber LIKE CONCAT(c.dialling_code, '%')
GROUP BY a.id
HAVING COUNT(*) > 1;
Obviously, you will need to batch the above query if your dataset is very large.
I hope I am not grossly over-simplifying things, but from what I understand of your question the query is just:
SELECT
r.id AS route_id,
r.number AS route_number,
c.name AS country_name,
SUM(a.duration_seconds) AS total_duration,
COUNT(a.id) AS total_calls,
COUNT(DISTINCT a.phonenumber) AS unique_numbers
FROM callrecord AS a
JOIN routes AS r ON a.route_id = r.id
JOIN countries c ON a.phonenumber LIKE CONCAT(c.dialling_code, '%')
WHERE a.startdatetime >= '2014-12-18'
AND a.startdatetime < '2014-12-19'
AND r.isenabled = 1
GROUP BY r.id, r.number, c.name
LIMIT 10 offset 0;
Please note the removal of DATE_FORMAT() from the startdatetime to make these criteria sargable, assuming a suitable index is available.
I'm writing a query where I need to select student name by who has a MAX gradelevel_id. How ever it still selects the other row with the same id of the student where I already define what gradelevel_id should I select.
schoolyear_id | student_id | gradelevel_id
407 18 307
409 18 309`
Query:
SELECT
student_mt.student_id,
registration_mt.firstname, registration_mt.middlename, registration_mt.lastname,
MAX(grade.gwa)
FROM schoolyear_student_lt
INNER JOIN gradelevel_mt ON gradelevel_mt.gradelevel_id = schoolyear_student_lt.gradelevel_id
INNER JOIN student_mt ON student_mt.student_id = schoolyear_student_lt.student_id
INNER JOIN registration_mt ON registration_mt.registration_id = student_mt.registration_id
INNER JOIN student_grade ON student_grade.student_id = schoolyear_student_lt.student_id
INNER JOIN grade ON grade.grade_id = student_grade.grade_id
WHERE gradelevel_mt.gradelevel_id = 309
GROUP BY student_mt.student_id;
If I define 307 in my WHERE CLAUSE still selects the student name which I should not already see in my row.
Output:
student_id | firstname | middlename | lastname | MAX(grade.gwa)
18 Billie Joe Armstrong 88
(This is more of a comment) I guess you accidentally stumbled on the quirky MySQL behavior of GROUP BY.
When using GROUP BY, in the SELECT clause we can only put the GROUP BY predicate (student_mt.student_id) and aggregate functions (MAX(grade.gwa)). Even though MySQL allows this, the DBEngine assumes you know what you are doing but might result in anomalies.
Why not get take the approach of getting the student_id(pk) for the MAX(grade.gwa) as a inner sub query and then do the INNER JOIN's with other tables to select what you wanted in the outer subquery.
I'd like to SELECT a count of the number of customers, the sum of customer order totals, and a count of customers in specific states.
I know how to do this in two queries easily, however the same WHERE constraints will be used, so it seems like it would be better to do it in one query and avoid repetition. I'm eager to improve my SQL but I can't work out how to combine them. Having them as two separate queries feels very clumsy.
Is there a way to combine them? What factors should I consider to determine if combining them is a good idea?
Customers Table
*-------------*-------------*--------------*------------*
| ID_Customer | ID_State | Name | ...etc... |
*-------------*-------------*--------------*------------*
States Table
*-------------*-------------*
| ID_State | Name |
*-------------*-------------*
Orders Table
*----------*-------------*--------------*------------*
| ID_Order | ID_Customer | ...etc... | Total |
*----------*-------------*--------------*------------*
Query 1.1 - Select Count of Customers and Count of Customers in specific states
SELECT
COUNT(*) AS Customers,
SUM(States.Name = 'California') AS California_Customers,
SUM(States.Name = 'New York') AS NewYork_Customers
FROM Customers
INNER JOIN States ON Customers.ID_State = States.ID_State
Query 1.2 - Select Sum of Customer Order Totals
SELECT
SUM(Total) AS SumOfOrderTotals
FROM Orders
INNER JOIN Customers ON Customers.ID_Customer = Orders.ID_Customer
Query 2 - An attempt at combining the queries into one (does not work)
SELECT
COUNT (DISTINCT(Customers.ID_Customer)) AS Customers,
SUM (Orders.Total) AS SumOfOrderTotals,
SUM (States.Name = 'California') AS California_Customers,
SUM (States.Name = 'New York') AS NewYork_Customers
FROM
Customers
INNER JOIN Orders ON Customers.ID_Customer = Orders.ID_Customer
INNER JOIN States ON Customers.ID_State = States.ID_State
Obviously this does not work as it is because the INNER JOIN between Customers and Orders means that States.Names are counted xN (where N is the number of orders a customer has) for each customer, making those totals wrong.
I considered a Subquery, however I'm not sure how to apply one in this case (if that is what I should be doing).
You need to do the aggregation before the join or use subqueries:
SELECT COUNT(DISTINCT(c.ID_Customer)) AS Customers,
o.SumOfOrderTotals,
SUM(s.Name = 'California') AS California_Customers,
SUM(s.Name = 'New York') AS NewYork_Customers
FROM Customers c JOIN
States s
ON c.ID_State = s.ID_State CROSS JOIN
(SELECT SUM(Total) as SumOfOrderTotals
FROM Orders o
) o;
You could also write this as:
SELECT COUNT(DISTINCT(c.ID_Customer)) AS Customers,
(SELECT SUM(Total)
FROM Orders o
) as SumOfOrderTotals,
SUM(s.Name = 'California') AS California_Customers,
SUM(s.Name = 'New York') AS NewYork_Customers
FROM Customers c JOIN
States s
ON c.ID_State = s.ID_State;
You place the subquery where you would have placed any additional field, as another thing in the SELECT clause.
SELECT
COUNT(*) AS Customers,
SUM(States.Name = 'California') AS California_Customers,
SUM(States.Name = 'New York') AS NewYork_Customers,
(SELECT SUM(Total) FROM Orders) AS SumOfOrderTotals
FROM Customers
INNER JOIN States ON Customers.ID_State = States.ID_State;
I have two tables. One is a call history table which logs calls made (starttime, endtime, phone number, user, etc). The other is an orders table which logs order details (order number, customer info, orderdate, etc.). Orders are not always created when a call is created so there isnt a guaranteed ID to match them up. Right now, I'm interested in getting totals by day. When I try to run a a query to sum calls and join orders by day I get the following error:
The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay
This is the query I use:
SELECT
DATE_FORMAT(c.date_call_start,'%Y-%m-%d') as date,
COUNT(c.id) as calls,
COUNT(o.id) as orders
FROM tbl_calls c
LEFT OUTER JOIN tbl_orders o
ON DATE_FORMAT(c.date_call_start,'%Y-%m-%d') = DATE_FORMAT(o.created,'%Y-%m-%d')
WHERE c.campaign_id = 1
AND DATE_FORMAT(c.date_call_start,'%Y-%m-%d') = '2013-12-09'
GROUP BY DATE_FORMAT(c.date_call_start,'%Y-%m-%d')
Even when there are only a few calls for a particular day, it still shows the same error. So I'm pretty sure it my query that needs work.
I have also tried a sub query, but that doesn't rollup the totals from the subquery.
SELECT
DATE_FORMAT(c.date_call_start,'%Y-%m-%d') as date,
count(c.id) as calls,
(select count(DISTINCT o.id)
FROM tbl_orders o
WHERE DATE_FORMAT(o.created,'%Y-%m-%d') = DATE_FORMAT(c.date_call_start,'%Y-%m-%d')
) as orders
FROM tb_calls c
WHERE c.campaign_id = 1
AND DATE_FORMAT(c.date_call_start,'%Y-%m-%d') BETWEEN '2013-12-09' AND '2013-12-15'
GROUP BY DATE_FORMAT(c.date_call_start,'%Y-%m-%d')
WITH ROLLUP
Any thoughts on how I can get this query to work? Ultimately I'd like a result like below so I can do other calculations like % orders etc.
date | calls | orders
------------------------------------
2013-12-01 | 100| 10
2013-12-02 | 125| 20
NULL | 225| 30
UPDATED:
Based on the answer I did the following:
created call_date field with a date field (no datetime) to tbl_calls
created date_order field with a date format (not datetime) to tbl_orders
Updated each table and set the new fields to = date_format(the_date_time_stamp,'%Y-%m-%d') from the same table.
Also added an index to each of the new date fields.
That made the following query work:
SELECT
c.call_date as date,
COUNT(DISTINCT c.id) as calls,
COUNT(DISTINCT o.id) as orders,
ROUND((COUNT(DISTINCT o.id) / COUNT(DISTINCT c.id))*100,2) as conversion
FROM tbl_calls c
JOIN tbl_orders o
ON c.call_date = o.date_order
WHERE c.campaign_id = 1
AND c.call_date BETWEEN '2013-12-09' AND '2013-12-15'
GROUP BY c.call_date
WITH ROLLUP
Which gives me the following result and I can build off of this. Thanks to each of you who provided suggestions. I tried each. All make sense. However, since I ultimately had to create the additional date fields I chose the answer by
date | calls | orders| conversion
-------------------------------------------
2013-12-09 | 151 | 6 | 3.97
2013-12-10 | 164 | 2 | 1.22
2013-12-11 | 165 | 6 | 3.64
2013-12-12 | 189 | 1 | 0.53
2013-12-13 | 116 | 4 | 3.45
null | 785 | 19 | 2.42
First - try the results of EXPLAIN SELECT.... where ... is the rest of your select query above.
Since you're performing the join on two fields which have a function applied to them - I'm take a guess and say MySQL is performing two full table scans and using type all for the join. See this for an explanation of the EXPLAIN output.
DATE_FORMAT(c.date_call_start,'%Y-%m-%d') = DATE_FORMAT(o.created,'%Y-%m-%d')
You'll most likely want to create a separate field in each table that contains just the result of the DATE_FORMAT call. Then create an index for each of these new fields. Then join on these new indexed fields. MySQL should like that much better.
Presumably you want to count the calls and orders for each date. However, that is not what your query does, because it creates a cartesian product for all orders on a given date.
Instead, summarize the data first by date and then combine the results. This may be what you want:
select c.date, calls, orders
from (select DATE_FORMAT(c.date_call_start, '%Y-%m-%d') as date, count(*) as calls
from tbl_calls c
WHERE c.campaign_id = 1 and
DATE_FORMAT(c.date_call_start, '%Y-%m-%d') = '2013-12-09'
group by DATE_FORMAT(c.date_call_start, '%Y-%m-%d')
) c left outer join
(select DATE_FORMAT(o.created,'%Y-%m-%d') as date, count(*) as orders
from tbl_orders o
group by DATE_FORMAT(o.created, '%Y-%m-%d')
) o
on c.date = o.date;
If #Barmar 's suggestion does not work, then you may need to split the fields into DATE and TIME.
A different direction is to make two temp tables (giving you three queries:
CREATE TEMPORARY TABLE `tbl_calls_temp` SELECT * FROM tbl_calls c WHERE DATE(c.date_call_start) = '2013-12-09' AND c.campaign_id = 1
Then do the same restricting for the tbl_orders TABLE
CREATE TEMPORARY TABLE `tbl_orders_temp` SELECT * FROM tbl_orders o WHERE DATE(o.created) = '2013-12-09'
Finally query against the two temporary tables. Depending on how much data you get, you may want to add indexes to the temporary tables... but in all likelihood you are facing a full-join
SELECT
DATE_FORMAT(c.date_call_start,'%Y-%m-%d') as date,
COUNT(c.id) as calls,
COUNT(o.id) as orders
FROM tbl_calls_temp c
LEFT OUTER JOIN tbl_orders_temp o
ON DATE_FORMAT(c.date_call_start,'%Y-%m-%d') = DATE_FORMAT(o.created,'%Y-%m-%d')
GROUP BY DATE_FORMAT(c.date_call_start,'%Y-%m-%d')
And that should be much faster... assuming you have any indexes in your initial tables that can be queried.
Now, I have the following query, which gives me the COUNT() of rows which have the same name. However, let's say that I just wanted to have all the rows printed out.
SELECT l.id, c.first_name, c.last_name,
l.source AS 'affiliateId', COUNT(*),
c.email, ls.create_date, ls.buyer
FROM lead_status AS ls
INNER JOIN leads AS l ON l.id = ls.lead_id
INNER JOIN contacts AS c ON c.lead_id = l.id
WHERE ls.discriminator = 'AUTO_POST' AND l.affiliate_id=1003
AND ls.winner =1 AND l.test =0
AND l.create_date BETWEEN '2011-10-03' AND '2011-10-19'
GROUP BY c.first_name, c.last_name HAVING COUNT(*)>1;
So I'm trying to go from:
joe smith 3
lisa martin 2
To the following:
joe smith
joe smith
joe smith
lisa martin
lisa martin
Help!
You can join with a numbers table:
SELECT T1.col1, T2.col2
FROM
(
-- your long query goes here
) T1
JOIN numbers
ON numbers.x <= T1.cnt
A numbers table is just a table that contains numbers:
+---+
| x |
+---+
| 1 |
| 2 |
| 3 |
etc... as many numbers as you will ever need
u can use expression COUNT(DISTINCT first_name) and then get rid of GROUP BY
so query will be
SELECT l.id, c.first_name, c.last_name, l.source AS 'affiliateId', COUNT(DISTINCT c.first_name, c.last_name) as CountRows,
c.email, ls.create_date, ls.buyer FROM lead_status AS ls
INNER JOIN leads AS l ON l.id = ls.lead_id
INNER JOIN contacts AS c ON c.lead_id = l.id
WHERE ls.discriminator = 'AUTO_POST' AND l.affiliate_id=1003
AND ls.winner =1 AND l.test =0 AND l.create_date BETWEEN '2011-10-03' AND '2011-10-19'
HAVING CountRows>1
I don't remember if MySQL supports subqueries, but I would do something like
select
first, last
from
table where id in (select id from table group by first, last having count(*) > 1)
order by
first, last
Add another join on the table(s) where the duplication appears. For the join condition, have the identifying info be the same (e.g. c.first_name = c2.first_name AND c.last_name = c2.last_name, or l.id = c2.id) and whatever distinguishes the records be different (e.g. l.create_date < l2.create_date). Lastly, group by the ID of the record that contains the duplicates or select distinct rows so you don't get repeats. Without knowing the table schema or where the duplicates might occur, I can't be any more specific.
join the result set back with the contacts tale on the first name and last name fields (assuming that first name + last name forms a unique key)