PostgreSQL : how to query from 3 tables (with 1 junction table)? - sql

table 'product'
------------------------------------------
| id | product_name | product_description|
------------------------------------------
| 1. | abc | this is abc's desc |
------------------------------------------
Junction table 'ingredient_product'
------------------------------------------
| id | product_id | ingredient_id |
------------------------------------------
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
| 1 | 1 | 4 |
| 1 | 1 | 5 |
| 1 | 1 | 6 |
------------------------------------------
table 'ingredient'
------------------------
| id | ingredient_name |
------------------------
| 1 | apple |
| 2 | chicken |
| 3 | beef |
| 4 | beet |
| 5 | oat |
| 6 | pea fibre |
------------------------
I have 3 tables and tried to query like below
SELECT
product.name AS product_name,
product.description AS product_description,
product.created_at AS product_created,
ingredient.name AS ingredient_name
FROM
product
JOIN
ingredient_product
ON
ingredient_product.product_id = product.id
JOIN
ingredient
ON
ingredient.id = ingredient_product.ingredient_id
WHERE
ingredient_product.product_id = 1;
and I get the result like below
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "banana"}
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "strawberry"}
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "canola oil"}
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "pilchard"}
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "ground beef"}
{product_name: "fromm gold", product_description: "For puppies and pregnant or nursing mothers. Taste… aid digestion and salmon oil for a healthy coat.", ingredient_name: "cranberry"}
I get the all different ingredients but wanted to show only once for the duplicates.
is there better way to query this type?
Thank you in advance!

You seem to want aggregation, something like this:
SELECT p.name AS product_name, p.description AS product_description,
p.created_at AS product_created,
ARRAY_AGG(i.name) AS ingredient_names
FROM product p JOIN
ingredient_product ip
ON ip.product_id = p.id JOIN
ingredient i
ON i.id = ip.ingredient_id
WHERE p.id = 1
GROUP BY p.id;
Notes:
Table aliases make the query easier to write and read.
The WHERE clause filters on the primary key of product rather than on the equivalent column in ingredient_product. I think that primary keys may help the optimizer.
This adds a GROUP BY, because you want one row per product. This is aggregating by the primary key, so the SELECT can contain other columns.
The array_agg() brings the ingredients together as an array.

Do you want to list the product details only once, together with a list of ingredients?
Have a look at aggregate functions e.g. there: https://www.postgresql.org/docs/9.5/functions-aggregate.html .
I think function string_agg will help you.

Related

Calculate Equation From Seperate Tables Data

I'm working on my senior High School Project and am reaching out to the community for help! (As my teacher doesn't know the answer to my question).
I have a simple "Products" table as shown below:
I also have a "Orders" table shown below:
Is there a way I can create a field in the "Orders" table named "Total Cost", and make that automaticly calculate the total cost from all the products selected?
Firstly, I would advise against storing calculated values, and would also strongly advise against using calculated fields in tables. In general, calculations should be performed by queries.
I would also strongly advise against the use of multivalued fields, as your images appear to show.
In general, when following the rules of database normalisation, most sales databases are structured in a very similar manner, containing with the following main tables (amongst others):
Products (aka Stock Items)
Customers
Order Header
Order Line (aka Order Detail)
A good example for you to learn from would be the classic Northwind sample database provided free of charge as a template for MS Access.
With the above structure, observe that each table serves a purpose with each record storing information pertaining to a single entity (whether it be a single product, single customer, single order, or single order line).
For example, you might have something like:
Products
Primary Key: Prd_ID
+--------+-----------+-----------+
| Prd_ID | Prd_Desc | Prd_Price |
+--------+-----------+-----------+
| 1 | Americano | $8.00 |
| 2 | Mocha | $6.00 |
| 3 | Latte | $5.00 |
+--------+-----------+-----------+
Customers
Primary Key: Cus_ID
+--------+--------------+
| Cus_ID | Cus_Name |
+--------+--------------+
| 1 | Joe Bloggs |
| 2 | Robert Smith |
| 3 | Lee Mac |
+--------+--------------+
Order Header
Primary Key: Ord_ID
Foreign Keys: Ord_Cust
+--------+----------+------------+
| Ord_ID | Ord_Cust | Ord_Date |
+--------+----------+------------+
| 1 | 1 | 2020-02-16 |
| 2 | 1 | 2020-01-15 |
| 3 | 2 | 2020-02-15 |
+--------+----------+------------+
Order Line
Primary Key: Orl_Order + Orl_Line
Foreign Keys: Orl_Order, Orl_Prod
+-----------+----------+----------+---------+
| Orl_Order | Orl_Line | Orl_Prod | Orl_Qty |
+-----------+----------+----------+---------+
| 1 | 1 | 1 | 2 |
| 1 | 2 | 3 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 1 | 1 | 4 |
| 3 | 2 | 3 | 2 |
+-----------+----------+----------+---------+
You might also opt to store the product description & price on the order line records, so that these are retained at the point of sale, as the information in the Products table is likely to change over time.

Try to find Partial and transitive dependencies

Cust_id| Name |Ord_no|Ord_Date |PROD-ID|Descr |Qty_ord|
C001 | Pink | O81 | 15-Apr |P005 |Chisel|6 |
C001 | Pink | O81 | 15-Apr |P004 |Jane |14 |
C0075 | Red | O99 | 16-Apr |P015 |Saw |3 |
C009 | Black| O56 | 16-Apr |P033 |Punch |24 |
C009 | Black| O56 | 16-Apr |P004 |Jane |9 |
C001 | Pink | O88 | 17-Apr |P015 |Saw |10 |
From this table example I am trying to understand both this dependencies. According to me partial dependencies have a primary composite key and transitive don't have.
I think Primary key are Ord_no and Prod_id.Not sure about Cust_id
The only non key column which depends on the whole key is Quantity. All the rest are partial dependencies.Not sure about Transitive dependency exist or not in this table
Partial Dependency in the table are :
• Cust_id and Name
• Prod_id and Decr
Also the Transitive Dependency in the table are as follow :
• Ord no_ and Ord_date can be ?
Update 1-I try to figure out but not sure about my answer.
I just want clarification like order no is unique, and determines the customer so how can two different order_no 81 & 88 can have same customer id C001.
Therefore I think no transitive dependency.
I see many dependencies in your current table which could be refactored:
The Cust_id most likely determines the customer name
The Ord_no determines the set of products included in that order
The PROD-ID determines the description of that product
I would suggest the following schema, involving four tables:
Customers
Cust_id (PK) | Name
C001 | Pink
C009 | Black
C0075 | Red
Products
PROD-ID (PK) | Descr
P004 | Jane
P005 | Chisel
P015 | Saw
P033 | Punch
Orders
Ord_no (PK) | Ord_Date | Cust_id
056 | 16-APR | C009
081 | 15-APR | C001
088 | 18-APR | C001
099 | 16-APR | C0075
OrdersDetails
Ord_no | PROD-ID | Qty-ord (primary key is Ord_no, PROD-ID)
O56 | P004 | 9
O56 | P033 | 24
O81 | P004 | 14
O81 | P005 | 6
O88 | P015 | 10
O99 | P015 | 3
Now if you want the current output you have, you can obtain it via a join query:
SELECT
c.Cust_id,
c.Name,
o.Ord_no,
o.Ord_Date,
od.PROD-ID,
p.Descr,
od.Qty-ord
FROM Customers c
INNER JOIN Orders o
ON c.Cust_id = o.Cust_id
INNER JOIN OrdersDetails od
ON o.Ord_no = od.Ord_no
INNER JOIN Products p
ON od.PROD-ID = p.PROD-ID;

Sql - Sort by values in a cartesian product

Let's say I have the following data:
Product
IdPk | Name
-------------
guid1 | Printer
guid2 | Oil
guid3 | Etc.
guid4 | Etc..
ProductPart
PartIdPk| ItemId | PartName
-------------------------
guid100 | guid1 | Ink
guid101 | guid1 | Paper
guid102 | guid2 | Automobile Fuel
guid103 | guid2 | Cooking
ProductPartType
TypeIdPk| ItemId | PartId | TypeName
---------------------------------
guid200 | guid1 | guid100 | Cyan < Types of ink
guid201 | guid1 | guid100 | Magenta
guid202 | guid1 | guid100 | Black
guid203 | guid1 | guid100 | Yellow
guid204 | guid1 | guid101 | Photocopier < Types of paper
guid205 | guid1 | guid101 | Envelope
guid206 | guid1 | guid101 | Card
guid207 | guid2 | guid102 | Petrol < Types of automobile fuel
guid208 | guid2 | guid102 | Diesel
guid209 | guid2 | guid103 | Olive < Types of cooking oil
guid210 | guid2 | guid103 | Sunflower
So, each product has one or more parts, and each part has one or more types.
I'm wanting to select a Product, its Parts and its Types. Assume that I could have many thousands of entries, so typically I'd like to filter at the same time as selecting. These three tables will often lead to a cartesian product query, and given that scenario I need to run a query which is equivalent to "give me the top 2 products ordered by (type name where the part is Ink) then (type name where the part is Cooking)"
Does anyone have any ideas? Many thanks in advance
What you are looking for is just a basic join query on the tables:
select ppt.*, pp.PartName, p.Name
from ProductPartType ppt join
ProductPart pp
on ppt.PartId = pp.PartId join
Product p join
pp.ItemId = p.idPK
where <whatever>
Then you can do whatever queries you want. The "top" query is unclear, because there are no natural things to order on.
I do have a quibble with your data structure, because you have product at both the product part and product part type levels. For consistency purposes, it is better to have this at only only level, probably product part.
Usually, "cartesian product" join is used to refer to a cross join, not an equi-join. For performance, you can add indexes onto the tables. However, you probably won't need them.

What SQL magic do I need to turn one column into several?

I need to print some tickets, each of which has enough room to hold one set of customer details along with codes for up to five items ordered by that customer. Customers who have ordered more than five items get multiple tickets. So from an orders table like this,
Customer | Item
---------|------
Bob | FTMCH
Bob | ZORP
Bob | KLUGE
Carol | FTMCH
Carol | MEEP
Carol | ZORP
Ted | FOON
Ted | SMOCK
Alice | ORGO
Carol | SQICK
Carol | BLECH
Carol | KLUGE
Carol | GLURP
I need a query that returns this:
Customer | Item1 | Item2 | Item3 | Item4 | Item5
---------|-------|-------|-------|-------|------
Alice | ORGO | null | null | null | null
Bob | FTMCH | ZORP | KLUGE | null | null
Carol | FTMCH | MEEP | ZORP | SQICK | BLECH
Carol | KLUGE | GLURP | null | null | null
Ted | FOON | SMOCK | null | null | null
Can some kind soul help me with the SQL for this? HSQL embedded database in OpenOffice.org Base, if it makes a difference.
OK, this works well enough:
SELECT
"Customer",
MAX(CASE WHEN "Slot" = 0 THEN "Item" END) AS "Item1",
MAX(CASE WHEN "Slot" = 1 THEN "Item" END) AS "Item2",
MAX(CASE WHEN "Slot" = 2 THEN "Item" END) AS "Item3",
MAX(CASE WHEN "Slot" = 3 THEN "Item" END) AS "Item4",
MAX(CASE WHEN "Slot" = 4 THEN "Item" END) AS "Item5"
FROM (
SELECT
l."Customer" AS "Customer",
l."Item" AS "Item",
COUNT(r."Item") / 5 AS "Ticket",
MOD(COUNT(r."Item"), 5) AS "Slot"
FROM "Orders" AS l
LEFT JOIN "Orders" AS r
ON r."Customer" = l."Customer" AND r."Item" < l."Item"
GROUP BY "Customer", "Item"
)
GROUP BY "Customer", "Ticket"
ORDER BY "Customer", "Ticket"
It makes this:
Customer | Item1 | Item2 | Item3 | Item4 | Item5
---------|-------|-------|-------|-------|-------
Alice | ORGO | | | |
Bob | FTMCH | KLUGE | ZORP | |
Carol | BLECH | FTMCH | GLURP | KLUGE | MEEP
Carol | SQICK | ZORP | | |
Ted | FOON | SMOCK | | |
Thanks to all who helped, both here and at Ask Metafilter.
(Followup edit:)
Jesus, this just gets worse :-(
Turns out the business rules allow the same customer to order the same item on multiple occasions, and that all outstanding orders are to be included on the one set of tickets. So my toy table should have looked more like this:
ID | Customer | Item
159 | Bob | FTMCH
264 | Bob | ZORP
265 | Bob | KLUGE
288 | Carol | FTMCH
314 | Carol | MEEP
323 | Carol | ZORP
327 | Ted | FOON
338 | Ted | SMOCK
358 | Alice | ORGO
419 | Carol | SQICK
716 | Carol | MEEP
846 | Carol | BLECH
939 | Carol | MEEP
950 | Carol | GLURP
979 | Carol | KLUGE
Carol's multiple MEEPs bugger the ranking logic in the original solution, and I've ended up with the following hideous monster:
SELECT
"Customer",
MAX(CASE WHEN "Slot" = 0 THEN "Item" END) AS "Item0",
MAX(CASE WHEN "Slot" = 1 THEN "Item" END) AS "Item1",
MAX(CASE WHEN "Slot" = 2 THEN "Item" END) AS "Item2",
MAX(CASE WHEN "Slot" = 3 THEN "Item" END) AS "Item3",
MAX(CASE WHEN "Slot" = 4 THEN "Item" END) AS "Item4",
MAX(CASE WHEN "Slot" = 0 THEN "Quantity" END) AS "Qty0",
MAX(CASE WHEN "Slot" = 1 THEN "Quantity" END) AS "Qty1",
MAX(CASE WHEN "Slot" = 2 THEN "Quantity" END) AS "Qty2",
MAX(CASE WHEN "Slot" = 3 THEN "Quantity" END) AS "Qty3",
MAX(CASE WHEN "Slot" = 4 THEN "Quantity" END) AS "Qty4"
FROM (
SELECT
"Customer",
"Item",
COUNT("ID") AS "Quantity",
"Rank" / 5 AS "Ticket",
MOD("Rank", 5) AS "Slot"
FROM (
SELECT
main."ID" AS "ID",
main."Customer" AS "Customer",
main."Item" AS "Item",
COUNT(less."Item") AS "Rank"
FROM "Orders" AS main
LEFT JOIN (
SELECT DISTINCT
"Customer",
"Item"
FROM "Orders") AS less
ON less."Customer" = main."Customer" AND less."Item" < main."Item"
GROUP BY "ID", "Customer", "Item"
)
GROUP BY "Customer", "Item", "Rank"
)
GROUP BY "Customer", "Ticket"
which makes this:
Customer | Item0 | Item1 | Item2 | Item3 | Item4 | Qty0 | Qty1 | Qty2 | Qty3 | Qty3 | Qty4
Bob | FTMCH | KLUGE | ZORP | | | 1 | 1 | 1 | | |
Carol | BLECH | FTMCH | GLURP | KLUGE | MEEP | 1 | 1 | 1 | 1 | 1 | 3
Carol | SQICK | ZORP | | | | 1 | 1 | | | |
Ted | FOON | SMOCK | | | | 1 | 1 | | | |
Alice | ORGO | | | | | 1 | | | | |
It does the job, I guess, but I'm feeling pretty lucky that the database involved is always going to be quite small (a few thousand rows).
Spiritually I'm an embedded-systems guy, not a database guy. Can anybody who does this for a living tell me whether this kind of nonsense is common? Would a query with four nested SELECTs and a LEFT JOIN merit a mention on the Daily WTF?
I believe this is only usable for T-SQL, but you can use PIVOT: http://msdn.microsoft.com/en-us/library/ms177410.aspx
I did something similar with a list of dates becoming the columns for calculations.
Not exactly what you asked, and MySQL rather than OpenOffice, but might give you an idea or someone else could work on it :
select
u.Customer,
group_concat(u.Item) items
from
(select
t.Item,
#n:=if(#c=t.Customer and #n<4,#n+1,0) c1,
#m:=if(#n,#m,#m+1) g,
#c:=t.Customer as Customer
from
t1 t, (select #m:=0) init
order
by t.Customer
) u
group by
u.g
Output :
+----------+------------------------------+
| Customer | items |
+----------+------------------------------+
| Alice | ORGO |
| Bob | FTMCH,ZORP,KLUGE |
| Carol | KLUGE,ZORP,BLECH,SQICK,GLURP |
| Carol | MEEP,FTMCH |
| Ted | FOON,SMOCK |
+----------+------------------------------+
This gets you most of the way there, but does not handle the duplicate order for Carol. That would be easy to do if there was something else to group on, like OrderID or OrderDate. Can you post the full schema?
select m1.Customer,
min(m1.Item) as Item1,
min(m2.item) as Item2,
min(m3.item) as Item3,
min(m4.item) as Item4,
min(m5.item) as Item5
from CustomerOrder m1
left outer join CustomerOrder m2 on m1.Customer = m2.Customer
and m2.item > m1.item
left outer join CustomerOrder m3 on m1.Customer = m3.Customer
and m3.item > m2.item
left outer join CustomerOrder m4 on m1.Customer = m4.Customer
and m4.item > m3.item
left outer join CustomerOrder m5 on m1.Customer = m5.Customer
and m5.item > m4.item
group by m1.Customer
Output:
Customer Item1 Item2 Item3 Item4 Item5
-------------- ---------- ---------- ---------- ---------- ----------
Alice ORGO NULL NULL NULL NULL
Bob FTMCH KLUGE ZORP NULL NULL
Carol BLECH FTMCH GLURP KLUGE MEEP
Ted FOON SMOCK NULL NULL NULL
The requirement is not uncommon, and can be supplied reasonably in SQL. But you have two issues blocking you.
1) You've entered an SQL tag, that means ISO/IEC/ANSI Standard SQL. The correct method to use is a cursor or cursor substitute (while loop, which does the same thing, but is faster). That avoids all these outer joins and handling massive result sets; then beating it into submission with GROUP BYs, etc. It also handles duplicates, mainly because it does it create them in the first place (via those five versions of the aliased table). And yes, it will keep getting worse, and when the database is reasonably populated it will be a performance hog.
2) Duplicates are not allowed in a Relational database, ie. in your source tables; you need to make the rows unique (and those keys/columns is not shown). No use trying to eliminate duplicates via code. If that is corrected, then all duplicates (real and created by the poor code) can be eliminated.
This requirement can also be supplied more elegantly using Subqueries; except that here you need two levels of nesting, one to build teach Item column, and two to obtain rank or Position. And that (standard SQL construct) pre-supposes that you have a Relational database (no duplicate rows). High Eek factor if you are not used to SQL. Which is why most coders use a cursor or cursor substitute.
But if you do not have SQL, its basic capabilities, (HSQL being some sub-standard implementation), then we are not using the same tool kit. The SQL code I can provide will not run for you, and we will keep going back and forth.
(Maybe we should have a "psuedo-SQL" tag.)
ID Column Prevents Duplicates ???
There is a myth that is prevalent in some parts of the industry, to that effect, due to books written by database beginners. As usual, myths have no scientific basis. Let's try a simple test. CREATE TABLE Person (
PersonId IDENTITY NOT NULL
PRIMARY KEY,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL
)
INSERT Person VALUES ("Fred", "Astaire")
1 row(s) affected
INSERT Person VALUES ("Ginger", "Rogers")
1 row(s) affected
INSERT Person VALUES ("Fred", "Astaire")
1 row(s) affected
SELECT * FROM Person
PersonId FirstName LastName
======== ============================== ==============================
1 Fred Astaire
2 Ginger Rogers
3 Fred Astaire
3 row(s) affected
That's a pure, unarguable duplicate row. The simple fact is. the Id column provides a row number, but does nothing to prevent duplicate rows. For that you need an Unique Index on the columns that determine uniqueness, as identified in the data model, for every relational table in the database (by definition, if the rows are not unique, it is not a Relational table). Otherwise it is just a file storage system. CREATE UNIQUE NONCLUSTERED INDEX U_Name
ON Person (LastName, FirstName)
There is another form of data integrity (duplication) which I might identify while I am at it. INSERT Person VALUES ("Fred", "Astair")
1 row(s) affected
INSERT Person VALUES ("Astaire", "Fred")
1 row(s) affected
All are preventable in SQL.

Zend Framework: How to combine three tables in one query using Joins?

I have three tables like this:
Person table:
person_id | name | dob
--------------------------------
1 | Naveed | 1988
2 | Ali | 1985
3 | Khan | 1987
4 | Rizwan | 1984
Address table:
address_id | street | city | state | country
----------------------------------------------------
1 | MAJ Road | Karachi | Sindh | Pakistan
2 | ABC Road | Multan | Punjab | Pakistan
3 | XYZ Road | Riyadh | SA | SA
Person_Address table:
person_id | address_id
----------------------
1 | 1
2 | 2
3 | 3
Now I want to get all records of Person_Address table but also with their person and address records like this by one query:
person_id| name | dob | address_id | street | city | state | country
----------------------------------------------------------------------------------
1 | Naveed | 1988 | 1 | MAJ Road | Karachi | Sindh | Pakistan
2 | Ali | 1985 | 2 | ABC Road | Multan | Punjab | Pakistan
3 | Khan | 1987 | 3 | XYZ Road | Riyadh | SA | SA
How it is possible using zend? Thanks
The reference guide is the best starting point to learn about Zend_Db_Select. Along with my example below, of course:
//$db is an instance of Zend_Db_Adapter_Abstract
$select = $db->select();
$select->from(array('p' => 'person'), array('person_id', 'name', 'dob'))
->join(array('pa' => 'Person_Address'), 'pa.person_id = p.person_id', array())
->join(array('a' => 'Address'), 'a.address_id = pa.address_id', array('address_id', 'street', 'city', 'state', 'country'));
It's then as simple as this to fetch a row:
$db->fetchRow($select);
In debugging Zend_Db_Select there's a clever trick you can use - simply print the select object, which in turn invokes the toString method to produce SQl:
echo $select; //prints SQL
I'm not sure if you're looking for SQL to do the above, or code using Zend's facilities. Given the presence of "sql" and "joins" in the tags, here's the SQL you'd need:
SELECT p.person_id, p.name, p.dob, a.address_id, street, city, state, country
FROM person p
INNER JOIN Person_Address pa ON pa.person_id = p.person_id
INNER JOIN Address a ON a.address_id = pa.address_id
Bear in mind that the Person_Address tells us that there's a many-to-many relationship between a Person and an Address. Many Persons may share an Address, and a Person may have more than one address.
The SQL above will show ALL such relationships. So if Naveed has two Address records, you will have two rows in the result set with person_id = 1.