MySQL SQL: SELECT (SELECT... as variable) FROM - mysql

I have a simple question but I can't find an answer (please give me a link if that question is on stackoverflow forum).
Table has orderid and quantity columns.
How to select orderid, quantity, total_in_order, where total_in_order is a sum of quantity for current orderid
So the table looks the same, but with additional column.
Example:
orderid - quantity - **total_in_order**
1 - 43 - **78**
1 - 24 - **78**
1 - 11 - **78**
2 - 31 - **31**
3 - 9 - **25**
3 - 16 - **25**
So, how to modify SELECT orderid, quantity, SUM(quantity) from orders; ?
Thank you.

Use a join to a subselect. In the subselect calculate the totals for each orderid.
SELECT
orders.orderid,
orders.quantity,
totals.total_in_order
FROM orders
JOIN
(
SELECT orderid, SUM(quantity) AS total_in_order
FROM orders
GROUP BY orderid
) AS totals
ON orders.orderid = totals.orderid

First approach:
SELECT
O.orderid,
O.quantity,
( select sum( O2.quantity)
from orders O2
where O.orderid = O2.orderid ) as qty
from
orders O

Here is an example on how to do it using a subquery. It should do the job.
SELECT A.orderid
A.quantity
B.sum
FROM
orders A JOIN ( SELECT orderid, sum(quantity) as sum from orders group by orderid) as B on(A.orderid=B.orderid)

Related

How to find the repeat customers in the list?

I Have Purchase Table Containing 5 Columns
Columns Names Are
CustomerID, BillID, ProductID, unatity, Payment_Type
Columns Values Are
CID00001, BID00001, PID001, 1, Card
Total Customers Count - 37156
DISTINCT Customers Count - 26053
How to Find the repeat Customers? (37156 - 26053 = 11103)
Aggregation is one way:
SELECT COUNT(*) AS num_repeat
FROM
(
SELECT CustomerID
FROM purchases
GROUP BY CustomerID
HAVING COUNT(*) > 1
) t;
To get the list of repeat customers,
SELECT CustomerID, COUNT(*) AS PurchaseCount
FROM purchases
GROUP BY CustomerID
HAVING COUNT(*) > 1
You can use this :
SELECT * FROM Purchase
WHERE CustomerID
IN(
SELECT CustomerID FROM Purchase
GROUP BY CustomerID HAVING COUNT(*) > 1
)

MySQL Find closest date and corresponding records between 2 tables

I'm trying to get the closest subscription date to each order date (only for subscription dates that are before order dates) and also the corresponding cancellation date. Is there anyway to do this? I use mainly MySQL but any SQL variant that works in Snowflake should be ok.
My Code:
Select DISTINCT o.ORDER_ID, o.CUSTOMER_EMAIL, o.order_DATE min(subs.START_DATE)
FROM ORDERS o
INNER JOIN subscriptions subs
GROUP BY ORDER_ID, CUSTOMER_EMAIL
,ORDER_DATE
ON o.CUSTOMER_EMAIL = subscriptions.CUSTOMER_EMAIL
ORDERS Table:
ORDER_ID
ORDER_DATE
CUSTOMER_EMAIL
1234
20/02/2021
jay#gmail.com
Subscriptions table:
|SUBSCRIPTION_ID | START_DATE | CUSTOMER_EMAIL | CANCEL_DATE |
|----------------|------------|----------------|-------------|
|1236 | 20/03/2021 |jay#gmail.com | 20/04/2021|
|1232 | 19/02/2021 |jay#gmail.com | 19/03/2021|
|1219 | 20/01/2021 |jay#gmail.com | 29/01/2021|
Expected Result:
ORDER_ID
ORDER_DATE
CUSTOMER_EMAIL
EARLIEST_SUBSCRIPTION_START
CLOSEST_SUBSCRIPTION_START
CLOSEST_SUBSCRIPTION_END
1234
20/02/2021
jay#gmail.com
20/01/2021
19/02/2021
19/03/2021
A snowflake focused solution:
So with some CTEs for to fake the data:
WITH orders(order_id, order_date, email) AS (
SELECT column1, to_date(column2,'DD/MM/YYYY'), column3 FROM VALUES
(1234, '20/02/2021', 'jay#gmail.com')
), subscriptions(subscription_id, start_date, email, cancel_date) AS (
SELECT column1, to_date(column2,'DD/MM/YYYY'), column3, to_date(column4,'DD/MM/YYYY') FROM VALUES
(1236, '20/03/2021', 'jay#gmail.com', '20/04/2021'),
(1232, '19/02/2021', 'jay#gmail.com', '19/03/2021'),
(1219, '20/01/2021', 'jay#gmail.com', '29/01/2021')
)
and this SQL:
SELECT
o.order_id,
o.order_date,
o.email,
FIRST_VALUE(s.start_date) OVER (PARTITION BY o.order_id, o.order_date, o.email ORDER BY s.start_date) as EARLIEST_SUBSCRIPTION_START,
start_date AS CLOSEST_SUBSCRIPTION_START,
cancel_date AS CLOSEST_SUBSCRIPTION_END
FROM orders AS o
JOIN subscriptions AS s
ON o.email = s.email AND o.order_date > s.start_date
QUALIFY row_number() over(PARTITION BY o.order_id, o.order_date, o.email ORDER BY s.start_date desc ) = 1
we get:
ORDER_ID
ORDER_DATE
EMAIL
EARLIEST_SUBSCRIPTION_START
CLOSEST_SUBSCRIPTION_START
CLOSEST_SUBSCRIPTION_END
1234
2021-02-20
jay#gmail.com
2021-01-20
2021-02-19
2021-03-19
This is using the WINDOW function FIRST_VALUE to get the first row for each row, then we use QUALIFY to drop the rows (per order,oder_date,email) that are not the first when sorted by subscription date in reverse.
Sorry, editing my answer. It isn't really clear what you are asking in total. If you just do a simple INNER JOIN LIKE:
SELECT ORDER_ID, ORDER_DATE, orders.CUSTOMER_EMAIL, subscriptions.START_DATE, subscriptions.SUBSCRIPTION_ID, subscriptions.CANCEL_DATE, DATEDIFF(ORDER_DATE, subscriptions.START_DATE) as DIFF
FROM orders INNER JOIN subscriptions ON orders.CUSTOMER_EMAIL = subscriptions.CUSTOMER_EMAIL
WHERE ORDER_DATE < subscriptions.START_DATE
You get results that look like:
ORDER_ID ORDER_DATE CUSTOMER_EMAIL START_DATE SUBSCRIPTION_ID CANCEL_DATE DIFF
1234 2021-01-20 jay#gmail.com 2021-02-20 1232 2021-03-19 -31
1234 2021-01-20 jay#gmail.com 2021-03-20 1236 2021-02-20 -59
Seems like you would have to place an order before the subscription starts, the the other way around ? Not sure what you mean by earliest vs. closest either.
Adding an update, which seems closer and requires the customer e-mail as the parameter:
SELECT ORDER_ID, ORDER_DATE, orders.CUSTOMER_EMAIL, earliest.EARLIEST_SUBSCRIPTION_START, closest.CLOSEST_SUBSCRIPTION_START, closestcancel.CLOSEST_SUBSCRIPTION_END from orders
LEFT JOIN
(SELECT MIN(START_DATE) as EARLIEST_SUBSCRIPTION_START from subscriptions WHERE CUSTOMER_EMAIL = 'jay#gmail.com') earliest ON orders.CUSTOMER_EMAIL = 'jay#gmail.com'
LEFT JOIN
(SELECT DATEDIFF(subscriptions.START_DATE,orders.ORDER_DATE) as days, START_DATE as CLOSEST_SUBSCRIPTION_START from orders INNER JOIN subscriptions ON orders.CUSTOMER_EMAIL = subscriptions.CUSTOMER_EMAIL WHERE orders.CUSTOMER_EMAIL = 'jay#gmail.com' ORDER BY ABS(days) LIMIT 1) closest ON orders.CUSTOMER_EMAIL = 'jay#gmail.com'
LEFT JOIN
(SELECT DATEDIFF(subscriptions.CANCEL_DATE,orders.ORDER_DATE) as days, CANCEL_DATE as CLOSEST_SUBSCRIPTION_END from orders INNER JOIN subscriptions ON orders.CUSTOMER_EMAIL = subscriptions.CUSTOMER_EMAIL WHERE orders.CUSTOMER_EMAIL = 'jay#gmail.com' ORDER BY ABS(days) LIMIT 1) closestcancel ON orders.CUSTOMER_EMAIL = 'jay#gmail.com'
You would have to run it on your own table since I'm not sure the data is correct. It is looking forwards and backwards in time for the "Closest" (sorting by Absolute value), so you might have to adjust some things there.

How can I find MIN value for each group and select the full row?

I have a PROJECTS table with PROJECT_ID, CUSTOMER_ID, COMPANY_ID and COST columns.
One customer could have one or several projects in different companies.
What I want is to choose a customer, who generates the smallest income for each company.
For example if the table looks like this
project_id customer_id company_id cost
1 1 1 1000
2 1 1 100
3 2 1 3000
4 1 2 300
5 2 2 100
, the expected answer is:
(COMPANY_ID) 1 | (CUSTOMER_ID) 1 | (COST) 1100
(COMPANY_ID) 2 | (CUSTOMER_ID) 2 | (COST) 100
Because the first customer generates 1000 + 100 = 1100 in total.
My query looks like this:
SELECT TABLE1.company_id, TABLE1.customer_id, MIN(profit)
FROM (
SELECT company_id, customer_id, SUM(projects.cost) AS profit
FROM projects
GROUP BY company_id,customer_id
) AS TABLE1
GROUP BY TABLE1.company_id;
It counts the MIN profit, but the ID's in CUSTOMER_ID column are always wrong. How can I build a connection between customers' IDs and their total profit for each company? Is it possible?
Thanks for helping.
One method for doing this is a "hack", because it uses string operations to get the value you want:
SELECT cc.company_id,
SUBSTRING_INDEX(GROUP_CONCAT(cc.customer_id ORDER BY profit ASC), ',', 1) as customer_id,
MIN(profit)
FROM (SELECT p.company_id, p.customer_id, SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
) cc
GROUP BY cc.company_id;
An alternative in MySQL is something like this:
SELECT p.company_id, p.customer_id, SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
HAVING SUM(p.cost) = (SELECT SUM(p2.cost)
FROM projects p2
WHERE p2.company_id = p.company_id
ORDER BY SUM(p2.cost) ASC
LIMIT 1
);
The two versions are subtly different:
The first will always return one customer, even if there are ties.
The first will convert the company_id to a string.
The first can run into overflow conditions, because the length of the intermediate result for group_concat() is controlled by a system parameter.
Use HAVING and ALL
SELECT p.company_id,
p.customer_id,
SUM(p.cost) AS profit
FROM projects p
GROUP BY p.company_id, p.customer_id
HAVING SUM(p.cost) <= ALL(
SELECT SUM(p2.cost)
FROM projects p2
WHERE p2.company_id = p.company_id
GROUP BY p2.customer_id
)
E.g.:
SELECT a.*
FROM
( SELECT customer_id
, company_id
, SUM(cost) total
FROM my_table
GROUP
BY customer_id
, company_id
) a
JOIN
( SELECT company_id
, MIN(total) min_total
FROM
( SELECT customer_id
, company_id
, SUM(cost) total
FROM my_table
GROUP
BY customer_id
, company_id
) x
GROUP
BY company_id
) b
ON b.company_id = a.company_id
AND b.min_total = a.total;

How to get rows with max date when grouping in MySQL?

I have a table with prices and dates on product:
id
product
price
date
I create a new record when price change. And I have a table like this:
id product price date
1 1 10 2014-01-01
2 1 20 2014-02-17
3 1 5 2014-03-28
4 2 25 2014-01-05
5 2 12 2014-02-08
6 2 30 2014-03-12
I want to get last price for all products. But when I group with "product", I can't get a price from a row with maximum date.
I can use MAX(), MIN() or COUNT() function in request, but I need a result based on other value.
I want something like this in final:
product price date
1 5 2014-03-28
2 30 2014-03-12
But I don't know how. May be like this:
SELECT product, {price with max date}, {max date}
FROM table
GROUP BY product
Alternatively, you can have subquery to get the latest get for every product and join the result on the table itself to get the other columns.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT product, MAX(date) mxdate
FROM tableName
GROUP BY product
) b ON a.product = b.product
AND a.date = b.mxdate
I think the easiest way is a substring_index()/group_concat() trick:
SELECT product,
substring_index(group_concat(price order by date desc), ',', 1) as PriceOnMaxDate
max(date)
FROM table
GROUP BY product;
Another way, that might be more efficient than a group by is:
select p.*
from table t
where not exists (select 1
from table t2
where t2.product = t.product and
t2.date > t.date
);
This says: "Get me all rows from the table where the same product does not have a larger date." That is a fancy way of saying "get me the row with the maximum date for each product."
Note that there is a subtle difference: the second form will return all rows that on the maximum date, if there are duplicates.
Also, for performance an index on table(product, date) is recommended.
You can use a subquery that groups by product and return the maximum date for every product, and join this subquery back to the products table:
SELECT
p.product,
p.price,
p.date
FROM
products p INNER JOIN (
SELECT
product,
MAX(date) AS max_date
FROM
products
GROUP BY
product) m
ON p.product = m.product AND p.date = m.max_date
SELECT
product,
price,
date
FROM
(SELECT
product,
price,
date
FROM table_name ORDER BY date DESC) AS t1
GROUP BY product;

MYSQL calculate amount from two table?

I have 2 mysql tables:
"Orders" table:
customer_id | money
3 120
5 80
3 45
3 70
6 20
"collecting" table:
customer_id | money
3 50
3 70
4 20
4 90
I want a result like:
"Total" table:
customer_id | Amount
3 115
4 110
5 80
6 20
"Total" table "customer_id" should be singular
Amount = (SUM(All customer orders.money) - SUM(All customer collecting.money))
"Money" can be NULL
"Orders" table can have customer_id and "Collecting" table may not have
Or
"Collecting" table can have customer_id and "Orders" table may not have
How can i write a single query for output "Total" table?
The following returns the result you expect.
SELECT
customer_id,
SUM(amount) as amount
FROM (
SELECT customer_id, SUM(money) as amount
FROM orders GROUP BY customer_id
UNION ALL
SELECT customer_id, SUM(money) * -1 as amount
FROM collecting GROUP BY customer_id
) as tb
GROUP BY customer_id;
customer_id = 4 returns -110, not 110, since it's only in the collecting table.
Example: http://www.sqlfiddle.com/#!2/3b922/5/0
The fastest way is to union your data with the money value being negative on the collecting table:
-- load test data
create table orders(customer_id int, money int);
insert into orders values
(3,120),
(5,80),
(3,45),
(3,70),
(6,20);
create table collecting(customer_id int,money int);
insert into collecting values
(3,50),
(3,70),
(4,20),
(4,90);
-- populate Total table
create table Total(customer_id int,Amount int);
insert into Total
select oc.customer_id,sum(oc.money) Amount
from (
select customer_id,coalesce(money,0) money from orders
union all
select customer_id,coalesce(-money,0) money from collecting
) oc
group by oc.customer_id;
-- return results
select * from Total;
SQL Fiddle: http://www.sqlfiddle.com/#!2/deebc
You need to do this by pre-aggregating the data. If I assume that the orders come first, you can use left outer join:
select o.customer_id, (o.money - coalesce(c.money)) as Amount
from (select o.customer_id, sum(o.money) as money
from orders o
group by o.customer_id
) o left outer join
(select c.customer_id, sum(c.money) as money
from collecting c
group by c.customer_id
) c
on o.customer_id = c.customer_id;
Something like this should work:
SELECT CASE
WHEN c.customer_id IS NULL
THEN o.customer_id
ELSE c.customer_id
END
,sum(o.MONEY) - sum(c.MONEY)
FROM orders o
OUTER JOIN collecting c ON c.customer_id = o.customer_id
GROUP BY 1
Try this:
SELECT customer_id, SUM(o_money - c_money) AS Amount
FROM (
SELECT customer_id, money AS o_money, 0 AS c_money FROM orders
UNION ALL
SELECT customer_id, 0 AS o_money, money AS c_money FROM collecting
) total
GROUP BY customer_id;
According to your description, you need a FULL [OUTER] JOIN, which is a combination of LEFT JOIN and RIGHT JOIN. Many databases don't support FULL JOIN, so you need to use UNION ALL to reach the same effect.