How to get record not joined result PostgreSQL - sql

I have
table customer {ID(primary key-autoincrement),CUSTOMERNAME}
table item {ID (primary Key-autoincrement),PRODUCT,PRICE}
table price {ID (primary key-autoincrement),CUSTOMERID (foreign key),ITEMID (foreign-key),PRICE}
Now I am filling data like this:
table customer : {1,ABC},{2,DEF}
table item :{1,EGG,50},{2,BRUSH,100},{3,SHOES,290},{4,SOCKS,120},{5,PILLOW,200}
table price :{1,1,3,320}
If I get the price for customer ABC (customer ID :1), then will be displayed result like this (5 records):
{1,1,EGG,50},{2,1,BRUSH,100},{3,1,SHOES,320},{4,1,SOCKS,120},{5,1,PILLOW,200}
But if I want to display price for customer DEF, then only displayed 4 records:
{1,1,EGG,50},{2,1,BRUSH,100},{4,1,SOCKS,120},{5,1,PILLOW,200}
How can I display the price for item with item ID (3) with default price 290 for customer DEF?
I use left outer join for this case then takes record with joined, then for second case they only returned 4 records.
Here my query (returned 4 records):
SELECT TMP."ID",TMP."IDCUSTOMER",TMI."NAME",
CASE WHEN TMP."PRICE" IS NULL THEN TMI."PRICE" ELSE TMP."PRICE" END AS "CUSTOMPRICE"
FROM mitem TMI
LEFT OUTER JOIN mprice TMP ON TMP."IDITEM"=TMI."ID"
LEFT OUTER JOIN mcustomer TMC ON TMC."ID"=TMP."IDCUSTOMER"
WHERE TMP."IDCUSTOMER"='2' OR TMP."ID" IS NULL
Any idea beside without using UNION to join result?

Use CROSS JOIN.
Like this:
SELECT TMP."ID",TMP."IDCUSTOMER",TMI."NAME",
CASE WHEN TMP."PRICE" IS NULL THEN TMI."PRICE" ELSE TMP."PRICE" END AS "CUSTOMPRICE"
FROM mitem TMI
CROSS JOIN mcustomer TMC
LEFT OUTER JOIN mprice TMP ON TMP."IDITEM"=TMI."ID"
AND TMP."IDCUSTOMER"= TMC."ID" AND TMP."IDCUSTOMER"= '2'
WHERE TMC."ID" = '2'

Related

How to subtract the old value with current value from database?

my question is I have 2 tables. let say Table Old and Table Current. Both tables have columns Quantity. Old.Quantity is the old values, and Current.Quantity is the current values.
Table Old :
id pro.no Quantity
1 123 3
Table Current :
id pro.no Quantity
1 123 2
SQL :
SELECT A.`Quantity`, B.`Quantity`
FROM Table Current A
LEFT JOIN Table Old B ON B.`pro.no` = A.`pro.no`
WHERE A.`id` = '1'
So, I want to subtract both values to get the new values which is 1 So that, when user key in the pro.no into the textbox, it'll show them they new value for Quantity is 1.
I am using VB.NET for the backend, and I'm still new for this language too.
You can simply subtract both columns, however as this is a left join you'll need to care for the null values:
SELECT CASE
WHEN o.quantity IS NULL THEN c.quantity
ELSE c.quantity - o.quantity
END
FROM current c LEFT JOIN old o ON ... WHERE ...
or alternatively
SELECT c.quantity - CASE WHEN o.quantity IS NULL THEN 0 ELSE o.quantity END
FROM current c LEFT JOIN old o ON ... WHERE ...

LEFT OUTER JOIN with 'field IS NULL' in WHERE works as INNER JOIN

Today I've faced some unexplainable (for me) behavior in PostgreSQL — LEFT OUTER JOIN does not return records for main table (with nulls for joined one fields) in case the joined table fields are used in WHERE expression.
To make it easier to grasp the case details, I'll provide an example. So, let's say we have 2 tables: item with some goods, and price, referring item, with prices for the goods in different years:
CREATE TABLE item(
id INTEGER PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE price(
id INTEGER PRIMARY KEY,
item_id INTEGER NOT NULL,
year INTEGER NOT NULL,
value INTEGER NOT NULL,
CONSTRAINT goods_fk FOREIGN KEY (item_id) REFERENCES item(id)
);
The table item has 2 records (TV set and VCR items), and the table price has 3 records, a price for TV set in years 2000 and 2010, and a price for VCR for year 2000 only:
INSERT INTO item(id, name)
VALUES
(1, 'TV set'),
(2, 'VCR');
INSERT INTO price(id, item_id, year, value)
VALUES
(1, 1, 2000, 290),
(2, 1, 2010, 270),
(3, 2, 2000, 770);
-- no price of VCR for 2010
Now let's make a LEFT OUTER JOIN query, to get prices for all items for year 2010:
SELECT
i.*,
p.year,
p.value
FROM item i
LEFT OUTER JOIN price p ON i.id = p.item_id
WHERE p.year = 2010 OR p.year IS NULL;
For some reason, this query will return a results only for TV set, which has a price for this year. Record for VCR is absent in results:
id | name | year | value
----+--------+------+-------
1 | TV set | 2010 | 270
(1 row)
After some experimenting, I've found a way to make the query to return results I need (all records for item table, with nulls in the fields of joined table in case there are no mathing records for the year. It was achieved by moving year filtering into a JOIN condition:
SELECT
i.*,
p.year,
p.value
FROM item i
LEFT OUTER JOIN (
SELECT * FROM price
WHERE year = 2010 -- <= here I filter a year
) p ON i.id = p.item_id;
And now the result is:
id | name | year | value
----+--------+------+-------
1 | TV set | 2010 | 270
2 | VCR | |
(2 rows)
My main question is — why the first query (with year filtering in WHERE) does not work as expected, and turns instead into something like INNER JOIN?
I'm severely blocked by this issue on my current project, so I'll be thankful about tips/hints on the next related questions too:
Are there any other options to achieve the proper results?
... especially — easily translatable to Django's ORM queryset?
Upd: #astentx suggested to move filtering condition directly into JOIN (and it works too):
SELECT
i.*,
p.year,
p.value
FROM item i
LEFT OUTER JOIN price p
ON
i.id = p.item_id
AND p.year = 2010;
Though, the same as my first solution, I don't see how to express it in terms of Django ORM querysets. Are there any other suggestions?
The first query does not work as expected because expectation is wrong. It does not work as INNER JOIN as well. The query returns a record for VCR only if there is no price for VCR at all.
SELECT
i.*,
y.year,
p.value
FROM item i
CROSS JOIN (SELECT 2010 AS year) y -- here could be a table
LEFT OUTER JOIN price p
ON (p.item_id = i.id
AND p.year = y.year);

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.

What is the best way to join tables

this is more like a general question.
I am looking for the best way to join 4, maybe 5 different tables. I am trying to create a Power Bi pulling live information from an IBM AS400 where customer service can type one of our parts number,
see how many parts we have in inventory, if none, see the lead time and if there are any orders already already entered for the typed part number.
SERI is our inventory table with 37180 records.
(active inventory that is available)
METHDM is our kit table with 37459 records.
(this table contains the bill of materials for custom kits, KIT A123 contains different part numbers in it witch are in SERI as well.)
STKA is our part lead time table with 76796 records.
(lead time means how long will it take for parts to come in)
OCRI is our sales order table with 6497 records.
(This table contains all customer orders)
I have some knowledge in writing queries but this one is more challenging of what I have created in the past. Should I start with the table that has the most records and start left joining the rest ?
From STKA 76796 records
Left join METHDM 37459 records on STKA
left join SERI 37180 records on STKA
left join OCRI 6497 records on STAK
Select
STKA.v6part as part,
STKA.v6plnt as plant,
STKA.v6tdys as pur_leadtime,
STKA.v6prpt as Pur_PrepLeadtime,
STKA.v6lead as Mfg_leadtime,
STKA.v6prpt as Mfg_PrepLeadTime,
METHDM.AQMTLP AS COMPONENT,
METHDM.AQQPPC AS QTYNEEDED,
SERI.HTLOTN AS BATCH,
SERI.HTUNIT AS UOM,
(HTQTY - HTQTYC) as ONHAND,
OCRI.DDORD# AS SALESORDER,
OCRI.DDRDAT AS PROMISED
from stka
left join METHDM on STKA.V6PART = METHDM.AQPART
left join SERI on STKA.V6PART = SERI.HTPART
left join OCRI on STKA.V6PART = OCRI.DDPART
Is this the best way to join the tables?
I think you already have your answer, but conceptually, there are a few issues here to deal with, and I figured I would give you a few examples, using data a little bit like yours, but massively simplified.
CREATE TABLE #STKA (V6PART INT, OTHER_DATA VARCHAR(50));
CREATE TABLE #METHDM (AQPART INT, KIT_ID INT, SOME_DATE DATETIME, OTHER_DATA VARCHAR(50));
CREATE TABLE #SERI (HTPART INT, OTHER_DATA VARCHAR(50));
CREATE TABLE #OCRI (DDPART INT, OTHER_DATA VARCHAR(50));
INSERT INTO #STKA SELECT 1, NULL UNION ALL SELECT 2, NULL UNION ALL SELECT 3, NULL; --1, 2, 3 Ids
INSERT INTO #METHDM SELECT 1, 1, '20200108 10:00', NULL UNION ALL SELECT 1, 2, '20200108 11:00', NULL UNION ALL SELECT 2, 1, '20200108 13:00', NULL; --1 Id appears twice, 2 Id once, no 3 Id
INSERT INTO #SERI SELECT 1, NULL UNION ALL SELECT 3, NULL; --1 and 3 Ids
INSERT INTO #OCRI SELECT 1, NULL UNION ALL SELECT 4, NULL; --1 and 4 Ids
So fundamentally we have a few issues here:
o the first problem is that the IDs in the tables differ, one table has an ID #4 but this isn't in any of the others;
o the second issue is that we have multiple rows for the same ID in one table;
o the third issue is that some tables are "missing" IDs that are in other tables, which you already covered by using LEFT JOINs, so I will ignore this.
--This will select ID 1 twice, 2 once, 3 once, and miss 4 completely
SELECT
*
FROM
#STKA
LEFT JOIN #METHDM ON #METHDM.AQPART = #STKA.V6PART
LEFT JOIN #SERI ON #SERI.HTPART = #STKA.V6PART
LEFT JOIN #OCRI ON #OCRI.DDPART = #STKA.V6PART;
So the problem here is that we don't have every ID in our "anchor" table STKA, and in fact there's no single table that has every ID in it. Now your data might be fine here, but if it isn't then you can simply add a step to find every ID, and use this as the anchor.
--This will select each ID, but still doubles up on ID 1
WITH Ids AS (
SELECT V6PART AS ID FROM #STKA
UNION
SELECT AQPART AS ID FROM #METHDM
UNION
SELECT HTPART AS ID FROM #SERI
UNION
SELECT DDPART AS ID FROM #OCRI)
SELECT
*
FROM
Ids I
LEFT JOIN #STKA ON #STKA.V6PART = I.Id
LEFT JOIN #METHDM ON #METHDM.AQPART = I.Id
LEFT JOIN #SERI ON #SERI.HTPART = I.Id
LEFT JOIN #OCRI ON #OCRI.DDPART = I.Id;
That's using a common-table expression, but a subquery would also do the job. However, this still leaves us with an issue where ID 1 appears twice in the list, because it has multiple rows in one of the sub-tables.
One way to fix this is to pick the row with the latest date, or any other ORDER you can apply to the data:
--Pick the best row for the table where it has multiple rows, now we get one row per ID
WITH Ids AS (
SELECT V6PART AS ID FROM #STKA
UNION
SELECT AQPART AS ID FROM #METHDM
UNION
SELECT HTPART AS ID FROM #SERI
UNION
SELECT DDPART AS ID FROM #OCRI),
BestMETHDM AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY AQPART ORDER BY SOME_DATE DESC) AS ORDER_ID
FROM
#METHDM)
SELECT
*
FROM
Ids I
LEFT JOIN #STKA ON #STKA.V6PART = I.Id
LEFT JOIN BestMETHDM ON BestMETHDM.AQPART = I.Id AND BestMETHDM.ORDER_ID = 1
LEFT JOIN #SERI ON #SERI.HTPART = I.Id
LEFT JOIN #OCRI ON #OCRI.DDPART = I.Id;
Of course you could also add some aggregation (SUM, MAX, MIN, AVG, etc.) to fix this problem (if it is indeed an issue). Also, I used a common-table expression, but this would work just as well with a subquery.
Expanding on a comment made on the question..
I would say I will start with SERI as that table contains the entire inventory for our facility and should cover the other tables
However the question said
SERI is our inventory table with 37180 records. (active inventory that is available)
In my experience, active inventory, isn't the same as all parts.
Normally, in a query like this, I'd expect the first table to be a Parts Master table of some sort that contains every possible part ID.

Selecting from table with categories of people

I created a database in ms sql , in the database I have three category of persons namely staff, customers, suppliers whom I stored in different tables create serial unique id for each.
Now these persons id are stored under person_id and a column names person type which stores whether its a staff, custimer or supplier in the transaction table, The problem lies in selecting the records from the transaction table like this pseudo code
Select t.*,s.na as staff,sp.name as supplier, c.name as customer
From Trans t
left join Staff s on s.id = t.pid
left join Suppliers sp on sp.id = t.pid
left join Customers c on c.id = t.pid
This returns one row, instead of at least 3 or more, How do I solve this problem
My trans table
person_id Person_type Trans_id
1 staff 1
1 customer 2
2 customer 3
3 suppler 4
1 staff 5
Expected output
person_name Trans_id
james 1
mark 2
dan 3
jude 4
james 5
Staff, Customers, and suppliers are stored in their different tables
That's what the Join does, combine data from multiple tables into one result row. If you want to "keep the rows", not combine them, you can use UNION
(
Select t.* From Trans t
left join Staff s on s.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Suppliers sp on sp.id = t.pid
)
UNION
(
Select t.* From Trans t
left join Customers c on c.id = t.pid
)
This will get you the multiple rows you want BUT still not sure you have defined it right. I see you are only taking columns from Trans, so you're not getting any data from the other tables. And you're doing left outer joins so the other tables won't affect the selection. So I think it's just that same as selecting from just Trans.
If what you want is data from Trans where there is corresponding entry in the other tables, then do the UNION, but also change the outer joins to inner.