I have 5 regions that each contain 1 store and 1 warehouse. I have a table that contains the regions and corresponding stores and warehouses. I also have a table that lists the inventories of both the stores and warehouses. Finally, have a table that lists the allowed quantities for the warehouse. If I look at the inventory table right now it would say something like:
FACILITY ID ITEM_NUMBER QTY
STORE 1 15D 2
WAREHOUSE 1 15D 1
The problem is, Store 1 and Warehouse 1 belong to region 1 and I would like to see it in this format:
REGION WAREHOUSE QTY OH STORE QTY OH
1 1 2 1 1
and so on... So I wrote the following SQL:
Create or replace view REGION_BALANCES
as
with WAREHOUSES as
(
select
A.REGION_CODE,A.REG_DESC,A.WAREHOUSE_NUMBER,A.FACILITY_ID,C.ITEM_NUMBER,sum(C.IN_STOCK_QTY) as IN_STOCK_QTY, B.ALLOWED_QTY
from REG_WHS_STR_ASSOC A
join ALLOWANCES B on (A.FACILITY_ID = B.FACILITY_ID)
join INVENTORIES C on (A.FACILITY_ID = FACILITY_ID) and B.ITEM_NUMBER = C.ITEM_NUMBER)
group by A.REGION_CODE,A.REG_DESC,A.WAREHOUSE_NUMBER,C.ITEM_NUMBER,B.ALLOWED_QTY
),
STORES as
(
select
A.REGION_CODE,A.REG_DESC,A.STORE_NUMBER,A.FACILITY_ID,B.ITEM_NUMBER,sum(B.IN_STOCK_QTY) as IN_STOCK_QTY
from REG_WHS_STR_ASSOC A
join INVENTORIES B on (A.FACILITY_ID = B.FACILITY_ID)
group by A.REGION_CODE,A.REG_DESC,A.STORE_NUMBER, B.ITEM_NUMBER
)
select A.REGION_CODE,A.REG_DESC,A.WAREHOUSE_NUMBER,A.FACILITY_ID,WAREHOUSES.ITEM_NUMBER,WAREHOUSES.IN_STOCK_QTY,WAREHOUSES.ALLOWED_QTY,STORES.STORE_NUMBER,STORES.FACILITY_ID,STORES.ITEM_NUMBER,STORES.IN_STOCK_QTY
from REG_WHS_STR_ASSOC A
join WAREHOUSES on (A.REGION_CODE = WAREHOUSES.REGION_CODE)
join STORES on (A.REGION_CODE = STORES.REGION_CODE)
order by 5,1 asc;
I have changed my join around from left joins to right joins to cross joins to inner joins; however, I either get 30 records (and I need hundreds of records) or I get Cartesian products. Can anyone provide advice to what I'm doing wrong?
Thanks in advance!
You were forgetting to link warehouses and stores by item number (plus left join is the better for this case)
Create or replace view REGION_BALANCES
as
with WAREHOUSES as
(
select
A.REGION_CODE, A.REG_DESC, A.WAREHOUSE_NUMBER,
A.FACILITY_ID, C.ITEM_NUMBER,sum(C.IN_STOCK_QTY) as IN_STOCK_QTY, B.ALLOWED_QTY
from REG_WHS_STR_ASSOC A
join ALLOWANCES B on (A.FACILITY_ID = B.FACILITY_ID)
join INVENTORIES C on (A.FACILITY_ID = FACILITY_ID) and B.ITEM_NUMBER = C.ITEM_NUMBER)
group by A.REGION_CODE,A.REG_DESC,A.WAREHOUSE_NUMBER,C.ITEM_NUMBER,B.ALLOWED_QTY
),
STORES as
(
select
A.REGION_CODE,A.REG_DESC,A.STORE_NUMBER,
A.FACILITY_ID,B.ITEM_NUMBER,sum(B.IN_STOCK_QTY) as IN_STOCK_QTY
from REG_WHS_STR_ASSOC A
join INVENTORIES B on (A.FACILITY_ID = B.FACILITY_ID)
group by A.REGION_CODE,A.REG_DESC,A.STORE_NUMBER, B.ITEM_NUMBER
)
select A.REGION_CODE, A.REG_DESC,A. WAREHOUSE_NUMBER, A.FACILITY_ID,
WAREHOUSES.ITEM_NUMBER, WAREHOUSES.IN_STOCK_QTY, WAREHOUSES.ALLOWED_QTY,
STORES.STORE_NUMBER, STORES.FACILITY_ID, STORES.ITEM_NUMBER, STORES.IN_STOCK_QTY
from REG_WHS_STR_ASSOC A
left join WAREHOUSES on (A.REGION_CODE = WAREHOUSES.REGION_CODE)
left join STORES on
(A.REGION_CODE = STORES.REGION_CODE AND STORES.ITEM_NUMBER = WAREHOUSES.ITEM_NUMBER)
order by 5,1 asc;
Related
I have been able to select this data, using these two sql queries
Query 1:
SELECT article_id, amount_required, amount_sold FROM products_articles,sales WHERE sales.product_id = products_articles.product_id
Query 2:
SELECT * FROM articles
What I want to do, is go through the first table (with amount sold and required) (it's fine that there are duplicate rows), and for each row in the table multiply the value of amount_sold and amount_required and then subtract that value from amount_in_stock where the ids match in the second table.
Example from the first row:
2 * 4 = 8, change amount_in_stock from 124 to 116.
And so on...
How can I do this using just sql?
UPDATE A
SET
A.amount_in_stock =(S.amountSold * S.amount_required)- A.amount_in_stock
FROM articles AS A
INNER JOIN
products_articles AS PA
ON PA.article_id= A.article_id
INNER JOIN Sales AS S
ON S.product_id=PA.product_id
Please try this:
Update articles a
inner join
(
SELECT article_id, sum(amount_required) amount_required, sum(amount_sold )amount_sold FROM products_articles inner join sales on sales.product_id = products_articles.product_id
group by article_id
)b on a.article_id=b.article_id
set a.amount_in_stock=a.amount_in_stock-(amount_required*amount_sold )
Since there could be multiple rows in product_articles and amount_sold I have used group by to sum the amounts.
For SQLite please try this:
Update articles
set amount_in_stock=(SELECT sum(amount_required) * sum(amount_sold ) FROM products_articles inner join sales on sales.product_id = products_articles.product_id
where products_articles.article_id=articles.article_id
group by article_id
)
where exists (SELECT * FROM products_articles inner join sales on sales.product_id = products_articles.product_id where products_articles.article_id=articles.article_id
)
I am trying to create the SQL necessary to accomplish the following. As an aside, I am using server side Report Builder against a hosted SQL Server database so am limited in what I can do. There are 3 tables. A salesperson table (sales), an items table (items), and a transaction table (tx).
Here is an example:
Sales Person table (sales)
Person A
Person B
Items ID table (items)
10
20
30
100
200
300
1000
2000
3000
Transaction table (tx)
100 (person A)
300 (person B)
300 (person A)
200 (person B)
Desired result in Report Builder:
Person A
Item 100: 1
Item 200: 0 (NULL)
Item 300: 1
-- NEW PAGE --
Person B:
Item 100: 0 (NULL)
Item 200: 1
Item 300: 1
My problem: here is the SQL I came up with. I need to be able to generate a consistent result set, regardless of whether an item was sold or not by a particular salesperson for easier import into Excel. In addition, I am only looking for items whose code is between 100 and 300 and within a specified date range. My SQL is ignoring the date range and item code range. I originally had these instructions in a WHERE clause but it returned only those lines that were in both tables and I lost the placeholder for any itemcode where the value was null (acting as an INNER join). Within Report Builder I will be counting how many of each items were sold by salesperson.
SELECT
tx.date, sales.salesperson, items.itemcode
FROM
tx
LEFT OUTER JOIN
itemcode ON (tx.itemcode = items.itemcode)
AND (date BETWEEN "10/1/2017" AND "12/31/2017")
AND (itemcode BETWEEN "100" AND "300")
INNER JOIN
sales ON (tx.salesID = sales.salesID)
ORDER BY
itemcode ASC
Many thanks for any and all insight into my challenge!
If you want all sales people and all items, then you can generate the rows using a cross join. You can bring in the available data using left join or exists:
select s.person, i.itemcode,
(case when exists (select 1
from tx
where tx.salesid = s.salesid and tx.itemcode = i.itemcode
)
then 1 else 0
end) as has_sold
from sales s cross join
items i
where i.itemcode between 100 and 300
order by s.saledid, i.itemcode;
If you want the count of items, use left join and group by:
select s.person, i.itemcode, count(tx.salesid) as num_sold
from sales s cross join
items i left join
tx
on tx.salesid = s.salesid and tx.itemcode = i.itemcode
where i.itemcode between 100 and 300
order by s.saledid, i.itemcode;
Here's an example that uses both cross join (to get all combinations of sales and items) and left join (to get the transactions for given dates)
SELECT
tx.date, sales.salesperson, items.itemcode
FROM
Items
CROSS JOIN
sales
LEFT OUTER JOIN
tx ON (items.itemcode = tx.itemcode)
AND date BETWEEN '10/1/2017' AND '12/31/2017'
AND (tx.salesID = sales.salesID)
WHERE
(items.itemcode BETWEEN '100' AND '300')
ORDER BY
itemcode ASC
(The following is a highly simplified description of my problem. The company policy does not allow me to describe the actual scenario in any detail.)
The DB tables involved are:
PRODUCTS:
ID Name
---------
1 Ferrari
2 Lamborghini
3 Volvo
CATEGORIES:
ID Name
----------
10 Sports cars
20 Safe cars
30 Red cars
PRODUCTS_CATEGORIES
ProductID CategoryID
-----------------------
1 10
1 30
2 10
3 20
LOCATIONS:
ID Name
------------
100 Sports car store
200 Safe car store
300 Red car store
400 All cars r us
LOCATIONS_CATEGORIES:
LocationID CategoryID
------------------------
100 10
200 20
300 30
400 10
400 20
400 30
Note that the locations are not directly connected to the products, only the categories. The customer should be able to see a list of locations that can provide all the product categories that the products they want to buy belong to. So, for example:
A customer wants to buy a Ferrari. This would be available from stores in categories 10 or 30. This gives us stores 100, 300 and 400 but not 200.
However, if a customer wants to buy a Volvo and a Lamborghini this would be available from stores in categories 10 and 20. Which only gives us store 400.
Another customer wants to buy a Ferrari and a Volvo. This they could get from a store in either categories 10 + 20 (sporty and safe) or categories 30 + 20 (red and safe).
What I need is a postgres query that takes a number of products and returns the locations where all of them can be found. I got started with arrays and the <# operator but got lost quickly. Here follows some example SQL that attempts to get stores where a Ferrari and a Lamborghini can be bought. It does not work correctly since it requires the locations to satisfy all the categories that all the selected cars belong to. It returns location 400 only but should return locations 400 and 100.
SELECT l.* FROM locations l
WHERE
(SELECT array_agg(DISTINCT(categoryid)) FROM products_categories WHERE productid IN (1,2))
<#
(SELECT array_agg(categoryid) FROM locations_categories WHERE locationid = l.id);
I hope my description makes sense.
Here is the query. You should insert a list of selected cars Ids pc.ProductId in (1,3) and in the end you should correct condition to selected cars count so if you select 1 and 3 you should write HAVING COUNT(DISTINCT pc.ProductId) = 2 if you select 3 cars then there have to be 3. This condition in HAVING give you condition that ALL cars are in these Locations:
SELECT Id FROM Locations l
JOIN Locations_Categories lc on l.Id=lc.LocationId
JOIN Products_Categories pc on lc.CategoryId=pc.CategoryID
where pc.ProductId in (1,3)
GROUP BY l.id
HAVING COUNT(DISTINCT pc.ProductId) = 2
Sqlfiddle demo
For example for one car it will be:
SELECT Id FROM Locations l
JOIN Locations_Categories lc on l.Id=lc.LocationId
JOIN Products_Categories pc on lc.CategoryId=pc.CategoryID
where pc.ProductId in (1)
GROUP BY l.id
HAVING COUNT(DISTINCT pc.ProductId) = 1
Only Ferrary demo
Volvo and a Lamborghini demo
(This basically elaborates on #valex's answer, though I didn't realise that until I posted; please accept #valex's not this one).
This can be done using only joins and aggregation.
Build a join tree mapping locations to products, as normal. Then join it with the list of desired products (one-column values rows) and filter the join to only matching product names. You now have one row with the location of a product wherever that product can be found.
Now group by location and return locations where the number of products present is equal to the number we're looking for (for ALL). For ANY we omit the HAVING filter because any location row returned by the join is what we want.
So:
WITH wantedproducts(productname) AS (VALUES('Volvo'), ('Lamborghini'))
SELECT l."ID"
FROM locations l
INNER JOIN locations_categories lc ON (l."ID" = lc."LocationID")
INNER JOIN categories c ON (c."ID" = lc."CategoryID")
INNER JOIN products_categories pc ON (pc."CategoryID" = c."ID")
INNER JOIN products p ON (p."ID" = pc."ProductID")
INNER JOIN wantedproducts wp ON (wp.productname = p."Name")
GROUP BY l."ID"
HAVING count(DISTINCT p."ID") = (SELECT count(*) FROM wantedproducts);
is what you want, basically.
For "stores with any of the wanted products" queries, drop the HAVING clause.
You an also ORDER BY the aggregate if you want to show stores with any match but sort based on number of matches.
You can also add a string_agg(p."Name") to the SELECT values-list if you want to list products that can be found at that store.
If you want your input to be an array rather than a values-list, just replace the VALUES (...) with SELECT unnest($1) and pass your array as the parameter $1, or write it literally in place of $1.
ANSWER IN PROGRESS: (I will add answers as I get the required result)
For your first question:
A customer wants to buy a Ferrari. This would be available from stores
in categories 10 or 30. This gives us stores 100, 300 and 400 but not
200.
SELECT DISTINCT l.id, l.name
FROM Products p
LEFT JOIN Product_Categories p_c
ON p.id = p_c.ProductId
LEFT JOIN Categories c
ON p_c.CategoryId = c.id
LEFT JOIN Locations_Categories l_c
ON c.id = l_c.CategoryId
LEFT JOIN Locations l
ON l_c.LocationId = l.id
WHERE p.id = 1
Second question:
However, if a customer wants to buy a Volvo and a Lamborghini this
would be available from stores in categories 10 and 20. Which only
gives us store 400.
SELECT DISTINCT l.id, l.name
FROM Products p
LEFT JOIN Product_Categories p_c
ON p.id = p_c.ProductId
LEFT JOIN Categories c
ON p_c.CategoryId = c.id
LEFT JOIN Locations_Categories l_c
ON c.id = l_c.CategoryId
LEFT JOIN Locations l
ON l_c.LocationId = l.id
WHERE l.id in (select id
from locations loc
join locations_categories locat1
on loc.id = locat1.LocationId
join locations_categories locat2
on loc.id = locat2.LocationId
where locat1.CategoryId = 10
AND locat2.categoryId = 20)
RESULT FOR SECOND QUESTION USING INTERSECT:
intersect will cross reference all the stores where 1 product can be found each time:
SELECT DISTINCT l.id, l.name
FROM Products p
LEFT JOIN Product_Categories p_c
ON p.id = p_c.ProductId
LEFT JOIN Categories c
ON p_c.CategoryId = c.id
LEFT JOIN Locations_Categories l_c
ON c.id = l_c.CategoryId
LEFT JOIN Locations l
ON l_c.LocationId = l.id
WHERE p.id = 2
INTERSECT
SELECT DISTINCT l.id, l.name
FROM Products p
LEFT JOIN Product_Categories p_c
ON p.id = p_c.ProductId
LEFT JOIN Categories c
ON p_c.CategoryId = c.id
LEFT JOIN Locations_Categories l_c
ON c.id = l_c.CategoryId
LEFT JOIN Locations l
ON l_c.LocationId = l.id
WHERE p.id = 3
For every new product you add a new INTERSECT statement and create a new select with the wanted product id
SQLFIDDLE: http://sqlfiddle.com/#!15/ce97d/15
Well, it's hard to totally avoid arrays here but I think I found a solution with less array functions.
Instead of selecting needed locations, I excluded non valid ones.
WITH needed_categories AS (
SELECT p."ID", array_agg(pc."CategoryID") AS at_least_one_should_match
FROM Products p
JOIN Products_Categories pc ON p."ID" = pc."ProductID"
WHERE p."ID" IN (1, 3)
GROUP BY p."ID"
),
not_valid_locations AS (
SELECT DISTINCT lc."LocationID", unnest(nc.at_least_one_should_match)
FROM Locations_Categories lc
JOIN needed_categories nc ON NOT ARRAY[lc."CategoryID"] && nc.at_least_one_should_match
EXCEPT
SELECT * FROM Locations_Categories
)
SELECT *
FROM Locations
WHERE "ID" NOT IN (
SELECT "LocationID" FROM not_valid_locations
);
Here is the SQLFiddle: http://sqlfiddle.com/#!15/e138d/78
This works but I'm still trying to avoid double seq scan of the Location_Categories.
The fact that cars can belong to multiple categories is a bit tricky, I solved this using arrays but I'm trying to get rid of these too.
I have three tables
Products Table :
ID
GeneralStockEnabled
RetailerID
Sources Table
ID
Name
RetailerID
and
ProductInventory Table
ProductID
SourceID
Stock
The user will pass both the #RetailerID and #ProductID in my Stored Procedure.
How Can I select All the sources for particular retailer and attach the stock value coming from the product inventory table to those sources exists in the product inventory table for a particular product id and also select the value of GeneralStockEnabled for that product? . Even I my product has no stocks, I still want to be able to retrieve all the sources for that retailer?.
Any Help is appreciated.
I have this SQL right now :
SELECT S.ID AS SourceID,S.Name AS SourceName,PIN.Stock
FROM Sources S
LEFT OUTER JOIN ProductInventory PIN
ON (S.ID = PIN.SourceID)
WHERE S.RetailerID = 1
AND PIN.ProductID = 1
but since my product inventory table has no records now. It is not selecting the left part which are the sources in this case.
try something like this:
select s.*, pr.Stock, p.GeneralStockEnabled
from sources s join
Products p on s.RetailerId = p.RetailerId left outer join
ProductInventory pr on pr.ProductId = p.Id
where s.RetailerId = #RetailerId and p.id = #ProductId
You can use joins for the same
http://www.w3schools.com/sql/sql_join.asp
I have 2 tables AP and INV where both have the columns [PROJECT] and [Value].
I want a query to return something like this :
PROJECT | SUM_AP | SUM_INV
I came up with the code below but it's returning the wrong results ( sum is wrong ).
SELECT AP.[PROJECT],
SUM(AP.Value) AS SUM_AP,
SUM(INV.Value) AS SUM_INV
FROM AP INNER JOIN INV ON (AP.[PROJECT] =INV.[PROJECT])
WHERE AP.[PROJECT] = 'XXXXX'
GROUP BY AP.[PROJECT]
The results from your query are wrong because the values you are trying to summarize are being grouped, which causes duplicate values to be included in the SUM.
You could solve it with a couple of sub-selects:
SELECT
AP1.[PROJECT],
(SELECT SUM(AP2.Value) FROM AP AS AP2 WHERE AP2.PROJECT = AP1.PROJECT) AS SUM_AP,
(SELECT SUM(INV2.Value) FROM INV AS INV2 WHERE INV2.PROJECT = AP1.PROJECT) AS SUM_INV
FROM AP AS AP1
INNER JOIN INV AS INV1
ON (AP1.[PROJECT] =INV1.[PROJECT])
WHERE AP1.[PROJECT] = 'XXXXX'
GROUP BY AP1.[PROJECT]
If you have N rows in AP with a given project ID, and M rows in INV with that ID, then the join between the two tables on the project ID will have a total of N*M rows for that project, because the same row in AP will be repeated for every row in INV that has that project ID, and vice versa. Hence why your counts are most likely off (because it's counting the same row in a given table multiple times due to repetition from the join).
Instead, you might want to try doing a join between the results of two subqueries, one which groups the first table by project ID and does that its sum, and the second which groups the other table by project ID and does that sum - then joining once you only have 1 row with sum for each project ID.
If PROJECT is the parent table, you should select FROM the project table, and do a left outer join on the two child tables:
SELECT PROJECT.PROJECT_ID, SUM(AP.Value) AS SUM_AP, SUM(INV.Value) AS SUM_INV
FROM PROJECT
LEFT OUTER JOIN AP ON (AP.[PROJECT] = PROJECT.[PROJECT_ID])
LEFT OUTER JOIN INV ON (INV.[PROJECT] = PROJECT.[PROJECT_ID])
WHERE PROJECT.[PROJECT_ID] = 'XXXXX'
GROUP BY PROJECT.[PROJECT_ID]
You could separate the two sum calculations. One way I can think of is to move the inventory calculation to a subquery, like:
SELECT
AP.[PROJECT]
, SUM(AP.Value) AS SUM_AP
, SummedInv as SUM_INV
FROM AP
LEFT JOIN (
SELECT PROJECT, SUM(Value) AS SUM_INV
FROM INV
GROUP BY PROJECT
) SummedInv ON SummedInv.Project = AP.Project
GROUP BY AP.PROJECT, SummedInv.SUM_INV
Because the SummedInv subquery is grouped on project, it's safe to group on SummedInv.SUM_INV in the outer query as well.
how about this query :
select SUM(gpCutBody.actualQty) as cutQty , SUM(gpSewBody.quantity) as sewQty
from jobOrder
inner join gpCutHead on gpCutHead.joNum = jobOrder.joNum
inner join gpSewHead on gpSewHead.joNum = jobOrder.joNum
inner join gpCutBody on gpCutBody.gpCutID = gpCutHead.gpCutID
inner join gpSewBody on gpSewBody.gpSewID = gpSewHead.gpSewID
where jobOrder.joNum = '36'
here is the link to the ERD: http://dl.dropbox.com/u/18794525/AUG%207%20DUMP%20STAN.png
Try:
SELECT AP.[PROJECT] AS PROJECT, SUM(AP.[Value]) AS SUM_AP, SUM(INV.[Value]) AS SUM_INV
FROM AP, INV
WHERE AP.[PROJECT] = INV.[PROJECT]
AND AP.[PROJECT] = 'XXXXX'
GROUP BY AP.[PROJECT]