SUM each row with corresponding row of another column - sql

I'm trying to update the values of a select number of rows from one column to each be incremented by a certain value taken from another column from another table but I keep hitting this wall : 'single-row subquery returns more than one row'.
Essentially what this is all about is deleting a hypothetical shopping cart (each row in the shopping cart corresponds to a certain item and a desired quantity of that item) but before doing that I must first make sure that all the item quantities from the cart are returned to the product relation which keeps track of all available items and the quantity of those items currently on supply.
UPDATE Product SET supply = (...) - subquery returning a single column list of all quantites to be added back to the respective product supplies, ordered by the product_id - even though it's not included in the select statement
WHERE product_id IN (...) - same query as the last one but returns the product ids of all the corresponding shopping cart items rather than their quantities, again ordered by product_id
All looks fine but ORACLE's not having it. Perhaps this can be looped somehow? If not I'll have to resort to doing it in the app layer (PHP)
Product relation:
product_id
Supply
...
1
10
...
2
20
...
3
10
...
Basket relation:
product_id
quantity
...
1
3
...
2
4
...
Upon the deletion of the cart I would like the two quantities from the cart table to be added back to their corresponding rows in the supply column in the product relation
This:
product_id
Supply
...
1
10
...
2
20
...
3
10
...
Turns into this:
product_id
Supply
...
1
13
...
2
24
...
3
10
...

You can use corelated subquery as follows:
Update product p
Set p.supply = p.supply + coalesce(select quantity from basket b
Where b.product_id = p.product_id), 0)
It is considered good to use exists to reduce the redo logs as follows:
Update product p
Set p.supply = p.supply + (select quantity from basket b
Where b.product_id = p.product_id)
WHERE EXISTS
(Select 1 from basket b
Where b.product_id = p.product_id)
Please note that we have removed the coalesce in this query as it is not required if we use exists.

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.

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

select if not a count

I have a 3 tables Product, sku and option
Product
ID FlatID Name
314E5E5E-0A7E-4DC5-872C-16DA8DA36439 0706 Jacket
SKU
ID productID QTY
6CFE849A-4856-43E8-9837-3B1D1122A701 314E5E5E-0A7E-4DC5-872C-16DA8DA36439 1
F76C9B5B-CEB5-44FE-AD27-69C4A3C124AE 314E5E5E-0A7E-4DC5-872C-16DA8DA36439 1
BB413DE8-E310-4393-95E9-8262D6EE515A 314E5E5E-0A7E-4DC5-872C-16DA8DA36439 1
866CD899-F25F-4C4D-9E56-A4A95A7BDC5D 314E5E5E-0A7E-4DC5-872C-16DA8DA36439 1
Options
skuid OptionID name
6CFE849A-4856-43E8-9837-3B1D1122A701 1C823E53-EE98-4B22-8118-287BCA55C4D8 BK
6CFE849A-4856-43E8-9837-3B1D1122A701 6D7168A0-775A-4C5B-B8E8-8CE3AF8A8665 LG
F76C9B5B-CEB5-44FE-AD27-69C4A3C124AE 1C823E53-EE98-4B22-8118-287BCA55C4D8 BK
F76C9B5B-CEB5-44FE-AD27-69C4A3C124AE F6A0974C-303D-4C6A-8CE1-39071C7DF566 MD
BB413DE8-E310-4393-95E9-8262D6EE515A FB4D7BC1-6C71-4E00-8C85-0DE93DC1C531 SM
BB413DE8-E310-4393-95E9-8262D6EE515A 1C823E53-EE98-4B22-8118-287BCA55C4D8 BK
866CD899-F25F-4C4D-9E56-A4A95A7BDC5D F6E0FB61-047E-4356-B085-630B38CBC7F2 XL
How to select SKU which don't have a one of option - for example in this case SKU 866CD899-F25F-4C4D-9E56-A4A95A7BDC5D don't have a color option with name BK - but should.
Sometimes product(SKU) have a one or two or three options - and how find a SKU which don't have a some of option - may be need to get max count and compare but i dont know. ThanX
For example one product have a 2 sku one of them have a tree option - other one have a one option how to show that have not max option for SKU
I usually prefer left outer joins for this problem where you are getting all the rows in table A, in this case SKU, regardless of there is a match in B, or options. then use the where clause to filter out the matches so you are left with only skus without options.
SELECT SKU.*
FROM SKU
LEFT OUTER JOIN Options
ON Options.skuid = SKU.id
WHERE Options.skuid IS NULL
You don't want any details of option table you just want check whether skuid present in option table so..
SELECT *
FROM sku A
WHERE NOT EXISTS (SELECT 1
FROM options
WHERE options.skuid = A.skuid)

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

My aggregate is not affected by ROLLUP

I have a query similar to the following:
SELECT CASE WHEN (GROUPING(Name) = 1) THEN 'All' ELSE Name END AS Name,
CASE WHEN (GROUPING(Type) = 1) THEN 'All' ELSE Type END AS Type,
sum(quantity) AS [Quantity],
CAST(sum(quantity) * (SELECT QuantityMultiplier FROM QuantityMultipliers WHERE a = t.b) AS DECIMAL(18,2)) AS Multiplied Quantity
FROM #Table t
GROUP BY Name, Type WITH ROLLUP
I'm trying to return a list of Names, Types, a summed Quantity and a summed quantity multiplied by an arbitrary number. All fine so far. I also need to return a sub-total row per Name and per Type, such as the following
Name Type Quantity Multiplied Quantity
------- --------- ----------- -------------------
a 1 2 4
a 2 3 3
a ALL 5 7
b 1 6 12
b 2 1 1
b ALL 7 13
ALL ALL 24 40
The first 3 columns are fine. I'm getting null values in the rollup rows for the multiplied quantity though. The only reason I can think this is happening is because SQL doesn't recognize the last column as an aggregate now that I've multiplied it by something.
Can I somehow work around this without things getting too convoluted?
I will be falling back onto temporary tables if this can't be done.
In your sub-query to acquire the multiplier, you have WHERE a=b. Are either a or b from the tables in your main query?
If these values are static (nothing to do with the main query), it looks like it should be fine...
If the a or b values are the name or type field, they can be NULL for the rollup records. If so, you can change to something similiar to...
CAST(sum(quantity * (<multiplie_query>)) AS DECIMAL(18,2)).
If a or b are other field from your main query, you'd be getting multiple records back, not just a single multiplier. You could change to something like...
CAST(sum(quantity) * (SELECT MAX(multiplier) FROM ...)) AS DECIMAL(18,2))