SQL queries across joined tables - mysql

In MySQL I have three tables:
Customer: which contains ID, Name, Balance, and Address
Orders: which contains the Order ID, Order Date, Shipping Date, and Customer ID as a foreign key
Order Lines: which contains Order ID, Part ID, and Number Ordered.
I'm trying to write a query such that I can figure out how many items each customer has ordered, but I'm not sure how to get discrete sums for each of the customers. The sample code I have so far just sums all the order lines together into one field.
SELECT
CONCAT(customer_last_name, ', ', customer_first_name) AS 'Customer',
SUM(number_ordered) AS 'Ordered'
FROM Customers t1
JOIN Orders t2
ON t1.Customer_id=t2.Customer_id
JOIN order_lines t3
ON t2.order_id=t3.order_id;
I'm pretty new to SQL and coding in general, so apologies if I'm missing something obvious.

As far as concerns, you just need to add a group by clause to that query to make it produce the result you want:
select
concat(c.last_name, ', ', c.first_name) as customer
sum(oi.number_ordered) as ordered
from customers c
join orders o on o.customer_id = c.customer_id
join order_lines oi on oi.order_id = o.order_id
group by c.id, c.last_name, c.first_name
This gives you one row per customer, along with the sum o number_ordered for all items they ordered.
Notes:
the column aliases should not be surrounded with single quotes (which stand for string litterals); usually no quoting is needed, unless the indentifier contains special characters, in which case you can use backticks
meaningful table aliases make the query easier to read and maintain

Related

Adding customers name alongside customer name

based on the DB schema I am trying to get for each customer the Id, date, customer name and the customer. Can anyone help?
I am struggling with adding the name of the customer. This is what I have tried so far:
SELECT customer,name,date
from table
You need two joins to the customers table. That said, you really need to learn to use meaningful table aliases rather than arbitrary letters. Table aliases are your friend, and arbitrary letters aren't so friendly.
So:
SELECT i.Id, i.BillingDate, c.Name as CustomerName,
cr.Name as ReferredByName
FROM Invoices i LEFT JOIN
Customers c
ON i.CustomerId = c.Id LEFT JOIN
Customers cr
ON i.ReferredBy = cr.Id;

Joining two tables using subquery in mysql

Im trying to write a SELECT statement with a subquery that determines the customer_id and the last_name of the customer
whose credit card expires soonest as shown in my orders table
and for this i want to use a WHERE clause but im stuck since the result only displays one customer...
here is what i have so far. if someone could guide me in the right path..
SELECT customer_id, last_name
FROM customers
WHERE NOT EXISTS
(SELECT card_expires
FROM orders
WHERE customers.customer_id = orders.customer_id
);
While I am not clear on your exact filtering or ordering requirements, I think you may want to start with the following.
SELECT customer_id, last_name, card_expires
FROM customers
left join (
SELECT card_expires
FROM orders
) o on (customers.customer_id = orders.customer_id)
Using this method you can see a full list of all customers and card_expires, and THEN you can start to filter the list down to what you want. You might build up the subquery on orders to select a specific subset of orders. Again I am not clear on what you are trying to do exactly. But by selecting all of the information first you will be able to see all of the information and be able to decide what to filter out.
You may decide you would ALSO like to see more columns from the order table to help you decide which orders to include / exclude.
If you wanted to only see a list of customers that do not have a card_expires entry
SELECT customer_id, last_name, card_expires
FROM customers
left join (
SELECT card_expires
FROM orders
) o on (customers.customer_id = orders.customer_id)
where o.card_expires is null
If you want to see only customers WITHOUT a card that expires in the future.
SELECT customer_id, last_name, card_expires
FROM customers
left join (
SELECT card_expires
FROM orders
where card_expires > now()
) o on (customers.customer_id = orders.customer_id)
where o.card_expires is null
When crafting an SQL statement, something that can make it easier to do is FIRST create an SQL statement which returns ALL of the columns you will use to filter the list by, THEN apply the filter. This will allow you to more easily add and remove criteria until you have it right.
I like to split each criteria on to its own separate line so that I can easily comment lines out with two dashes. EX:
SELECT customer_id, last_name, card_expires
FROM customers
left join (
SELECT card_expires
FROM orders
where card_expires > now()
) o on (customers.customer_id = orders.customer_id)
where 0=0
and o.card_expires is null
-- and last_name = 'Smith'
Use LEFT JOIN/INNER JOIN, i assume you want ORDER BY ASC, since you want the soonest
SELECT customer_id, last_name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
ORDER BY card_expires ASC;

Joining two tables, including count, and sorting by count in MySQL

Have the need to run a bit more complex of a MySQL query. I have two tables that I need to join where one contains the primary key on the other. That's easy enough, but then I need to find the number of occurrences of each ID returned as well, and ultimately sort all the results by this number.
Normally this would just be a group by, but I also need to see ALL of the results (so if it were a group by containing 10 records, I'd need to see all 10, as well as that count returned as well).
So for instance, two tables could be:
Customers table:
CustomerID name address phone etc..
Orders table:
OrderID CustomerID product info etc..
The idea is to output, and sort the orders table to find the customer with the most orders in a given time period. The resultant report would have a few hundred customers, along with their order info below.
I couldn't figure out a way to have it return the rows containing ALL the info from both tables, plus the number of occurences of each in one row. (customer info, individual orders info, and count).
I considered separating it into multiple queries (get the list of top customers), then a bunch of sub-queries for each order programmatically. But that was going to end up with many hundreds of sub-queries every time this is submitted.
So I was hoping someone might know of an easier way to do this. My thought was to have a return result with repeated information, but get it only in one query.
Thanks in advance!
SELECT CUST.CustomerID, CUST.Name, ORDR.OrderID, ORDR.OrderDate, ORDR.ProductInfo, COUNTS.cnt
FROM Customers CUST
INNER JOIN Orders ORDR
ON ORDR.CustomerID = CUST.CustomerID
INNER JOIN
(
SELECT C.CustomerID, COUNT(DISTINCT O.OrderID) AS cnt
FROM Customers C
INNER JOIN Orders O
ON O.CustomerID = C.CustomerID
GROUP BY C.CustomerID
) COUNTS
ON COUNTS.CustomerID = CUST.CustomerID
ORDER BY COUNTS.cnt DESC, CustomerID
This will return one row per order, displayed by customer, ordered by the number of orders for that customer.

MySQL Left Join Taking awhile

I am trying to get a list of possible customers along with the sum of their order history (ltv)
Without the order by, this query loads in under a second. With the order by and the query is taking over 90 seconds.
SELECT a.customerid,a.firstname,a.lastname,Orders.ltv
FROM customers a
LEFT JOIN (
SELECT customerid,
SUM(amount) as ltv
FROM orders
GROUP BY customerid) Orders
ON Orders.customerid=a.customerid
ORDER BY
Orders.ltv DESC
LIMIT 0,10
Any ideas how this could be sped up?
EDIT: I guess I cleaned up the query a little too much. The query is acually a little more complicated then this version. Other data is selected from the customers table, and can be sorted against as well.
Without the actual schema it is a bit hard to know how data is related but I guess this query should be equivalent and more performant:
SELECT a.customerid, coalesce(sum(o.amount), 0) TotalLtv FROM customers a
LEFT JOIN orders o ON a.customerid = o.cusomterid
GROUP BY a.customerid
ORDER BY TotalLtv DESC
LIMIT 10
The coalesce will make sure you return 0 for the customers without orders.
As #ypercube made me notice, an index on amount won't help either. You could give it a try to:
ALTER TABLE orders ADD INDEX(customer, amount)
After your question update
If you need to add more fields that functionally depend on the a.customerid in the select clause you can use the non-standard MySQL group by clause. This will result in better performance than grouping by a.customerid, a.firstname, a.lastname:
SELECT a.customerid, a.firstname, a.lastname, coalesce(sum(o.amount), 0) TotalLtv
FROM customers a
LEFT JOIN orders o ON a.customerid = o.cusomterid
GROUP BY a.customerid
ORDER BY TotalLtv DESC
LIMIT 10
A few things here. First it doesn't appear that you need to join the customers table at all here since you are only using it for the customerid, which already exists in orders table. If you have more than 10 customer id's with corresponding amounts, you will never even need to see the list of customer id's which don;t have amounts that you would get with LEFT JOIN from customers. As such, you should be able to reduce your query to this:
SELECT customerid, SUM(amount) AS ltv
FROM orders
GROUP BY customerid
ORDER BY ltv DESC LIMIT 0,10
You would need an index on customerid. Unfortunately, the sort is on a calculated field, so there is not a lot you can do to speed this up from that point.
I see the updated question. Since you do need additional fields from customers, I will revise my answer to include the customer table
SELECT c.customerid, c.firstname, c.lastname, coalesce(o.ltv, 0) AS total
FROM customers AS c
LEFT JOIN (
SELECT customerid, SUM(amount) as ltv
FROM orders
GROUP BY customerid
ORDER BY ltv DESC LIMIT 0,10) AS o
ON c.customerid = o.customerid
Note that I am joining on a sub-selected table as you were doing in your original query, however I have performed the sort and limit on the sub-selected table so you don't have to sort all the records without any entries on orders table.
Two things. First, don't use an inner query. MySQL does allow ORDER BY on a projection alias. Second, you should get a considerable improvment by having a B-TREE index on the composed key (customerid, amount). Then the engine will be able to execute this query by a simple traversal of the index, without fetching any row data.

MySQL: Selecting from 3 tables

I have the following query:
SELECT
DISTINCT sites.site_id,
sites.site_name,
sites.site_url,
earnings.cust_id
FROM
sites,
earnings
WHERE sites.site_id = earnings.site_id AND sites.site_id IN('8', '1666')
That query gives me very well the information asked. It returns two rows, one for site 8 and another for site 1666, with the information on them from those tables.
Now, I want that the cust_id number be used to select from another table (let's say table customers) where they are stored by id and where other info is such as name, last name, etc.
Basically what I need is to expand that query to extract customer name and last name from the table customers, using the ids obtained.
Same way you got the info from two tables. Add a comma, add the third table name, and add the relationship to your WHERE clause like you did with the first two tables.
SELECT
DISTINCT sites.site_id,
sites.site_name,
sites.site_url,
earnings.cust_id,
customers.name,
customers.last_name
FROM
sites,
earnings,
customers
WHERE sites.site_id = earnings.site_id AND sites.site_id IN('8', '1666') AND customers.id = earnings.cust_id
I think it's clearer to write out the JOINs though:
SELECT
sites.site_id,
sites.site_name,
sites.site_url,
earnings.cust_id,
customers.name,
customers.last_name
FROM
sites
INNER JOIN
earnings
ON
earnings.site_id = sites.site_id
INNER JOIN
customers
ON
customers.id = earnings.cust_id
WHERE
sites.site_id IN (8, 1666)
GROUP BY
sites.site_id