sqlite - join tables or subquery? - sql

I have the following tables:
create table part_category(id text primary key);
create table parts (id text primary key not null,
cat references part_category(id));
create table products (id text primary key not null);
create table product_parts (product references products(id),
part references parts(id),
qty integer);
create table locations (id text primary key not null,
stage text not null);
create table stock (part references parts(id),
cat references part_category(id),
location references locations(id),
qty integer,
date text);
create table orders (part references parts(id),
cat references parts(cat),
product references products(id),
qty integer not null default 0,
date_order text,
date_due text,
date_done text,
status boolean,
primary key(part, product, date_due));
And I'd like to have this returned from a select:
Part, Category, Product, Qty, Date Ordered, Date Due, qty of material, qty of stock, qty of wip
The columns bolded above are the ones that I can't figure out. Below is my select with the subquery where I'm trying to get the qty of stock.
The problem is the query is returning zero for everything.
orders = db.execute('''select distinct o.part, o.cat, o.product, o.qty,
o.date_order, o.date_due, o.date_done,
julianday(date_due) - julianday(date_order) as days_due,
(select stock.qty from stock, orders
where stock.part = orders.part and stock.location = 'stock' and orders.status = 1)
as qty_stock
from orders as o join stock as s on o.part = s.part
where o.status = 1
order by o.date_due asc, o.product asc, o.part asc''').fetchall()
Example output is
for item in orders:
print item['part'], item['qty'], item['qty_stock']
SOME_PART_NUMBER 3 0
But should be:
SOME_PART_NUMBER 3 22

I'm unsure about your business logic.
I guess this is what you want.
select distinct o.part, o.cat, o.product, o.qty,
o.date_order, o.date_due, o.date_done,
julianday(date_due) - julianday(date_order) as days_due,
qs.stockQuantity as qty_stock
from orders as o
join stock as s on o.part = s.part
left join (select stock.part, sum(stock.qty) stockQuantity
from stock ss
join orders oo on ss.part = oo.part
where ss.location = 'stock' and oo.status = 1
group by stock.part
) qs on qs.part = o.part
where o.status = 1
order by o.date_due asc, o.product asc, o.part asc

The title says "join tables OR subquery". The sql does both. I'm not sayin' that's the problem. But it certainly adds a level of complexity that could be error prone. You could try removing the subquery and replace it with s.qty, then add s.location = "stock" to the WHERE clause.

Related

Calculating average price of items purchased by customers

I have three tables: customer, order and line items. They are set up as follows:
CREATE TABLE cust_account(
cust_id DECIMAL(10) NOT NULL,
first VARCHAR(30),
last VARCHAR(30),
address VARCHAR(50),
PRIMARY KEY (cust_id));
CREATE TABLE orders(
order_num DECIMAL(10) NOT NULL,
cust_id DECIMAL(10) NOT NULL,
order_date DATE,
PRIMARY KEY (order_num));
CREATE TABLE lines(
order_num DECIMAL(10) NOT NULL,
line_id DECIMAL(10) NOT NULL,
item_num DECIMAL(10) NOT NULL,
price DECIMAL(10),
PRIMARY KEY (order_id, line_id),
FOREIGN KEY (item_id) REFERENCES products);
Using Oracle, I need to write a query that presents the average item price for for those customers that made more than 5 or more purchases. This is what I've been working with:
SELECT DISTINCT cust_account.cust_id,cust_account.first, cust_account.last, lines.AVG(price) AS average_price
FROM cust_account
JOIN orders
ON cust_account.cust_id = orders.cust_id
JOIN lines
ON lines.order_num = orders.order_num
WHERE lines.item_num IN (SELECT lines.item_num
FROM lines
JOIN orders
ON lines.order_num = orders.order_num
GROUP BY lines.order_num
HAVING COUNT(DISTINCT orders.cust_id) >= 5
);
... INNER JOIN all your tables together
... GROUP BY customer and compute the average price of each customer's lines
... use a HAVING clause to limit the results to groups having 5 or more purchases
Query:
SELECT ca.first, ca.last, avg(l.price) avg_price
FROM cust_account ca
INNER JOIN orders o ON o.cust_id = ca.cust_id
INNER JOIN lines l ON l.order_num = o.order_number
GROUP BY ca.first, ca.last
HAVING COUNT(distinct l.line_id) >=5
-- OR, maybe your requirement is ...
-- HAVING COUNT(distinct o.order_num) >= 5
-- ... the question was a bit unclear on this point
I think this is it. I don't think it will work right away (I know nothing about oracle) but I think you will get the idea:
SELECT orders.cust_id,
AVG(lines.price) AS average_price
FROM lines
JOIN orders ON orders.order_num = orders.order_num
WHERE orders.cust_id IN (SELECT orders.cust_id
FROM orders
GROUP BY orders.cust_id
HAVING COUNT(*) >= 5)
GROUP BY orders.cust_id;
Subquery selects customers that have more than 5 orders.
And main query just gets all lines from all orders made by this customers.
I guess you can eliminate subquery by using HAVING DISTINCT .... Anyways, one with subquery should work just fine.
UPD.
something like this
SELECT orders.cust_id,
AVG(lines.price) AS average_price
JOIN orders ON orders.order_num = orders.order_num
GROUP BY orders.cust_id
HAVING COUNT(DISTINCT orders.id) >= 5;

How to compose select request using many-to-many relationship in PostgreSQL?

I have this tables:
CREATE TABLE orders (
order_id serial PRIMARY KEY
, number_of_things int
);
CREATE TABLE things (
thing_id serial PRIMARY KEY
, cost int
);
CREATE TABLE orders_to_things (
order_id int REFERENCES orders (order_id)
, thing_id int REFERENCES things (thing_id)
);
How to compose a request for select all orders where cost of things more than some number?
I tried to use:
SELECT orders.order_id
FROM orders
INNER JOIN orders_to_things ON (orders_to_things.order_id = orders.order_id)
JOIN things ON (orders_to_things.thing_id=things.thing_id)
WHERE (select SUM(things.cost) FROM things) > *some number*
but didn't get the correct result.
Try this:
SELECT O.order_id, sum(T.cost)
FROM orders O
INNER JOIN orders_to_things ON orders_to_things.order_id = orders.order_id
JOIN things T ON orders_to_things.thing_id=things.thing_id
GROUP BY O.order_id
HAVING T.cost > 'number....'
If you want all order ids, you don't need the orders table. The simplest way to write the query is:
SELECT ott.order_id, sum(t.cost)
FROM orders_to_things ott JOIN
things t
ON ott.thing_id = t.thing_id
GROUP BY ott.order_id
HAVING sum(t.cost) > <number>;

NOT EXIST clause

I am trying to find Products that have never been ordered. My 2 tables look like this.
CREATE TABLE Orders
(OrderNum NUMBER(10) NOT NULL,
OrderDate DATE NOT NULL,
Cust NUMBER(10),
Rep NUMBER(10),
Mfr CHAR(3) NOT NULL,
Product CHAR(5) NOT NULL,
Qty NUMBER(5) NOT NULL,
Amount NUMBER(9,2) NOT NULL,
CONSTRAINT OrdersPK
PRIMARY KEY (OrderNum));
CREATE TABLE Products
(Mfr CHAR(3) NOT NULL,
Product CHAR(5) NOT NULL,
Description VARCHAR2(20) NOT NULL,
Price NUMBER(9,2) NOT NULL,
QtyOnHand NUMBER(5),
CONSTRAINT ProductsPK
PRIMARY KEY (Mfr, Product));
The code I currently have looks like this.
SELECT Mfr, Product
FROM Products
WHERE NOT EXISTS (SELECT Products.Mfr
FROM Orders, Products
WHERE Orders.Mfr = Products.Mfr);
Although I am not getting any errors there are also no results showing up.
**EDIT: There are 26 Products and 19 of them have been ordered. I am expecting to get 7 Results but I am getting 0.
You can use NOT EXISTS, but you need to compare both keys:
SELECT p.Mfr, p.Product
FROM Products p
WHERE NOT EXISTS (SELECT 1
FROM Orders o
WHERE o.Mfr = p.Mfr AND
o.Product = p.Product
);
This is a case where it makes lots of sense to have an auto generated primary key that can be used for foreign key relationships.
Try this one
SELECT Mfr, Product
FROM Products
WHERE NOT EXISTS (SELECT Orders.Mfr
FROM Orders
WHERE Orders.Mfr = Products.Mfr AND Orders.Product = Products.Product);
An alternative is to use the set operation operator EXCEPT - as you want "the set of Products that don't exist in Orders":
SELECT
Mfr,
Product
FROM
Products
EXCEPT
SELECT
DISTINCT
Mfr,
Product
FROM
Orders
You can then use this as a subquery to get full product information.
SELECT
*
FROM
Products
INNER JOIN (
SELECT
Mfr,
Product
FROM
Products
EXCEPT
SELECT
DISTINCT
Mfr,
Product
FROM
Orders
) AS ProductsWithNoOrders ON
Products.Mfr = ProductsWithNoOrders.Mfr AND
Products.Product = ProductsWithNoOrders.Product

MS SQL Make a column of one table depend on other table values sum

I have a table "Order" like this, which contains information about the order.
Order ID | ... | Order Total
The order, however, consists of several items, there's also an "Item Order" table:
Item Order ID | Order ID | Item ID
And the "Item" table:
Item ID | Cost
Thus, Order <-> Item Order have a one-to-many relationship and Item Order <-> Item have many-to-one relationship.
Logically, the Order Total should depend on the cost of each item order in it, which would add the cost of the Item to the Total.
How do I set up the dependencies so, that the Order Total depends on all Item Orders corresponding to this order and sums up all needed Item costs? I guess it should also update upon adding new item orders to the order each time.
As indicated in the comments, I'd normally prefer not to store redundant, potentially incorrect data. If, however, there's a performance issue with calculating the total on the fly, the next best option is to get the system to do the calculations for you. This is an option if you use an indexed view.
Table setup:
create table dbo.Orders (
OrderID int not null,
/* NO Total here */
constraint PK_Orders PRIMARY KEY (OrderID)
)
go
create table dbo.Items (
ItemID int not null,
Cost decimal (19,4) not null,
constraint PK_Items PRIMARY KEY (ItemID)
)
go
create table dbo.OrderItems (
OrderItemID int not null,
OrderID int not null,
ItemID int not null,
/* I'd normally prefer Order/Item/Quantity and making Order/Item the PK */
constraint PK_OrderItems PRIMARY KEY (OrderItemID),
constraint FK_OrderItems_Orders FOREIGN KEY (OrderID) references Orders (OrderID),
constraint FK_OrderItems_Items FOREIGN KEY (ItemID) references Items (ItemID)
)
And now we can create the view:
create view dbo.OrderTotals
with schemabinding
as
select
OrderID,
COUNT_BIG(*) as LineCount, /* Required for indexed view with aggregate */
SUM(Cost) as OrderTotal
from
dbo.Items i
inner join
dbo.OrderItems o
on
i.ItemID = o.ItemID
group by
OrderID
go
create unique clustered index IX_OrderTotals on OrderTotals (OrderID)
Now, as you perform inserts, updates and deletes against the OrderItems or Items tables, this view's index (which actually contains all of the view data) is automatically updated for you.
This avoids any concerns about corner cases which you may miss if you use e.g. triggers to perform the updates manually.
You can create two triggers on Item Order and Item tables.
Trigger on Item table:
CREATE TRIGGER T_Item_Recalc_Order_Total
ON Item
FOR DELETE, INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE o
SET [Order Total] = c.[Cost]
FROM Order o
INNER JOIN
(
SELECT i_o.[Order_ID]
,SUM(Cost) AS Cost
FROM Item_Order i_o
INNER JOIN Item i
ON i.[Item ID] = i_o.[Item ID]
WHERE i_o.[Item ID] IN (
-- Sum up cost for changed rows only
SELECT COALESCE(d.[Item ID], i.[Item ID]) AS [Item ID]
FROM deleted d
FULL OUTER JOIN inserted i
ON d.[Item ID] = i.[Item ID]
WHERE d.[Item ID] IS NULL OR i.[Item ID] IS NULL OR d.[Cost] <> i.[Cost]
)
GROUP BY i_o.[Order_ID]
) c
ON o.[Order_ID] = c.[Order_ID]
END
Trigger on Item Order table:
CREATE TRIGGER T_Item_Order_Recalc_Order_Total
ON Item_Order
FOR DELETE, INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE o
SET [Order Total] = c.[Cost]
FROM Order o
INNER JOIN
(
SELECT i_o.[Order_ID]
,SUM(Cost) AS Cost
FROM Item_Order i_o
INNER JOIN Item i
ON i.[Item ID] = i_o.[Item ID]
WHERE i_o.[Order_ID] IN (
-- Sum up cost for changed rows only
SELECT deleted.[Order_ID]
UNION
SELECT inserted.[Order_ID]
)
GROUP BY i_o.[Order_ID]
) c
ON o.[Order_ID] = c.[Order_ID]
END

Select newest entry from a joined MySQL table

I have stock quantity information in my database.
1 table, "stock", holds the productid (sku) along with the quantity and the filename from where it came.
The other table, "stockfile", contains all the processed filenames along with dates.
Now I need to get all the products with their latest stock quantity values.
This gives me ALL the products multiple times with all their stock quantity (resulting in 300.000 records)
SELECT stock.stockid, stock.sku, stock.quantity, stockfile.filename, stockfile.date
FROM stock
INNER JOIN stockfile ON stock.stockfileid = stockfile.stockfileid
ORDER BY stock.sku ASC
I already tried this:
SELECT * FROM stock
INNER JOIN stockfile ON stock.stockfileid = stockfile.stockfileid
GROUP BY sku
HAVING stockfile.date = MAX( stockfile.date )
ORDER BY stock.sku ASC
But it did not work
SHOW CREATE TABLE stock:
CREATE TABLE stock (
stockid bigint(20) NOT NULL AUTO_INCREMENT,
sku char(25) NOT NULL,
quantity int(5) NOT NULL,
creationdate datetime NOT NULL,
stockfileid smallint(5) unsigned NOT NULL,
touchdate datetime NOT NULL,
PRIMARY KEY (stockid)
) ENGINE=MyISAM AUTO_INCREMENT=315169 DEFAULT CHARSET=latin1
SHOW CREATE TABLE stockfile:
CREATE TABLE stockfile (
stockfileid smallint(5) unsigned NOT NULL AUTO_INCREMENT,
filename varchar(25) NOT NULL,
creationdate datetime DEFAULT NULL,
touchdate datetime DEFAULT NULL,
date datetime DEFAULT NULL,
begindate datetime DEFAULT NULL,
enddate datetime DEFAULT NULL,
PRIMARY KEY (stockfileid)
) ENGINE=MyISAM AUTO_INCREMENT=265 DEFAULT CHARSET=latin1
This is an example of the frequently-asked "greatest-n-per-group" question that we see every week on StackOverflow. Follow that tag to see other similar solutions.
SELECT s.*, f1.*
FROM stock s
INNER JOIN stockfile f1
ON (s.stockfileid = f1.stockfileid)
LEFT OUTER JOIN stockfile f2
ON (s.stockfileid = f2.stockfileid AND f1.date < f2.date)
WHERE f2.stockfileid IS NULL;
If there are multiple rows in stockfile that have the max date, you'll get them both in the result set. To resolve this, you'd have to add some tie-breaker conditions into the join on f2.
Thanks for adding the CREATE TABLE info. That's very helpful when you're asking SQL questions.
I see from the AUTO_INCREMENT table options that you have 315k rows in stock and only 265 rows in stockfile. Your stockfile table is the parent in the relationship, and the stock table is the child, with a column stockfileid that references the primary key of stockfile.
So your original question was misleading. You want the latest row from stock, not the latest row from stockfile.
SELECT f.*, s1.*
FROM stockfile f
INNER JOIN stock s1
ON (f.stockfileid = s1.stockfileid)
LEFT OUTER JOIN stock s2
ON (f.stockfileid = s2.stockfileid AND (s1.touchdate < s2.touchdate
OR s1.touchdate = s2.touchdate AND s1.stockid < s2.stockid))
WHERE s2.stockid IS NULL;
I'm assuming you want "latest" to be relative to touchdate, so if you want to use creationdate instead, you can do the edit.
I've added a term to the join so that it resolves ties. I know you said the dates are "practically unique" but as the saying goes, "one in a million is next Tuesday."
Okay, I think I understand what you're trying to do now. You want the most recent row per sku, but the date by which to compare them is in the referenced table stockfile.
SELECT s1.*, f1.*
FROM stock s1
JOIN stockfile f1 ON (s1.stockfileid = f1.stockfileid)
LEFT OUTER JOIN (stock s2 JOIN stockfile f2 ON (s2.stockfileid = f2.stockfileid))
ON (s1.sku = s2.sku AND (f1.date < f2.date OR f1.date = f2.date AND f1.stockfileid < f2.stockfileid))
WHERE s2.sku IS NULL;
This does a self-join of stock to itself, looking for a row with the same sku and a more recent date. When none is found, then s1 contains the most recent row for its sku. And each instance of stock has to join to its stockfile to get the date.
Re comment about optimization: It's hard for me to test because I don't have tables populated with data matching yours, but I'd guess you should have the following indexes:
CREATE INDEX stock_sku ON stock(sku);
CREATE INDEX stock_stockfileid ON stock(stockfileid);
CREATE INDEX stockfile_date ON stockfile(date);
I'd suggest using EXPLAIN to analyze the query without the indexes, and then create one index at a time and re-analyze with EXPLAIN to see which one gives the most direct benefit.
Use:
SELECT DISTINCT s.stockid,
s.sku,
s.quantity,
sf.filename,
sf.date
FROM STOCK s
JOIN STOCKFILE sf ON sf.stockfileid = s.stockfileid
JOIN (SELECT t.stockfileid,
MAX(t.date) 'max_date'
FROM STOCKFILE t
GROUP BY t.stockfileid) x ON x.stockfileid = sf.stockfileid
AND x.max_date = sf.date
select *
from stock
where stockfileid in (
select top 1 stockfileid
from stockfile
order by date desc
)
There are two common ways to accomplish this: a sub query or a self-join.
See this example of selecting the group-wise maximum at the MySQL site.
Edit, an example using a subquery:
SELECT stock.stockid, stock.sku, stock.quantity,
stockfile.filename, stockfile.date
FROM stock
INNER JOIN stockfile ON stock.stockfileid = stockfile.stockfileid
WHERE stockfile.date = (SELECT MAX(date) FROM stockfile);