select particular items ordered n times - mysql

i have two tables, one invoice and the other the details where i need to select products ordered n times by a particular customer within a date range
the tables in part looks like this
Invoice
invid | custid | invdate
----------------------------
101 | 11 | 2014-2-10
102 | 22 | 2014-2-15
103 | 22 | 2014-3-01
104 | 11 | 2014-3-14
Details
invid | item
------------
101 | bread
102 | bread
103 | chips
104 | chips
102 | bread
103 | bread
104 | chips
101 | bread
from the code above, i need to select say all customers who ordered the same items 2 times or more within 2014-2-10 and 2014-3-09, excluding any customer who purchased the same item in the week 2014-3-10 to 2014-3-14
for example
if customer orders bread 2 times between date1 and date2 and did not order the same bread between date3 and date4 then it should be in the output
and date the expected output should be
custid | item | item_count
22 | bread | 2
the custid 11 would have NOT fit the list, because they also purchased in the week 2014-3-10 to 2014-3-14, but it they did not purchased the same item in the passed dates
this is what i tried
SELECT
i.custid, d.ITEM,COUNT(d.ITEM) as orders
From `details` d
LEFT JOIN `invoices` i on i.invid= d.invid
WHERE
i.invdate >= '2014-2-10' AND
i.invdate <= '2014-3-14' AND
i.custid NOT IN
(SELECT custid FROM `invoices` WHERE invdate >= '2014-3-10')
Group By i.invid, d.ITEM
HAVING COUNT(d.ITEM) >= 2
when i run again the full table, i get 1 item instead of 6. I did manually using excel through a number of functions to be sure, in this case none

Typical MySQL error. You mistakenly group by invid instead of custid.
SELECT
i.custid, d.ITEM, COUNT(d.ITEM) as orders
From `details` d
LEFT JOIN `invoices` i on i.invid= d.invid
WHERE
i.invdate >= '2014-2-10' AND
i.invdate <= '2014-3-14' AND
i.custid NOT IN
(SELECT custid FROM `invoices` WHERE invdate >= '2014-3-10')
Group By i.custid, d.ITEM
HAVING COUNT(d.ITEM) >= 2;
EDIT: Okay, here is a closer look at it.
Correct the GROUP BY as already mentioned.
You outer join invoices although there should be no details record without an invoices record. Change this to INNER JOIN.
You are confusing dates. The purchase date shall be between '2014-2-10' and '2014-3-09' and must not be between '2014-3-10' to '2014-3-14'
Then: You don't want to exclude customers who bought something in that latter week. You want to exclude customer-item combinations that occured then.
My suggestion: select from both date ranges and check then if all macthes for a customer-item combination are within the desired week and still have a count of at least two:
select
i.custid,
d.item,
count(d.item) as orders
from invoices i
inner join details d on d.invid = i.invid
where i.invdate between '2014-2-10' and '2014-3-09'
or i.invdate between '2014-3-10' and '2014-3-14'
group by i.custid, d.item
having count(*) >= 2 and max(i.invdate) between '2014-2-10' and '2014-3-09;

SELECT i1.custid, d1.ITEM, COUNT(*) orders
FROM (invoices i1 JOIN details d1 USING (invid))
LEFT JOIN (invoices i2 JOIN details d2 USING (invid))
ON i2.custid = i1.custid
AND d2.ITEM = d1.ITEM
AND i2.invdate BETWEEN '2014-03-10' AND '2014-03-14'
WHERE i1.invdate BETWEEN '2014-02-10' AND '2014-03-09'
AND i2.custid IS NULL
GROUP BY i1.custid, d1.ITEM
HAVING orders >= 2
See it on sqlfiddle.

Related

SQL left join two times

The user table looks like this:
user_id
name
surname
1
a
aa
2
b
bb
3
c
cc
The book's table looks like this:
user_id
book_name
1
book1
1
book2
1
book3
2
book1
The expenses table looks like this:
user_id
amount_spent
date
1
10
2020-02-03
1
30
2020-02-02
1
10
2020-02-01
1
15
2020-01-31
1
13
2020-01-15
2
15
2020-02-01
3
20
2020-02-01
The result which I want:
CountUsers
amount_spent
2
65
Explanation: I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
Now how the query should look like?
I am using MySQL version 8.
I have tried:
SELECT
count(*), sum(amount_spend) as total_amount_spend
FROM
(select sum(amount_spend) as amount_spend
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id WHERE books.book_name ='book1 GROUP BY expanses.user_id) src'
And the result is wrong because I am getting a higher amount_spend than in my table result above. I think while joining the table there are some duplicates but I do not know how to fix them.
I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
I am thinking:
select count(*), sum(e.amount_spent)
from user_books ub join
expenses e
on ub.user_id = e.user_id
where book_name = 'book1';
Note: This assumes that user_books doesn't have duplicate rows.
FIDDLE
You miss the date part in your code.
SELECT
count(*), sum(amount_spent) as total_amount_spend
FROM
(select sum(amount_spent) as amount_spent
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id
WHERE books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03'
GROUP BY expanses.user_id) src;
will do a job.
Please note that you don't need to have left join here (unless you're sure that it may happen that no expenses at all for given user will be), and you don't need to have grouping in subquery. So your query could look like:
select count(distinct expanses.user_id), sum(amount_spent) as amount_spent
from expanses
inner join books on books.user_id = expanses.user_id
where books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03';

MySQL query to get data between and not between where clause

This MySQL query is to get data from two tables exam_dates and section_strength and join them to get total persons from which sections has completed there exam in march quarter:
select s.section, count(section), r.strength
from exam_dates s
left join section_strength r
on s.section=r.section_name
where s.semester_exam between '2017-01-01' and '2017-03-31'
group by s.section
But this query is showing only those sections name who have completed the exam in march quarter. I want all the name of sections in left side and number of persons completed next column and then the strength of that section.
first table contains=> details of student completed exam on "so and so dates"
example:
id| personal_no| section_name| semester_dates
1 | 777878 | hrm |2017-01-12
2 | 748587 |it |2017-05-10
another table having strength of individual sections:
id|section_name |strength
1 | hrm | 10
2 | it | 15
Now I want my query to show result of all sections name in left side and then total student completed test in march quarter and then total strength of that section.
for eg.
id|section_name|total_completed|strength
1 | hrm | 2 | 10
2 | it | 5 | 15
Your second date is before your first date. Try reversing the order of the dates:
select s.section, count(section), r.strength
from exam_dates s
left join section_strength r on s.section=r.section_name
where s.semester_exam between '2017-03-31' and '2017-01-01'
group by s.section

mysql: get daily average price of product table with version

my products table :
ProductId(inc.key) | Price | VersionCreatedDate | MainProductId
1 | 15 | 1-11-2016 | 1
2 | 20 | 1-11-2016 | 2
3 | 30 | 1-11-2016 | 3
4 | 10 | 2-11-2016 | 1 -> mainProductId 1 changed price(-5$)
5 | 20 | 3-11-2016 | 3 -> mainProductId 3 changed price(-10$)
6 | 30 | 4-11-2016 | 3 -> mainProductId 3 changed price(+10$)
I want to display the output as like this
Date | AvgPrice
1-11-2016 | 21.67 ((15+20+30)/3)
2-11-2016 | 20 ((10+20+30)/3)
3-11-2016 | 16.67 ((10+20+20)/3)
4-11-2016 | 20 ((10+20+30)/3)
How do I get the output with sql code?
Assuming you have a calendar table with all dates you need. And you have a main_products table with MainProductId as primary/unique key. The following query should return average prices for every day in october 2016.
select sub.date, avg(sub.Price) as Price
from (
select
c.date,
m.MainProductId,
(
select p.Price
from products
where p.MainProductId = m.MainProductId
and p.VersionCreatedDate < c.date + interval 1 day
order by p.VersionCreatedDate desc
limit 1
) as Price
from callendar c
cross join main_products m
where c.date between '2016-10-01' and '2016-10-31'
) sub
group by sub.date
order by sub.date
The subquery (derived table aliased as sub) returns a combination of all dates in the range and all "main products" from the main_products table. The recent price each "main product" for a specific date is calculated in the subselect (correlated subquery in the SELECT clause) using ORDER BY and LIMIT 1. This allows us to group the subquery result by date and calculate the average price per date.
It is even possible to eliminate the derived table and hope that mysql can use an index to GROUP BY date instead of working on a temp table:
select c.date, avg((
select p.Price
from products
where p.MainProductId = m.MainProductId
and p.VersionCreatedDate < c.date + interval 1 day
order by p.VersionCreatedDate desc
limit 1
)) as Price
from callendar c
cross join main_products m
where c.date between '2016-10-01' and '2016-10-31'
group by c.date
order by c.date
I have no clue if that query can be executed effiently (especially if mysql can). You should however have at least the following indexes: callendar(date), products(MainProductId, VersionCreatedDate)

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;

Group BY product using MAX(price) or MAX(date) according to time interval

I've been searching for answers for 2 day and still nothing. Please, help me.
I have a database with products, product's prices and the date when this prices were registered:
product_id | price | date
-------------------------
1 | 8.95 | 2012-12-01
2 | 3.40 | 2012-12-01
1 | 9.05 | 2012-12-19
3 | 2.34 | 2012-12-24
3 | 2.15 | 2012-12-01
1 | 8.80 | 2012-12-19
1 | 8.99 | 2012-12-02
2 | 3.45 | 2012-12-02
Observe that is possible to have different price values for a product on the same day (rows 3 and 6). This is because there are many suppliers for a single product. There is a supplier column on database too, but I found it irrelevant for the solution. You can add it to the solution if I'm wrong.
Basically what I want is to write a query that returns two combined sets of data, as follow:
First set is made by minimum price of products inserted in the last month. As today is jan, 15, query should read rows 3, 4 and 6, apply the minimum price, and return only rows 4 and 6, both with minimum price for that product on the last month.
Second set is made by last products inserted, with no price registered on last month. i.e, for products not shown in the first set, query should search for the last inserted ones.
I hope that is clear. Ask me more if it isn't.
The query result for this database should be:
product_id | price | date
-------------------------
1 | 8.80 | 2012-12-19 <-Min price for product 1 on last month
3 | 2.34 | 2012-12-24 <-Min price for product 3 on last month
2 | 3.45 | 2012-12-02 <-No reg for product 2 on last month, show last reg.
I've tried everything: UNION, (DATE_SUB(CURDATE(), INTERVAL 1 MONTH), MIN(price), MAX(date) etc, etc. Nothing works. I don't know where to search now, please help me.
(SELECT product_id, MIN(price), date
FROM products
WHERE date + INTERVAL 1 MONTH > NOW()
GROUP BY product_id)
UNION
(SELECT product_id, price, MAX(date)
FROM products
WHERE product_id NOT IN (SELECT product_id
FROM products
WHERE date + INTERVAL 1 MONTH > NOW()
GROUP BY product_id)
GROUP BY product_id)
This should work but I'm not sure it's the most optimized way to do it.
something like this will do the trick:
SELECT * FROM (
SELECT DISTINCT b.product_id, IF (c.min IS NULL,(SELECT ROUND(e.price,2) FROM products AS e WHERE e.product_id = b.product_id ORDER BY e.date DESC LIMIT 1 ),c.min) AS min, IF (c.date IS NULL,(SELECT f.date FROM products AS f WHERE f.product_id = b.product_id ORDER BY f.date DESC LIMIT 1 ),c.date) AS date, IF(c.min IS NULL,'<-No reg for product 2 on last month, show last reg.','<-Min price for product 1 on last month') as text FROM products AS b
LEFT JOIN
(SELECT a.product_id, round(MIN(a.price),2) AS min, a.date FROM products AS a WHERE a.date BETWEEN DATE_SUB(CURDATE(), INTERVAL 1 MONTH) AND CURDATE() GROUP BY a.product_id) AS c
ON (b.product_id = c.product_id)
) AS d
ORDER BY d.text, d.product_id
Gives output:
product_id|min|date|text
1|8.80|2012-12-19|<-Min price for product 1 on last month
3|2.34|2012-12-24|<-Min price for product 1 on last month
2|3.45|2012-12-02|<-No reg for product 2 on last month, show last reg.
Break it down into several sub-queries:
Products with prices in the last month, min price
join in date for that price
UNION
Products with no-prices in the last month, max date
join in price on that date
SQL Fiddle
Here
Query
SELECT MINPRICE.product_id, P.date, MINPRICE.price
FROM
(
-- Min price in last 31 days
SELECT product_id, MIN(price) AS price
FROM Prices
WHERE DATEDIFF(CURDATE(), date) < 31
GROUP BY product_id
) MINPRICE
-- Join in to get the date that the price occured on
INNER JOIN Prices P ON
P.product_id = MINPRICE.product_id
AND
P.price = MINPRICE.price
UNION
SELECT MAXDATE.product_id, MAXDATE.date, P.price
FROM
(
-- Product with no price in last 31 days - get most recent date
SELECT product_id, MAX(date) AS date
FROM Prices
WHERE product_id NOT IN
(
SELECT product_id
FROM Prices
WHERE DATEDIFF(CURDATE(), date) < 31
)
) MAXDATE
-- join in price on that date
INNER JOIN Prices P ON
P.product_id = MAXDATE.product_id
AND
P.date = MAXDATE.date
Not that I tested but you can try...
SELECT * FROM
(SELECT * FROM (
SELECT * FROM table ORDER BY date DESC)
as tmp GROUP BY product_id) t1
LEFT JOIN
(SELECT * FROM (
SELECT * FROM table WHERE date => CURDATE() ORDER BY price)
as tmp2 GROUP BY product_id) t2
ON t1.product_id = t2.product_id