Mysql query for calculating stock of books in library - mysql

I have three tables tblinventory, tbldisbursement, tblmissinglost. I want to calculate the total stock of books after deducting disbursements and returns for the current day.
select bn.No_of_books_procured
- (count(mis.Unr_ret_donated_discareded))
- (count(case when dis.dis_disbursed_return = 1
then dis.dis_disbursed_return end ) )as Stock
from tbl_inventorylibbooks bn
right
join tbl_limgt_booksmissing_lost_adjust mis
on mis.Book_Name_missingbk = bn.Id_inventory
left
join tbllibmange_disbursement dis
on dis.dis_book_name_fk_id = bn.Id_inventory
where bn.Id_inventory = 14
On few entries I get the correct answer and the other results entries the query is showing wrong answers. Can you kindly provide a solution for this?
tblinventory
id Name
----------
1 xyz
consider total books procured=10
tbldisbursement
**booknameFK disbursed/returned**
----------------------------------------
Booknafk1 1
Booknafk2 0
Booknafk3 1
where 1=disbursed and 0=returned
tblmissinglost
**id BooknameFk missng**
----------------------------------------
1 Booknafk1 lost
2 Booknafk1 lost
----------------------------------------
Also check this query
select (bn.No_of_books_procured) as procured,
count(distinct mis.Id_missingbooks_pid) as missing,
count( case when bs.dis_disbursed_return!=0 then
bs.dis_disbursed_return end) as disbursed
from tbl_limgt_booksmissing_lost_adjust mis
join tbllibmange_disbursement bs on
mis.book_name_missingbk=bs.dis_book_name_fk_id
join tbl_inventorylibbooks bn on bs.dis_book_name_fk_id=bn.Id_inventory
where bn.Id_inventory=14 group by bn.Id_inventory
I am getting duplicate entries

Select Sum(BookCount) Stock from (
Select Name , 1 BookCount from tblinventory
Union
select booknameFK Name , Case when disbursedORreturned=1 then -1 else 1 end as BookCount from tbldisbursement
Union
select booknameFK Name , -1 BookCount from tblmissinglost
)T
This will give you total number of books in stock as of now.

Related

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

SQL : Show date when product was present in 6 months of data

I am pretty new to sql but have a problem I can't seem to find an answer to yet.
I need to find the month where the number of months a product in a certain segment showed up in a report is equal to 6. However, a product may not show up in sequential months, shows up multiple times a month, and some products have not beensold in 6 months yet.
I have a database with the following attributes:
Entry_Id Product_Name Sold_Date Payment_Amount Segment
======================================================================
112341 ABC Product 2017/12/20 10.50 Segment 1
112342 123 Product 2016/08/21 11.20 Segment 1
112343 ABC Product 2017/12/20 11.50 Segment 1
112344 123 Product 2017/08/21 11.20 Segment 1
112345 123 Product 2017/06/12 11.20 Segment 1
112346 123 Product 2016/06/21 11.20 Segment 1
112347 123 Product 2016/05/02 11.20 Segment 1
112348 123 Product 2015/04/01 11.20 Segment 1
112348 123 Product 2018/01/05 11.20 Segment 1
I would like to get something to the following effect
Product_Name Date where N = 6 segment
=================================================
ABC Product N/A Segment 1
123 Product 2018/01/05 Segment 1
The Day of month does not matter, just the month where the the number of months it has shown up in is equal to 6.
This is my first question and I will be as active as possible, please ask any clarifying questions.
Thank you!
Use GROUP BY and COUNT() to count the number of months that each product was sold in, and HAVING to filter the results.
SELECT t1.product_name, max_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date
FROM yourTable
GROUP BY product_name
HAVING COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) >= 6
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
The t2 subquery finds all the products that were sold in at least 6 different months. Joining that with the table finds the row with the last date, so its segment column can be selected.
If you want to include the products that weren't sold in at least 6 months, with N/A in that column, you can move the test out of the subquery.
SELECT product_name, IF(month_count >= 6, max_date, "N/A") AS last_date, segment
FROM yourTable AS t1
JOIN (
SELECT product_name, MAX(sold_date) AS max_date, COUNT(DISTINCT YEAR(sold_date), MONTH(sold_date)) as month_count
FROM yourTable
GROUP BY product_name
) AS t2 ON t1.product_name = t2.product_name AND t1.sold_date = t2.max_date
You can do this with a correlated subquery:
select t.*
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
);
Because of the count(distinct), this will show all records from the 6th month. Also, this is not particularly efficient, but it does use just standard SQL.
You can summarize this to one row per segment/product:
select t.segment, t.product, min(t.sold_date)
from t
where 6 = (select count(distinct year(t2.sold_date), month(t2.sold_date))
from t t2
where t2.segment = t.segment and and t2.product = t.product and
t2.sold_date <= t.sold_date
)
group by t.segment, t.product;

Distinct outside group in sql

I have a table called order_status_log, which logs who changed order statuses.
Simplified table and query below:
order_id user_id status time
1 1 1 2016-01-27 19:35:44
2 2 2 2016-01-27 19:36:45
4 3 2 2016-01-27 19:37:43
2 1 5 2016-01-27 19:38:41
I also have SQL which counts changes by each user:
SELECT
COUNT(*) as count,
user_id
FROM order_status_log
WHERE status = 1
GROUP BY user_id
ORDER BY count
Now I want to improve my query to count only first status changes in order.
In other words I need unique order_id with older time.
How I can change my query to do that?
Something like this?
SELECT *
FROM order_status_log o
WHERE NOT EXISTS
( SELECT 'x'
FROM order_status_log o2
WHERE o2.user_id = o.user_id
AND o2.time < o.time)

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;

MySQL: Find the average value per entry for the last x records

I'm trying to figure out how to grab the average rating for each salesperson over their last 100 ratings if they are currently employed, and if they have an average rating less than 3 (out of 5).
I have the following tables (leaving out information that isn't needed in the query):
users
id name employed
-----------------------
1 John 1
2 Sue 1
3 Bob 0
...
sales
id users_id
------------------
100 3
101 2
102 3
103 1
...
ratings
sales_id rating
-----------------
100 4
101 5
102 5
103 2
...
The current query I have searches everything and returns the average for all orders ever but I want it to only grab the most recent 100 ratings (or less if the salesperson hasn't sold that many items), still excluding anyone that is no longer employed or has a rating for their last 100 orders greater than 3. This is the current query:
SELECT u.name, avg(r.rating) as avg_rating, count(r.rating)
FROM users AS u
JOIN sales AS s ON s.users_id = u.id
JOIN ratings AS r ON r.sales_id = s.id
WHERE u.employed = 1
GROUP BY u.id
HAVING avg_rating <= 3;
Any help would be great! Thanks! :D
You can use my sql variables to keep track of the number of ratings so that you can get only recent 100 ratings , ordering by sales_id so you get recent ratings.
SQL FIDDLE DEMO
SELECT T.name, avg(T.rating) as avg_rating, count(T.rating)
FROM
(
SELECT u.name, r.rating, #num := if (#name = name, #num+1, 1) as rn,
#name:= name as var_name
FROM users AS u
JOIN sales AS s ON s.users_id = u.id
JOIN ratings AS r ON r.sales_id = s.id
AND u.employed = 1
JOIN ( select #name :='' , #num :=1) var
order by sales_id desc
)T
where T.rn <=100
GROUP BY T.name
HAVING avg_rating <= 3