Most Recent Price for an Item - sql-server-2014

I have an issue where i cant find out how to get the latest price for an item.
My table consists of lots of fields but i only really need three
ITEM
UNITPRICE
AUDTDATE
What i want to see is
| ITEMNO | QTYONHAND |
|--------|-----------|
| 1 | 12|
| 2 | 13|
| 3 | 4|
Throught the course of time as im sure evberyone knows what you invoice the price at is different so what i want to see is only the most recent invoice price for every item.
I have tired
SELECT ITEM,UNITPRICE,max(AUDTDATE) from OEINVD
WHERE ITEM is NOT NULL
GROUP BY ITEM,UNITPRICE
ORDER BY ITEM
But it gives multiple for each sku :(
Ideally i want to see one sku and one price (latest price we invoiced at)
Please let me know if you can help :)

Try using TIES along with ROW_NUMBER:
SELECT TOP 1 WITH TIES ITEM, UNITPRICE, AUDTDATE
FROM OEI
ORDER BY ROW_NUMBER() OVER (PARTITION BY ITEM ORDER BY AUDITDATE DESC);

Related

MySQL limitations to simplify Query

Please note that I'm an absolute n00b in MySQL but somehow I managed to build some (for me) complex queries that work as they should. My main problem now is that for a many of the queries we're working on:
The querie is becoming too big and very hard to see through.
The same subqueries get repeated many times and that is adding to the complexity (and probably to the time needed to process the query).
We want to further expand this query but we are reaching a point where we can no longer oversee what we are doing. I've added one of these subqueries at the end of this post, just as an example.
!! You can fast foward to the Problem section if you want to skip the details below. I think the question can be answered also without the additional info.
What we want to do
Create a MySQL query that calculates purchase orders and forecasts for a given supplier based on:
Sales history in a given period (past [x] months = interval)
Current stock
Items already in backorder (from supplier)
Reserved items (for customers)
Supplier ID
I've added an example of a subquery at the bottom of this message. We're showing just this part to keep things simple for now. The output of the subquery is:
Part number
Units sold
Units sold (outliers removed)
Units sold per month (outliers removed)
Number of invoices with the part number in the period (interval)
It works quite OK for us, although I'm sure it can be optimised. It removes outliers from the sales history (e.g. one customer that orders 50 pcs of one product in one order). Unfortunately it can only remove outliers with substantial data, so if the first order happens to be 50 pcs then it is not considered an outlier. For that reason we take the amount of invoices into account in the main query. The amount of invoices has to exceed a certain number otherwise the system wil revert to a fixed value of "maximum stock" for that product.
As mentioned this is only a small part of the complete query and we want to expand it even further (so that it takes into account the "sales history" of parts that where used in assembled products).
For example if we were to build and sell cars, and we want to place an
order with our tyre supplier, the query calculates the amount of tyres we need to order based on the sales history of the various car models (while also taking into account the stock of the cars, reserved cars and stock of the tyres).
Problem
The query is becomming massive and incomprehensible. We are repeating the same subqueries many times which to us seems highly inefficient and it is the main cause why the query is becomming so bulky.
What we have tried
(Please note that we are on MySQL 5.5.33. We will update our server soon but for now we are limited to this version.)
Create a VIEW from the subqueries.
The main issue here is that we can't execute the view with parameters like supplier_id and interval period. Our subquery calculates the sum of the sold items for a given supplier within the given period. So even if we would build the VIEW so that it calculates this for ALL products from ALL suppliers we would still have the issue that we can't define the interval period after the VIEW has been executed.
A stored procedure.
Correct me if I'm wrong but as far as I know, MySQL only allows us to perform a Call on a stored procedure so we still can't run it against the parameters (period, supplier id...)
Even this workaround won't help us because we still can't run the SP against the parameters.
Using WITH at the beginning of the query
A common table expression in MySQL is a temporary result whose scope is confined to a single statement. You can refer this expression multiple times with in the statement.
The WITH clause in MySQL is used to specify a Common Table Expression, a with clause can have one or more comms-separated subclauses.
Not sure if this would be the solution because we can't test it. WITH is not supported untill MySQL version 8.0.
What now?
My last resort would be to put the mentioned subqueries in a temp table before starting the main query. This might not completely eliminate our problems but at least the main query will be more comprehensible and with less repetition of fetching the same data. Would this be our best option or have I overlooked a more efficient way to tackle this?
Many thanks for your kind replies.
SELECT
GREATEST((verkocht_sd/6*((100 + 0)/100)),0) as 'units sold p/month ',
GREATEST(ROUND((((verkocht_sd/6)*3)-voorraad+reserved-backorder),0),0) as 'Order based on units sold',
SUM(b.aantal) as 'Units sold in period',
t4.verkocht_sd as 'Units sold in period, outliers removed',
COUNT(*) as 'Number of invoices in period',
b.art_code as 'Part number'
FROM bongegs b -- Table that has all the sales records for all products
RIGHT JOIN totvrd ON (totvrd.art_code = b.art_code) -- Right Join stock data to also include items that are not in table bongegs (no sales history).
LEFT JOIN artcred ON (artcred.art_code = b.art_code) -- add supplier ID to the part numbers.
LEFT JOIN
(
SELECT
SUM(b.aantal) as verkocht_sd,
b.art_code
FROM bongegs b
RIGHT JOIN totvrd ON (totvrd.art_code = b.art_code)
LEFT JOIN artcred ON (artcred.art_code = b.art_code)
WHERE
b.bon_datum > DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
and b.bon_soort = "f" -- Selects only invoices
and artcred.vln = 1 -- 1 = Prefered supplier
and artcred.cred_nr = 9117 -- Supplier ID
and b.aantal < (select * from (SELECT AVG(b.aantal)+3*STDDEV(aantal)
FROM bongegs b
WHERE
b.bon_soort = 'f' and
b.bon_datum > DATE_SUB(CURDATE(), INTERVAL 6 MONTH)) x)
GROUP BY b.art_code
) AS t4
ON (b.art_code = t4.art_code)
WHERE
b.bon_datum > DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
and b.bon_soort = "f"
and artcred.vln = 1
and artcred.cred_nr = 9117
GROUP BY b.art_code
Bongegs | all rows from sales forms (invoices F, offers O, delivery notes V)
| art_code | bon_datum | bon_soort | aantal |
|:---------|:---------: |:---------:|:------:|
| item_1 | 2021-08-21 | f | 6 |
| item_2 | 2021-08-29 | v | 3 |
| item_6 | 2021-09-03 | o | 2 |
| item_4 | 2021-10-21 | f | 6 |
| item_1 | 2021-11-21 | o | 6 |
| item_3 | 2022-01-17 | v | 6 |
| item_1 | 2022-01-21 | o | 6 |
| item_4 | 2022-01-26 | f | 6 |
Artcred | supplier ID's
| art_code | vln | cred_nr |
|:---------|:----:|:-------:|
| item_1 | 1 | 1001 |
| item_2 | 1 | 1002 |
| item_3 | 1 | 1001 |
| item_4 | 1 | 1007 |
| item_5 | 1 | 1004 |
| item_5 | 2 | 1008 |
| item_6 | 1 | 1016 |
| item_7 | 1 | 1567 |
totvrd | stock
| art_code | voorraad | reserved | backorder |
|:---------|:---------: |:--------:|:---------:|
| item_1 | 1 | 0 | 5 |
| item_2 | 0 | 0 | 0 |
| item_3 | 88 | 0 | 0 |
| item_4 | 9 | 0 | 0 |
| item_5 | 67 | 2 | 20 |
| item_6 | 112 | 9 | 0 |
| item_7 | 65 | 0 | 0 |
| item_8 | 7 | 1 | 0 |
Now, on to the query. You have LEFT JOINs to the artcred table, but then include artcred in the WHERE clause making it an INNER JOIN (required both left and right tables) in the result. Was this intended, or are you expecting more records in the bongegs table that do NOT exist in the artcred.
Well to be honest I was not fully aware that this would essentially form an INNER JOIN but in this case it doesn't really matter. A record that exists in bongegs always exists in artcred as well (every sold product must have a supplier). That doesn't work both ways since a product can be in artcred without ever being sold.
You also have RIGHT JOIN on totvrd which implies you want every record in the TotVRD table regardless of a record in the bongegs table. Is this correct?
Yes it is intended. Otherwise only products with actual sales in the period would end up in the result and we also wanted to include products with zero sales.
One simplification:
and b.aantal < ( SELECT * from ( SELECT AVG ...
-->
and b.aantal < ( SELECT AVG ...
A personal problem: my brain hurts when I see RIGHT JOIN; please rewrite as LEFT JOIN.
Check you RIGHTs and LEFTs -- that keeps the other table's rows even if there is no match; are you expecting such NULLs? That is, it looks like they can all be plain JOINs (aka INNER JOINs).
These might help performance:
b: INDEX(bon_soort, bon_datum, aantal, art_code)
totvrd: INDEX(art_code)
artcred: INDEX(vln, cred_nr, art_code)
Is b the what you keep needing? Build a temp table:
CREATE TEMPORARY TABLE tmp_b
SELECT ...
FROM b
WHERE ...;
But if you need to use tmp_b multiple times in the same query, (and since you are not yet on MySQL 8.0), you may need to make it a non-TEMPORARY table for long enough to run the query. (If you have multiple connections building the same permanent table, there will be trouble.)
Yes, 5.5.33 is rather antique; upgrade soon.
(pre
By getting what I believe are all the pieces you had, I think this query significantly simplifies the query. Lets first start with the fact that you were trying to eliminate the outliers by selecting the standard deviation stuff as what to be excluded. Then you had the original summation of all sales also from the bongegs table.
To simplify this, I have the sub-query ONCE internal that does the summation, counts, avg, stddev of all orders (f) within the last 6 months. I also computed the divide by 6 for per-month you wanted in the top.
Since the bongegs is now all pre-aggregated ONCE, and grouped per art_code, it does not need to be done one after the other. You can use the totals directly at the top (at least I THINK is similar output without all actual data and understanding of your context).
So the primary table is the product table (Voorraad) and LEFT-JOINED to the pre-query of bongegs. This allows you to get all products regardless of those that have been sold.
Since the one aggregation prequery has the avg and stddev in it, you can simply apply an additional AND clause when joining based on the total sold being less than the avg/stddev context.
The resulting query below.
SELECT
-- appears you are looking for the highest percentage?
-- typically NOT a good idea to name columns starting with numbers,
-- but ok. Typically let interface/output name the columns to end-users
GREATEST((b.verkocht_sdperMonth * ((100 + 0)/100)),0) as 'units sold p/month',
-- appears to be the total sold divided by 6 to get monthly average over 6 months query of data
GREATEST( ROUND(
( (b.verkocht_sdperMonth * 3) - v.voorraad + v.reserved - v.backorder), 0), 0)
as 'Order based on units sold',
b.verkocht_sd as 'Units sold in period',
b.AvgStdDev as 'AvgStdDeviation',
b.NumInvoices as 'Number of invoices in period',
v.art_code as 'Part number'
FROM
-- stock, master inventory, regardless of supplier
-- get all products, even though not all may be sold
Voorraad v
-- LEFT join to pre-query of Bongegs pre-grouped by the art_code which appears
-- to be basis of all other joins, std deviation and average while at it
LEFT JOIN
(select
b.arc_code,
count(*) NumInvoices,
sum( b.aantal ) verkocht_sd,
sum( b.aantal ) / 6.0 verkocht_sdperMonth,
avg( b.aantal ) AvgSale,
AVG(b.aantal) + 3 * STDDEV( b.aantal) AvgStdDev
from
bongegs b
JOIN artcred ac
on b.art_code = ac.art_code
AND ac.vln = 1
and ac.cred_nr = 9117
where
-- only for ORDERS ('f') and within last 6 months
b.bon_soort = 'f'
AND b.bon_datum > DATE_SUB(CURDATE(), INTERVAL 6 MONTH)
group by
b.arc_code ) b
-- result is one entry per arc_code, thus preventing any Cartesian product
ON v.art_code = b.art_code
GROUP BY
v.art_code

In SQL, can I get the average of a group of numbers, but only for those records with matching labels?

I have two MySQL tables, one with information about items (called item), and another with customer reviews for those items (called review).
Item contains the label as a text-based primary key, along with some other attributes.
Review contains an auto-incrementing reviewID, along with the item label as a foreign key, and then a review score out of 5.
For example:
Item
| LABEL | INFO_1 | INFO_2 | etc|
book fict 2010
book2 nonfic 1997
...
Review
| REVIEWID | LABEL | SCORE |
001 book 4
002 book 5
003 book2 5
I want to create a view so that I can see the average review score per item. I have tried to select
AVG(review.score) AS avgscore
but that returns the same average of every review in the system, without separating it by item label. Do I need to do some kind of join or add further arguments? Sorry if this is a stupid question. I'm not exactly super-fluent in MySQL.
My desired result is something like the following:
AverageScoreView
| LABEL | AVGSCORE |
book 4.5
book2 5
you can use:
SELECT LABEL, AVG(SCORE) AS AVGSCORE
FROM Review
GROUP BY LABEL

What is different way to store order item and item can have sub item?

The application has order and an order can have items.
For it we had database tables like following.
Table 1 - T_ORDER(pk_order_id, order_no, date)
Table 2 - T_ORDER_ITEM (pk_order_itemid, fk_order_id, ean, quantity, price)
Now we have to support the case that order can have bundle item means one bundle item contain more than one real item. A bundle item will have all the property that an normal item can have like ean, quantity, price.
Basically bundle is virtual group of one or more real item but it can have property like real item like quantity - so user can place order more than one such bundle item, ean - for identification, price - it can have price including all real item in the bundle.
I wish to know best database table design to support this case? and what are advantage and disadvantage of one over another and which points should I consider during selecting one of them?
Option 1 - New tables for bundle and bundle item
In addition of above two tables , we add two new tables like
T_ORDER_BUNDLE(pk_order_bundle_id, fk_order_id, ean, quantity, price
T_ORDER_BUNDLE_ITEM (pk_order_bundle_itemid, fk_order_bundle_id, ean, quantity, price)
Option 2 - New table to store bundle and use existing table for item
T_ORDER_BUNDLE(pk_order_bundle_id, fk_order_id, ean, quantity, price
Just add new column like fk_order_bundle_id in T_ORDER_ITEM table.
Option 3 - New table to store sub items of an item
Bundle item can be stored in existing T_ORDER_ITEM table.
New table - T_ORDER_ITEM_SUBITEM(pk_order_item_subitem, fk_order_itemid, ean, quantity, price)
Note :-
We do not have any master table for items. Table T_ORDER_ITEM contain all item data and relation to order. As order can accepted for any item and we don't have any fixed list of items to sale.
You are dealing with item bundles, where for example the bundle is a tennis racket set consisting of three items: two rackets and one ball. You have considered three approaches on how to model this in your database. I'm proposing a fourth way.
What you have so far:
items (item_id, ean, description, list_price)
orders (order_id, order_no, date)
order_item (order_item_id, order_id, item_id, quantity, price)
This is what the item table may look like:
item_id | ean | description | list_price
--------+---------+------------------+-----------
1 | 1234567 | tennis racket | 50.00
2 | 2345678 | tennis ball | 4.00
Now there is two things we need: A bundle item for the tennis set and the assignment which items belong to the set.
First let's add the bundle item to the item table:
item_id | ean | description | list_price
--------+---------+------------------+-----------
1 | 1234567 | tennis racket | 50.00
2 | 2345678 | tennis ball | 4.00
3 | 3456789 | tennis set | 98.00
The set price 98 is cheaper than the sum of the set's content 50 + 50 + 4 = 104, which is often the reason to buy a set.
Now, for the association we create an association table we can call bundles or something alike. It shows which item contains which other items and how many of these.
bundles (bundle_item_id, bundled_item_id, amount)
And then let's fill it with our data:
bundle_item_id | bundled_item_id | amount
---------------+-----------------+-------
3 | 1 | 2
3 | 2 | 1

MySQL SELECT rows which respond to specific MULTIPLE values in one of the columns

I've looked all over the Web, and in StackOverflow as well, and have found many similar topics, but none that answers specifically my needs. So I'm sending this out there, knowing there's probably some little thing I'm missing in order for it to work.
Let's imagine the following table:
location | title | description | quantity
============|===========|=============|==========
shelf | apple | red | 2
drawer | banana | yellow | 4
shelf | kiwi | green | 2
cupboard | lemon | yellow | 1
fridge | melon | orange | 3
drawer | peach | orange | 1
What I want to do is select from this table all items that are either in the drawer or on the shelf (so drawer AND shelf) and then order them by, for example, first, location ascending and, second, quantity descending.
So what I have (after searching all over the web) is the following code, which is the only one that doesn't return an error, but it doesn't return any items either:
SELECT * FROM items WHERE location = 'shelf' AND location = 'drawer' ORDER BY location ASC, quantity DESC
Where am I going wrong? Any feedback will be greatly appreciated!
Usually questions go toward how to select values from multiple tables and then join them. However, I need values from only one table; however, these values need to respond to specific multiple values they share.
SELECT * FROM items WHERE location = 'shelf' or location = 'drawer'
ORDER BY location ASC, quantity DESC
or
SELECT * FROM items WHERE location in ('shelf','drawer')
ORDER BY location ASC, quantity DESC
although for just two items, I'd probably go with the first myself, 3 or more, i might use in()
Additional Info: The reason why you are getting no records returned is because you are using AND in your condition. Remember that there is one and only one value for every column in a row.
try to use this.....
In case you want to use it in php you can modify my code and replace it with yours. I had a similar problem when i was developing this:
$data = mysql_query("SELECT * FROM cancer WHERE Age ='1'OR Age = '2' OR Age = '3'OR Age = '4'OR Age = '5'OR Age = '6'OR Age = '7'OR Age = '8'OR Age = '9'")
In case of sql, use:
SELECT * FROM items WHERE location = 'shelf' or location = 'drawer'
ORDER BY location ASC, quantity DESC

mysql query in grouping one field and get corrosponding values

I might not be clear in the title, which I am trying to clear here.
I have mysql table that has field as
zip_code | vendor_id | service_id | date | m_shift | e_shift
What I have done is, with a condition, I am grouping vendor_id and service_id using my condition query (if either of the m_shift or e_shift is non zero and the date lies inside provided date range with provided zip_code, list vendor_id and service_id).
Listing is successful, now what I want to do is, group service_id (identical service_id) and gather the vendor_id that has service_id which is grouped. eg
vendor_id | service_id
1 12
2 13
2 12
3 13
My required result is grouping service_id which would be 12 and 13. Then vendor_id which has service_id 12 i.e 1 and 2 and 13 i.e 2 and 3 should be retrieved.
What can I do for solving this problem.
Hope I am clear, if not please ask me to make more clear.