SQL displaying both records found and not found in items orders - sql

My task is to display customers that have and have not ordered a specific item in the same results. My tables joined on CustomerNumber:
Customers Table:
CustomerNumber CustomerName
------------- ------------
1007 H&G Groceries
2548 Jims Restaurant
2005 Tangs Asian Foods
Orders Table:
CustomerNumber ItemNumber ItemDescripton NumberOrdered
-------------- ---------- -------------- -------------
1007 2055 Cheese 3
2548 8784 Canned Beans 6
2005 1199 Dozen Large Eggs 10
If I were to request the purchasing history of ItemNumber=2055
This is the way I would like to display the results. Now keep in mind all customers are in the Orders table for one item at least once
CustomerName ItemNumber ItemDescription NumberOrdered
------------ ---------- --------------- -------------
H&G Groceries 2055 Cheese 3
Jims Restaurant 0
Tangs Asian Food 0
Actually NumberOrder could just be blank and not necessary have a 0
This is what I have tried.
Select c.CustomerName,
o.ItemNumber,
o.ItemDescription,
o.NumberOrdered
From Customers C
Left Join Orders o ON c.CustomerNumber = o.CustomerNumber
Where o.OrderNumber = 2055;
This only returns the one record for H&G Groceries.

Where o.OrderNumber = 2055
or o.OrderNumber is null
You have to account for nulls in the WHERE clause when you're working with outer joins.
Alternately, you could put the condition in the JOIN condition:
Select c.CustomerName,
o.ItemNumber,
o.ItemDescription,
o.NumberOrdered
From Customers C
Left Join Orders o
On c.CustomerNumber = o.CustomerNumber
And o.OrderNumber = 2055;

you should use a left outer join. i.e.
Select * from CustomersTable
LEFT JOIN OrdersTable on CustomerTable.CustomerNumber = OrdersTable.CustomerNumber

Related

How to select rows in parent table based on a value in child table

Consider the following tables, what would be an efficient query to return 1 row for each order that has at least 1 child row with a specific warehouse code? I am using SQL Server 2016.
Table: Orders
OrderNo OrderDt Status Type
------- ---------- ------ --------
200123 11/20/2020 NEW SHIPPING
200124 11/21/2020 NEW IN-STORE
200125 11/21/2020 NEW SHIPPING
Table: OrderItems
OrderNo ItemCode Warehouse
------- -------- ---------
200123 Item1 10
200124 Item1 10
200124 Item2 20
200125 Item2 20
If I query for Warehouse 10:
OrderNo OrderDt Status Type
------- ---------- ------ --------
200123 11/20/2020 NEW SHIPPING
200124 11/21/2020 NEW IN-STORE
If I query for Warehouse 20:
OrderNo OrderDt Status Type
------- ---------- ------ --------
200124 11/21/2020 NEW IN-STORE
200125 11/21/2020 NEW SHIPPING
Assuming that when you say "When I query for Warehouse X" you are running individual queries for a single warehouse, just a simple INNER JOIN and a WHERE clause should do it:
SELECT DISTINCT o.OrderNo, o.OrderDt, o.Status, o.Type
FROM Orders o
INNER JOIN OrderItems oi ON o.OrderNo = oi.OrderNo
WHERE Warehouse = 10;
SELECT DISTINCT o.OrderNo, o.OrderDt, o.Status, o.Type
FROM Orders o
INNER JOIN OrderItems oi ON o.OrderNo = oi.OrderNo
WHERE Warehouse = 20;
If you want to have everything in one result set add ItemCode and Warehouse to the query and leave out the WHERE clause.
SELECT
o.OrderNo,
o.OrderDt,
o.Status,
o.Type,
oi.ItemCode,
oi.Warehouse
FROM Orders o
INNER JOIN OrderItems oi ON o.OrderNo = oi.OrderNo;
You can use exists:
select o.*
from orders o
where exists (
select 1
from orderitems oi
where oi.orderno = o.orderno and oi.warehouse = 10
)
For performance with this query, consider an index on orderitems(orderno, warehouse).
You can use the below query we filter out the warehouse prior joining the tables:
select distinct o.orderNo, o.OrderDt, o.Status, o.Type from orders o, (select orderNo, Warehouse from orderitems
where warehouse = ##) o1
on o1.orderNo = o.orderNo

Fairly basic query (maybe needs subquery and or outer join)?

I am not sure why my brain can't process this right now but it seems like this would be an easy query to write.
I have 2 tables:
- Orders
(pk)orderID
customername
orderDate
- OrderDetails
(pk)detailsID
orderID
productName
So when a new order is placed, one row gets added to Orders with customer info and in the orderDetails table a row gets added for each product ordered.
So picture these 2 layouts:
ORDERS
orderID customerName orderDate
1 John Smith 2/21/2020
2 Bill Adams 2/21/2020
3 Susan Conrad 2/21/2020
4 Amy Fetcher 2/21/2020
ORDERDETAILS
detailsID orderID productName
1 1 pillow
2 1 mattress
3 2 pillow
4 3 sheets
5 3 headboard
6 3 mattress
7 4 pillow
8 4 framed art
9 4 decals
10 4 plant stand
In my query, I want to select all from orders where the date = today. I am joining the ORDERDETAILS table because I want to know anytime I sold a pillow in my results.
I only want 1 row per order to be displayed in the results so in orderID 1 for example, they ordered a pillow and mattress. In the results under productName for that row, I want it to display pillow.
If someone didn't order a pillow, I still need the row to show for their order today and under productName I dont care what it shows if it's not a pillow. It can show nothing or one of the other product names they ordered.
I know this doesn't work:
SELECT ORDERS.*, ORDERDETAILS.*
FROM ORDERS INNER JOIN ORDERDETAILS ON ORDERS.orderID = ORDERDETAILS.orderID
WHERE productName = "pillow"
I am not sure if I need subqueries or some sort of outer left join maybe both?
Any help is much appreciated, thank you!
You can do it this way
select detailsID, a.orderID, productName, customerName, orderDate from
(
select detailsID, orderID, productName from ORDERDETAILS where productName = 'pillow'
union
select detailsID, orderID, productName from
(
select detailsID, orderID, productName, rank()over(partition by orderID order by detailsID) as _Rank from ORDERDETAILS
)a where _Rank = 1
and orderID not in (select orderID from ORDERDETAILS where productName = 'pillow')
) a
inner join Orders b on a.orderID = b.orderID
Output:
detailsID orderID productName customerName orderDate
1 1 pillow John Smith 2/21/2020
3 2 pillow Bill Adams 2/21/2020
4 3 sheets Susan Conrad 2/21/2020
7 4 pillow Amy Fetcher 2/21/2020
Since you're only interested in some order details, but all headers, you want a LEFT JOIN. And since you only want to filter the results from ORDERDETAILS, you'll want your filtering criteria in the JOIN, rather than in a WHERE clause that also applies to the ORDERS table.
SELECT ORDERS.*, ORDERDETAILS.*
FROM
ORDERS
LEFT JOIN ORDERDETAILS
ON ORDERS.orderID = ORDERDETAILS.orderID
AND productName = "pillow"
That should get you most of the way there. If there's a possibility of more than one detail record per order that includes pillow, then you'll want to use a sub-query against the details table to skinny it down further to just the information you're interested in. And at that point, I'd move the filter into the sub-query.

How to get all products from one order to the list

I have written a query that returns data, here is my code
SELECT o.Id, p.ProductName, po.Quantity, p.Price, DeliveryMethod,
OrderDate, TotalSum, u.UserName FROM [Order] o
JOIN ProductsOrders po ON o.Id = po.OrderId
JOIN Product p ON po.ProductId = p.Id
JOIN Users u ON o.UserId = u.Id
ORDER BY o.Id
And it returns next:
OrderId ProductName Quantity Price DeliveryMethod OrderDate TotalSum UserName
98 Bed3 1 3000 Express 2018-08-04 7015 Ivan
98 Bed4 1 4000 Express 2018-08-04 7015 Ivan
100 Bed2 1 2000 2018-08-05 2000 Ivan
101 Bed2 1 2000 2018-08-05 5000 Ivan
101 Bed3 1 3000 2018-08-05 5000 Ivan
102 Bed0 1 0 Standard 2018-09-04 1005 Ivan
We can notice that for each product - one record
But I want to group all products by one order and get something like that:
- orderId
- deliveryMethod
- userName
- product1 quantity price
- ...
- productN quantity price
- totalAmount
101
Courier
John Doe
Black bag 1 1500
Fat Bike 1 5000
Thermos bottle 5 200
Total amount 7500
How can I modify the query?
The proper way to do this, IMO, would be to select 2 datasets. First dataset containing the parent Order data, and the second containing the individual ProductsOrders (product line items) for the Orders in the first dataset.
-- Select Orders
SELECT
[o].[Id] AS [OrderId]
,[o].[DeliveryMethod]
,[o].[OrderDate]
,[u].[UserName]
FROM [Order] [o]
INNER JOIN [Users] [u] ON [o].[UserId] = [u].[Id]
WHERE
[o].[Id] IN(SomeRangeOfOrderIds) -- (Or however you'd like to filter the results)
-- Select Products (product line items)
SELECT
[p].[ProductName]
,[po].[Quantity]
,[p].[Price]
FROM [ProductsOrders] [po]
INNER JOIN [Product] [p] ON [po].[ProductId] = [p].[Id]
WHERE
[po].[OrderId] IN(SameRangeOfOrderIdsAsTheQueryAbove)
...then take those results and display them however you'd like. Will this be appearing in a report? Or on a web page?
I have just implemented this issue in LINQ. Should have noticed that there is many-to-many relationship
var orders = db.Orders.Select(o => new
{
o.Id,
o.DeliveryMethod,
o.OrderDate,
o.UserId,
o.TotalSum,
Products = o.ProductsOrders.Select(p => new { p.Product.ProductName, p.Quantity, p.Product.Price}).ToList()
}).ToList();

SQL - Return group of records from different tables not including specific row

I am new at programming and SQL, so sorry if I do not include enough info.
[I have these 2 tables that are linked by an OrderID. Table1 includes OrderIDs and customer information such as FirstName, LastName, and Address. Table2 includes OrderIDs and order details such as ItemName, Price, and Quantity.
Each OrderID in Table2 might have multiple ItemName entries with the same OrderID.]1
CustInfo
OrderID FirstName LastName Address
1 Bob Pratt 123
2 Jane Doe 456
3 John Smith 789
4 Barbara Walters 147
Orders
OrderID ItemName Price Quantity
1 Milk 4.00 1
1 Eggs 5.00 2
2 Cheese 5.00 1
2 Bread 5.00 1
3 Milk 4.00 2
4 Yogurt 5.00 2
I'm trying to make a query that will send back a list of every Order, listing the OrderID and ItemName among other info, as long as the order doesn't include a specific type of item (which would be in ItemName). So if an OrderID contains 2 ItemName, one of which is the one I do not want, the entire order (OrderID) should not show up in my result.
For example, based off the img included, if I wanted to show all orders as long as they do not have Milk as an ItemName, the result should only show OrderID 2 and 4.
2 Cheese 5.00 1
2 Bread 5.00 1
4 Yogurt 5.00 2
This is what I have tried but this would return OrderIDs even though Milk is technically part of that OrderID.
SELECT OrderID, FirstName, LastName, ItemName, Price, Quantity
FROM CustInfo
JOIN Orders
ON CustInfo.OrderID = Orders.OrderID
WHERE ItemName != 'Milk'
Can you help?
select o.OrderID, o.ItemName, c.FirstName, c.LastName -- include other fields if needed
from Orders o
left join CustInfo c on o.OrderID = c.OrderID
where o.OrderID not in (
select OrderID from Orders where ItemName = 'Milk'
)
If you want the whole order to not show up, rather than just individual rows, you can use WHERE NOT EXISTS:
SELECT o.OrderID, d.ItemName, d.Price, d.Quantity
FROM Orders o
JOIN OrderDetails d ON o.OrderID=d.OrderID
WHERE NOT EXISTS
(
SELECT * FROM OrderDetails d2
WHERE o.OrderID=d2.OrderID
AND d.ItemName = 'Milk'
)
SELECT T1.OrderID, T1.FirstName, T1.LastName, T1.Address, T2.OrderID, T2.ItemName, T2.Price, T2.Quantity
FROM Table2 as T2
LEFT JOIN Table1 as T1 ON T1.OrderID = T2.OrderID
WHERE T2.OrderID <> (SELECT OrderID FROM Table2 WHERE ItemName='Milk');

Sum between 3 linked tables SQL Server

I am trying to pull a sum from linked table.
Order:
OrderID LocationID OrderDate
100 1 1/1/2000
200 2 1/2/2000
OrderedItems:
ID OrderID ItemID
1 100 1
2 200 2
3 200 2
4 100 3
OrderItem:
ItemID ItemName Cost
1 Mobile1 100.00
2 Mobile2 200.00
3 Mobile3 300.00
The Order table is effectively a group of OrderedItems. Each row in OrderedItems links back to OrderItem via the ItemID.
I am trying to add a column to the below query for order total.
Order Number Location Date Ordered Order Total
-------------------------------------------------------
100 Sydney 1/1/2000 400
200 Brisbane 1/2/2000 400
The current query I have is:
SELECT
Order.OrderID AS [Order Number],
OL.Name AS [Location],
Order.OrderDate AS [Date Ordered]
FROM
Order
LEFT JOIN
Office_Locations AS OL ON OL.id = Order.LocationID
I have tried to follow this link however I am needing to link through 3 tables for the values to add.
Any hep would be great!
You're not finding a sum from three tables. You're finding a sum from one table: the OrderItem table. The only trick is getting the JOIN and GROUP BY expressions done correctly to make that column available.
SELECT o.OrderID As [Order Number], l.Name As Location
, o.OrderDate As [Date Ordered], SUM(i.Cost) As [Order Total]
FROM [Order] o
INNER JOIN Office_Locations l on l.id = o.LocationID
INNER JOIN OrderedItems oi on oi.OrderID = o.OrderID
INNER JOIN OrderItem i ON i.ItemID = oi.ItemID
GROUP BY o.OrderID, l.Name, o.OrderDate
SQLFIDDLE
You need to use SUM to get the total Cost:
SQL Fiddle
SELECT
[Order Number] = o.OrderID,
Location = ol.Name,
[Date Ordered] = o.OrderDate,
[Order Total] = SUM(i.Cost)
FROM [Order] o
INNER JOIN OrderedItems oi
ON oi.OrderId = o.OrderId
INNER JOIN OrderItem i
ON i.ItemID = oi.ItemID
LEFT JOIN Office_Locations ol
ON ol.id = o.LocationID
GROUP BY
o.OrderID, o.OrderDate, ol.Name
As commented by Joel Coehoorn, it's more normal to have a quantity field in the OrderedItems table than to repeat them. Following his advise, your OrderedItems table should be:
ID OrderID ItemID Quantity
1 100 1 1
2 200 2 2
3 100 3 1
Additional Notes:
Use meaningful aliases to improve readability.
Refrain from using reserved words as your object names i.e. Order could be renamed as OrderHeader.