Create row if column does not consist ID from another column - sql

I have database which contain three tables product, product_size (variations) and handbook. Product contains all data about the product, handbook knows which size I have and product_size contain information about products which have size(s) from handbook and also contain size_value.
So I need to insert values in column product_size where size_price will contain price of product, handbook_id = 666 and product_id is ID of products which doesn't have product size. Is there any way to do that?
Product
id
price
name
Product_Size
id
product_id
handbook_id
size_price
Handbook
id
name

All products would mean rows from product table. So size may not be in handbook or may not be in product_size table.
Select p.name
From product p left join
Product_Size ps
On p.id= ps.product_id
Left join Handbook h
On h.id=ps.handbook_id
Where handbook_id is null

You can phrase the insert like this:
insert into product_size (product_id, handbook_id, size_price)
select p.product_id, 666, p.price
from product p
where not exists (select 1
from product_size ps
where ps.product_id = p.product_id and ps.handbook_id = 666
);
Note: This can produce unexpected results if you attempt to run two of these inserts at the same time. To protect your data from duplicates, you should have a unique constraint/index on product_size(product_id, handbook_id).

Related

Return stock in a warehouse even if there is no row for that given stock

I have 5 different tables:
Toasters: product name (foreign key to products and primary key), slots, serial
Microwaves: product name (same as toaster), wattage
Products: product name (primary key)
Stock: product (fk to product), warehouse (fk to warehouse), amount
Warehouse: name (primary key)
toasters and microwaves are child tables of products (although its not using postgres inheritance, since there are issues with it). They represent different models of toasters (simplified to just slots and wattage here). Every toaster and microwave has exactly 1 entry in the products table.
Now the goal is to create a query that essentially gives me an amount of all products across all warehouses for a given list of product names. The problem is, that some warehouses may not have a stock entry for a certain product. They also have either one stock per product or none.
I have managed to make it work for a single warehouse:
--join together all 3 product tables and select all desired products
WITH selIProducts AS(
SELECT
--Get the products category by checking if the table is part of the query
(CASE
WHEN toasters IS NOT NULL THEN 'toasters'
WHEN microwaves IS NOT NULL THEN 'microwaves'
ELSE 'ERROR'
END) as category,
products.name as productName,
*
FROM products
--I need a full join to include everything
FULL JOIN toasters ON toasters.name=products.name
FULL JOIN microwaves ON microwaves.name=products.name
WHERE
products.name IN (
'TOASTMASTER 3000',
'TOASTMASTER 3000Rev01',
'A3452 Ultra Microwave Oven',
)
),
warehouseStock AS
(
--only works with one inventory
SELECT * FROM STOCK
WHERE stock.warehouse='WH-1'
)
-- left join to ensure all item categories are included
SELECT COALESCE(warehouseStock.amount,0) as amount,* FROM selProducts
LEFT JOIN warehouseStock ON selIProducts.itemId=warehouseStock.item
It tried replacing WHERE stock.warehouse='WH-1' with WHERE stock.warehouse IN ('WH-1','WH-2') but that doesn't work since the desired product types are only joined once, instead of once per warehouse.
The final result should look like this:
Warehouse productName amount wattage slots category
WH-1 TOASTMASTER 3000 0 null 2 toasters
WH-1 TOASTMASTER 3000Rev01 1 null 3 toasters
WH-1 A3452 Ultra Microwave Oven 1 3000 null microwave
WH-2 TOASTMASTER 3000 2 null 2 toasters
WH-2 TOASTMASTER 3000Rev01 0 null 3 toasters
WH-2 A3452 Ultra Microwave Oven 0 3000 null microwave
I don't know how I am I should get postgres to return a null when there isn't a stock in a given warehouse.
Does anybody have any ideas?
You seem to want all products and all warehouses. That suggests a cross join to generate the rows:
SELECT v.warehouse, p.productname,
COALESCE(s.amount, 0) as amount
FROM selProducts p CROSS JOIN
(VALUES ('WH-1'), ('WH-2')) v(warehouse) LEFT JOIN
stock s
ON p.itemId = s.item AND v.warehouse = s.warehouse;
You might have another source for the warehouses, if you don't want to list them explicitly.
Add a table of warehouses wanted.
WITH selIProducts AS(
SELECT
--Get the products category by checking if the table is part of the query
(CASE
WHEN toasters IS NOT NULL THEN 'toasters'
WHEN microwaves IS NOT NULL THEN 'microwaves'
ELSE 'ERROR'
END) as category,
products.name as productName,
*
FROM products
--I need a full join to include everything
FULL JOIN toasters ON toasters.name=products.name
FULL JOIN microwaves ON microwaves.name=products.name
WHERE
products.name IN (
'TOASTMASTER 3000',
'TOASTMASTER 3000Rev01',
'A3452 Ultra Microwave Oven',
)
),
warehousesWanted AS
(
SELECT *
FROM Warehouse
WHERE name in ('WH-1', 'WH-2')
)
-- left join to ensure all item categories are included
SELECT COALESCE(warehouseStock.amount,0) as amount, *
FROM selIProducts sp
CROSS JOIN warehousesWanted ww
LEFT JOIN Stock ON Stock.itemId = sp.itemId
and ww.Name = Stock.Warehouse;
You may need to correct ON clause as I'm not sure what are proper column names of your real tables.

Query returns more rows

I have two tables. product_type listing name of products with product id. There are 5 product types which have same product id (1) as they are shared along with 4 other product types with unique product ids (2 to 9). Other table is product. It has the list of customer ids along with what product template id associated with it.
I want to get a list of products which are being used by customer. But I am getting a list with duplicate product rows if there are two customers using the same product. I just want to get a unique list of products being used by all the customers.
Product Table
Product Product_id
AML 1
EDU 1
EXM 1
JEXM 2
JFSA 3
Customer Table
Customer_id Product_id
112 1
113 2
114 1
115 3
116 4
117 2
The query:
SELECT CTE.ProductType, CTE.PRODUCT_ID, DECODE(CT.PRODUCT_ID,NULL,0,1) AS HasCustomer
FROM (
SELECT
LISTAGG(pt.product_type, ', ') WITHIN GROUP (ORDER BY pt.product_type) as ProductType,
pt.PRODUCT_ID
FROM product_type pt
group by pt.PRODUCT_ID) CTE
JOIN CUSTOMER CT ON CT.PRODUCT_ID = CTE.PRODUCT_ID;
Blockquote
I think you want:
SELECT c.customer_id,
LISTAGG(p.product, ', ') WITHIN GROUP (ORDER BY p.product) as products
FROM customers c JOIN
products p
ON c.PRODUCT_ID = p.PRODUCT_ID
GROUP BY c.customer_id;
For each customer, this will give the list of products for the customer.
I based this on your sample data. I don't see the relationship between your query and the sample data.
EDIT:
If you want all products used by any customer, then simply do:
select distinct c.product_id
from customers c;

PL SQL query to find a list of identifiers that matches all values in a list

Let's assume I have a table that has the following data:
SHELF_ID PRODUCT_ID
shelf1 product1
shelf1 product2
shelf1 product3
shelf2 product1
shelf2 product2
shelf3 product1
I made a query 'queryA' that returns from another table
PRODUCT_ID
product1
product2
Now I want to use the 'queryA' in another query to identify which shelfs have at least all the products returned in 'queryA'
by looking at the first table you easily realize its shelf1 and shelf2, but how do I make this in PL SQL?
Thank you
I think this query can help you to find what you want :
Basically what did I do :
First I did COUNT(DISTINCT ) to product_id for each shelf and then checked if this count equal or greater than queryA product list that's mean the shelf products match.
Then I excluded products if they don't match with queryA with using EXISTS() . If you want to see also not matched products you don't need to use that filter.
--DROP TABLE shelves;
CREATE TABLE shelves
(
SHELF_ID VARCHAR(100)
,PRODUCT_ID VARCHAR(100)
);
INSERT INTO shelves
VALUES
('shelf1','product1')
,('shelf1','product2')
,('shelf1','product3')
,('shelf2','product1')
,('shelf2','product2')
,('shelf3','product1');
--DROP TABLE queryA;
CREATE TABLE queryA
(
PRODUCT_ID VARCHAR(100)
);
INSERT INTO queryA VALUES ('product1'),('product2');
SELECT *
FROM shelves S
WHERE S.SHELF_ID IN (
SELECT S.SHELF_ID
--,COUNT(DISTINCT S.PRODUCT_ID) PRODUCTCOUNT
FROM shelves S
GROUP BY S.SHELF_ID
HAVING COUNT(DISTINCT S.PRODUCT_ID)>=(SELECT COUNT(DISTINCT PRODUCT_ID)
FROM queryA Q )
)
AND EXISTS (SELECT 1
FROM queryA Q
WHERE S.PRODUCT_ID = Q.PRODUCT_ID
)
You can do this as:
with products as (
<your query here)
)
select s.shelf_id
from shelves s join
products p
on s.product_id = p.product_id
group by s.shelf_id
having count(distinct s.product_id) = (select count(*) from products);
Basically, this counts the number of matches on each shelf, and makes sure that the count of matches is the total count of products.
If there are no duplicates in shelves, you can use having count(*) = . . .).
You can do this - no aggregation needed. (Other than the SELECT DISTINCT in the top query; it would be MUCH better if you had an additional table, SHELVES, with SHELF_ID as primary key, then the query could be much more efficient.)
select distinct shelf_id
from shelves s
where not exists (select product_id
from queryA -- Your existing query goes here
where (s.shelf_id, product_id)
not in (select shelf_id, product_id
from shelves
)
)
;

how to use inner join queries for four tables

I have four tables in my database(notes, expense, category and items).
items table will have so many items on the particular category id
items table have fields
id(primary key), item name(text), unit(text), category id(foreign key)
category table will have just two fields
category table have fields
id(primary key), cat_id(integer), category name(text).
notes table is used to have a record for each shopping note.
notes table have fields
note_id(primary key), date(DATE), total cost(integer).
expense table is used to store the items purchased and its corresponding quantity and price under the particular shopping note using note id as the foreign key
expense table has fields
id(primary key), note_id(foreign key), quantity(integer), price(integer), item_name(text)
When I input the from date,to date and a particular category i need the items under that category that was purchaed between the from and to date.
i need a query that will give output as:
ITEM NAME TOTALQTY TOTALPRICE
carrot 5kg 500
can anyone help me with a solution??
Try this
select A.name, sum(C.unit), A.unit, sum(C.price)
from items as A
INNER JOIN categories as B ON A.category=B._id
INNER JOIN expenses as C ON A.name=C.item_name
INNER JOIN notes as D ON C.Note_id=D._id
where
D.date1 between '2012-01-01' and '2012-03-31' and B.name='Vegetables'
group by
A.name, A.unit

How to ensure outer join with filter still returns all desired rows?

Imagine I have two tables in a DB like so:
products:
product_id name
----------------
1 Hat
2 Gloves
3 Shoes
sales:
product_id store_id sales
----------------------------
1 1 20
2 2 10
Now I want to do a query to list ALL products, and their sales, for store_id = 1. My first crack at it would be to use a left join, and filter to the store_id I want, or a null store_id, in case the product didn't get any sales at store_id = 1, since I want all the products listed:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
WHERE store_id = 1 or store_id is null;
Of course, this doesn't work as intended, instead I get:
name sales
---------------
Hat 20
Shoes 0
No Gloves! This is because Gloves did get sales, just not at store_id = 1, so the WHERE clause has filtered them out.
How then can I get a list of ALL products and their sales for a specific store?
Here are some queries to create the test tables:
create temp table test_products as
select 1 as product_id, 'Hat' as name;
insert into test_products values (2, 'Gloves');
insert into test_products values (3, 'Shoes');
create temp table test_sales as
select 1 as product_id, 1 as store_id, 20 as sales;
insert into test_sales values (2, 2, 10);
UPDATE: I should note that I am aware of this solution:
SELECT name, case when store_id = 1 then sales else 0 end as sales
FROM test_products p
LEFT JOIN test_sales s ON p.product_id = s.product_id;
however, it is not ideal... in reality I need to create this query for a BI tool in such a way that the tool can simply add a where clause to the query and get the desired results. Inserting the required store_id into the correct place in this query is not supported by this tool. So I'm looking for other options, if there are any.
Add the WHERE condition to the LEFT JOIN clause to prevent that rows go missing.
SELECT p.name, coalesce(s.sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
AND s.store_id = 1;
Edit for additional request:
I assume you can manipulate the SELECT items? Then this should do the job:
SELECT p.name
,CASE WHEN s.store_id = 1 THEN coalesce(s.sales, 0) ELSE NULL END AS sales
FROM products p
LEFT JOIN sales s USING (product_id)
Also simplified the join syntax in this case.
I'm not near SQL, but give this a shot:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id AND store_id = 1
You don't want a where on the whole query, just on your join