Calculating Stock movement History - sql

I am currently trying to output Product code and Movement to show a complete history of how many of a product we have sold.
I have two tables PUB.movement and PUB.product
I've made it this far
SELECT a."prod-code" AS productcode, count(*) AS Movement
FROM (SELECT mov."tran-date", pro."prod-code"
FROM PUB."movement" mov,
PUB."product" pro
WHERE mov.SKU=pro.SKU
AND mov."move-type" = 'i'
AND pro."prod-group" like 'SLA%') a
GROUP BY a."prod-code"
The output generated is:
PRODUCTCODE | MOVEMENT
0490786 1
0500012 1
0566003 1
0566004 1
0650594 1
0920127 1
0920154 1
1000557M1 1
1000578M1m 19
The only issue I have is if more than one is invoiced at that time it only counts that invoice as 1, not the quantity invoiced. There is a qty column in PUB.movement. I just can't incorporate it with the current query to output the correct stock movement.

Sounds like you want a SUM() of the qty field rather than a count of movements? Try this:
Select pro."prod-code" As productcode, Sum(mov.qty) As Movement
From PUB.movement As mov
Inner Join PUB.product As pro On mov.SKU = pro.SKU
Where mov."move-type" = 'i'
And pro."prod-group" like 'SLA%'
Group By pro."prod-code";

Related

COUNT with multiple LEFT joins [duplicate]

This question already has answers here:
Two SQL LEFT JOINS produce incorrect result
(3 answers)
Closed 12 months ago.
I am having some troubles with a count function. The problem is given by a left join that I am not sure I am doing correctly.
Variables are:
Customer_name (buyer)
Product_code (what the customer buys)
Store (where the customer buys)
The datasets are:
Customer_df (list of customers and product codes of their purchases)
Store1_df (list of product codes per week, for Store 1)
Store2_df (list of product codes per day, for Store 2)
Final output desired:
I would like to have a table with:
col1: Customer_name;
col2: Count of items purchased in store 1;
col3: Count of items purchased in store 2;
Filters: date range
My query looks like this:
SELECT
DISTINCT
C_customer_name,
C.product_code,
COUNT(S1.product_code) AS s1_sales,
COUNT(S2.product_code) AS s2_sales,
FROM customer_df C
LEFT JOIN store1_df S1 USING(product_code)
LEFT JOIN store2_df S2 USING(product_code)
GROUP BY
customer_name, product_code
HAVING
S1_sales > 0
OR S2_sales > 0
The output I expect is something like this:
Customer_name
Product_code
Store1_weekly_sales
Store2_weekly_sales
Luigi
120012
4
8
James
100022
6
10
But instead, I get:
Customer_name
Product_code
Store1_weekly_sales
Store2_weekly_sales
Luigi
120012
290
60
James
100022
290
60
It works when instead of COUNT(product_code) I do COUNT(DSITINCT product_code) but I would like to avoid that because I would like to be able to aggregate on different timespans (e.g. if I do count distinct and take into account more than 1 week of data I will not get the right numbers)
My hypothesis are:
I am joining the tables in the wrong way
There is a problem when joining two datasets with different time aggregations
What am I doing wrong?
The reason as Philipxy indicated is common. You are getting a Cartesian result from your data thus bloating your numbers. To simplify, lets consider just a single customer purchasing one item from two stores. The first store has 3 purchases, the second store has 5 purchases. Your total count is 3 * 5. This is because for each entry in the first is also joined by the same customer id in the second. So 1st purchase is joined to second store 1-5, then second purchase joined to second store 1-5 and you can see the bloat. So, by having each store pre-query the aggregates per customer will have AT MOST, one record per customer per store (and per product as per your desired outcome).
select
c.customer_name,
AllCustProducts.Product_Code,
coalesce( PQStore1.SalesEntries, 0 ) Store1SalesEntries,
coalesce( PQStore2.SalesEntries, 0 ) Store2SalesEntries
from
customer_df c
-- now, we need all possible UNIQUE instances of
-- a given customer and product to prevent duplicates
-- for subsequent queries of sales per customer and store
JOIN
( select distinct customerid, product_code
from store1_df
union
select distinct customerid, product_code
from store2_df ) AllCustProducts
on c.customerid = AllCustProducts.customerid
-- NOW, we can join to a pre-query of sales at store 1
-- by customer id and product code. You may also want to
-- get sum( SalesDollars ) if available, just add respectively
-- to each sub-query below.
LEFT JOIN
( select
s1.customerid,
s1.product_code,
count(*) as SalesEntries
from
store1_df s1
group by
s1.customerid,
s1.product_code ) PQStore1
on AllCustProducts.customerid = PQStore1.customerid
AND AllCustProducts.product_code = PQStore1.product_code
-- now, same pre-aggregation to store 2
LEFT JOIN
( select
s2.customerid,
s2.product_code,
count(*) as SalesEntries
from
store2_df s2
group by
s2.customerid,
s2.product_code ) PQStore2
on AllCustProducts.customerid = PQStore2.customerid
AND AllCustProducts.product_code = PQStore2.product_code
No need for a group by or having since all entries in their respective pre-aggregates will result in a maximum of 1 record per unique combination. Now, as for your needs to filter by date ranges. I would just add a WHERE clause within each of the AllCustProducts, PQStore1, and PQStore2.

Pull column value from second to last row

I'm stuck in a loop of figuring out a game plan for this in SQL. Below is my sample data. I'm trying to create another column called "Starting Balance" which would be the amount in "Ending Balance" for the previous LINE. When I have that, I would only like to display where reason = count and forget about the rest.
I can't even fathom what approach to take and any advice would be appreciated.
Sample Data:
ITEM ID
ITEM
LAST UPDATED
REASON
ENDING BALANCE
LINE
123
Pencil
9/1/2020
Correction
400
1
123
Pencil
9/2/2020
Correction
450
2
123
Pencil
9/3/2020
Count
500
3
Expected Output:
ITEM ID
ITEM
LAST UPDATED
REASON
Starting Balance
ENDING BALANCE
123
Pencil
9/3/2020
Count
450
500
if "previous LINE" means the row with Lastupdated before current row:
select * from (
select * , lag(ENDING_BALANCE,1,0) over (partition by ItemId order by LASTUPDATED) as Starting Balance
from table
) t where t.REASON = 'count'
I can't bugtest this, but maybe something like:
SELECT
a.ITEM_ID,
a.ITEM,
a.LAST_UPDATED,
"Count" AS REASON,
b.ENDING_BALANCE AS Starting_Balance,
a.ENDING_BALANCE AS ENDING_BALANCE,
FROM table a
LEFT JOIN table b
ON a.ITEM_ID = b.ITEM_ID, a.LINE = b.LINE + 1
Note that we're joining two copies of the same table together here and labeling them a and b.
No clue if you can do ONs like that, but you could also make the join table have a LINE +1 column which you then use to join.

SQL statement, check if other rows have the same value

I have a SQL statement that imports my product inventory from a Access.MDB file. The select statement is below. (well a portion of it)
SELECT
Brand, DESCRIPTION AS Model,
SECONDDESCRIPTION AS Description,
PRODUCT AS [Product Code], TYPE AS Batch, INACTIVE,
CORE AS [Core Range],
IIF([CUSTORD] IS NULL, ROUND(ON_HAND), (IIF(TYPE = 'DISP',ROUND(ON_HAND),ROUND(ON_HAND)-CUSTORD))) AS SOH
You may notice that the select statement will minus any items that are on a customer order from the SOH values. for clarity below is the line that does just that.
IIF([CUSTORD] IS NULL, ROUND(ON_HAND), (IIF(TYPE = 'DISP',ROUND(ON_HAND),ROUND(ON_HAND)-CUSTORD))) AS SOH
The problem i have is, that 1 product code, can have multiple batches, and if an item only has a qty of 1 in each batch, and then the customer order column also contains a 1, this results in 1 - 1 = 0.
However the customer orders column is really indicating that only 1 of the product codes in on a customer order, not that specific batch.
Is there a way to check if that product code has already been "Selected" and has a Customer Order Qty against it and if it does then ignore the customer order qty against this next batch in the table?
To help explain it a little here is a rough idea of the table that would be imported.
Product
Batch_Number
ON_HAND
CUSTORD
Apples
123456
5
1
Apples
234567
1
1
Apples
587554
1
1
Bananas
1548777
1
0
so in the table above with my existing select statement, my results would be
Apples 4 in batch 123456
Bananas 1 in batch 1548777
As the next two lines of apples would actually end up with a value of 0 in batches 234567 and 587554
my program is set to then only return to the user values of items they can sell with a SOH qty > 0
so i need the final datatable in my program to look like this:
Product
Batch_Number
ON_HAND
CUSTORD
Apples
123456
5
1
Apples
234567
1
0
Apples
587554
1
0
Bananas
1548777
1
0
In my table Batch Number is the unique identifier and does not occur twice in the table.
Im working in VB.NET so if it could not be done in the SQL select statement i could be open to the idea of adjusting the values in the dataset datatable, however that would probably be made harder by the fact that the SQL Select statement i'm using never actually imports the CUSTORD column of data into my datatable. As i was trying to handle the SOH values directly at the select statement level.
Hope i have not confused anyone, and explained it as simple as possible.
I have no idea what your initial code has to do with the question. But let me assume that you have a table in the format shown in the question and you want to set on_hand to 0 for all but the first row for each product. You can use:
select product, batch_number, custord,
iif( t.batch_number = (select top 1 t2.batch_number
from t as t2
where t2.product = t.product
order by t2.on_hand desc, t2.batch_number
),
t.on_hand, 0
) as adjusted_on_hand
from t
order by product, on_hand desc, batch_number

SQL - Possible to sum rows between particular values?

my apologies if this is a duplicate but I could not find an answer to my particular question. I have a table that lists products on a sales order, and their various quantities. Some products are components for other products and are denoted so with a flag. I would like to know if there is a way to have a running total for the parent/normal items that would reset on each parent/normal item.
Here is an example of the table data and my desired output:
OrderNo Item Qty Regular Line
349443 AFU20451-KIT1 1 Y 1
349443 AFU20451 0 N 2
349443 HAWKE-14252 1 N 3
349443 RGPM-25H4 1 N 4
349443 AV-003-265 1 Y 5
349443 AV-A00090-KIT 1 Y 6
349443 AV-A00091 1 N 7
349443 AV-A00090 1 N 8
349443 AV-00043 1 N 9
349443 AV457/310GR/FP 2 Y 10
desired output:
OrderNo Item Qty
349433 AFU20451-KIT1 3
349433 AV-003-265 1
349433 AV-A00090-KIT 4
349433 AV457/310GR/FP 2
As you can see, I would like to reset the sum every time it says Y, only include the parent item (I could get around this as I can keep the order of the items the same, could maybe use row number). I have been trying to use Over and Partition by in order to do this, but to no avail. Let me know if this is even possible or if you need any further information.
with cte as
(
select OrderNo,
-- only return the main item
case when Regular = 'Y' then Item end AS Item,
Qty,
-- assign a unique number to each `YNNN..` component group
-- needed for GROUP BY in next step
sum(case when Regular = 'Y' then 1 else 0 end)
over (partition by OrderNo
order by Line
rows unbounded preceding) as grp
from myTable
)
select OrderNo,
-- find the matching value for the main component
max(Item),
sum(Qty)
from cte
group by OrderNo, grp
Current representation is against 1st Codd's rule.
Rule 1: The information rule: All information in a relational data
base is represented explicitly at the logical level and in exactly one
way – by values in tables.
But I believe you can still create FUNCTION/PROCEDURE and iterate row one by one with IF statement for Y/N. E.g. you create new table, IF Y - add new row to table, IF N - add +1 to QTY to latest row.
I would create two separate tables: manufacturer & part, to get the values so you don't have to hand-jam each inventory, or care about where they fall in the invoice list.
[1
[]2
Then, all you would need to do is compare the values to the part table to get this data. It's more work upfront, but will pay off to have this all saved and stored. A future sample query would look something like:
SELECT OrderNo.OrderTable, Item.OrderTable, Sum(Qty.OrderTable) AS Quantity
FROM OrderTable INNER JOIN Part ON OrderTable.Item = Table.PartName
GROUP BY OrderNo.OrderTable, Item.OrderTable, Regular.OrderTable, Part.ParentID;
try this:
select orderno, item, sum(qty) over(partition by regular order by regular)
from your_table
group by orderno, item, regular

Omit item from Sum SQL

I'm very new to programming and SQL, I can't figure this one out, perhaps I haven't learned the concept yet, but I'm hoping you can help me. Sorry if it's too easy and boring.
/*2.37 Write an SQL statement to display the WarehouseID and the sum of
QuantityOnHand,grouped by WarehouseID. Omit all SKU items that have 3 or more items
on hand from the sum, and name the sum TotalItemsOnHandLT3 and display the results
in descending order of TotalItemsOnHandLT3.*/
SELECT WarehouseID, SUM(QuantityOnHand) AS TotalItemsOnHandLT3
FROM INVENTORY
GROUP BY WarehouseID
HAVING COUNT(WarehouseID) >= 3
ORDER BY TotalItemsOnHandLT3 DESC
"Omit all SKU items that have 3 or more items on hand from the sum", sounds more like :
FROM INVENTORY WHERE QuantitiyOnHand < 3
rather than :
HAVING COUNT(WarehouseID) >= 3
INVENTORY is the list of products (SKU = Stock Keeping Unit = Individual Product Stored in the warehouse) where every product has a WarehouseID. This warehouseID presumably determines where the product is stored.
By Omit all SKU items, it asks you to only display those products that are stored in minimum 3 places in the warehouse. This can be done with the having clause,
HAVING COUNT(WarehouseID) >= 3
I do not know the structure and data of your INVENTORY table, but simply put, Consider your data is like this:
SKUID WareHouseID QuantityOnHand
1 1 10
1 2 10
2 1 10
1 3 5
2 2 20
In the above case, Product = 1 (SKUID), is stored in 3 different warehouses whereas product 2 is stored in 2 warehouses. Hence,
SKUID COUNT(WareHouseID) SUM(QuantityOnHand)
1 3 25
2 2 30
In this case, your query will only "Omit" product 1, and not the product 2.
Its says Omit, they why
HAVING COUNT(WarehouseID) >= 3
and not
HAVING COUNT(WarehouseID) < 3