I have issue with a SQL request or my database structure. I would like to have supplier/customer database. As the ordered price products and sold products can change, I'd like to keep an historic on each order / sales to create in the future statistics on them. I'm at the beginning of my project and using 6 simple tables :
Client | OrderC | OrderDetail
-----------------+------------------+--------------------------------------
Client_ID (Pk) | Order_ID (Pk) | OrderD_ID (Pk)
Name | Client_ID (Fk) | Order_ID (Fk)
| date | Product_ID (Fk)
| | Qty
| | PU_Vte (sales price)
| | For_Cmd_ID (Fk from For_ID-Cmd table Forever_cmd)
_________________|__________________|_____________________________________
|(supplier database)| (detail of supplier order)
Product | Forever_cmd | For_Ord_Detail
----------------+-------------------+----------------------------------------------------
Product_ID (Pk) | For_ID_CMD (Pk) | For_Det_Id (Pk)
Name | date |ID_cmd_For (Fk from For_ID-CMD on Forever_cmd table)
| |Product_ID (Fk source Product_ID on Product table)
| | Qte (= quantity)
| | PUHA (= supplier price)
All is working fine until I have similar products on different supplier orders. The result of my sql request create extra result for the customers.
Example:
I create 2 supplier orders (S1 and S2) containing same product ID (P1) with different supplier prices. I create a customer order and I choose to sale product P1 from S1.
When I query to obtain a view by client with products and to track them from supplier order, the result add all similar products from all supplier orders to the customers. Instead of only the products ordered by the customers.
I don't know if it is from my query or database consistency.
Here is my sql request :
SELECT C.Name,
O.order_id,
F.For_ID_CMD,
P.Name,
D.Qte,
D.PU_Vte,
X.PUHA
FROM Client AS C
JOIN OrderC O ON C.Client_ID = O.Client_ID
JOIN OrderDetail D ON O.Order_ID = D.Order_ID
JOIN For_Ord_Detail X ON D.Product_ID = X.Product_ID
JOIN Forever_Cmd F ON F.For_ID_CMD = X.ID_cmd_For
JOIN Product P ON D.Product_ID = P.Product_ID;
Could someone please help me ?
First, a quick code review:
"date" is a special word. I'd recommend not using it as a column header. Be a bit more descriptive about the type of date these are. OrderC_date and Forever_cmd_date. Or OC_date, FC_date.
You've used both "Qty" and "Qte" for quantity. Be consistent. Or even better, be more descriptive of what those quantities are.
For your duplicates, remove your JOINs one at a time until you figure out which one is causing the dupes. I'm betting you need to narrow your JOIN criteria on one of those tables.
===========================================================
EDIT
This should point you in the right direction to find your duplicates.
SQL Fiddle
MS SQL Server 2014 Schema Setup:
CREATE TABLE Product ( Product_ID int, Name varchar(20) ) ;
INSERT INTO Product (Product_ID, Name)
VALUES
(1, 'Widget1')
, (2, 'Widget2')
;
CREATE TABLE Forever_cmd ( For_ID_CMD int, [date] date ) ;
INSERT INTO Forever_cmd ( For_ID_Cmd, [date] ) /* Why a date? */
VALUES (1,'10/26/1985'), (2,'6/27/2012');
CREATE TABLE For_Ord_Detail ( For_Det_Id int, ID_cmd_For int, Product_ID int, Qte int, PUHA decimal(10,2) ) ;
INSERT INTO For_Ord_Detail ( For_Det_Id, ID_cmd_For, Product_ID, Qte, PUHA )
VALUES
(1,1,1,10,2.00)
, (2,1,2,10,12.00)
, (3,2,2,20,20.00)
;
Query 1:
SELECT F.For_ID_CMD, P.Name, X.PUHA
FROM Product P
LEFT OUTER JOIN For_Ord_Detail X ON P.Product_ID = X.Product_ID <<<<<
LEFT OUTER JOIN Forever_Cmd F ON X.ID_cmd_For = F.For_ID_CMD
Results:
| For_ID_CMD | Name | PUHA |
|------------|---------|------|
| 1 | Widget1 | 2 |
| 1 | Widget2 | 12 | << Why did this "duplicate"?
| 2 | Widget2 | 20 | << Why did this "duplicate"?
Hint: How do you determine which supplier you get your product from if multiple suppliers have the same product?
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 10 months ago.
Improve this question
I am creating a pizza shipping website, and I need to represent the orders and their content somewhere in the database.
The problem is, I have absolutely no idea how to store items and their quantities.
My first thought is to create an order_content table, which contains the order's id as a foreign key, and has two extra columns : item id and quantity.
Another problem : I have multiple types of items : pizzas, drinks, extras etc... So id's aren't unique across categories. I can't just say, for example in order_content item_id 1, quantity 1, because item_id 1 can mean drink with id 1, pizza with id 1, etc...
Another big problem : I have a custom pizza which can have 3 to 6 custom ingredients. I have ingredients in a table with their unique id's... How can I represent this custom pizza in orders ?
Thank you
PS : I am fairly a beginner in SQL - Relational databases
Here is a schema that you should experiment with and modify to suit your needs.
create table clients(
id serial,
name varchar(25) not null,
email varchar(25) not null,
telephone varchar(25),
constraint pk_clients_id primary key (id),
constraint uq_clients_email unique(email)
);
create table items(
id serial,
name varchar(25),
price decimal(5,2),
constraint pk_items_id primary key (id)
);
create table orders(
id serial,
client_id int,
constraint pk_orders_id primary key (id),
constraint fk_orders_client foreign key (client_id) references clients(id)
);
create table toppings(
id serial,
name varchar(25) not null,
constraint pk_topping primary key (id));
create table order_details(
order_id int,
item_id int,
quantity int,
topping_1 int,
topping_2 int,
topping_3 int,
topping_4 int,
topping_5 int,
topping_6 int,
constraint fk_order_details_order_id foreign key (order_id) references orders(id),
constraint fk_custom_pizza_id foreign key (item_id) references items(id),
constraint pk_order_details_order_item_ids primary key(order_id, item_id)
);
✓
✓
✓
✓
✓
insert into clients (name, email, telephone) values ('Andrew','andrew#gmail.com','0123456789');
insert into items (name, price) values('custom pizza','20.00'),('1.5 litre coca-cola',5);
insert into toppings (name) values('mozzarella'),('parma ham'),('mushrooms'),('olives'),('red peppers'),('salmon');
1 rows affected
2 rows affected
6 rows affected
with order_number as
(insert into orders (client_id) values (1)
returning id)
insert into order_details
select order_number.id,1,1,1,2,3,4,5,6 from order_number
union all
select order_number.id,2,1,null,null,null,null,null,null from order_number;
2 rows affected
select
c.id,
c.name,
o.id order_number,
od.item_id,
i.name,
i.price,
od.quantity * i.price as line_total,
t1.name topping_1,
t2.name topping_2,
t3.name topping_3,
t4.name topping_4,
t5.name topping_5,
t6.name topping_6
from clients c
left join orders o on c.id = o.client_id
left join order_details od on o.id = od.order_id
left join items i on od.item_id = i.id
left join toppings t1 on od.topping_1 = t1.id
left join toppings t2 on od.topping_2 = t2.id
left join toppings t3 on od.topping_3 = t3.id
left join toppings t4 on od.topping_4 = t4.id
left join toppings t5 on od.topping_5 = t5.id
left join toppings t6 on od.topping_6 = t6.id
id | name | order_number | item_id | name | price | line_total | topping_1 | topping_2 | topping_3 | topping_4 | topping_5 | topping_6
-: | :----- | -----------: | ------: | :------------------ | ----: | ---------: | :--------- | :-------- | :-------- | :-------- | :---------- | :--------
1 | Andrew | 1 | 1 | custom pizza | 20.00 | 20.00 | mozzarella | parma ham | mushrooms | olives | red peppers | salmon
1 | Andrew | 1 | 2 | 1.5 litre coca-cola | 5.00 | 5.00 | null | null | null | null | null | null
db<>fiddle here
In general, storage systems of shopping sites are using relations between an order and an order line.
You could organize your DB like this and answer your problems
order
order_line
product
ingredient
ingredient_for_product
id
order_id
product_id
ingredient_id
ingredient_id
...
quantity
current_unit_price
name
order_line_id
unit_price
product_type
additional_price
quantity
product_id
product is an abstract concept that holds all sold products by your company. If you need to be more precise, you can either add fields that will be completed depending on the value of product_type or create another table with a one-on-one relationship with the product table.
you have a unit_price in the order_line table and a current_unit_price in the product table.
This has two uses :
if you change the price of your product a posteriori, you will still keep the price your customer paid
it allows you to store a price which is different from your registered "current_unit_price". For example, adding the value of supplementary ingredients
You might want to take a look at the fact table and dimension table definitions. Once this concept is understood it will be clear for you the the order table will be a fact table, and tables such as ingredients will be a dimension table.
I have this table with product and prices and I'm asked to make code where it filters out the products that have same price as some other product from the same list. Well I made this:
SELECT name
FROM Products
WHERE NOT price = (SELECT price FROM Products);
Amazing I know. It seems to work somewhat, but when it came to situation where table was like this:
Product Price
+-----------+------------+
|tomato | 1.00 |
+-----------+------------+
|potato | 2.00 |
+-----------+------------+
|rock | 3.00 |
+-----------+------------+
|hot tamale | 4.00 |
+-----------+------------+
Now for some reason, even if it worked in all the other tests I had, it gives me this:
+-----------+
|potato |
+-----------+
|rock |
+-----------+
|hot tamale |
+-----------+
Instead of the right answer:
+-----------+
|tomato |
+-----------+
|potato |
+-----------+
|rock |
+-----------+
|hot tamale |
+-----------+
Anyone who can clear this out for my tiny mind :d? I'm in the very beginning of my journey in SQL so try to keep it simple.
I would recommend window functions:
select p.*
from (select p.*, count(*) over (partition by price) as cnt
from products p
) p
where cnt = 1;
This counts the number of rows that have the same price and returns those rows where the price is unique.
Maybe we can use group by:
create table products (product varchar2(10) , price number);
insert into products values('tomato', 1);
insert into products values('potato', 2);
insert into products values('rock', 2);
insert into products values('hot tamale', 4);
select * from products where price in (
select price from products
group by price
having count(*) = 1);
Notice that I've changed price for 'rock' to have two products with same price to eliminate it.
You can do it all in a single aggregate query:
select min(product) as product
from products
group by price
having count(*) = 1
;
The point of min(product) is that you must have an aggregate function (since you are not grouping by product); but, since you are only looking at the groups consisting of exactly one row each, min(product) is in fact the product from that single row per group.
Trying to accomplish this;
TABLE PRODUCTS
id | product_id | product
------------------------------
1 | 123| acme widget
------------------------------
2 | 456| acme gadget
TABLE ORDERS
id | lineItems
------------------------------
1 | [{ id: 123, quantity: 10}, { id: 456, quantity: 5}]
USING
SELECT a.*
FROM orders a
LEFT OUTER JOIN products b ON b.product_id = a.products -> 'id'
in order to return
id | product_id | product | quantity
------------------------------------------
1 | 123 | acme widget | 10
------------------------------------------
1 | 456 | acme gadget | 5
I solved this. I created a view
CREATE VIEW view_lineItems
AS
SELECT
aa.id AS order_id,
-- (p->>'id')::bigint AS product_id,
bb.*,
(p->>'quantity')::integer AS quantity
FROM public.pim_orders aa, json_array_elements(aa.products) p
LEFT OUTER JOIN pim_batch bb ON bb.product_id = (p->>'id')::bigint
ORDER BY aa.id;
and then joined that view to the original query (stored procedure) and called in the columns I needed.
because of the nature of the line items view i wasn't able to create a primary key - which I would need for a materialized view - as it is possible for there to be multiple products as each line item, and also possible for the same product to be listed multiple times for different orders.
but this worked fine for my needs :) I'm now able to decide which view I'll join specific tables for returned results - whether on the order, or on the line item.
cheers!
I am new to foreign key, but I understand the concept very well.
I have found lot of documentation on how to create / delete them but not how to use them. My schema is as follows.
Stock table:
PartID | Model | Type | Vendor
------------------------------
1 | DDr2 | RAM | shop1
2 | DDr3 | RAM | shop1
3 | WD1 | HDD | shop2
4 | WD2 | HDD | shop2
Then product Table
ProdID | Name | PartID1 | PartID2 ...
1 | PC1 | 1 | 2
2 | PC1 | 3 | 4
How do I use select to get
| PC1 | DDr2 | DDR3 |
| PC1 | WD1 | WD2 |
with PartID2 and PartID3 foreign key linked to PartID primary key?
The concept of Foreign Keys is to link the IDs in one table to the lisk of unique IDs in another. In your example, you have unique parts with unique IDs and Products that can use those parts, so in your product table, you could have multiple part IDs being used in multiple rows.
Foreign Keys are used to keep referential integrity in your database, you can use joins to get the Data you want:
SELECT A.NAME,
B.Model,
C.Model
FROM PRODUCTS A
INNER JOIN PARTS B ON B.PARTID1 = A.PARTID
INNER JOIN PARTS C ON C.PARTID1 = A.PARTID
WHERE A.PRODID = 1
The short answer is you could do
select p.name, a.model as part1, b.model as part2, c.model as part3
from product p, stock a, stock b, stock c
where p.partid1 = a.partid and p.partid2 = b.partid and p.partid3 = c.partid
The longer answer is that this isn't really a good table design for what you're trying to do. It assumes that you always have a fixed number of parts for any item (or at least no more than some fixed number). A better design would be:
Part Table:
partID | model | type | vendor
Product Table:
productID | name
Product_Parts Table:
productID | partID
where productID in Product_parts is a foreign key into Product and partID is a foreign key into the Part table.
SELECT s1.Name, p1.Model, p2.Model FROM stock st
INNER JOIN product p1
ON st.PartID1 = p1.PartID1
INNER JOIN product p2
ON st.PartID2 = p2.PartID1
Take one JOIN at the time first join stock and parts table
then again join result of this join to parts table.
SQL parser will use parts table as two separate tables an so you can have two results from same tabe in single row.
you can join in a table more than once in the same sql statement. in this case, you need to join your stock table twice, once to get the name of each part in your product.
SELECT pr.ProdID, s1.Model, s2.Model
FROM Product pr, Stock s1, Stock s2
WHERE pr.PartID1 = s1.PartID
AND pr.PartID2 = s2.PartID
Using a LEFT OUTER JOIN means that the product will still be returned event if the Part1ID or Part2ID values are set to NULL.
SELECT P.Name,
S1.Model,
S2.Model
FROM Product P
LEFT OUTER JOIN Stock S1 ON P.PartID1 = S1.PartID
INNER JOIN Stock S2 ON P.PartID2 = S2.PartID
I have products, invoices and clients. On the client invoice is a product with a selling price without tax. I need to report for each client, for product_id =45, the lowest selling price and also a selling price from the very first invoice.
I can group everything except the last condition. I know it can be done with a subselect, but I'd like to avoid them.
Simplified database structure:
table clients
-clent_id serial
table products
-product_id serial
-name text
table invoices
-invoice_id serial
-client_id int
table invoices_rows
-invoice_row_id serial
-invoice_id int
-product_id int
-price double precision
Use window functions in combination with DISTINCT to get the lowest and the first price at the same time (without subselect as requested):
SELECT DISTINCT ON (i.client_id)
i.client_id
, min(ir.price) OVER (PARTITION BY i.client_id) AS min_price
, first_value(ir.price) OVER (PARTITION BY i.client_id
ORDER BY ir.invoice_id) AS first_price
FROM invoices_rows ir
JOIN invoices i USING (client_id)
WHERE ir.product_id = 45;
Apply DISTINCT ON (client_id) to get just one row per client_id. DISTINCT is applied after window functions, while GROUP BY would be applied before.
I am assuming that "first invoice" can be interpreted as "lowest invoice_id".
Do you need "lowest selling price" for each client? Or the overall lowest price for the product? I changed to "per client_id" now. Seems more likely.
If you would not mind a subselect or CTE, this would probably perform best:
WITH x AS (
SELECT i.client_id
, min(ir.price) AS min_price
, min(ir.invoice_id) AS invoice_id
FROM invoices_rows ir
JOIN invoices i USING (client_id)
WHERE ir.product_id = 45
)
SELECT x.*, ir.price AS first_price
FROM x
JOIN invoices_rows ir USING (invoice_id)
I think that to get the selling price from the very first invoice you would need information on sale_date or a valid criteria that you didn't explain in your request.
There are different ways to achieve that result, but the one I prefer is based only on aggregate functions. The following sample uses postges but also other DB may give you the same functionality.
postgres=# select *
postgres-# from prices_products;
product_name | customer_name | price | sell_date
--------------+---------------+-------+------------
knife | mark | 100 | 2011-01-20
book | cris | 20 | 2011-05-12
book | mark | 25 | 2010-09-30
book | cris | 30 | 2012-02-15
(4 rows)
postgres=#
postgres=# select product_name, customer_name,m as maximum, arr[1] as first_date_val
postgres-# from (
postgres(# select product_name, customer_name, max(price) as m, array_agg(price order by sell_date) as arr
postgres(# from prices_products
postgres(# group by product_name, customer_name
postgres(# ) a;
product_name | customer_name | maximum | first_date_val
--------------+---------------+---------+----------------
book | cris | 30 | 20
book | mark | 25 | 25
knife | mark | 100 | 100
(3 rows)
postgres=#