SQL Select - Calculated Column if Value Exists in another Table - sql

Trying to work through a SQL query with some very limited knowledge and experience. Tried quite a few things I've found through searches, but haven't come up with my desired result.
I have four tables:
ORDERS
[ID][DATE]
ORDER_DETAILS
[ID][ITEM_NO][QTY]
ITEMS
[ITEM_NO][DESC]
KITS
[KIT_NO][ITEM_NO]
Re: KITS - [KIT_NO] and [ITEM_NO] are both FK to the ITEMS table. The concatenation of them is the PK.
I want to select ORDERS, ORDERS.DATE, ORDER_DETAILS.ITEM_NO, ITEMS.DESC
No problem. A few simple inner joins and I'm on my way.
The difficulty lies in adding a column to the select statement, IS_KIT, that is true if:
EXISTS(SELECT null FROM KITS WHERE KITS.ITEM_NO = ORDER_DETAILS.ITEM_NO).
(if the kits table contains the item, flag this row)
Is there any way to calculate that column?

There are different ways to do this.
The simplest is probably a LEFT JOIN with a CASE calculated column:
SELECT
o.date,
od.item_no,
i.desc,
CASE WHEN k.item_no IS NULL THEN 0 ELSE 1 END AS is_kit
FROM orders o
JOIN order_details od ON od.id=o.id
JOIN items i ON i.item_no = od.item_no
LEFT JOIN kits k ON k.item_no = od.item_no
But you could also use a SUBSELECT:
SELECT
o.date,
od.item_no,
i.desc,
(SELECT COUNT(*) FROM kits k WHERE k.item_no = od.item_no) AS is_kit
FROM orders o
JOIN order_details od ON od.id=o.id
JOIN items i ON i.item_no = od.item_no

Related

oracle sql statement help to query against multiple tables

I am struggling with a sql statement. I am hoping a guru can help a beginner out, currently I have multiple select in statements.. but think there is a better way as I have been stuck.
Below are the tables and pertinent columns in each table
country
-country_id
barcodes_banned_in_country
-barcode(varchar)
-country_id
-country_name
orders
-order_id
-country_name
item
-order_id
-item_id
-barcode(varchar)
The goal is to get all orders that are banned based off the barcode banned list.
Any help with this sql statement would be appreciated.
One option uses exists:
select o.*
from orders o
where exists (
select 1
from barcodes_banned_in_country bic
inner join item i on i.barcode = bic.barcode
where i.order_id = o.order_id and bic.country_name = o.country_name
)
This brings all orders whose at least one item have a barcode that is banned in the order's country.
If, on the other hand, you want the list of the banned barcodes per order, then you can join and aggregate:
select o.order_id, o.country_name, listagg(i.barcode, ',') banned_barcodes
from orders o
inner join item i
on i.order_id = o.order_id
inner join barcodes_banned_in_country bic
on i.barcode = bic.barcode
and bic.country_name = o.country_name
group by o.order_id, o.country_name
Note that, as commented by MT0, you should really be storing the id of the country in orders rather than the country name. Accordingly, you wouldn't need the country name in the banned barcodes table.

Postgres distinct inner join (one to many) with n constraints on joined table

I'm trying to find the most efficient way to perform a Postgres query.
I have 2 tables
Orders (id, order_name)
Items (id, order_id, item_name)
Each order in the orders table has between 1 and n items listed in the items table.
What I want is to perform a query that returns all order that contains item Burger, item Pizza and item Coke.
Is the call below the best way to do such query?
SELECT DISTINCT ON (orders.id)
orders.id
FROM orders
INNER JOIN items i1 on orders.id = i1.order_id
INNER JOIN items i2 on orders.id = i2.order_id
INNER JOIN items i3 on orders.id = i3.order_id
WHERE i1.item_name = 'Burger'
AND i2.item_name = 'Fries'
AND i3.item_name = 'Coke'
Your approach works, and should be relatively efficient. Assuming that there are no duplicate order id / item names, you can remove distinct from the other query, which avoids unecessary work.
You could also use aggregation. It involves less additional typing when the number of items increases:
select o.*
from orders o
inner join items i on i.order_id = o.id
where i.item_name in ('Burger', 'Fries', 'Coke') -- any of the three items
group by o.order_id
having count(*) = 3 -- all items match
Again, this assumes no duplicates in items; else, you need having count(distinct i.item_name) = 3 instead.

2 Tables - one customer, one transactions. How to handle a customer with no transaction?

I have 2 tables-one customers, one transactions. One customer does not have any transactions. How do I handle that? As I'm trying to join my tables, the customer with no transaction does not show up as shown in code below.
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName, Orders.TxnDate, Orders.Amount
FROM Orders
INNER JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
I need to be able to account for the customer with no transaction such as querying for least transaction amount.
Use below updated query - Right Outer join is used instead of Inner join to show all customers regardless of the customer placed an order yet.
SELECT Orders.Customer_Id, Customers.AcctOpenDate,
Customers.CustomerFirstName, Customers.CustomerLastName,
Orders.TxnDate, Orders.Amount
FROM Orders
Right Outer JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
INNER Joins show only those records that are present in BOTH tables
OUTER joins gets SQL to list all the records present in the designated table and shows NULLs for the fields in the other table that are not present
LEFT OUTER JOIN (the first table)
RIGHT OUTER JOIN (the second table)
FULL OUTER JOIN (all records for both tables)
Get up to speed on the join types and how to handle NULLS and that is 90% of writing SQL script.
Below is the same query with a left join and using ISNULL to turn the amount column into 0 if it has no records present
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName
, Orders.TxnDate, ISNULL(Orders.Amount,0)
FROM Customers
LEFT OUTER JOIN Orders ON Orders.Customer_Id=Customers.Customer_Id;
try this :
SELECT Orders.Customer_Id, Customers.AcctOpenDate, Customers.CustomerFirstName, Customers.CustomerLastName, Orders.TxnDate, Orders.Amount
FROM Orders
Right OUTER JOIN Customers ON Orders.Customer_Id=Customers.Customer_Id;
I strongly recommend LEFT JOIN. This keeps all rows in the first table, along with matching columns in the second. If there are no matching rows, these columns are NULL:
SELECT c.Customer_Id, c.AcctOpenDate, c.CustomerFirstName, c.CustomerLastName,
o.TxnDate, o.Amount
FROM Customers c LEFT JOIN
Orders o
ON o.Customer_Id = c.Customer_Id;
Although you could use RIGHT JOIN, I never use RIGHT JOINs, because I find them much harder to follow. The logic of "keep all rows in the first table I read" is relatively simple. The logic of "I don't know which rows I'm keeping until I read the last table" is harder to follow.
Also note that I included table aliases and change the CustomerId to come from customers -- the table where you are keeping all rows.
Using CASE will replace "null" with 0 then you can sum the values. This will count customers with no transactions.
SELECT c.Name,
SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END) as TransactionsPerCustomer
FROM Customers c
LEFT JOIN Transactions t
ON c.Name = t.customerID
group by c.Name
SELECT c.Name,
SUM(CASE WHEN t.ID IS NULL THEN 0 ELSE 1 END) as numberoftransaction
FROM customers c
LEFT JOIN transactions t
ON c.Name = t.customerID
group by c.Name

Find records that do not have related records in SQL

I have 2 tables (Orders, OrderItems) that are related based on a column OrderID. I need to find all Orders that do not have any OrderItems.
We use JOIN to find related data. To find data without any related data, we can use an anti-join.
The following joins the tables, then selects those without any order items. This tends to be more efficient that a WHERE id NOT IN (...) style query.
select *
from
Orders O
left outer join OrderItems I
on I.OrderId = O.Id
where
I.Id is null
Select * From Orders Where OrderID not in (Select Distinct OrderID From OrderItems)
try with LEFT EXCEPTION JOIN
select *
from Orders
LEFT EXCEPTION JOIN OrderItems ON ...

What is the most efficient way to write a select statement with a "not in" subquery?

What is the most efficient way to write a select statement similar to the below.
SELECT *
FROM Orders
WHERE Orders.Order_ID not in (Select Order_ID FROM HeldOrders)
The gist is you want the records from one table when the item is not in another table.
For starters, a link to an old article in my blog on how NOT IN predicate works in SQL Server (and in other systems too):
Counting missing rows: SQL Server
You can rewrite it as follows:
SELECT *
FROM Orders o
WHERE NOT EXISTS
(
SELECT NULL
FROM HeldOrders ho
WHERE ho.OrderID = o.OrderID
)
, however, most databases will treat these queries the same.
Both these queries will use some kind of an ANTI JOIN.
This is useful for SQL Server if you want to check two or more columns, since SQL Server does not support this syntax:
SELECT *
FROM Orders o
WHERE (col1, col2) NOT IN
(
SELECT col1, col2
FROM HeldOrders ho
)
Note, however, that NOT IN may be tricky due to the way it treats NULL values.
If Held.Orders is nullable, no records are found and the subquery returns but a single NULL, the whole query will return nothing (both IN and NOT IN will evaluate to NULL in this case).
Consider these data:
Orders:
OrderID
---
1
HeldOrders:
OrderID
---
2
NULL
This query:
SELECT *
FROM Orders o
WHERE OrderID NOT IN
(
SELECT OrderID
FROM HeldOrders ho
)
will return nothing, which is probably not what you'd expect.
However, this one:
SELECT *
FROM Orders o
WHERE NOT EXISTS
(
SELECT NULL
FROM HeldOrders ho
WHERE ho.OrderID = o.OrderID
)
will return the row with OrderID = 1.
Note that LEFT JOIN solutions proposed by others is far from being a most efficient solution.
This query:
SELECT *
FROM Orders o
LEFT JOIN
HeldOrders ho
ON ho.OrderID = o.OrderID
WHERE ho.OrderID IS NULL
will use a filter condition that will need to evaluate and filter out all matching rows which can be numerius
An ANTI JOIN method used by both IN and EXISTS will just need to make sure that a record does not exists once per each row in Orders, so it will eliminate all possible duplicates first:
NESTED LOOPS ANTI JOIN and MERGE ANTI JOIN will just skip the duplicates when evaluating HeldOrders.
A HASH ANTI JOIN will eliminate duplicates when building the hash table.
"Most efficient" is going to be different depending on tables sizes, indexes, and so on. In other words it's going to differ depending on the specific case you're using.
There are three ways I commonly use to accomplish what you want, depending on the situation.
1. Your example works fine if Orders.order_id is indexed, and HeldOrders is fairly small.
2. Another method is the "correlated subquery" which is a slight variation of what you have...
SELECT *
FROM Orders o
WHERE Orders.Order_ID not in (Select Order_ID
FROM HeldOrders h
where h.order_id = o.order_id)
Note the addition of the where clause. This tends to work better when HeldOrders has a large number of rows. Order_ID needs to be indexed in both tables.
3. Another method I use sometimes is left outer join...
SELECT *
FROM Orders o
left outer join HeldOrders h on h.order_id = o.order_id
where h.order_id is null
When using the left outer join, h.order_id will have a value in it matching o.order_id when there is a matching row. If there isn't a matching row, h.order_id will be NULL. By checking for the NULL values in the where clause you can filter on everything that doesn't have a match.
Each of these variations can work more or less efficiently in various scenarios.
You can use a LEFT OUTER JOIN and check for NULL on the right table.
SELECT O1.*
FROM Orders O1
LEFT OUTER JOIN HeldOrders O2
ON O1.Order_ID = O2.Order_Id
WHERE O2.Order_Id IS NULL
I'm not sure what is the most efficient, but other options are:
1. Use EXISTS
SELECT *
FROM ORDERS O
WHERE NOT EXISTS (SELECT 1
FROM HeldOrders HO
WHERE O.Order_ID = HO.OrderID)
2. Use EXCEPT
SELECT O.Order_ID
FROM ORDERS O
EXCEPT
SELECT HO.Order_ID
FROM HeldOrders
Try
SELECT *
FROM Orders
LEFT JOIN HeldOrders
ON HeldOrders.Order_ID = Orders.Order_ID
WHERE HeldOrders.Order_ID IS NULL