Selecting Orders with multiple line items but not when one or more line items contains a defined list - sql

I am having trouble with a SQL Server query. I have a couple of tables involved [Order] (I know, not named well) and [Order Entry].
Order Entry is basically a "Line Item" on an Order (so there are one or more per Order). There are various columns in Order Entry, one of which is ItemID (there is only one ItemID per Order Entry). I want a query that returns all rows (Orders) that don't contain one or more Order Entry's with a list of ItemID's defined in a list.
Here is what I have so far:
SELECT DISTINCT
oe.OrderID, StoreID
FROM
OrderEntry oe
INNER JOIN
[order] o ON o.ID = oe.OrderID
AND o.StoreID = oe.StoreID
AND oe.ItemID NOT IN (60, 856, 857, 858, 902, 59, 240, 57, 217, 853, 855, 854, 41)
What I want to do seem similar to this (below) but I can't figure it out:
SELECT all orders with more than one item and check all items status
Help please! (much appreciated)

If I'm understanding you correctly, you want something like this. Starting your query with the Orders table and using a left join will ensure that you get the orders you're looking for. By left joining with a match on ItemID, you can then check for nulls in your where statement to find the orders that don't have those line items.
select distinct o.OrderID, o.StoreID
from Orders o
left join OrderEntry oe on oe.OrderID = o.ID and oe.StoreID = o.StoreID
and oe.ItemID in (60,856,857,858,902,59,240,57,217,853,855,854,41)
where oe.OrderID is null
So to break this down a bit:
"select distinct... from Orders" means "get me everything from orders"
"left join OrderEntry on..." means "get me all OrderEntry records that meet this criteria; if no records meet the criteria, nulls are OK"
"where oe.OrderID is null" means "I only want to see the items in Orders that had no match in the left join"
If we had used an inner join instead, we'd have lost that "nulls are OK" part, so the where clause wouldn't work.

SELECT DISTINCT oe.OrderID, StoreID
FROM OrderEntry oe
WHERE NOT EXISTS (SELECT *
FROM [order] o
WHERE o.ID = oe.OrderID AND
o.StoreID = oe.StoreID AND
oe.ItemID IN ( 60,856,857,858,902,59,240,57,217,853,855,854,41 ))
Is this what you were after?

Try this:
select OrderId, StoreId
from Order O
where o.orderId not in (select OrderId from
OrderEntry d where d.ItemId IN (10,6,7,5) )
Regards

SELECT oe.OrderID, StoreID
FROM OrderEntry oe
INNER JOIN [order] o
ON o.ID = oe.OrderID
AND o.StoreID = oe.StoreID
GROUP BY oe.OrderID, StoreID
AND SUM (CASE WHEN oe.ItemID NOT IN ( 60,856,857,858,902,59,240,57,217,853,855,854,41 )
THEN 1
ELSE 0
END) = 0

I was able to take a few pieces from several of you (Miguel Guzman - you provided the spark I needed to get this) and got it working. Here is my final Query:
SELECT o.ID, o.StoreID
FROM [Order] o
JOIN PSD_ServiceTicket st
ON o.ID = st.WorkOrderID
AND o.StoreID = st.StoreID
WHERE o.StoreID = 101
AND o.Time >= '10/1/2013'
AND o.Time <= '10/18/2013'
AND o.ID NOT IN (SELECT OrderID
FROM OrderEntry oe
INNER JOIN [order] o
ON o.ID = oe.OrderID
AND o.StoreID = oe.StoreID
WHERE oe.StoreID = 101
AND o.Time >= '10/1/2013'
AND o.Time <= '10/18/2013'
AND oe.ItemID IN ( 60,856,857,858,902,59,240,57,217,853,855,854,41 )
)
AND (st.ServiceTypeID = 1 OR st.ServiceTypeID = 4 )
Thanks to everyone!

Related

SQL - Tracking Monthly Sales

I am writing a query to summarize sales by month. My problem is that my query only returns records for months with sales. For example, I am looking over a 15 month range. But for one specific part in the example below only 3 of the 15 months had sales.
I'm hoping to have 15 records show up and the other ones have 0's for sales. The reason I am hoping for the additional records is I want to take the standard deviation of this, and dropping records impacts that calculation.
Sample Code:
SELECT I.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(*) as TotalDemand
FROM OrderDetails OD
INNER JOIN Orders O on O.Id = OD.OrderId
INNER JOIN Items I on I.Id = OD.ItemId
WHERE
O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
AND I.PartNumber = '5144831-2'
GROUP BY I.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
Sample Current Output:
Part # | Year | Month | Demand
5144831-2 2017 1 1
5144831-2 2017 2 3
5144831-2 2016 3 1
Desired Output:
I would want an additional row such as:
5144831-2 2016 11 0
To show there were no sales in Nov 2016.
I do have a temp table #_date_array2 with the possible months/years, I think I need help incorporating a LEFT JOIN.
If you want to use left join, you would not be able to use it directly with the inner join. You can do the inner join inside the parenthesis and then do the left join outside to avoid messing with the results of left join. Try this:
SELECT Z.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(Z.OrderId) as TotalDemand
FROM Orders O
LEFT JOIN
(
SELECT OrderId, PartNumber
FROM
OrderDetails OD
INNER JOIN Items I ON I.Id = OD.ItemId
AND I.PartNumber = '5144831-2'
) Z
ON O.Id = Z.OrderId
AND O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
GROUP BY Z.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
To get a count of 0 for months with no order, avoid using count(*) and use count(OrderId) as given above.
Note - You will have to make sure the Orders table has all months and years available i.e. if there is no CreateDate value of, say, November 2016 in the Orders table(left table in the join), the output will also not produce this month's entry.
Edit:
Can you try this:
SELECT Z.PartNumber as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
COUNT(O.OrderId) as TotalDemand
FROM Orders O
RIGHT JOIN
(
SELECT OrderId, PartNumber
FROM
OrderDetails OD
INNER JOIN Items I ON I.Id = OD.ItemId
AND I.PartNumber = '5144831-2'
) Z
ON O.Id = Z.OrderId
AND O.CreateDate >= '1-1-2016'
AND O.CreateDate <= '3-31-2017'
GROUP BY Z.PartNumber, YEAR(O.CreateDate) , MONTH(O.CreateDate);
Assuming you have sales of something in every month, the simplest solution is to switch to conditional aggregation:
SELECT '5144831-2' as PartNumber,
YEAR(O.CreateDate) as CreateDateYear,
MONTH(O.CreateDate) as CreateDateMonth,
SUM(CASE WHEN I.PartNumber = '5144831-2' THEN 1 ELSE 0 END) as TotalDemand
FROM OrderDetails OD INNER JOIN
Orders O
ON O.Id = OD.OrderId INNER JOIN
Items I
ON I.Id = OD.ItemId
WHERE O.CreateDate >= '2016-01-01' AND
O.CreateDate <= '2017-03-31'
GROUP BY YEAR(O.CreateDate) , MONTH(O.CreateDate);
Note: This is something of a hack for solving the problem. More robust solutions involve generating the dates and using LEFT JOIN (or similar functionality). However, this is often the fastest way to get the result.
based on all of your comments on other posts etc it seems like you have a table that has a date range you want and you want to be able to run the analysis for multiple/all of the part numbers. So the main issue is you will need a cartesian join between your date table and partnumbers that were sold during that time in order to accomplish you "0"s when not sold.
;WITH cteMaxMinDates AS (
SELECT
MinDate = MIN(DATEFROMPARTS(CreateDateYear,CreateDateMonth,1))
,MaxDate = MAX(DATEFROMPARTS(CreateDateYear,CreateDateMonth,1))
FROM
#_date_array2
)
;WITH cteOrderDetails AS (
SELECT
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
FROM
#_date_array2 d
INNER JOIN Orders o
ON d.CreateDateMonth = MONTH(o.CreateDate)
AND d.CreateDateYear = YEAR(o.CreateDate)
INNER JOIN OrderDetails od
ON o.Id = od.OrderId
INNER JOIN Items i
ON od.ItemId = i.Id
AND i.PartNumber = '5144831-2'
)
, cteDistinctParts AS (
SELECT DISTINCT PartNumber
FROM
cteOrderDetails
)
SELECT
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
,COUNT(od.PartNumber) as TotalDemand
FROM
#_date_array2 d
CROSS JOIN cteDistinctParts p
LEFT JOIN cteOrderDetails od
ON d.CreateDateYear = od.CreateDateYear
AND d.CreateDateMonth = od.CreateDateMonth
AND p.PartNumber = od.PartNumber
GROUP BY
d.CreateDateYear
,d.CreateDateMonth
,I.PartNumber
To get ALL part numbers simply remove AND i.PartNumber = '5144831-2' join condition.

SELECT records with condition that filters the last chronilogical multiple and specific value of a column

I have a joined table that looks like that:
my goal is to filter all records that was created after the last 'active' value inside LineStatusName Column. (the yellow marked rows in the attached image).
here is what i have done so far, it is almost work as desired, but the problem is that the date that returns from the nested select steatment is not the date of the highest chronological datetime value of 'active' and if i try to do ORDER BY Changes.ChangeDateTim in the end of the nested select i get a syntax error:
Conversion failed when converting the nvarchar value '30-9000241' to data type int.
I will be grateful if someone can suggest a better solution to achieve that task or to improve my query.
SELECT Orders.OrderID,LineStatuses.LineStatusName,OrderTypes.OrderTypeName,
Changes.ChangeDateTime,Orders.ProjectNumber,Changes.Comments,Changes.ChangeTypeID
FROM Orders
INNER JOIN Changes ON Changes.ItemID = Orders.OrderID
INNER JOIN LineStatusSettings ON LineStatusSettings.LineStatusSettingID = Changes.NewValue
INNER JOIN LineStatuses ON LineStatuses.LineStatusID= LineStatusSettings.LineStatusID
INNER JOIN OrderTypes ON OrderTypes.OrderTypeID = LineStatusSettings.OrderTypeID
WHERE Orders.OrderID = 194 AND Orders.Deleted=0
AND
Changes.ChangeDateTime > (
SELECT TOP 1 Changes.ChangeDateTime
FROM Orders
INNER JOIN Changes ON Changes.ItemID = Orders.OrderID
INNER JOIN LineStatusSettings ON LineStatusSettings.LineStatusSettingID = Changes.NewValue
INNER JOIN LineStatuses ON LineStatuses.LineStatusID= LineStatusSettings.LineStatusID
INNER JOIN OrderTypes ON OrderTypes.OrderTypeID = LineStatusSettings.OrderTypeID
WHERE LineStatuses.LineStatusName = 'active'
) AND OrderTypes.OrderTypeName NOT IN ('disconnected line')
ORDER BY Changes.ChangeDateTime
Here is one method:
with jt as (
<your query here>
)
select jt.*
from jt
where jt.date > (select max(jt2.date)
from jt jt2
where jt2.orderid = jt.orderid and jt2.linestatusname = 'Active'
);

How to combine two queries?

I need to combine the two following queries...
SELECT Products.ProductId, Products.ProductDescription, SUM(Inventory.QuantityOutstanding) AS Inventory, Products.AverageCost AS Cost
FROM Products INNER JOIN
Inventory ON Products.Product = Inventory.Product
WHERE (Inventory > 0) AND (Products.ProductId LIKE 'CAS%') OR
(Products.ProductId LIKE 'ASY%')
GROUP BY Products.ProductId, Products.ProductDescription, Products.AverageCost
ORDER BY Products.ProductId
Which gives a table like...
ProductID ProductDescription Inventory Cost
-------------------------------------------------------
AB CD 0??? 0
UV XY 5 555
. . . .
. . . .
. . . .
And
SELECT Components.ProductId, SUM(SalesOrderItems.QuantityOutstanding) AS Schedule
FROM Structures INNER JOIN
Products AS Components ON Structures.Component = Components.Product INNER JOIN
Products AS Products ON Products.Product = Structures.Product AND
Structures.StructureVersion = Products.StructureVersion LEFT OUTER JOIN
SalesOrders INNER JOIN
SalesOrderItems ON SalesOrders.SalesOrder = SalesOrderItems.SalesOrder ON
Products.Product = SalesOrderItems.Product
WHERE ((Components.ProductId LIKE 'CAS%') OR (Components.ProductId LIKE 'ASY%')) AND (SalesOrderItems.DueDate < DATEADD(m, 3, GETDATE())) AND (SalesOrderItems.QuantityOutstanding > 0)
GROUP BY Components.ProductId, Products.ProductId
ORDER BY Components.ProductId
Which gives a table like...
ProductId Schedule
-------------------------
AB 360
UV 3
. .
. .
. .
I basically want to have a table that displays the ProductId, (Inventory - Schedule) AS XSStock, and Cost like this...
ProductId XSStock (>0 only) Cost
-------------------------------------------
UV 2 222
. . .
. . .
. . .
I thought this may be UNION or a subquery but I can't seem to make either work?
I have only begun to use SQL recently so if you could explain your response that would be great!
KATIA EDIT QUERY:
SELECT ProductId, ProductDescription, Inventory, Cost, SUM(Orders) AS Demand, (Inventory - SUM(Orders)) AS XSStock
FROM (SELECT X.ProductId, X.ProductDescription, X.Inventory, X.Cost, SUM(Y.Schedule) AS Orders
FROM (SELECT Products.ProductId, Products.ProductDescription, SUM(Inventory.QuantityOutstanding) AS Inventory, Products.AverageCost AS Cost
FROM Products INNER JOIN
Inventory ON Products.Product = Inventory.Product
WHERE (Products.ProductId LIKE 'CAS%') OR
(Products.ProductId LIKE 'ASY%')
GROUP BY Products.ProductId, Products.ProductDescription, Products.AverageCost) AS X,
(SELECT Components.ProductId, SUM(SalesOrderItems.QuantityOutstanding) AS Schedule
FROM Structures INNER JOIN
Products AS Components ON Structures.Component = Components.Product INNER JOIN
Products AS Products ON Products.Product = Structures.Product AND
Structures.StructureVersion = Products.StructureVersion LEFT OUTER JOIN
SalesOrders INNER JOIN
SalesOrderItems ON SalesOrders.SalesOrder = SalesOrderItems.SalesOrder ON
Products.Product = SalesOrderItems.Product
WHERE ((Components.ProductId LIKE 'CAS%') OR (Components.ProductId LIKE 'ASY%')) AND (SalesOrderItems.DueDate < DATEADD(m, 3, GETDATE())) AND (SalesOrderItems.QuantityOutstanding > 0)
GROUP BY Components.ProductId, Products.ProductId) AS Y
WHERE (Y.ProductId LIKE X.ProductId)
GROUP BY X.ProductId, X.ProductDescription, X.Inventory, X.Cost)
WHERE ((Inventory - SUM(Orders)) > 0)
GROUP BY ProductId, ProductDescription, Inventory, Cost
ORDER BY ProductId
I am now getting error message...
Incorrect syntax near the keyword 'WHERE'
This is in line 30, but I don't know why?
Also you can try to aliasing both query and Selecting them in the select query
SELECT x.a, y.b FROM (SELECT * from a) as x, (SELECT * FROM b) as y
To fix Inventory = 0 you just need to add brackets here:
`(Inventory > 0) AND *((Products.ProductId LIKE 'CAS%') OR (Products.ProductId LIKE 'ASY%'))`
About query: why don't you want to make selection from Products and join everything there? You have ProductsId in all tables. I tried to combine it into one, maybe it will help you:
SELECT Products.ProductId, Products.ProductDescription, SUM(Inventory.QuantityOutstanding) AS Inventory, Products.AverageCost AS Cost
SUM(SalesOrderItems.QuantityOutstanding) AS Schedule
from Products
inner join Inventory ON Products.Product = Inventory.Product
inner join structures AS Components ON Structures.Component = Products.Product INNER JOIN
inner join structurs as structures.Product = Product.Product and
Structures.StructureVersion = Products.StructureVersion
LEFT OUTER JOIN
SalesOrders INNER JOIN SalesOrderItems ON SalesOrders.SalesOrder = SalesOrderItems.SalesOrder ON
Products.Product = SalesOrderItems.Product
WHERE (Inventory > 0) AND ((Products.ProductId LIKE 'CAS%') OR (Products.ProductId LIKE 'ASY%'))
and ((Products.ProductId LIKE 'CAS%') OR (Products.ProductId LIKE 'ASY%'))
AND (SalesOrderItems.DueDate < DATEADD(m, 3, GETDATE())) AND (SalesOrderItems.QuantityOutstanding > 0)
GROUP BY Products.ProductId, Products.ProductDescription, Products.AverageCost
ORDER BY Products.ProductId

SQL: How to include SubQuery column result in WHERE clause?

How do I include the result of a sub query into a WHERE clause?
For example, I have the following statement (cut down because the original is large):
SELECT o.ID AS OrderSpecsID, o.CustomerID, o.EstimateNo, o.OrderYear,
(SELECT COUNT(*)
FROM OrderSpecs AS os
WHERE (o.OrderID = OrderID)) AS AmendmentCount
FROM OrderSpecs AS o LEFT OUTER JOIN
Orders ON o.OrderID = Orders.ID
WHERE (o.CustomerID = 30)
I want to include the AmendmentCount field in my WHERE clause like so:
WHERE (o.CustomerID = 30) AND (AmendmentCount > 0)
However, if I set the above I get the following error:
Invalid column name 'AmendmentCount'
How can I make the AmendmentCount field available to my WHERE clause?
Many thanks,
Rob
Use a CTE or subquery:
with cte as (
SELECT o.ID AS OrderSpecsID, o.CustomerID, o.EstimateNo, o.OrderYear,
(SELECT COUNT(*)
FROM OrderSpecs AS os
WHERE (o.OrderID = OrderID)
) AS AmendmentCount
FROM OrderSpecs AS o LEFT OUTER JOIN
Orders
ON o.OrderID = Orders.ID
)
select *
from cte
where (CustomerID = 30) and (AmendmentCount > 0);
If your case, though, the better way to write the query is probably to use window functions:
select os.*
from (select os.ID AS OrderSpecsID, os.CustomerID, os.EstimateNo, os.OrderYear,
count(o.OrderId) over (partition by os.OrderId) as AmendmentCount
from OrderSpecs os left outer join
Orders o
on os.OrderID = o.ID
) os
where (CustomerID = 30) and (AmendmentCount > 0);
I am a little unclear if the filter on CustomerId should be in the subquery or outer query in this case. One or the other should work for what you are doing.
you can put your select with a join in a query
FROM OrderSpecs
join (SELECT COUNT(*), OrderID
FROM OrderSpecs AS os
WHERE (o.OrderID = OrderID)) AS Amendment on Amendment.OrderID = OrderSpecs.OrderID

Forcing a sql query to return results

Here is my SQL:
-- Order information
SELECT ISNULL(p.PaymentAmt, 0) AS PaymentAmt, o.TaxAmt, o.PostAmount, o.OrderDate, o.PublicNotes, u.userid, PostAmount + TaxAmt AS Total, PostAmount - PaymentAmt AS Due
FROM Orders o
INNER JOIN Payment p ON p.OrderID = o.OrderID
INNER JOIN Users u ON o.EnteredBy = u.UserKey
Where o.OrderID = 5267
Right now for this specific OrderID it returns 0 rows just the column headers. Is there a way to force this to return 0's for the value and empty strings for the rest?
I think it should give you desired results :
WITH CTE1 AS (SELECT ISNULL(p.PaymentAmt, 0) AS PaymentAmt, o.TaxAmt, o.PostAmount,
o.OrderDate, o.PublicNotes, u.userid, PostAmount + TaxAmt AS Total, PostAmount -
PaymentAmt AS Due
FROM Orders o
INNER JOIN Payment p ON p.OrderID = o.OrderID
INNER JOIN Users u ON o.EnteredBy = u.UserKey
Where o.OrderID = 5267
AND o.EnteredBy = u.UserKey )
SELECT CTE1.*
FROM CTE1
UNION ALL
SELECT 0,0, NULL,NULL,NULL,NULL,NULL
WHERE NOT EXISTS (SELECT 1 FROM CTE1)
Another way:
SELECT ISNULL(p.PaymentAmt, 0) AS PaymentAmt, o.TaxAmt, o.PostAmount,
o.OrderDate, o.PublicNotes, u.userid,
PostAmount + TaxAmt AS Total, PostAmount - PaymentAmt AS Due
FROM Orders o
INNER JOIN Payment p ON p.OrderID = o.OrderID
AND o.OrderID = 5267 --- condition moved here
INNER JOIN Users u ON o.EnteredBy = u.UserKey
RIGHT JOIN (SELECT 1 dummy) trick ON 1=1 --- this line was added
--- AND o.EnteredBy = u.UserKey --- this is redundant