SQL - min(date) with conditionals - mysql

We ran a promotion where users can receive their first subscription order free. Price = $0.00 when a user uses the promo. I am interested in the data from Example A.
Example A - User 50 started with the promo and continued for two months
order_id user_id price created_at
1 50 0.00 2018-01-15
5 50 20.00 2018-02-15
9 50 20.00 2018-03-15
Example B - User 100 was already an active subscriber who cancelled his account and reactivated with the promo, I do not wish to count him
order_id user_id price created_at
2 100 20.00 2018-01-16
3 100 0.00 2018-01-17
7 100 20.00 2018-02-17
--Here is my query--
This returns all users who have multiple orders
WHERE at least one of their orders has a price = 0.00
-This dataset returns example A and example B
--My question--
Most of this data is correct (Example A) but a handful of them I want to omit because they are skewing my data (Example B). I want to remove Example B users.
I want to remove people who's first order was not the promo.
How can I request that their FIRST order had a price = 0.00? I was thinking something with min(created_at)?

You can get the time of the first order using:
select user_id, min(created_at) as min_ca
from t
group by user_id;
Next, you can get the price of the first order using:
select oi.*
from order_items oi join
(select user_id, min(created_at) as min_ca
from order_items oi
group by user_id
) ooi
on oi.user_id = ooi.user_id and oi.created_at = ooi.min_ca
where oi.price = 0.00;
Then you can get all records, using join, in, or exists;
select oi.*
from order_items oi join
order_items oi1
on oi.user_id = oi1.user_id join
(select user_id, min(created_at) as min_ca
from order_items oi
group by user_id
) u1
on oi1.user_id = u1.user_id and oi1.created_at = u1.min_ca
where oi1.price = 0.00;

You can use EXISTS to check that for the record with zero price there is no earlier created_at:
SELECT COUNT(*), user_id
FROM Promo
WHERE user_id IN (
-- Query below yields [user_id]s of users who got the promo
-- that wasn't a result of a cancellation and re-activation
SELECT user_id
FROM Promo p
WHERE p.price = 0 AND NOT EXISTS (
-- Look for a record with the same user ID and an earlier date
-- than p.created_at, which is the date of the promo with 0.00 price
SELECT *
FROM Promo pp
WHERE pp.user_id=p.user_id AND pp.created_at < p.created_at
)
)
GROUP BY user_id

Related

Select rows from table A where ID doesn't exists on table B or if exists, select only if is more than 3 months old

I have two tables, one that stores the carts that the customers create by adding products in the front end of our ecommerce website, the other table (orders) is populated after the customer finishes checkout.
Some users have abandoned carts, so I want to check the carts table comparing it against the orders table by two conditions:
If user has a cart entry and no orders entries of his own, then select that cart
If user has a cart entry and some order entries, but user has no orders in the last three months and the cart is newer than three months, then select that cart
Table carts
cart_id user_id modified_on
5477 1125 2022-01-04 15:31:31
5476 2998 2022-01-04 14:34:31
5474 1305 2022-01-03 21:52:57
5473 986 2022-01-03 13:13:12
5471 3040 2022-01-03 01:32:28
Table orders
order_id user_id created_on
44 927 2018-11-23 00:26:43
46 932 2018-11-26 14:36:28
47 945 2018-11-26 15:35:34
48 948 2018-11-27 21:33:37
53 945 2018-12-02 18:20:55
So far I have come with this query nut I know it's wrong
SELECT DISTINCT
`vmc`.`user_id`,
`vmo`.`order_id`,
`vmc`.`created_on` AS `Order Created On`,
`vmc`.`modified_on` AS `Cart Last Modified`,
FROM `carts` `vmc`
LEFT JOIN `orders` `vmo`
ON `vmc`.`user_id` = `vmo`.`user_id`
WHERE `vmo`.`order_id` IS NULL
OR (`vmo`.`created_on` <= NOW() - INTERVAL 3 MONTH AND `vmc`.`cart_id` <> NULL)
ORDER BY `vmc`.`modified_on` DESC
The following query should give you what you want:
SELECT
`carts`.`user_id`,
`sq2`.`order_id`,
`sq2`.`last_order_date` AS `Last Order Date`,
`carts`.`cart_id`,
`carts`.`modified_on` AS `Cart Last Modified`
FROM `carts`
LEFT JOIN (
SELECT `user_id`, `order_id`, `sq1`.`last_order_date`
FROM `orders` INNER JOIN (
SELECT `user_id`, MAX(`created_on`) as `last_order_date`
FROM `orders`
GROUP BY `order_id`
) `sq1` ON `orders`.`user_id` = `sq1`.`user_id`
) `sq2` ON `carts`.`user_id` = `sq2`.`user_id`
WHERE `sq2`.`order_id` IS NULL OR `sq2`.`last_order_date` <= NOW() - INTERVAL 3 MONTH
ORDER BY `carts`.`modified_on` DESC
The subquery with alias sq1 produces a relation of each unique user_id in the orders table with the maximum created_on date (aliased as last_order_date) for that user_id. The subquery with alias sq2 does an inner join of the previous query with the orders table so that we can also get the order_id column associated with that maximum created_on row for each user_id. Finally, we do a left join of the carts table with the sq2 table and apply our conditions.

MySQL How to get the row with max date when joining multiple tables?

My goal is the get a list of current prices and prices at the time of whatever date is given. The price as of today is always product.price. Each time a new price is set, an entry is added to product_audit and revinfo.
If we are looking for what the prices were on 2020-11-31, it would return:
num CurrentPrice OldPrice
--------------------------------------
1001 100 175
1030 110 100
2010 150 130
EDIT FOR CLARIFICATION: My intention is to get what the price was on a specific day. So OldPrice is actually the newest entry in Product_aud/revinfo that is before or on the set date (in this case, 2020-11-31). Looking specifically at code 1001, the price was changed on 2020-08-02, 2020-09-26, and 2020-01-08. If we are looking at 2020-11-31, that means it should grab 2020-09-26 because it is the soonest date before then. This means the price of 1001 on 2020-11-31 was 175.
There are three tables: Product, product_audit, revinfo
Everytime the price is changed, an entry is added to product_audit with the new price and a reference to a new entry in revinfo that has the date/time. Revinfo contains entries for other audit tables mixed in.
product.id = product_audit.id
product_audit.rev = revinfo.id
product
id num price
------------------------
1 1001 100
2 1030 110
3 2010 150
product_audit
id rev price
------------------------
1 1 200
1 3 175
1 6 100
2 2 100
2 7 110
3 4 130
3 5 120
3 8 150
revinfo
id timestamp
-------------------
1 2020-08-02
2 2020-09-25
3 2020-09-26
4 2020-11-12
5 2020-12-20
6 2021-01-08
7 2021-01-09
8 2021-01-23
Of course this just returns the oldest price from product_audit:
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN product_audit ON product_audit.id = product.id
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE rev.timestamp <= "2020-11-31"
GROUP BY product.id
I tried nesting joins like this based on some stuff I was reading, but quickly realized it still wasn't going to get the right price:
SELECT product.id, product.num, product.price AS CurrentPrice, revisions.price AS OldPrice
FROM product
LEFT JOIN (SELECT product_audit.id AS id, product_audit.price AS price, MAX(revinfo.timestamp) AS timestamp
FROM product_audit
LEFT JOIN revinfo ON product_audit.rev = revinfo.id
WHERE revinfo.timestamp <= $DATE{Date}
GROUP BY product_aud.id) AS revisions ON revisions.id = product.id
I can't seem to think of how to get to that last step. Some sort of WHERE timestamp = (SELECT...) maybe? But I haven't been able to figure that out.
Also, just a heads up, I'm limited to statements that start with SELECT because of permissions. I can't add functions or anything like that.
I had to assume how we were getting the "old" price, and my assumption was that you wanted the "earliest" revision record, so I used Row_number and a derived table to get that record and then use it in the join constraint for the revision table... not exactly sure what your logic is, but here is a fiddle with the resultset that matches your "desired results"
SELECT product.num, product.price AS CurrentPrice, product_audit.price AS OldPrice
FROM product
LEFT JOIN (select p.price, p.id, p.rev,
ROW_NUMBER() over (partition by p.id order by p.rev asc) as rn
From product_audit p
) AS product_audit ON product_audit.id = product.id
and product_audit.rn = 1
LEFT JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.timestamp <= '2020-11-31';
https://www.db-fiddle.com/f/fbvrgo2gRLoBPhgwQnuvY9/3
WITH cte AS ( SELECT product.num,
product.price CurrentPrice,
product_audit.price OldPrice,
ROW_NUMBER() OVER (PARTITION BY product.num
ORDER BY revinfo.`timestamp` DESC) rn
FROM product
JOIN product_audit ON product_audit.id = product.id
JOIN revinfo ON revinfo.id = product_audit.rev
WHERE revinfo.`timestamp` <= #date
)
SELECT num, CurrentPrice, OldPrice
FROM cte
WHERE rn = 1;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a276ec8ad89e3c2f3aaeee411072fa3e

Mysql SUM and GROUP BY from 3 tables

I have 3 database tables as follows:
projects:
id
project_name
project_location
status
project_expenses
id
project_id
expense_category
expense_subcategory
amount
userid
date
payment_status
project_status
project_income
id
project_id
project_income
date
userid
project_status
projects.id, project_expenses.project_id and project_income.project_id are related.
I need to create a query displaying Project_ID,Project_Name,SUM of Project_Income,SUM of Project_expenses
I tried the following query but not getting correct result.
SELECT p.id AS id, p.project_name AS project_name, SUM(i.project_income) AS income, SUM(e.amount) AS expenses
FROM project_income i, project_expenses e, projects p
WHERE i.project_id = p.id AND e.project_id = p.id AND p.status = 'Active'
GROUP BY id
I have currently 2 rows in project_income and 4 rows in project_expenses. The result is project_income displays double the values. Something is wrong with my JOIN.
Being a newbie I am unable to understand what i am doing wrong?? Requesting help...
Use sub-selects in the query result columns. No need for GROUP BY.
SELECT p.id
, p.project_name
, ( SELECT SUM(i.project_income)
FROM project_income i
WHERE i.project_id = p.id
) AS income
, ( SELECT SUM(e.amount)
FROM project_expenses e
WHERE e.project_id = p.id
) AS expenses
FROM projects p
WHERE p.status = 'Active'
The problem with the query in the question is best explained with an example.
You say there are 2 rows in project_income and 4 rows in project_expenses. Let say the 2 incomes are 1000 and 1500, and the 4 expenses are 615, 750, 840, and 900.
Since there are no restrictions between them, that means you'll get the cross join, i.e. 8 records:
income expense
1000 615
1000 750
1000 840
1000 900
1500 615
1500 750
1500 840
1500 900
Now, when you sum income you get 4 times the value you want, and when you sum expense you get 2 times the value you want.

MYSQL Query users orders if they used a certain coupon code

I'm trying to find out if a user who used a discount code has ordered after the initial order.
The three tables are: order, user, coupon_uses
From coupon_uses, the only data I'm retrieving is users who used code via:
SELECT *
FROM coupon_uses
WHERE coupon_id = 21921
I would get a return a table with the user ID, order ID, and Coupon they used.
ID Coupon_ID User_ID Order_ID
11 21921 148871 1448181
21 21921 888381 1448191
31 21921 888411 1448201
41 21921 354311 1448211
51 21921 452671 1448221
61 21921 684791 1448231
Now, I need to check the users that are returned in the first query (who used the code) verses the entire order table:
I tried something like this:
SELECT order.user_id,
COUNT(*) AS Total_Orders
FROM `order`
WHERE `order`.user_id = (
SELECT user_id
FROM coupon_uses
WHERE coupon_id = 21921)
AND order.order_status != "Cancelled"
GROUP BY order.user_id ASC
ORDER BY `order`.orderplaced_ts
But I receive Subquery returns more than 1 row.
The desired result would be return a list of user IDs, with the total orders they placed and the date of of the order.
User_ID Total_Orders Last_Order
148871 17 2015_01_01
888381 19 2015_01_01
888411 3 2015_01_14
354311 5 2015_05_01
452671 99 2015_02_01
684791 213 2015_01_05
Thanks.
Try this:
SELECT o.user_id, COUNT(*) AS total_orders
FROM order AS o
INNER JOIN coupon_uses AS c ON o.user_id = c.user_id
WHERE c.coupon_id = 21921
GROUP BY o.user_id
ORDER BY MIN(o.orderplaced_ts)
This assumes that a user can only use a coupon once.

Join tables and preform aggregation on each of them

I have the following tables:
table part_list:
part_number | description | type
100 blablabla blabla
table part_list_supplier:
part_id | artikel
100 100100
100 200100
and I have this query:
select part_list.part_number, part_list.description, part_list.type, group_concat(coalesce(part_list_supplier.artikel, "nothing")) as "artikel"
from part_list
left join part_list_supplier on (part_list.part_number = part_list_supplier.part_id)
group by part_list.part_number;
this is the result:
part_number | description | type | artikel
100 blablablabla blabla 100100,200100
but I want to show the total stock per partnumber behind it. table receipt:
Number | import
100 5
100 10
table sales:
Number | sold
100 5
this is my query for one table:
SELECT SUM(sold) AS sold
FROM sales WHERE number = '".$partnumber.”'
but I want to calculate the stock per number and that must be shown behind the other results.
the full result:
part_number | description | type | artikel | stock
100 blablablabla blabla 100100,200100 10
The stock should be 10 because the total number of imports is 15 (5 + 10) and the total number of sales is 5.
I broke this up into pieces to solve it. I started by writing two queries, one that counted total receipt and one that counted total sales:
SELECT r.number, SUM(r.import) AS totalIn
FROM receipt r
GROUP BY r.number;
SELECT s.number, SUM(s.sold) AS totalOut
FROM sales s
GROUP BY s.number;
Then, I used those as two subqueries of a join to get the stock:
SELECT r.number, totalIn - totalOut AS stock
FROM(
SELECT r.number, SUM(r.import) AS totalIn
FROM receipt r
GROUP BY r.number) r
JOIN(
SELECT s.number, SUM(s.sold) AS totalOut
FROM sales s
GROUP BY s.number) s ON s.number = r.number;
Once I verfied this gave the proper stock, I was able to include those subqueries into your original query to build this:
SELECT pl.part_number, pl.description, pl.type,
GROUP_CONCAT(COALESCE(pls.artikel, "Nothing.")) AS artikel,
r.totalIn - s.totalOut AS stock
FROM part_list pl
LEFT JOIN part_list_supplier pls ON pls.part_id = pl.part_number
JOIN(
SELECT number, SUM(import) AS totalIn
FROM receipt
GROUP BY number) r ON r.number = pl.part_number
JOIN(
SELECT number, SUM(sold) AS totalOut
FROM sales
GROUP BY number) s ON s.number = r.number
GROUP BY pl.part_number;
Here is an SQL Fiddle example.
I may not be understanding your question properly, but can't you just add sum(sales.sold) to your select statement and join the sales table? E.g.:
select part_list.part_number, part_list.description, part_list.type, group_concat(coalesce(part_list_supplier.artikel, "nothing")) as "artikel", sum(sales.sold)
from part_list
left join part_list_supplier on (part_list.part_number = part_list_supplier.part_id)
left join sales on (part_list.part_number = sales.number
group by part_list.part_number;