i have 2 table's one with customers and one with orders
SELECT customers.customer_name, orders.order_date
FROM customers
Left
Join orders on (customers.customer_id = orders.customer_id)
WHERE not orders.customer_id IN (SELECT customer_id from orders where Year(order_date) = Year(#Parameter1))
and not orders.order_date is null
this works but i want to do this for each year to get somthing like this as result
|Year | customer_id |
|2010 | 1 |
|2010 | 2 |
|2011 | 2 |
|2011 | 3 |
|2012 | 1 |
You want a list showing customers and years that are not present in the orders table. So get a list of all customers combined with all years and then subtract the customers and years that you find in the orders table:
select o.yr, c.customer_id
from customers c
cross join (select distinct year(order_date) as yr from orders) o
where (c.customer_id, o.yr) not in (select customer_id, year(order_date) from orders);
Scorpio is sort of right, you do have to use the year function:
SELECT Year(orders.order_date), customers.customer_id
FROM customers
LEFT JOIN orders
ON (customers.customer_id = orders.customer_id)
WHERE NOT orders.customer_id
IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
AND
NOT orders.order_date is NULL
You can use the year method in the SELECT section of your query
Update:
To display all the customers who didn't order in a year, you don't need the join. You can do it with a single subselect:
SELECT #Parameter1 AS Year, customer_id
FROM customers
WHERE customers.customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE Year(order_date) = Year(#Parameter1)
)
Related
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 1 year ago.
I have some task that I should do it with sql.
Below is the function I currently use.
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 1,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
UNION
(SELECT ProductName, sum(Quantity*Price) Revenue, Country
FROM products p
JOIN orderdetails d
ON p.ProductID = d.ProductID
JOIN orders o
ON o.OrderID = d.OrderID
JOIN customers c
ON c.CustomerID = o.CustomerID
WHERE Country =
(
SELECT DISTINCT Country
FROM customers
LIMIT 2,1
)
GROUP BY ProductName
ORDER BY Revenue DESC
LIMIT 1)
My task is "Find best selling products based on revenue for each country!"
The result I want is below:
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
You can access the data I use from this link RawDatabase
the sample data that I use is like this
ProdName
Country
Revenue
coco
Argentina
120
bread
Austria
10000
crunch
Austria
13265
Cote de Blaye
Austria
18445
milk
Argentina
254
Tofu
Argentina
279
From this data I want to select only the best product for each country by revenue. In the data there were 21 country. What should I do so I can get the result below
ProductName
Revenue
Country
Tofu
279
Argentina
Côte de Blaye
18445
Austria
The only way in my mind is only filtering the data by each country then get the best product then union all of them like the code I give on top. I wonder if there's another way.
Using row_number window function or compare to country maxrevenue
DROP TABLe if exists t;
create table t
(ProdName varchar(20), Country varchar(20), Revenue int);
insert into t values
('coco' ,'Argentina' ,120),
('bread' ,'Austria' ,10000),
('crunch','Austria' ,13265),
('Cote de Blaye' ,'Austria', 18445),
('milk' ,'Argentina' ,254),
('Tofu' ,'Argentina' ,279);
select *
from
(
select prodname,country,revenue,
row_number() over(partition by country order by revenue desc) rn
from t
) s
where rn = 1;
or
select *
from t
join (select t.country,max(t.revenue) maxrevenue from t group by t.country) t1
on t1.country = t.country and t1.maxrevenue = t.revenue;
+---------------+-----------+---------+----+
| prodname | country | revenue | rn |
+---------------+-----------+---------+----+
| Tofu | Argentina | 279 | 1 |
| Cote de Blaye | Austria | 18445 | 1 |
+---------------+-----------+---------+----+
2 rows in set (0.001 sec)
Table - Customers
id FirstName LastName
23 James Smith
24 Tom Raven
25 Bob King
Table - Orders
id CustomerID
30 23
31 24
32 23
33 25
Table - Products
OrderID Product1 Product2 Product3
30 1 0 0
31 2 0 0
32 0 1 1
33 2 1 0
I want to count the total number of Products for each Customer so answers would be:
CustomerID FirstName LastName Total
23 James Smith 3
24 Tom Raven 2
25 Bob King 3
So far I've got for the SQL query:
SELECT Customers.id, Orders.id,
FROM Customers
INNER JOIN Orders ON Customers.id = Orders.CustomerID
INNER JOIN Products ON Orders.id = Products.OrderID
Not sure how to count up the products though.
Any help would be much appreciated.
select c.id as CustomerID
,(sum(p.Product1) + sum(p.Product2) + sum(p.Product3)) as ProductCount
from Customers c
inner join Orders o on c.id = o.CustomerID
inner join Products p on o.id = p.OrderID
group by c.id
You could use
SELECT c.id as CustomerID, c.firstname, c.lastname,
sum(p.Product1 + p.Product2 + p.Product3) as total
FROM Customers c
INNER JOIN Orders o
ON c.id=o.CustomerID
INNER JOIN Products p
ON o.id=p.OrderID
group by c.id,c.firstname, c.lastname;
And as #Thorsten Kettner's comment, you should consider normalizing your table design.
Add a GROUP BY clause! Normally it includes the selected columns that are not arguments to a set function.
SELECT O.CustomerID, C.FirstName, C.LastName, count(*) as Total
FROM Customers C
INNER JOIN Orders O ON C.id = O.CustomerID
INNER JOIN Products P ON O.id = P.OrderID
GROUP BY O.CustomerID, C.FirstName, C.LastName
PS. Now using table aliases, to save some typing.
SELECT Customers.id, Customers.firstname, Customers.lastname,count(*) as total FROM Customers
INNER JOIN Orders
ON Customers.id=Orders.CustomerID
INNER JOIN Products
ON Orders.id=Products.OrderID
group by Customers.id,Customers.firstname, Customers.lastname
A more conventional approach might be as follows:
DROP TABLE IF EXISTS customers;
CREATE TABLE customers
(customer_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,firstname VARCHAR(20) NOT NULL
,lastname VARCHAR(20) NOT NULL
);
INSERT INTO customers VALUES
(23,'James','Smith'),
(24,'Tom','Raven'),
(25,'Bob','King');
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(order_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,customer_id INT NOT NULL
);
INSERT INTO orders VALUES
(30 ,23),
(31 ,24),
(32 ,23),
(33 ,25);
DROP TABLE IF EXISTS order_detail;
CREATE TABLE order_detail
(order_id INT NOT NULL
,product_id INT NOT NULL
,quantity INT NOT NULL
,PRIMARY KEY(order_id,product_id)
);
INSERT INTO order_detail VALUES
(30 ,1 ,1),
(31 ,1 ,2),
(33 ,1 ,2),
(32 ,2 ,1),
(33 ,2 ,1),
(32 ,3 ,1);
SELECT c.*
, SUM(od.quantity) total
FROM customers c
JOIN orders o
ON o.customer_id = c.customer_id
JOIN order_detail od
ON od.order_id = o.order_id
GROUP
BY c.customer_id;
+-------------+-----------+----------+-------+
| customer_id | firstname | lastname | total |
+-------------+-----------+----------+-------+
| 23 | James | Smith | 3 |
| 24 | Tom | Raven | 2 |
| 25 | Bob | King | 3 |
+-------------+-----------+----------+-------+
SELECT DISTINCT CustomerID, FirstName, LastName, Total
FROM ( SELECT id AS CustomerID, FirstName, LastName FROM Customers ) AS c
NATURAL JOIN
( SELECT id AS OrderID, CustomerID FROM Orders ) AS o
NATURAL JOIN
( SELECT OrderID, SUM( Product1 + Product2 + Product3 ) AS Total
FROM Products
GROUP
BY OrderID ) AS p;
3 tables.
table_customers - customer_id, name
table_orders - order_id, customer_id, order_datetime
table_wallet - customer_id, amount, type // type 1- credit, type 2- debit
Need to get all customers, their total balance, and their last order date and order id. If customer have not placed any return order date as 0000-00-00 and order id as 0.
This is my query.
SELECT
C.customer_id,
C.name,
COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
COALESCE( max( O.order_id ) , '0' ) AS last_order_id,
COALESCE( max( date( O.order_datetime ) ) , '0000-00-00' ) AS last_order_date
FROM
table_customers as C
LEFT JOIN
table_wallet as W
ON C.customer_id = W.customer_id
LEFT JOIN
table_orders AS O
ON W.customer_id = O.customer_id
group by C.customer_id
ORDER BY C.customer_id
Everything is coming correct except customer's value. From result it seems its getting added multiple times.
I have created the fiddle here. http://sqlfiddle.com/#!9/560f2/1
What is wrong in query? Can anyone help me on this?
Edit: Expected result
customer_id name value last_order_id last_order_date
1 abc 20 3 2016-06-22
2 def 112.55 0 0000-00-00
3 pqrs 0 4 2016-06-15
4 wxyz 0 0 0000-00-00
The issue is that the join between orders and wallet will produce as many rows as there as orders for each wallet, when you really just want one row per wallet from the order table (since you only use the max values). In your test case you get 3 rows for customer 1 which makes the sum 60 (3*20).
One way to solve this is to change to this:
SELECT
C.customer_id,
C.name,
COALESCE( SUM(CASE WHEN type = 2 THEN -W.amount ELSE W.amount END), 0) AS value,
COALESCE( O.order_id , '0' ) AS last_order_id,
COALESCE( DATE( O.order_datetime ) , '0000-00-00' ) AS last_order_date
FROM table_customers AS C
LEFT JOIN table_wallet AS W ON C.customer_id = W.customer_id
LEFT JOIN (
SELECT
customer_id,
MAX(order_id) AS order_id,
MAX(order_datetime) AS order_datetime
FROM table_orders
GROUP BY customer_id
) AS O ON c.customer_id = O.customer_id
GROUP BY C.customer_id
ORDER BY C.customer_id
As you see the orders table is replaced by a derived table that gets you one row per customer.
Running the query above gets you the following result:
| customer_id | name | value | last_order_id | last_order_date |
|-------------|------|--------|---------------|-----------------|
| 1 | abc | 20 | 3 | 2016-06-22 |
| 2 | def | 112.55 | 0 | 0000-00-00 |
| 3 | pqrs | 0 | 4 | 2016-06-15 |
| 4 | wxyz | 0 | 0 | 0000-00-00 |
To further illustrate from the previous answers, if we simply remove your group by statement, you can easily see why you are double counting. The following code:
SELECT
C.*,
O.order_id, O.order_datetime,
W.amount, W.type
FROM
table_customers as C
LEFT JOIN
table_wallet as W
ON C.customer_id = W.customer_id
LEFT JOIN
table_orders AS O
ON W.customer_id = O.customer_id
Will yield the result:
customer_id name order_id order_datetime amount type
1 abc 1 April, 22 2016 23:53:09 20 1
1 abc 2 May, 22 2016 23:53:09 20 1
1 abc 3 June, 22 2016 23:53:09 20 1
2 def (null) (null) 100 1
2 def (null) (null) 12.55 1
3 pqrs (null) (null) (null) (null)
4 wxyz (null) (null) (null) (null)
Note the duplication of Customer ID 1 with amount 20.
This is the classic combinatorial explosion problem when you JOIN tables containing unrelated data.
You need to compute each customer's balance in a subquery. That subquery must yield either one row or zero rows per customer_id. It might look like this. (http://sqlfiddle.com/#!9/560f2/8/0)
SELECT customer_id,
SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
FROM table_wallet
GROUP BY customer_id
Similarly, you need to retrieve each customer's latest order in a subquery (http://sqlfiddle.com/#!9/560f2/10/0) . Again, it needs either one row or zero rows per customer_id.
SELECT customer_id,
MAX(order_id) AS order_id,
DATE(MAX(order_datetime)) AS order_date
FROM table_orders
GROUP BY customer_id
Then, you can LEFT JOIN those two subqueries as if they were tables, to your table_customers. The subqueries are tables; they're virtual tables. (http://sqlfiddle.com/#!9/560f2/12/0)
SELECT c.customer_id,
c.name,
w.value,
o.order_id,
o.order_date
FROM table_customers c
LEFT JOIN (
SELECT customer_id,
SUM(CASE WHEN type = 2 THEN -amount ELSE amount END) AS value
FROM table_wallet
GROUP BY customer_id
) w ON c.customer_id = w.customer_id
LEFT JOIN (
SELECT customer_id,
MAX(order_id) AS order_id,
DATE(MAX(order_datetime)) AS order_date
FROM table_orders
GROUP BY customer_id
) o ON c.customer_id = o.customer_id
Your mistake was this: you joined two tables each with multiple rows for each customer id. For example, a particular customer might have had two orders and three wallet rows. Then, the join results in six rows representing all the possible combinations of wallet and order rows. That's called combinatorial explosion.
The solution I outlined makes sure there's only one row (or maybe no rows) to join for each customer_id, and so eliminates the combinatorial explosion.
Pro tip: Using subqueries like this makes it easy to test your query: you can test each subquery separately.
There are two tables:
* ORDER
- id
- pay_type
* ORDER_PRICE
- order_id
- dt
- price
Order price can be changed, for example:
order_id | price | dt
1 | 100.3 | 2013-10-25
1 | 105.7 | 2013-10-28
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
I can select price history for any date like that:
SELECT
o.`id`,
(SELECT op.`price` FROM `order_price` op
WHERE op.`order_id`=o.`id` AND op.`dt` <= '2013-10-26'
ORDER BY op.`dt` DESC LIMIT 1) order_price
FROM `order` o
It gives me right prices for given date
order_id | price | dt
1 | 100.3 | 2013-10-25
2 | 207.4 | 2013-09-13
4 | 98.0 | 2013-10-03
But i need the sum of the second column (no matter what order number, only one number - 405.7 in this case).
Is there a solution for such a situation? There can be thousands of orders, so i think it will be wrong to sum records outside the mysql. Maybe it's the wrong all the way from the start and i need other structure? Thank you for your time and help.
I suspect the core query should look more like this...
SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt;
...which can be rewritten this way, to give totals
SELECT o.order_id
, SUM(op.price) price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
GROUP
BY order_id,dt
WITH ROLLUP;
...or this way
SELECT SUM(price) total
FROM
( SELECT o.order_id
, op.price
, op.dt
FROM orders o
JOIN order_price op
ON op.order_id = o.order_id
JOIN
( SELECT order_id, MAX(dt) max_dt FROM order_price WHERE dt < '2013-10-26' GROUP BY order_id ) x
ON x.order_id = op.order_id
AND x.max_dt = op.dt
) z;
...or just pull the total at the application level.
Below is my query that groups my customers together, and sums their total money spent over the last two years. Works like a charm. However I need a little more which goes over my sql knowledge.
Is there any way to select three more columns and populate it with the money they spent for each of those years (2010, 2011, 2012)?
SELECT SUM(price) AS money_spent_total, co.customer_id, cu.first_name, cu.last_name, cu.email_primary, cu.phone_primary, co.date_paid
FROM customer_order AS co
INNER JOIN customer AS cu ON (cu.customer_id = co.customer_id)
WHERE cu.customer_id != 32518 AND co.date_paid > "2010-1-1" AND co.date_paid < "2013-1-1"
GROUP BY co.customer_id
You need to GROUP BY YEAR(date_paid) in addition to customer_id. You should also not be selecting any columns which are not in your GROUP BY. In addition, you can use the BETWEEN operator instead of > and < for your date range.
SELECT Year(co.date_paid),
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id
To get the columns in your original SELECT, you would do something like (not tested):
SELECT a.date_year_paid,
a.customer_id,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT Year(co.date_paid) AS date_year_paid,
co.customer_id,
Sum(price) AS money_spent_total
FROM customer_order AS co
INNER JOIN customer AS cu
ON ( cu.customer_id = co.customer_id )
WHERE cu.customer_id != 32518
AND co.date_paid BETWEEN '2010-01-01' AND '2013-01-01'
GROUP BY Year(co.date_paid),
co.customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
If you were doing the INNER JOIN just for customer info, then it can be dropped.
SELECT a.customer_id,
a.date_year_paid,
a.money_spent_total,
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id, YEAR(date_paid) AS date_year_paid, SUM(price) AS money_spent_total
FROM customer_order
GROUP BY customer_id, YEAR(date_paid)) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Lastly, if you want to group the years into columns:
SELECT a.customer_id,
a.y2010 AS '2010_money_paid',
a.y2011 AS '2011_money_paid',
a.y2012 AS '2012_money_paid',
cu.first_name,
cu.last_name,
cu.email_primary,
cu.phone_primary
FROM (SELECT customer_id,
Sum(CASE
WHEN Year(date_paid) = 2010 THEN price
ELSE 0
end) AS 'y2010',
Sum(CASE
WHEN Year(date_paid) = 2011 THEN price
ELSE 0
end) AS 'y2011',
Sum(CASE
WHEN Year(date_paid) = 2012 THEN price
ELSE 0
end) AS 'y2012'
FROM customer_order
GROUP BY customer_id) a
LEFT JOIN customer cu
ON cu.customer_id = a.customer_id
Result
| CUSTOMER_ID | 2010_MONEY_PAID | 2011_MONEY_PAID | 2012_MONEY_PAID | FIRST_NAME | LAST_NAME | EMAIL_PRIMARY | PHONE_PRIMARY |
------------------------------------------------------------------------------------------------------------------------------
| 1 | 9000 | 3000 | 2000 | Bob | Smith | bob#smith.com | 1112223333 |
| 2 | 4000 | 5000 | 1000 | Tom | Jones | tom#jones.com | 2223334444 |
See the demo