Given a pretty standard set of related tables like Customers, Invoices and Line Items. I need to make a query like "Select all customers that have invoices that have 5 line items or more" or "Select all customers that have more than 2 invoices" or lastly "Select all customers that have line items totaling more than $100"
I'm doing this the hard way now (walking through all the records manually) and I know it's the most inefficient way but I don't know enough SQL to construct these queries. I'm using PHP5, MySQL and CakePHP 1.25.
Start by joining them all together:
select c.id
from customers c
left join invoices i on i.customer_id = c.id
left join lineitems li on li.invoice_id = i.id
group by c.id
To filter customers with more than 5 line items or more, add:
having count(li.id) >= 5
Filtering customers with two or more invoices is trickier, since we're joining the lineitems table. There may be multiple rows per invoice. So to count only unique invoices, we have to add distinct to the count, like:
having count(distinct i.id) >= 2
To filter customers with more than $100 in items, add:
having sum(li.cost) > 100
You can use math inside the sum, in case you're storing separate line item counts and prices:
having sum(li.itemcount * li.itemcost) > 100
For the first two you can use GROUP BY and HAVING COUNT(*)>4 and for the last one you can use SUM(field).
If you want SQL then please show your attempt.
Say you had:
customers.id
line_items.user_id
line_items.numItems
Query would look like:
SELECT * FROM customers
JOIN line_items
ON line_items.user_id WHERE line_items.numItems > 5
Your WHERE clause will change depending on what you wanted to return.
Haven't worked with SQL Joins in a while, but I think it's something like that.
Here is some help with the second one that should get you going:
SELECT customer_id, COUNT(*) AS num_invoices
FROM invoices
GROUP BY customer_id
HAVING COUNT(*) > 2
Related
I'm currently having an issue where I run a query on multiple tables getting the results, but they are all being considered independent. I've tried a couple ways of combining them, but because my SQL knowledge is limited I can't seem to get what I want to happen.
SELECT DISTINCT t.*, s.quantity, s.rrp, ts.thumbnail, ts.bigpic, t.rating
FROM tyres t
INNER JOIN stocklevels s
ON t.stockcode = s.stockcode
LEFT JOIN tyre_treads ts
ON t.treadid = ts.recid
LEFT JOIN reseller r
ON s.city=r.recid
WHERE s.quantity> 0 AND s.rrp > 0
I've tried adding GROUP BY t.recid and a couple other basic solutions but this doesn't seem to work. I've added a couple images which might help.
As you can see the bottom Toyo tyres are the same, just with varying cities and quantities.
Here they are on the website.
I'm wanting to combine they so that they say minimum 6 in stock and shows only once on the site.
As long as there is at least one column in your SQK resukt, which contains different values (like city in your example for "the same" tire, group by won't work. You must adapt your SQL statement in a way, that it only picks columns with the same values. Especially, you should remove the t.* from your sql and name all columns (You then will not need the distinct anymore).
Then, you sum over quantity to get the combined value for this column as wanted.
SELECT r.recid, sum(s.quantity), s.rrp, ts.thumbnail, ts.bigpic, t.rating
FROM tyres t
INNER JOIN stocklevels s
ON t.stockcode = s.stockcode
LEFT JOIN tyre_treads ts
ON t.treadid = ts.recid
LEFT JOIN reseller r
ON s.city=r.recid
WHERE s.quantity> 0 AND s.rrp > 0
GROUP BY recid
I have four tables:
users, orders, orders_product and products.
They are connected to each other by foreign key
user tables contains: id, name, email and username.
product table contains: id, product_name, product_description and product_price
orders table contains: id, u_id(foreign key).
orders_product table contains: id, product_id(foreign key), order_id(foreign key).
Now I was trying to fetch the name of a user with the total price of a particular order that he has placed.
The maximum I could went for was something like this:
SELECT prod.order_id,
SUM(product_price) AS Total
FROM products
INNER JOIN
(SELECT orders.id AS order_id,
orders_product.product_id
FROM orders
INNER JOIN orders_product ON orders.id = orders_product.order_id
WHERE order_id=1) AS prod ON products.id = prod.product_id;
It showed me total price of a particular order. Now I have two questions:
Is that query correct. It looks like a very long query. Can the same result be achieved with a smaller one?
How to fetch the name of a user with the total price of a particular order that he has placed.
Hi some addition to #Gordon Linoff
your query seems ok.
if you store your price data in order_products it will be good and some benefit, one of these benefit is aggregation will be simple. Second benefit if product price change it will not affect to order.
Your query is correct for one order, but it can be improved:
Don't use a subquery unless necessary. In MySQL this introduces additional overhead.
You are only looking at one order, which seems on the light site. You should remove the where clause.
You should be using a group by because you want aggregation.
You need to join in the user table to get the name.
I also added table aliases (abbreviations for table names). This makes the query a bit more readable:
SELECT u.name, SUM(p.product_price) as Total
FROM orders_product op INNER JOIN
orders o
ON o.id = op.order_id INNER JOIN
products p
ON p.id = op.product_id INNER JOIN
users u
on o.userid = u.id
WHERE op.order_id = 1
GROUP BY u.name;
Your SQL is wrong. Because You want to calculate specific to user. But your SQL is specific to Order. Your SQL will give result for One Order. Please make it User Specific by giving user name or what ever is unique.
I am trying to work out the percentage of a number of students who meet certain criteria.
I have 3 separate tables that I need to get data from, and then I need to get the total from one table (student) as the total of students.
Then I need to use this total, to divide the COUNT of the no of students in the 2nd query.
So basically I am trying to get a count of ALL the students that are in the DB first.
Then count the no of students that appear in my main query (the one returning the data).
Then I need to perform the calculation that will take the noOfStudents (2) and divide by the main total (24) (no of students in DB) then *100 to give me the percentage of students who have met the criteria in the main query.
This is what I have so far:
SELECT * FROM (
(
SELECT s.firstname, s.lastname, s.RegistrationDate, s.Email, d.ReviewDate,(r.description) AS "Viva" , COUNT(*) AS "No of Students"
FROM student s
INNER JOIN dates d
ON s.id=d.student_identifier
INNER JOIN reviews r
ON d.review_Identifier=r.id
WHERE r.description = "Viva Date"
GROUP BY s.student_identifier
ORDER BY s.student_identifier)
) AS Completed
WHERE Completed.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01'
;
I need to output the fields following the second SELECT and this data in turn will be displayed via PHP/HTML code on a page (the BETWEEN dates will be sent via '%s').
I wondered if I should be using 2 separate queries and then getting the value (24) from the first query to perform the calculation in the second query, but I have not been able to work out how to save as 2 separate queries and then reference the first query.
I am also not sure if it is possible to display an overall % total at the same time as outputting the individual rows that meet the criteria?
I am trying to teach myself SQL, so I apologise if I have made any glaring mistakes/assumptions in any of the above, and would appreciate any advice that's out there.
Thank you.
Could you do this?
SELECT COUNT(*) as TotalPopulation,
COUNT(d.student_identifier='student') as TotalStudents,
COUNT(d.student_identifier='student')/ count(*) *100 as Percentage of students
from students s
inner join dates d
on s.id = d.student_identifier
inner join reviews r
on r.id = d.review_Identifier
WHERE d.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01' and r.description = 'Viva Date';
You do not need first name last name if you are just looking for counts, necessarily.
This get's the count(*) of table, then whatever flag you use to identify a student in the second count(), you just had it grouped by before, which could give you wrong results considering there's much else in your select before aggregation.
You could also try:
SELECT d.student_identifier, s.firstname, s.lastname,
s.RegistrationDate, s.Email, d.ReviewDate,(r.description) AS "Viva"
FROM student s
INNER JOIN dates d
ON s.id=d.student_identifier
INNER JOIN reviews r
ON d.review_Identifier=r.id
WHERE r.description = "Viva Date" and d.ReviewDate BETWEEN '2012-01-01' AND '2014-12-01'
ORDER BY s.student_identifier
Now, if you want to return a list, that's the second one, if you want to return a count, you would use the first query and adjust to your student_identifier.
I have a table USERS and a table ORDERS. In my backend office I'm attempting to output a table with all users (customers) and SUM their individual order total, so to simplify I'm saying:
SELECT users.id, SUM(orders.total) as spent FROM users
JOIN orders ON users.id=orders.customer_id GROUP BY users.id
(Note: do not pay attention to the syntax, this is just to illustrate the point. the syntax is fine when I run it.)
I now have say 4 users in total and the ORDERS table looks something like this:
order_id customer_id total
1 1 25
2 2 10
3 1 5
Then my query will output ONLY those users that can be found in the ORDERS table and my backend customer overview table will look unfortunately like this:
Customer ID Spent in Total
1 30
2 10
ignoring completely the other 2 users who have not yet placed any orders. What I want to see is this:
Customer ID Spent in Total
1 30
2 10
3 0
4 0
Is there a way to do this?
My guess is that it has something to do with special joins like inner, outer, but I don't know the difference there.
Also what I thought about was to run two queries, selecting * from users and then running a foreach to sum up order total, but this seems inefficient.
Because sometimes one picture is worth more than thousand words:
You need a left join, and on some (old) versions of MySQL also an IFNULL().
SELECT
users.id,
IFNULL(SUM(orders.total),0) as spent
FROM users
LEFT JOIN orders ON users.id=orders.customer_id
GROUP BY users.id
select u.id, sum(o.Total)
from Users u
left outer join Orders o on u.id = o.customer_id
group by u.id
I have two tables with following fields:
...
orders.orderID
orders.orderValue
and
payments.orderID
payments.payVal
In payments.payVal there will be incremental payments for each order (many-to-one).
What I need it so select ALL orders from orders where there is payment left (orders.orderValue - ((sum)payments.payVal) > 0 ).
The only thing I can come up to right now is a (foreach) using the orderID, but I cannot do that for some particular reasons. I also cannot add a column inside table to hold the value for some reasons too.
What I need, is to perform the entire selection in one single SQL Query something that resembles this idea: SELECT * FROM orders WHERE <... each(orderValue - (sum(payVal))) > 0 ...>
SELECT *, SUM(p.payVal) AS TotalPayed
FROM orders o
LEFT JOIN payments p ON o.orderID = p.orderID
GROUP BY o.orderID
HAVING SUM(p.payVal) < o.orderValue
This should provide you with the necessary fields, although I would advise you to select specific fields with this query.
The LEFT JOIN makes sure you get every order, even if there are no payments made yet.
SQL Fiddle
Looks like we came to the same solution,
http://sqlfiddle.com/#!2/9a657/18
SELECT * FROM `orders`
LEFT JOIN `payments` AS p ON `orders`.`orderID` = p.`orderID`
GROUP BY `orders`.`orderID`
HAVING SUM(p.`payVal`) <= 0