SQL - Join on changing dates - sql

I've been scratching my head on this for a week now.
Consider two tables - one tallying inventory:
+------------+--------------+----------+-------------------+
| product_id | product_name | date | on_hand_inventory |
+------------+--------------+----------+-------------------+
| 1 | Product A | 6/1/2019 | 37 |
| 1 | Product A | 6/2/2019 | 36 |
| 1 | Product A | 6/3/2019 | 35 |
| 1 | Product A | 6/4/2019 | 40 |
| 1 | Product A | 6/5/2019 | 42 |
+------------+--------------+----------+-------------------+
... and another tracking costs:
+------------+----------------+------------+------------+------------+
| product_id | cost_component | cost_value | start_date | end_date |
+------------+----------------+------------+------------+------------+
| 1 | FOB | 15 | 1/1/2019 | 6/1/2019 |
| 1 | FOB | 15.5 | 6/2/2019 | 6/3/2019 |
| 1 | FOB | 16 | 6/4/2019 | 12/31/9999 |
+------------+----------------+------------+------------+------------+
The layout of the cost table is what's driving me nuts. I need to join these
tables to keep a running valuation of on-hand inventory, and I can't think of a
method in SQL that would let me select the appropriate row in the cost table.
A join on produt_id doesn't work because it would return all cost components for
that item whether or not they apply to that date. I feel like should be
involving a CASE statement, but I'm not sure what that would look like.
This is in MSSQL 2016, for what its worth.

If you want the most recent cost, you can use join:
select t.*, c.*
from inventory i join
costs c
on c.product_id = i.product_id and
i.date between c.start_date and c.end_date;

Related

how to sum a column of a queried table in sql

I have this table from a query and I would like to find the sum of the Total Cost (after discount). I have searched for a solution but I can't seem to find the one I'm looking for.
Here are the sample data from my tables
Booking
+-----------+-------------+----------+------------+
| vehicleNo |  bookingDay | driverNo | acc_status |
+-----------+-------------+----------+------------+
| 10 | 13/06/2021 | 2 | B |
| 10 | 14/06/2021 | 0 | B |
| 10 | 15/06/2021 | 2 | B |
| 20 | 17/06/2021 | 2 | B |
+-----------+-------------+----------+------------+
Vehicle
+-----------+-------------+----------------+------+
| vehicleNo |  vehicleReg | make_model | cost |
+-----------+-------------+----------------+------+
| 10 | IN10NGT | Nissan R34 GTR | 90 |
| 20 | IN10MRX | Mazda RX7 | 70 |
| 30 | IN10TSU | Toyota Supra | 80 |
+-----------+-------------+----------------+------+
Here is the query
SELECT IF(COUNT(Vehicle.vehicleNo) > 1, ROUND(Vehicle.cost,1) * ROUND(COUNT(Vehicle.vehicleNo) * 0.9,1), Vehicle.cost * ROUND(COUNT(Vehicle.vehicleNo),1)) AS 'Total after discount'
FROM Booking
INNER JOIN Vehicle
ON Vehicle.vehicleNo = Booking.vehicleNo
WHERE Booking.driverNo = 2
GROUP BY Vehicle.vehicleNo
ORDER BY Vehicle.vehicleNo;
an here is the result
+----------------------+
| Total after discount |
+----------------------+
| 162 |
| 70 |
+----------------------+
and I am expecting to have a table after calculating the sum like this
+----------------------+
| Overall cost |
|after discount |
+----------------------+
| 232 |
+----------------------+
any help is much appreciated.
i have figured it out. i used this query:
SELECT SUM(Cost) FROM (SELECT IF(COUNT(Vehicle.vehicleNo) > 1, ROUND(Vehicle.cost,1) * ROUND(COUNT(Vehicle.vehicleNo) * 0.9,1), Vehicle.cost * ROUND(COUNT(Vehicle.vehicleNo),1)) AS Cost
FROM Vehicle INNER JOIN Booking
ON Vehicle.vehicleNo = Booking.vehicleNo AND Booking.driverNo = 2
GROUP BY Vehicle.vehicleNo) AS x;

BigQuery / SQL aggregate data from one field, many records

I have the following tables:
rooms
+--------+------------+
| room_id| room_name |
+--------+------------+
| 1 | Kitchen |
| 2 | Bedroom |
+--------+------------+
room_products
+--------+------------+
| room_id| product_id |
+--------+------------+
| 1 | 101 |
| 1 | 102 |
| 1 | 103 |
| 1 | 104 |
| 2 | 105 |
| 2 | 106 |
| 2 | 107 |
+--------+------------+
products
+------------+--------------+
| product_id | product_name |
+------------+--------------+
| 101 | Kettle |
| 102 | Toaster |
| 103 | Microwave |
| 104 | Cooker |
| 105 | Bed |
| 106 | Lamp |
| 107 | Clock |
+------------+--------------+
The output I'm trying to get is:
+----------+-------------------------------------+
| room_name| product_name_aggregated |
+----------+-------------------------------------+
| Kitchen | Kettle, Toaster, Microwave, Cooker |
| Bedroom | Bed, Lamp, Clock |
+----------+-------------------------------------+
The code I have so far is:
SELECT r.room_name, STRING_AGG(p.product_name)
FROM rooms r
JOIN room_products rp ON rp.room_id = r.room_id
JOIN products p ON p.product_id = rp.product_id
WHERE r.room_id = 1 OR r.room_id = 2;
Think I'm getting all kinds of confused. Would really appreciate any help... Thank you.
You are almost there:
SELECT r.room_name, STRING_AGG(p.product_name, ',')
FROM rooms r JOIN
room_products rp
ON rp.room_id = r.room_id JOIN
products p
ON p.product_id = rp.product_id
WHERE r.room_id IN (1, 2)
GROUP BY r.room_name;
The changes are:
Added a second argument to STRING_AGG().
Changed the WHERE to use IN (more convenient; OR also works).
Added GROUP BY.
Note that I would recommend ARRAY_AGG() rather than STRING_AGG() -- arrays are quite useful in BigQuery.

How do I compute an aggregate calculation on similar sql elements in a table?

I am having a problem with generating the SQL task I want.
My goal is to have a margin calculation per each item category. The code works, but in some cases, an item might have four different costs, so when I execute the query, I get four lines with each of them the margin calculation. I would like to have one that summarizes everything.
How can I do it? I tried removing the cost from the selection at the beginning and group by at the end, but I get an error saying that it is not a valid group by expression.
If possible, I would like to know how I could compute the margin for all the entries in the table, regardless of their cost and price.
SELECT
pc.product_description,
round(li.price,2) as price,
round(li.cost_price,2) as cost_price,
SUM(li.quantity) as Total_Items_sold,
round(SUM(li.quantity)*li.price-SUM(li.quantity)*li.cost_price,2) as Gross_Margin_$,
FROM
product_description as pc
LEFT JOIN orders as li ON
pc.product_id = li.SKU
WHERE
pc.product_description = 'Sweater' and
li.state = 'complete' and
li.created_at like '2019%'
GROUP BY
pc.product_description,
li.price,
li.cost_price
Creating some dummy data for your scenario:
create table product_description
(
product_id varchar(10),
product_description varchar(50)
)
create table orders
(
SKU varchar(10),
price decimal(10,2),
cost_price decimal(10,2),
quantity int,
[state] varchar(10),
created_at varchar(20) -- Ugly, but you're treating this as a string in your query
)
insert into product_description
values
('PRO001', 'Sweater'),
('PRO002', 'Jeans'),
('PRO003', 'Shoes'),
('PRO004', 'Dress'),
('PRO005', 'Blouse')
insert into orders
values
('PRO001',29.99,13.50,3,'complete','2019-11-19'),
('PRO001',26.99,12.50,1,'complete','2018-06-18'),
('PRO004',37.99,20.75,2,'complete','2019-11-17'),
('PRO003',19.99,6.50,10,'complete','2019-11-16'),
('PRO003',19.99,6.25,5,'complete','2019-11-15'),
('PRO002',23.99,10.50,13,'complete','2019-11-14'),
('PRO004',37.99,21.00,3,'complete','2019-11-13'),
('PRO001',29.99,13.50,7,'incomplete','2019-11-12'),
('PRO003',18.99,5.50,9,'complete','2019-11-11'),
('PRO004',39.99,23.50,18,'complete','2019-11-11'),
('PRO005',19.99,11.50,23,'complete','2019-11-10'),
('PRO001',29.99,13.50,12,'complete','2019-11-09')
Resulting in the following data in these tables:
select * from product_description
/----------------------------------\
| product_id | product_description |
|------------|---------------------|
| PRO001 | Sweater |
| PRO002 | Jeans |
| PRO003 | Shoes |
| PRO004 | Dress |
| PRO005 | Blouse |
\----------------------------------/
select * from orders
/------------------------------------------------------------------\
| SKU | price | cost_price | quantity | state | created_at |
|--------|-------|------------|----------|------------|------------|
| PRO001 | 29.99 | 13.50 | 3 | complete | 2019-11-19 |
| PRO001 | 26.99 | 12.50 | 1 | complete | 2018-06-18 |
| PRO004 | 37.99 | 20.75 | 2 | complete | 2019-11-17 |
| PRO003 | 19.99 | 6.50 | 10 | complete | 2019-11-16 |
| PRO003 | 19.99 | 6.25 | 5 | complete | 2019-11-15 |
| PRO002 | 23.99 | 10.50 | 13 | complete | 2019-11-14 |
| PRO004 | 37.99 | 21.00 | 3 | complete | 2019-11-13 |
| PRO001 | 29.99 | 13.50 | 7 | incomplete | 2019-11-12 |
| PRO003 | 18.99 | 5.50 | 9 | complete | 2019-11-11 |
| PRO004 | 39.99 | 23.50 | 18 | complete | 2019-11-11 |
| PRO005 | 19.99 | 11.50 | 23 | complete | 2019-11-10 |
| PRO001 | 29.99 | 13.50 | 12 | complete | 2019-11-09 |
\------------------------------------------------------------------/
The following query gives you what you want, I believe:
select
pd.product_description as Product,
sum((o.quantity * o.price) - (o.quantity * o.cost_price)) as GrossMargin
from orders o
left join product_description pd on o.SKU = pd.product_id
group by pd.product_description
Results:
/-----------------------\
| Product | GrossMargin |
|---------|-------------|
| Blouse | 195.27 |
| Dress | 382.27 |
| Jeans | 175.37 |
| Shoes | 325.01 |
| Sweater | 377.27 |
\-----------------------/
If you then want to further filter the results on a specific year or product, you can:
select
pd.product_description as Product,
sum((o.quantity * o.price) - (o.quantity * o.cost_price)) as GrossMargin
from orders o
left join product_description pd on o.SKU = pd.product_id
where o.created_at like '2019%'
and pd.product_description = 'Sweater'
group by pd.product_description
Which you'll see, excludes the order from 2018:
/-----------------------\
| Product | GrossMargin |
|---------|-------------|
| Sweater | 362.78 |
\-----------------------/

Outer Join multible tables keeping all rows in common colums

I'm quite new to SQL - hope you can help:
I have several tables that all have 3 columns in common: ObjNo, Date(year-month), Product.
Each table has 1 other column, that represents an economic value (sales, count, netsales, plan ..)
I need to join all tables on the 3 common columns giving. The outcome must have one row for each existing combination of the 3 common columns. Not every combination exists in every table.
If I do full outer joins, I get ObjNo, Date, etc. for each table, but only need them once.
How can I achieve this?
+--------------+-------+--------+---------+-----------+
| tblCount | | | | |
+--------------+-------+--------+---------+-----------+
| | ObjNo | Date | Product | count |
| | 1 | 201601 | Snacks | 22 |
| | 2 | 201602 | Coffee | 23 |
| | 4 | 201605 | Tea | 30 |
| | | | | |
| tblSalesPlan | | | | |
| | ObjNo | Date | Product | salesplan |
| | 1 | 201601 | Beer | 2000 |
| | 2 | 201602 | Sancks | 2000 |
| | 5 | 201605 | Tea | 2000 |
| | | | | |
| | | | | |
| tblSales | | | | |
| | ObjNo | Date | Product | Sales |
| | 1 | 201601 | Beer | 1000 |
| | 2 | 201602 | Coffee | 2000 |
| | 3 | 201603 | Tea | 3000 |
+--------------+-------+--------+---------+-----------+
Thx
Devon
It sounds like you're using SELECT * FROM... which is giving you every field from every table. You probably only want to get the values from one table, so you should be explicit about which fields you want to include in the results.
If you're not sure which table is going to have a record for each case (i.e. there is not guaranteed to be a record in any particular table) you can use the COALESCE function to get the first non-null value in each case.
SELECT COALESCE(tbl1.ObjNo, tbl2.ObjNo, tbl3.ObjNo) AS ObjNo, ....
tbl1.Sales, tbl2.Count, tbl3.Netsales

SQL Join Help - Sum and Apply Filter to Tables Before Join

I have a Stock View (that lists all the individual pieces of stock and the stock date) and a Sales View (that lists all of the sales and the date the sale occurred).
Stock View:
+----+------+-----+------------+
| ID | Item | Qty | Date |
+----+------+-----+------------+
| 1 | A | 3 | 01/01/2000 |
| 2 | A | 2 | 02/02/2000 |
| 3 | D | 9 | 05/06/2000 |
| 4 | F | 22 | 09/01/2001 |
| 5 | A | 10 | 01/04/2001 |
| 6 | C | 12 | 01/01/2002 |
+----+------+-----+------------+
Sales View:
+------+-----+------------+
| Item | Qty | Date |
+------+-----+------------+
| B | 3 | 01/01/2001 |
| B | 77 | 01/12/2001 |
| C | 9 | 02/02/2002 |
| A | 10 | 03/03/2002 |
| G | 2 | 05/06/2002 |
| C | 3 | 09/10/2012 |
+------+-----+------------+
I want to join these tables..but before doing so:
Stock view needs to be filtered between 2 date parameters #StockFrom and #StockTo
Sales view needs to be filtered between 2 date parameters #SalesFrom and #SalesTo
Sales view then needs to be grouped by Item and have the Qty Summed (so the date field needs to be dropped although it is being filtered on) and then joined onto the Stock View on the Item field.
So in essence I want to see the Stock View as it is (but filtered on dates) with an extra column showing the sales that have occurred between 2 dates for that item.
Desired Output:
+----+------+-----+------------+-------+
| ID | Item | Qty | Date | Sales |
+----+------+-----+------------+-------+
| 1 | A | 3 | 01/01/2000 | 10 |
| 2 | A | 2 | 02/02/2000 | 10 |
| 3 | D | 9 | 05/06/2000 | 0 |
| 4 | F | 22 | 09/01/2001 | 0 |
| 5 | A | 10 | 01/04/2001 | 10 |
| 6 | C | 12 | 01/01/2002 | 12 |
+----+------+-----+------------+-------+
Thanks to any help in advance!
SELECT
Stock.*,
IFNULL(SUM(Sales.Qty),0) AS Sales
FROM Stock
LEFT JOIN Sales ON Stock.Item=Sales.Item
WHERE Stock.Date BETWEEN #StockFrom AND #StockTo
AND (
Sales.Date BETWEEN #SalesFrom AND #SalesTo
OR Sales.Date IS NULL
)
GROUP BY Stock.ID
This is for MySQL, as you didn't specify the diaclect. SQLfiddle
EDIT
SELECT
Stock.ID AS ID,
MIN(Stock.Item) AS Item,
MIN(Stock.Qty) AS Qty,
MIN(Stock.Date) AS Date,
CASE WHEN SUM(Sales.Qty) IS NULL THEN 0 ELSE SUM(Sales.Qty) END AS Sales
FROM Stock
LEFT JOIN Sales ON Stock.Item=Sales.Item
WHERE Stock.Date BETWEEN #StockFrom AND #StockTo
AND (
Sales.Date BETWEEN #SalesFrom AND #SalesTo
OR Sales.Date IS NULL
)
GROUP BY Stock.ID
works for MS SQL (SQLfiddle)
Please try below query for MS Sql Server:
SELECT DISTINCT
a.ID,
a.Item,
a.Qty,
a.Date,
ISNULL(SUM(b.Qty) OVER (PARTITION BY a.Item, a.[Date]), 0) Sales
FROM
StockView a LEFT JOIN SalesView b on a.Item=b.Item