Exclusive Disjunction - sql

I have a table with product prices. There are two price types: A = offer price, B = standard price. Example:
product_id | price | price_type
-------------------------------------
001 | 0.99 | A
001 | 1.49 | B
I'm looking for a SELECT-statement that returns
the standard price (type B), if there's no offer price (type A) for this product,
the offer price (type A) whenever the product has a price of type A (disregarded if a price of type B is maintained or not).
For my example data above the statement has to return the offer price = 0.99 = type A.

This can be done with a left join. I am assuming that you will at least always have a standard price row for a product.
select p1.product_id,
coalesce(p2.price, p1.price) as price
from product_prices p1
left join product_prices p2
on p2.product_id = p1.product_id
and p2.price_type = 'A'
where p1.price_type = 'B'

You could left join two queries on the table and use coalesce to determine if there's an offer or not:
SELECT b.product_id, COALESCE(a.price, b.price)
FROM (SELECT product_id, price
FROM products
WHERE price_type = 'B') b
LEFT JOIN (SELECT product_id, price
FROM products
WHERE price_type = 'A') a ON a.product_id = b.product_id

Based on your example, it could be done by:
select *
from product
where price_type in (select min(price_type)
from product group by product_id);

Related

How to do a nested COALESCE subquery in a join for 3 different tables?

I have these 3 tables, And using query, have table sorted on distinct on group/product.
Now I would like to add a child table each of these 3 table with say just 1 column default_something
Query:
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
ORDER BY pg.id, p.prod_id, v.version DESC;
Product Group Table
id group_name
---------------------------
1 Nice
2 Very Nice
Product table
prod_id name group_id
---------------------------
1 something 2
2 psp3 1
3. other one 2
Version Table
version_id prod_id version
---------------------------
1 2 1.0
2 2 1.1
3 3 2.3
4 1 0.1
5. 1 0.2
Product Group Child Table
pgt_child_id group_id default_something
---------------------------------
1 2 root2
2 1 root1
Product Child table
pt_child_id prod_id default_something
-------------------------------------------
1 3 override2
Version Child Table
v_child_id version_id default_something
-------------------------------------------
1 2 winner
Running the query DBFiddle I get this now...
Group_name prod_name version
---------------------------------
Nice psp3 1.1
Very Nice something 0.2
Very Nice other one 2.3
What I want is like this
Group_name prod_name version default_something
-----------------------------------------------------
Nice psp3 1.1 winner
Very Nice something 0.2. root2
Very Nice other one 2.3. override2
Basically if Version table has the field default_something.. that always wins.. If Version Table does not and if Product Table has the field default_something, that would win.. And Group Product Table basically has the lowest priority so if Version Table and Product Table does not have the field then value from Group Product Table wins.
I would assume SELECT COALESCE() would work to get the value which wins from 3 child tables.. I just have not figured out how to put that in a subquery with a join or something.
You can make just a COALESCE with with three subqueries:
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version,
COALESCE((select default_something from version_child where version_id = v.id),
(select default_something from product_child where prod_id = p.prod_id),
(select default_something from product_group_child where group_id = pg.id)
) as something
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
ORDER BY pg.id, p.prod_id, v.version DESC;
see the amended DBFiddle
or left join the three tables and make the coalesce on the default_somethings
SELECT DISTINCT ON (pg.id, p.prod_id)
pg.group_name, p.name AS prod_name, v.version,
COALESCE(v2.default_something,
p2.default_something,
pg2.default_something
) as something
FROM product_group pg
LEFT JOIN product p ON pg.id = p.group_id
LEFT JOIN version v ON v.prod_id = p.prod_id
LEFT JOIN version_child v2 ON v2.version_id = v.id
LEFT JOIN product_child p2 ON p2.prod_id = p.prod_id
LEFT JOIN product_group_child pg2 ON pg2.group_id = pg.id
ORDER BY pg.id, p.prod_id, v.version DESC;
DBFiddle

Insert Data if not exists (from 2 tables) and Update otherwise

Good day. I have 3 tables:
tblWarehouseProducts:
ProductID
ProductName
ProductCode
Quantity
tblBranchProducts:
ProductID
ProductCode
ProductCode
Quantity
Location
tblStockMoves:
ProductID
DestinationLocation
Quantity
ReferenceNumber
Basically, the process is that Branch X requests a product from Warehouse Y. Warehouse Y then creates a request order(called a Stock Move) and stores the request in tblStockMove.
Say for this case, we have a Stock Move with Reference Number XYZ:
REFERENCE NO. | PRODUCT ID | DESTINATION | QTY |
XYZ | 1 | BRANCH Y | 5 |
XYZ | 2 | BRANCH Y | 6 |
(where ProductID 1 is Coke and ProductID 2 is Pepsi.) Branch X on the other hand has this product on stock:
PRODUCT ID | PRODUCT NAME | PRODUCT CODE | QUANTITY | LOCATION |
1 | COKE | ABC123 | 6 | Branch X |
I am currently trying to check if the items from tblStockMoves exist in tblBranchProducts.
If Product 1 exists, it will add the Qty from tblStockMoves to the current Qty in tblBranchProducts. Product 2 will be added as a new entry since it is a new item.
I am using this query below but so far, all it does is update the stock of ProductID 1 while ignoring (not inserting) Product ID 2.
IF EXISTS (select ProductID, Location
from tblBranchProducts a
where Location = 'Branch X'
and a.ProductID in (select b.ProductID
from tblStockMoves b
where b.ReferenceNumber = 'XYZ'
and b.DestinationLocation = 'Branch X'))
BEGIN
UPDATE tblBranchProducts
SET Quantity = a.Quantity + b.Quantity
FROM tblBranchProducts a
INNER JOIN tblStockMoves b ON a.ProductID = b.ProductID
WHERE
b.ReferenceNumber = 'XYZ'
AND b.DestinationLocation = 'Branch X'
END
ELSE
BEGIN
INSERT INTO tblBranchProducts (ProductID, ProductName, ProductCode, Quantity, Location)
SELECT
b.ProductID, a.ProductName, a.ProductCode, b.Quantity, b.DestinationLocation
FROM
tblStockMoves b
INNER JOIN
tblWarehouseProducts a ON b.ProductID = a.ProductID
WHERE
b.ReferenceNumber = 'XYZ'
AND b.DestinationLocation = 'Branch X'
Other details such as Product Name and Product Code are pulled from tblWarehouseProducts and then inserted to tblBranchProducts.
Can anyone tell me why my query only updates the existing stock of Product 1 and not inserting Product 2?
Your answers are deeply appreciated!
You can do it dynamically for all products with out IF's , just addthe conditions required:
/*will insert all the unmatched products*/
INSERT INTO tblBranchProducts (ProductID, ProductName, ProductCode, Quantity, Location)
SELECT b.ProductID, a.ProductName, a.ProductCode, b.Quantity, b.DestinationLocation
FROM tblStockMoves b
inner join tblWarehouseProducts a on b.ProductID = a.ProductID
LEFT JOIN tblBranchProducts c ON(a.productid = b.productid)
where c.productid is null
And:
/*will update all the matching products*/
update tblBranchProducts a
INNER join tblStockMoves b on a.productid = b.productid
set a.quantity= b.qty

sql 2000 select id where multiple row conditions are met

I am struggling to get my head around this sql.
I have a function that returns a list of items associated with a Bill of Materials BOM.
The result of the sql select
SELECT
BOM,
ITEMID,
QTY
FROM boms
WHERE
bom='A'
is
BOM | ITEMID | QTY
A | ITEMB | 1
A | ITEMC | 2
Now using that result set I am looking to query my salestable to find sales where ITEMB and ITEMC were sold in enough quantity.
The format of the salestable is as follows
SELECT
salesid,
itemid,
sum(qtyordered) 'ordered'
FROM salesline
WHERE
itemid='ITEMB'
or itemid='ITEMC'
GROUP BY salesid, itemid
This would give me something like
salesid | itemid | ordered
SO-10000 | ITEMB | 1
SO-10001 | ITEMB | 1
SO-10001 | ITEMC | 1
SO-10002 | ITEMB | 1
SO-10002 | ITEMC | 2
ideally I would like to return only SO-10002 as this is the only sale where all necessary units were sold.
Any suggestions would be appreciated. Ideally one query would be ideal but I am not sure if that is possible. Performance is not a must as this would be run once a week in the early hours of the morning.
EDIT
with the always excellent help, the code is now complete. I have wrapped it all up into a UDF which simply returns the sales for a specified BOM over a specified period of time.
Function is
CREATE FUNCTION [dbo].[BOMSALES] (#bom varchar(20),#startdate datetime, #enddate datetime)
RETURNS TABLE
AS
RETURN(
select count(q.SALESID) SOLD FROM (SELECT s.SALESID
FROM
(
SELECT s.SALESID, ITEMID, SUM(qtyordered) AS SOLD
FROM salesline s inner join SALESTABLE st on st.salesid=s.SALESID
where st.createddate>=#startdate and st.CREATEDDATE<=#enddate and st.salestype=3
GROUP BY s.SALESID, ITEMID
) AS s
JOIN dbo.BOM1 AS b ON b.ITEMID = s.ITEMID AND b.QTY <= s.SOLD
where b.BOM=#bom
GROUP BY s.SALESID
HAVING COUNT(*) = (SELECT COUNT(*) FROM dbo.BOM1 WHERE BOM = #bom)) q
)
This should return all sales with an exact match, i.e. same itemid and same quantity:
SELECT s.salesid
FROM
(
SELECT salesid, itemid, SUM(qtyordered) AS ordered
FROM salesline AS s
GROUP BY salesid, itemid
) AS s
JOIN
boms AS b
ON b.itemid = s.itemid
AND b.QTY = s.ordered
WHERE b.BOM='A'
GROUP BY s.salesid
HAVING COUNT(*) = (SELECT COUNT(*) FROM boms WHERE BOM='A');
If you want to return a sale where the quantity is greater than boms.qty youhave to change the join accordingly:
JOIN
boms AS b
ON b.itemid = s.itemid
AND b.QTY <= s.ordered
Untested...
You can do this aggregation and a having clause:
select salesid
from salesline sl
group by salesid
having sum(case when itemid = 'ITEMB' then 1 else 0 end) > 0 and
sum(case when itemid = 'ITEMA' then 1 else 0 end) > 0;
Each condition in the having clause is counting the number of rows with each item.
I think this may get you the results you need. You'll have to replace #BOM with your bom:
SELECT
DISTINCT salesid
FROM
salesline sl
INNER JOIN boms b ON
b.bom = #BOM
AND b.itemid = sl.itemid
GROUP BY salesid, itemid
HAVING SUM(qtyordered) >= b.qty
From what I gather, the first query is used to get the thresholds for returning qualifying sales? Based on your example data rows I assumed that there will only be one line per salesid + itemid (basically acting as a dual field primary key) in the salesline table? If that is true I don't think there is a need to do a SUM as you have in your second example query. Let me know if I'm mistaken in any of my assumptions and I'll adjust my answer.

SQL Query Help SUM of Columns accross multiple tables

Hi I wonder if you can help with the following query , I am going around in circles trying to get the syntax correct.
I have two Tables Orders
OrderID | Product ID | LineTotal
1 ABC 2
2 CDE 3
2 DEF 1
and Products Table Containing the Weight and Cost
ProductID | Weight | Cost
ABC 1 1
CDE 2 2
DEF 1 0.5
So for each order ID I need to SUM the LineTotal the Weight and the Cost.
Thanks for some pointers on how to go about this as I am getting errors with joins and silly results
Thanks
It should be very simple if I got the task right:
SELECT o.OrderID, o.ProductID, sum = (o.LineTotal + p.Weight + p.Cost)
FROM ORDERS o
INNER JOIN PRODUCTS p on o.ProductID = p.ProductID
Try this.
Select t3.OrderID , SUM(t3.SUM1) As TotalSum
From (Select t1.*,t2.Weight,t2.Cost,t1.LineTotal+t2.Weight+t2.Cost AS Sum1
from Orders t1
INNER JOIN Products t2
ON t1.ProductID=t2.ProductID ) t3
Group BY t3.OrderID

SELECT Statement using INTERSECT

I have two tables: tblProduct which has list of Products, and tblConsumer which has consumer name with consumed product ID.
Now I need to find the name of consumers who have consumed all products from the product table.
I tried to solve this with using INTERSECT, but the problem is I have provide each productid in WHERE clause. This syntax gives the result that I wanted, but how do I write this query where I don’t need to specify each productID.
SELECT ConsumerName FROM tblConsumer WHERE ProductID= 1
INTERSECT
SELECT ConsumerName FROM tblConsumer WHERE ProductID =2
INTERSECT
SELECT ConsumerName FROM tblConsumer WHERE ProductID =3
tblProduct
---------------------------------
ProductID | Product Name
---------------------------------
1 | Mango
2 | Orange
3 | Banana
tblConsumer
---------------------------------
ConsumerName | ProductID
---------------------------------
David | 1
David | 3
David | 2
Henry | 3
Henry | 2
If you're actually wanting to list all the Products in tblProducts, then you can use NOT EXISTS...
Otherwise, if you have a list of the Products you want to check, you can do something like:
SELECT c.ConsumerName
FROM tblConsumer AS c
WHERE c.ProductID IN (1,2,3)
GROUP BY c.ConsumerName
HAVING COUNT(DISTINCT c.ProductID) = (SELECT COUNT(DISTINCT p.ProductID) FROM tblProduct WHERE p.ProductID IN (1,2,3))
;
But I think maybe you just want to use NOT EXISTS to eliminate the Consumers for whom there's a record they haven't bought.
Like this:
SELECT *
FROM tblPerson AS pn CROSS JOIN tblProduct AS pt /* Every possible combo */
WHERE NOT EXISTS (SELECT * FROM tblConsumer c
WHERE c.ConsumerName = pn.ConsumerName
AND c.ProductID = pt.ProductID)
;
I have an other small solution:
SELECT * FROM tblConsumer
WHERE NOT EXISTS (SELECT * FROM tblProduct
LEFT JOIN tblConsumer C ON tblProduct.ProductID = C.ProductID AND tblConsumer .ConsumerName = C.ConsumerName
WHERE C.ConsumerName IS NULL)
It will work if you add a new entry too. It just checks, that is there any record, where you cant make a connection between the given Consumer and a Product.