Find all related records - sql

I have an Order table that has a LinkedOrderID field.
I would like to build a query that finds all linked orders and returns them in the result set.
select OrderID,LinkOrderID from [Order] where LinkOrderID is not null
OrderID LinkOrderID
787016 787037
787037 787786
787786 871702
I would like a stored proc that returns the following:
OrderID InheritanceOrder
787016 1
787037 2
787786 3
871702 4
I would also like to make sure I don't have an infinite loop

DECLARE #Order TABLE (OrderID INT NOT NULL, LinkOrderID INT NOT NULL)
INSERT
INTO #Order
VALUES (787016, 787037)
INSERT
INTO #Order
VALUES (787037, 787786)
INSERT
INTO #Order
VALUES (787786, 871702)
/*
INSERT
INTO #Order
VALUES (871702, 787016)
*/
;WITH q (OrderId, LinkOrderId, InheritanceOrder, FirstItem) AS
(
SELECT OrderID, LinkOrderId, 1, OrderID
FROM #Order
WHERE OrderID = 787786
UNION ALL
SELECT o.OrderId, o.LinkOrderId, q.InheritanceOrder + 1, q.FirstItem
FROM q
JOIN #Order o
ON o.OrderID = q.LinkOrderId
WHERE o.OrderID <> q.FirstItem
UNION ALL
SELECT LinkOrderId, NULL, q.InheritanceOrder + 1, q.FirstItem
FROM q
WHERE q.LinkOrderID NOT IN
(
SELECT OrderID
FROM #Order
)
)
SELECT OrderID, InheritanceOrder
FROM q
ORDER BY
InheritanceOrder
This assumes that both OrderID and LinkOrderID are unique (i. e. it's a linked list, not a tree).
Works with the last insert uncommented too (which makes a loop)

To check for an infinite loop, there are two checks:
First, make sure that you start with an _id that ever appears in the LinkOrderID
select o1.OrderID from Order o1
left outer join Order o2 on o1.OrderId = o2.LinkOrderID
where o2.LinkOrderID is null;
This will give you a list that are the starts of your linked lists.
Then, make sure none of your _ids ever show up more than once.
select * from {
select LinkOrderId, count(*) as cnt from Order
} where cnt > 1;
If these two conditions are true (you are starting from an order that never appears in the Linked list and you have no OrderIds that are linked multiple times) then you can't have a loop.

Sweet: i found the solution thanks to Quassnoi's awesome code. I tweaked it to first walk up to the oldest parent, and then walk down through all children. Thanks again!
-- =============================================
-- Description: Gets all LinkedOrders for OrderID.
-- It will first walk up and find oldest linked parent, and then next walk down recursively and find all children.
-- =============================================
alter PROCEDURE Order_Order_GetAllLinkedOrders
(
#StartOrderID int
)
AS
--Step#1: find oldest parent
DECLARE #oldestParent int
;WITH vwFirstParent (OrderId) AS
(
SELECT OrderID
FROM [Order]
WHERE OrderID = #StartOrderID
UNION ALL
SELECT o.OrderId
FROM vwFirstParent
JOIN [Order] o
ON o.LinkOrderID = vwFirstParent.OrderId
)
select #oldestParent = OrderID from vwFirstParent
--Step#2: find all children, prevent recursion
;WITH q (OrderId, LinkOrderId, InheritanceOrder, FirstItem) AS
(
SELECT OrderID, LinkOrderId, 1, OrderID
FROM [Order]
WHERE OrderID = #oldestParent
UNION ALL
SELECT o.OrderId, o.LinkOrderId, q.InheritanceOrder + 1, q.FirstItem
FROM q
JOIN [Order] o
ON o.OrderID = q.LinkOrderId
WHERE o.OrderID <> q.FirstItem
UNION ALL
SELECT LinkOrderId, NULL, q.InheritanceOrder + 1, q.FirstItem
FROM q
WHERE q.LinkOrderID NOT IN
(
SELECT OrderID
FROM [Order]
)
)
SELECT OrderID,LinkOrderId, InheritanceOrder
FROM q

Related

Get Orders status that can be delivered with reducing quantity from stock without curson or While loop

Just like any retail business we have an Orders table and an Inventory table. What I am trying to do is to check Orders for which we have enough stock available to dispatch. A few things I need to consider:
If all the items in an order are available only then consider this order to be “Deliverable”
Check Order's deliverable status in the order of OrderID (int value) .i.e OrderID = 1 then 2 and so on.
Before checking for deliverability of next order, reduce the available of stock for the next order (not update the Inventory table but just take into account the stock quantity that has been already consumed by previous orders).
If we do not have enough stock for 1 or more items in the order, completely ignore the order and do not reduce available stock quantity for the next order to be checked.
In the following example:
Order = 100 is fully deliverable because we have enough stock for all products.
Order = 200 is not fully deliverable because PID 2 requires Qty 5 but we only have 3 left after 2 being consumed by the Order 100
Finally, Order = 300 is also fully deliverable because we have enough stock for all products.
Test data
INSERT INTO #Inventory (PID, Qty)
VALUES (1 , 10)
, (2 , 5)
, (3 , 2)
INSERT INTO #Order (OrderID, PID, Qty)
VALUES (100 , 1 , 2) --\
, (100 , 2 , 2) ----> This order is fully available
, (100 , 3 , 1) --/
, (200 , 1 , 2) --\
, (200 , 2 , 5) ----> This order is not fully available
, (200 , 3 , 1) --/ because of PID 2 only 3 QTY left
, (300 , 1 , 2) --\
, (300 , 2 , 2) ----> This order is fully available
, (300 , 3 , 1); --/
Expected output:
OrderID Status
------------------------
100 Deliverable
200 NOT Deliverable
300 Deliverable
My attempt: I know that it is far from the actual solution but I still wanted to share what I have been trying :)
WITH OrderCTE AS
(
SELECT
DENSE_RANK() OVER (ORDER BY OrderID) AS OrderRN
, OrderID
, PID
, Qty
FROM
#Order
)
, CTE AS
(
SELECT
o.OrderID
, o.PID
, o.Qty
, i.Qty - o.Qty AS QtyAvailable
, o.OrderRN AS OrderRN
FROM
OrderCTE o
INNER JOIN
#Inventory i ON i.PID = o.PID
WHERE
o.OrderID IN (SELECT TOP 1 o.OrderID
FROM #Order o
WHERE NOT EXISTS (SELECT 1 FROM #Inventory i
WHERE i.PID = o.PID AND i.Qty < o.Qty)
ORDER BY o.OrderID)
UNION ALL
SELECT
o.OrderID
, o.PID
, o.Qty
, o.Qty - c.QtyAvailable
, c.OrderRN + 1
FROM
OrderCTE o
INNER JOIN
#Inventory i ON i.PID = o.PID
INNER JOIN
CTE c ON c.OrderRN + 1 = o.OrderRN AND c.PID = o.PID
WHERE
o.Qty <= c.QtyAvailable
)
SELECT *
FROM CTE
The method below doesn't produce correct results. When I put all pieces together I got:
+---------+--------------------+
| OrderID | OrderIsDeliverable |
+---------+--------------------+
| 100 | 1 |
| 200 | 0 |
| 300 | 0 |
+---------+--------------------+
Order=300 was marked as non deliverable, because my query processes all Products independently and this is not correct. The previous Order=200 hogged the quantity for PID=3, even though this Order=200 was not deliverable overall (based on Products other than PID=3) and it should not affect the following orders. But it did affect the following orders, which is not correct.
I don't see how to write a single query without explicit loop(s) here.
Alas.
You can simulate a loop using recursive CTE.
I will show you a query that does the core thing and leave the rest to you, because overall it becomes too long.
The main idea - you need a running total that resets when it reaches a threshold. There are many questions on this topic, I used this as a basis for my answer.
In the query below I'm looking only at a slice of your data, only at one specific PID = 2.
CTE_RN gives us row numbers to iterate upon. CTE_Recursive is the main loop which checks if the running total exceeds the limit. If it does, it discards the Qty from that Order and sets the OrderIsDeliverable flag.
Query
WITH
CTE_RN
AS
(
SELECT
O.OrderID
,O.PID
,O.Qty
,I.Qty AS LimitQty
,ROW_NUMBER() OVER (ORDER BY O.OrderID) AS rn
FROM
#Order AS O
INNER JOIN #Inventory AS I ON I.PID = O.PID
WHERE O.PID = 2 -- this would become a parameter
)
,CTE_Recursive
AS
(
SELECT
CTE_RN.OrderID
,CTE_RN.PID
,CTE_RN.Qty
,CTE_RN.LimitQty
,CTE_RN.rn
-- this would generate a simple running total
--,CTE_RN.Qty AS SumQty
-- the very first order may exceed the limit
,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
THEN 0
ELSE CTE_RN.Qty
END AS SumQty
,CASE WHEN CTE_RN.Qty > CTE_RN.LimitQty
THEN 0
ELSE 1
END AS OrderIsDeliverable
FROM
CTE_RN
WHERE
CTE_RN.rn = 1
UNION ALL
SELECT
CTE_RN.OrderID
,CTE_RN.PID
,CTE_RN.Qty
,CTE_RN.LimitQty
,CTE_RN.rn
-- this would generate a simple running total
--,CTE_RN.Qty + CTE_Recursive.SumQty AS SumQty
-- check if running total exceeds the limit
,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
THEN CTE_Recursive.SumQty -- don't increase the running total
ELSE CTE_RN.Qty + CTE_Recursive.SumQty
END AS SumQty
,CASE WHEN CTE_RN.Qty + CTE_Recursive.SumQty > CTE_RN.LimitQty
THEN 0
ELSE 1
END AS OrderIsDeliverable
FROM
CTE_RN
INNER JOIN CTE_Recursive ON CTE_Recursive.rn + 1 = CTE_RN.rn
)
SELECT * FROM CTE_Recursive
;
Result
+---------+-----+-----+----------+----+--------+--------------------+
| OrderID | PID | Qty | LimitQty | rn | SumQty | OrderIsDeliverable |
+---------+-----+-----+----------+----+--------+--------------------+
| 100 | 2 | 2 | 5 | 1 | 2 | 1 |
| 200 | 2 | 5 | 5 | 2 | 2 | 0 |
| 300 | 2 | 2 | 5 | 3 | 4 | 1 |
+---------+-----+-----+----------+----+--------+--------------------+
Now you need to run this query for each PID. I would wrap this query into a table-valued function with parameter and pass PID as parameter. Maybe you can do it without a function as well. Obviously, to create a function you can't have table variables, you need actual tables to reference in your function, so adjust the code accordingly.
Then call it something like this:
SELECT
...
FROM
#Inventory AS I
CROSS APPLY dbo.MyFunc(I.PID) AS A
This would return the same number of rows as in the #Order table. Then you need to group this by OrderID and look at the OrderIsDeliverable flag. If this flag is 0 at least once for an Order, this Order is not deliverable.
Something like this:
SELECT
A.OrderID
,MIN(OrderIsDeliverable) AS OrderIsDeliverable
FROM
#Inventory AS I
CROSS APPLY dbo.MyFunc(I.PID) AS A
GROUP BY
A.OrderID
;
Ideally, you should try various approaches (cursor, recursive CTE, etc.), make sure that you have appropriate indexes, measure their performance on your real data and hardware and decide which one to use.
EDIT:
Because I'm ambitious, I've now also found a solution with CTE. Please give my feedback if you find any bug or incorrect results. My old cursor solution is below.
New code with CTE:
DECLARE #OrderQty TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
CountOfOrder INT NOT NULL,
StockQty INT NOT NULL,
Qty INT NOT NULL,
DeliverableOrderQty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #OrderQty
(OrderID, PID, CountOfOrder, StockQty, Qty, DeliverableOrderQty)
SELECT o.OrderID,
o.PID,
foo.CountOfOrder,
foo.StockQty,
o.Qty,
foo.StockQty / IIF(o.Qty = 0,1,o.Qty) AS DeliverableOrderQty
FROM #Order AS o
INNER JOIN (SELECT o.PID,
COUNT(DISTINCT o.OrderID) AS CountOfOrder,
i.Qty AS StockQty,
SUM(o.Qty) AS TotalOrderOty
FROM #Order AS o
INNER JOIN #Inventory AS i ON o.PID = i.PID
GROUP BY o.PID,
i.Qty) AS foo ON o.PID = foo.PID
DECLARE #OrdersDeliverableQty TABLE
(OrderID INT NOT NULL PRIMARY KEY,
CountOfOrder INT NOT NULL,
DeliverableQty INT NOT NULL)
INSERT INTO #OrdersDeliverableQty
(OrderID, CountOfOrder, DeliverableQty)
SELECT oq.OrderID,
oq.CountOfOrder,
MIN(oq.DeliverableOrderQty) AS DeliverableQty
FROM #OrderQty AS oq
GROUP BY oq.OrderID,
oq.CountOfOrder
DECLARE #AllOrders TABLE
(OrderID INT NOT NULL PRIMARY KEY)
INSERT INTO #AllOrders
(OrderID)
SELECT o.OrderID
FROM #Order AS o
GROUP BY o.OrderID
DECLARE #DeliverableOrder TABLE
(OrderID INT NOT NULL PRIMARY KEY);
WITH CTE_1(RankID, OrderID, PID, StockQty, Qty)
AS (SELECT RANK() OVER(
ORDER BY oq.PID,
oq.DeliverableOrderQty DESC,
oq.Qty,
oq.OrderID) AS RankID,
oq.OrderID,
oq.PID,
oq.StockQty,
oq.Qty
FROM #OrderQty AS oq
INNER JOIN #OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty),
CTE_2(MinRankID, MaxRankID)
AS (SELECT MIN(c.RankID) AS MinRankID,
MAX(c.RankID) AS MaxRankID
FROM CTE_1 AS c),
CTE_3(NextRankID, MaxRankID, RankID, OrderID, PID, StockQty, RestQty, Qty)
AS (SELECT c2.MinRankID + 1 AS NextRankID,
c2.MaxRankID AS MaxRankID,
c.RankID,
c.OrderID,
c.PID,
c.StockQty,
c.StockQty - c.Qty AS RestQty,
c.Qty
FROM CTE_1 AS c
INNER JOIN CTE_2 AS c2 ON c.RankID = c2.MinRankID
UNION ALL
SELECT c3.NextRankID + 1 AS NextRankID,
c3.MaxRankID,
c3.NextRankID,
c1.OrderID,
c1.PID,
c1.StockQty,
CASE
WHEN c3.PID = C1.PID
THEN c3.RestQty
ELSE c1.StockQty
END - c1.Qty AS RestQty,
c1.Qty
FROM CTE_3 AS c3
INNER JOIN CTE_1 AS c1 ON c3.NextRankID = c1.RankID
WHERE c3.NextRankID <= c3.MaxRankID)
INSERT INTO #DeliverableOrder
(OrderID)
SELECT c.OrderID
FROM CTE_3 AS c
WHERE c.RestQty >= 0
SELECT ao.OrderID,
CASE
WHEN oo.OrderID IS NULL
THEN 'NOT Deliverable'
ELSE 'Deliverable'
END AS STATUS
FROM #AllOrders AS ao
LEFT JOIN #DeliverableOrder AS oo ON ao.OrderID = oo.OrderID
Test Data:
DECLARE #Inventory TABLE
(PID INT NOT NULL PRIMARY KEY,
Qty INT NOT NULL)
DECLARE #Order TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
Qty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #Inventory
(PID, Qty)
VALUES (1,10),
(2,6),
(3,5)
INSERT INTO #Order
(OrderID, PID, Qty)
VALUES (100,1,2), (100,2,2), (100,3,2),
(200,1,2), (200,2,5), (200,3,1),
(300,1,2), (300,2,2), (300,3,0),
(400,1,2), (400,2,1), (400,3,2),
(500,1,5), (500,2,5), (500,3,5),
(600,1,1), (600,2,1), (600,3,1),
(700,1,0), (700,2,1), (700,3,1)
Result:
OrderID Status
100 Deliverable
200 NOT Deliverable
300 Deliverable
400 NOT Deliverable
500 NOT Deliverable
600 Deliverable
700 Deliverable
If you need more information or explanations, leave a comment.
Old code with cursor:
DECLARE #OrderQty TABLE
(OrderID INT NOT NULL,
PID INT NOT NULL,
CountOfOrder INT NOT NULL,
StockQty INT NOT NULL,
Qty INT NOT NULL,
DeliverableOrderQty INT NOT NULL,
PRIMARY KEY CLUSTERED(OrderID,PID))
INSERT INTO #OrderQty
(OrderID, PID, CountOfOrder, StockQty, Qty, DeliverableOrderQty)
SELECT o.OrderID,
o.PID,
foo.CountOfOrder,
foo.StockQty,
o.Qty,
foo.StockQty / IIF(o.Qty = 0,1,o.Qty) AS DeliverableOrderQty
FROM #Order AS o
INNER JOIN (SELECT o.PID,
COUNT(DISTINCT o.OrderID) AS CountOfOrder,
i.Qty AS StockQty,
SUM(o.Qty) AS TotalOrderOty
FROM #Order AS o
INNER JOIN #Inventory AS i ON o.PID = i.PID
GROUP BY o.PID,
i.Qty) AS foo ON o.PID = foo.PID
DECLARE #OrdersDeliverableQty TABLE
(OrderID INT NOT NULL PRIMARY KEY,
CountOfOrder INT NOT NULL,
DeliverableQty INT NOT NULL)
INSERT INTO #OrdersDeliverableQty
(OrderID, CountOfOrder, DeliverableQty)
SELECT oq.OrderID,
oq.CountOfOrder,
MIN(oq.DeliverableOrderQty) AS DeliverableQty
FROM #OrderQty AS oq
GROUP BY oq.OrderID,
oq.CountOfOrder
DECLARE #AllOrders TABLE
(OrderID INT NOT NULL PRIMARY KEY)
INSERT INTO #AllOrders
(OrderID)
SELECT o.OrderID
FROM #Order AS o
GROUP BY o.OrderID
DECLARE #DeliverableOrder TABLE
(OrderID INT NOT NULL PRIMARY KEY)
DECLARE #OrderID INT,
#PID INT,
#StockQty INT,
#Qty INT
DECLARE #LastPIDCursor INT
DECLARE #QtyRest INT
DECLARE order_qty_cursor CURSOR
FOR SELECT oq.OrderID,
oq.PID,
oq.StockQty,
oq.Qty
FROM #OrderQty AS oq
INNER JOIN #OrdersDeliverableQty AS ohmttoq ON oq.OrderID = ohmttoq.OrderID
AND oq.DeliverableOrderQty = ohmttoq.DeliverableQty
ORDER BY oq.PID,
oq.DeliverableOrderQty DESC,
oq.Qty
OPEN order_qty_cursor
FETCH NEXT FROM order_qty_cursor INTO #OrderID,
#PID,
#StockQty,
#Qty
WHILE ##Fetch_Status = 0
BEGIN
IF #LastPIDCursor IS NULL
OR #LastPIDCursor <> #PID
BEGIN
SET #QtyRest = #StockQty - #Qty
END
ELSE
BEGIN
SET #QtyRest = #QtyRest - #Qty
END
IF #QtyRest >= 0
AND NOT EXISTS (SELECT 1
FROM #DeliverableOrder
WHERE OrderID = #OrderID)
BEGIN
INSERT INTO #DeliverableOrder
(OrderID)
VALUES
(#OrderID)
END
SET #LastPIDCursor = #PID
FETCH NEXT FROM order_qty_cursor INTO #OrderID,
#PID,
#StockQty,
#Qty
END
CLOSE order_qty_cursor
DEALLOCATE order_qty_cursor
SELECT ao.OrderID,
CASE
WHEN oo.OrderID IS NULL
THEN 'NOT Deliverable'
ELSE 'Deliverable'
END AS STATUS
FROM #AllOrders AS ao
LEFT JOIN #DeliverableOrder AS oo ON ao.OrderID = oo.OrderID

how to get some records in first row in sql

I have q query like this:
Select WarehouseCode from [tbl_VW_Epicor_Warehse]
my output looks like this
WarehouseCode
Main
Mfg
SP
W01
W02
W03
W04
W05
But sometimes I want to get W04 as the first record, sometimes I want to get W01 as the first record .
How I can write a query to get some records in first row??
Any help is appreciated
you could try and select the row with the code you want to appear first by specifying a where condition to select that row alone then you can union all another select with all other rows that doesn't have this name
as follows
SELECT WarehouseCode FROM Table WHERE WarehouseCode ='W04'
UNION ALL
SELECT WarehouseCode FROM Table WHERE WarehouseCode <>'W04'
Use a parameter to choose the top row, which can be passed to your query as required, and sort by a column calculated on whether the value matches the parameter; something like the ORDER BY clause in the following:
DECLARE #Warehouses TABLE (Id INT NOT NULL, Label VARCHAR(3))
INSERT #Warehouses VALUES
(1,'W01')
,(2,'W02')
,(3,'W03')
DECLARE #TopRow VARCHAR(3) = 'W02'
SELECT *
FROM #Warehouses
ORDER BY CASE Label WHEN #TopRow THEN 1 ELSE 0 END DESC
May be you don't need to store this list in table? And you want something like this?
SELECT * FROM (VALUES ('WarehouseCode'),
('Main'),
('Mfg'),
('SP'),
('W01'),
('W02'),
('W03'),
('W04'),
('W05')) as v(s)
Here you can change order manually as you want.
As commented by #ankit bajpai
You are looking for Custom sorting that is achieve by CASE with ORDER BY statement
Whenever you want WAo4 on top you can use
ORDER BY Case When col = 'W04' THEN 1 ELSE 2 END
Example below:
Select col from
(
select 'Main' col union ALL
select 'Mfg' union ALL
select 'SP' union ALL
select 'W01' union ALL
select 'W02' union ALL
select 'W03' union ALL
select 'W04' union ALL
select 'W05'
) randomtable
ORDER BY Case When col = 'W04' THEN 1 ELSE 2 END
EDIT: AFTER MARKED AS ANSWER
IN support of #Maha Khairy because that IS MARKED AS ANSWER and the only answer which is DIFFRENT
rest all are pushing OP to use "ORDER by with case statements"
let`s use UNION ALL APPROCH
create table #testtable (somedata varchar(10))
insert into #testtable
Select col from
(
select 'W05' col union ALL
select 'Main' union ALL
select 'Mfg' union ALL
select 'SP' union ALL
select 'W01' union ALL
select 'W02' union ALL
select 'W03' union ALL
select 'W04'
) randomtable
Select * From #testtable where somedata = 'W04'
Union ALL
Select * From #testtable where somedata <> 'W04'
The result set is rendering data to the grid as requested the OP
idia is to get first all rows where equal to 'W04' is and then
not equal to 'W04' and then concatinate the result. so that rows 'W04'
will always be on the top because its used in the query first, fair enough.
, but that is not the only point to use (custom sorting/sorting) in that fasion there is one another
and a major one that is PERFORMANCE
yes
"case with order by" will never able to take advantages of KEY but Union ALL will be, to explore it more buld the test table
and check the diffrence
CREATE TABLE #Orders
(
OrderID integer NOT NULL IDENTITY(1,1),
CustID integer NOT NULL,
StoreID integer NOT NULL,
Amount float NOT NULL,
makesrowfat nchar(4000)
);
GO
-- Sample data
WITH
Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows
Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows
Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows
Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows
Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows
FinalCte AS (SELECT ROW_NUMBER() OVER (ORDER BY C) AS Number FROM Cte5)
INSERT #Orders
(CustID, StoreID, Amount)
SELECT
CustID = Number / 10,
StoreID = Number % 4,
Amount = 1000 * RAND(Number)
FROM FinalCte
WHERE
Number <= 1000000;
GO
lets now do the same for custid "93190"
Create NONclustered Index IX_CustID_Orders ON #Orders (CustID)
INCLUDE (OrderID ,StoreID, Amount ,makesrowfat )
WARM CHACHE RESULTS
SET STATISTICS TIME ON
DECLARE #OrderID integer
DECLARE #CustID integer
DECLARE #StoreID integer
DECLARE #Amount float
DECLARE #makesrowfat nchar(4000)
Select #OrderID =OrderID ,
#CustID =CustID ,
#StoreID =StoreID ,
#Amount =Amount ,
#makesrowfat=makesrowfat
FROM
(
Select * From #Orders where custid =93190
Union ALL
Select * From #Orders where custid <>93190
)TLB
**
--elapsed time =2571 ms.
**
DECLARE #OrderID integer
DECLARE #CustID integer
DECLARE #StoreID integer
DECLARE #Amount float
DECLARE #makesrowfat nchar(4000)
Select #OrderID =OrderID ,
#CustID =CustID ,
#StoreID =StoreID ,
#Amount =Amount ,
#makesrowfat=makesrowfat
From #Orders
ORDER BY Case When custid = 93190 THEN 1 ELSE 2 END
elapsed time = 70616 ms
**
UNION ALL performance 2571 ms. ORDER BY CASE performance
70616 ms
**
UNION ALL is a clear winner ORDER BY IS not ever nearby in performance
BUT we forgot that SQL is declarative language,
we have no direct control over how data has fetch by the sql, there is a software code ( that changes with the releases)
IN between
user and sql server database engine, which is SQL SERVER OPTIMIZER that is coded to get the data set
as specified by the USER and its has responsibility to get the data with least amount of resources. so there are chances
that you wont get ALWAYS the result in order until you specify ORDER BY
some other references:
#Damien_The_Unbeliever
Why would anyone offer an ordering guarantee except when an ORDER BY clause is included? -
there's an obvious opportunity for parallelism (if sufficient resources are available) to compute each result set in parallel and serve each result row
(from the parallel queries) to the client in whatever order each individual result row becomes available. –
Conor Cunningham:
If you need order in your query results, put in an ORDER BY. It's that simple. Anything else is like riding in a car without a seatbelt.
ALL COMMENTS AND EDIT ARE WELCOME

SQL ALL IN clause

I have been searching for this, but didn't find anything special.
Is it possible to have an SQL query which will act like ALL IN? To better explain, Here is a table structure.
Orders table
OrderItem table (having several columns, but mainly ProductID, OrderID)
ProductGroup table (several columns, but mainly GroupID and ProductID)
I want to write a query which will select all those order which belongs to a specific ProductGroup. So if I have a group named "XYZ" with ID = 10. It has One ProductID in it. Say ProductID01
An order came in with two order items. ProductID01 and ProductID02. To find all orders in the specific Product Group I can use a simple SQL like
SELECT bvc_OrderItem.ProductID, bvc_OrderItem.OrderID
From bvc_OrderItem
INNER JOIN bvc_Product_Group_Product with (nolock) ON bvc_OrderItem.ProductID = bvc_Product_Group_Product.ProductID
WHERE bvc_Product_Group_Product.GroupID = 10
Or I can write using an IN Clause
SELECT bvc_OrderItem.ProductID, bvc_OrderItem.OrderID
From bvc_OrderItem
WHERE ProductID IN (
SELECT ProductID FROM bvc_Product_Group_Product WHERE GroupID=10
)
However, This will return all orders where one or more ProductIDs are part of the product group. I need to return the order row ONLY if ALL of the order items are part of the Product Group
So basically, I need an IN Clause which will considered matched if ALL of the values inside IN Clause matches the rows in bvc_OrderItem.
Or if we are using the Join, then the Join should only succeed if ALL rows on the left have values in the corresponding right table.
If I could write it more simply, I would write it like this
Select ID FROM Table WHERE ID IN (1, 2, 3, 4)
and if the table contains all rows with ids 1,2,3,4; it should return success. If any of these IN values is missing, it should return false and nothing should be selected.
Do you think it is possible? Or there is a workaround to do that?
You can get the list of orders in a variety of ways, such as:
SELECT oi.OrderID
FROM bvc_OrderItem oi JOIN
bvc_Product_Group_Product pgp
ON oi.ProductID = pgp.ProductId AND
pgp.GroupID = 10
GROUP BY oi.OrderID
HAVING COUNT(DISTINCT oi.ProductID) = (SELECT COUNT(*)
FROM bvc_Product_Group_Product
WHERE GroupID = 10
);
Getting the specific products requires an additional join. In most cases, the list of orders is more useful.
The problem with your ALL IN syntax is that it doesn't do what you want. You want to select orders. The syntax:
SELECT bvc_OrderItem.ProductID, bvc_OrderItem.OrderID
From bvc_OrderItem
WHERE ProductID ALL IN (SELECT ProductID
FROM bvc_Product_Group_Product
WHERE GroupID = 10
)
This doesn't specify that you intend for the grouping to be by OrderId, as opposed to some other level.
More fundamentally, though, the SQL language is inspired by relational algebra. The constructs of SELECT, JOIN, WHERE, and GROUP BY directly relate to relational algebra fundamental constructs. The notion of ALL IN -- although sometimes useful -- can be expressed using the more basic building blocks.
You can do it by this tricky statement:
DECLARE #Items TABLE
(
OrderID INT ,
ProductID INT
)
DECLARE #Groups TABLE
(
ProductID INT ,
GroupID INT
)
INSERT INTO #Items
VALUES ( 1, 1 ),
( 1, 2 ),
( 2, 1 ),
( 3, 3 ),
( 3, 4 )
INSERT INTO #Groups
VALUES ( 1, 10 ),
( 2, 10 ),
( 3, 10 ),
( 4, 15 )
SELECT OrderID
FROM #Items i
GROUP BY OrderID
HAVING ( CASE WHEN 10 = ALL ( SELECT gg.GroupID
FROM #Items ii
JOIN #Groups gg ON gg.ProductID = ii.ProductID
WHERE ii.OrderID = i.OrderID ) THEN 1
ELSE 0
END ) = 1
Output:
OrderID
1
2
Also(this is better):
SELECT OrderID
FROM #Items i
JOIN #Groups g ON g.ProductID = i.ProductID
GROUP BY OrderID
HAVING MIN(g.GroupID) = 10
AND MAX(g.GroupID) = 10

Query matching stock trading buyers/sellers based on conditions

I'm trying to create a stored procedure that matches buyers and sellers for a fake stock market program. I eventually I'd like to use this to populate a transaction table to finalize peoples orders. This would also be in a SQL Server Agent Job. Here's the table I have:
Id UserId Type QtyRemaining Price CreatedOn
1 3 BUY 50 1.00 2012-09-09 05:25:48.4470000
2 6 BUY 50 1.00 2012-09-09 19:25:34.4300000
3 5 SELL 30 1.00 2012-09-09 19:22:59.5900000
4 3 SELL 50 0.90 2012-09-09 06:39:34.9100000
5 2 SELLALL 50 1.00 2012-09-09 04:10:01.8400000
There are several conditions that need to be satisfied to make these matches:
A buyer must look for a seller that has >= amount of shares that the buyer wants if its a 'SELL' order. If its a 'SELLALL' order then the Qty must be equal for the buyer to buy the stock. E.g. The seller must be selling 50 shares and the buyer MUST be buying 50 shares at the same price or lower.
A buyer wants the minimum price for the stock.
If there are multiple sellers with the conditions above a buyer takes the oldest selling stock first after the minimum price.
So the pairs of traders would be #1 and #4, #2 and #5. Therefore #3 would still be pending.
Here's the code I was playing around with but I can't get it to match up properly with the minimum price and the oldest first:
select o.*, oa.*, r.* from [Order] o
join OrderActivity oa on o.Id = oa.OrderId
join (
select o2.Id, o2.VideoId, o2.UserId, oa2.Price, oa2.QtyRemaining, o2.[Type] from [Order] o2
join OrderActivity oa2 on o2.Id = oa2.OrderId
where o2.Type = 'buy' and oa2.Status = 'open'
) as r on (o.VideoId = r.VideoId and oa.Price <= r.Price and r.QtyRemaining = oa.QtyRemaining and o.UserId != r.UserId)
where (o.Type = 'sell' or o.Type = 'sellall') and oa.Status = 'open'
try this
Briefly ,
1.Rank buyer based on the createddate(named as stocks in the below query) 2.Rank seller based on the price,createddate(named as lowNoldstock)3.Get the matching rank
select stocks.*,lowNoldStock.*
from (select *,row_number() over(order by createdon) as buyerrank
from stocktable(nolock) where c='buy' ) stocks
inner join
(select *,row_number() over(order by price,createdon) as sellerrank
from stocktable(nolock) where [type]='sell' or [type]='sellall' ) lowNoldstock
on (stocks.qty<=lowNoldStock.qty and lowNoldStock.type='sell')
or (lowNoldStock.type='sellall' and stocks.qty=lowNoldStock.qty and stocks.price>=lowNoldStock.price)
where lowNoldStock.sellerrank=stocks.buyerrank
test script in sql fiddle ,for some reason it is showing partial result in sql fiddle
This works in my local database
You are welcome to play with this:
declare #Transactions as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
insert into #Transactions ( Id, UserId, Type, QtyRemaining, Price, CreatedOn ) values
( 1, 3, 'BUY', 50, 1.00, '2012-09-09 05:25:48.447' ),
( 2, 6, 'BUY', 50, 1.00, '2012-09-09 19:25:34.430' ),
( 3, 5, 'SELL', 30, 1.00, '2012-09-09 19:22:59.590' ),
( 4, 3, 'SELL', 50, 0.90, '2012-09-09 06:39:34.910' ),
( 5, 2, 'SELLALL', 50, 1.00, '2012-09-09 04:10:01.840' )
-- Split the transactions into temporary working tables.
declare #Buyers as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
declare #Sellers as Table
( Id Int, UserId Int, Type VarChar(8), QtyRemaining Int, Price Money, CreatedOn DateTime )
insert into #Buyers
select Id, UserId, Type, QtyRemaining, Price, CreatedOn
from #Transactions
where Type = 'BUY'
insert into #Sellers
select Id, UserId, Type, QtyRemaining, Price, CreatedOn
from #Transactions
where Type like 'SELL%'
-- Process the buy orders in the order in which they were created.
declare #BuyerId as Int = ( select top 1 Id from #Buyers order by CreatedOn )
declare #SellerId as Int
declare #Balance as Int
while #BuyerId is not NULL
begin
-- Pair a seller, if possible, with the current buyer.
; with Willard as (
select Row_Number() over ( order by S.Price, S.CreatedOn ) as Priority,
S.Id as S_Id, S.QtyRemaining as S_QtyRemaining,
B.QtyRemaining as B_QtyRemaining
from #Sellers as S inner join
#Buyers as B on B.Id = #BuyerId and
case
when S.Type = 'SELL' and B.QtyRemaining <= S.QtyRemaining then 1
when S.Type = 'SELLALL' and B.QtyRemaining = S.QtyRemaining then 1
else 0
end = 1
)
select #SellerId = S_Id, #Balance = S_QtyRemaining - B_QtyRemaining
from Willard
where Priority = 1
-- Display the result.
select #BuyerId as BuyerId, #SellerId as SellerId, #Balance as RemainingShares
-- Update the working tables.
if #Balance = 0
delete from #Sellers
where Id = #SellerId
else
update #Sellers
set QtyRemaining = #Balance
where Id = #SellerId
delete from #Buyers
where Id = #BuyerId
-- Find the next buy order.
set #BuyerId = ( select top 1 Id from #Buyers order by CreatedOn )
end
-- Display any unfilled orders.
select 'Unmatched Buy', *
from #Buyers
select 'Unmatched Sell', *
from #Sellers

SUM by two different GROUP BY

I'm getting the wrong result from my report. Maybe i'm missing something simple.
The report is an inline table-valued-function that should count goods movement in our shop and how often these spareparts are claimed(replaced in a repair).
The problem: different spareparts in the shop-table(lets call it SP) can be linked to the same sparepart in the "repair-table"(TSP). I need the goods movement of every sparepart in SP and the claim-count of every distinct sparepart in TSP.
This is a very simplified excerpt of the relevant part:
create table #tsp(id int, name varchar(20),claimed int);
create table #sp(id int, name varchar(20),fiTsp int,ordered int);
insert into #tsp values(1,'1235-6044',300);
insert into #tsp values(2,'1234-5678',400);
insert into #sp values(1,'1235-6044',1,30);
insert into #sp values(2,'1235-6044',1,40);
insert into #sp values(3,'1235-6044',1,50);
insert into #sp values(4,'1234-5678',2,60);
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id
)
SELECT TspName, SUM(Claimed) As Claimed, Sum(Ordered) As Ordered
FROM cte
Group By TspName
drop table #tsp;
drop table #sp;
Result:
TspName Claimed Ordered
1234-5678 400 60
1235-6044 900 120
The Ordered-count is correct but the Claimed-count should be 300 instead of 900 for TspName='1235-6044'.
I need to group by Tsp.ID for the claim-count and group by Sp.ID for the order-count. But how in one query?
Edit: Actually the TVF looks like(note that getOrdered and getClaimed are SVFs and that i'm grouping in the outer select on TSP's Category):
CREATE FUNCTION [Gambio].[rptReusedStatistics](
#fromDate datetime
,#toDate datetime
,#fromInvoiceDate datetime
,#toInvoiceDate datetime
,#idClaimStatus varchar(50)
,#idSparePartCategories varchar(1000)
,#idSpareParts varchar(1000)
)
RETURNS TABLE AS
RETURN(
WITH ExclusionCat AS(
SELECT idSparePartCategory AS ID From tabSparePartCategory
WHERE idSparePartCategory IN(- 3, - 1, 6, 172,168)
), Report AS(
SELECT Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,SP.Inventory
,Gambio.getGoodsIn(SP.idSparePart,#FromDate,#ToDate) GoodsIn
,Gambio.getOrdered(SP.idSparePart,#FromDate,#ToDate) Ordered
--,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
-- Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,NULL)END AS Claimed
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1)END AS ClaimedReused
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus)END AS Costsaving
FROM Gambio.SparePart AS SP
INNER JOIN tabSparePart AS TSP ON SP.fiTabSparePart = TSP.idSparePart
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
WHERE Cat.idSparePartCategory NOT IN(SELECT ID FROM ExclusionCat)
AND (#idSparePartCategories IS NULL
OR TSP.fiSparePartCategory IN(
SELECT Item From dbo.Split(#idSparePartCategories,',')
)
)
AND (#idSpareParts IS NULL
OR TSP.idSparePart IN(
SELECT Item From dbo.Split(#idSpareParts,',')
)
)
)
SELECT Category
--, Part
--, PartNumber
, SUM(Inventory)As InventoryCount
, SUM(GoodsIn) As GoodsIn
, SUM(Ordered) As Ordered
--, SUM(Claimed) As Claimed
, SUM(ClaimedReused)AS ClaimedReused
, SUM(Costsaving) As Costsaving
, Count(*) AS PartCount
FROM Report
GROUP BY Category
)
Solution:
Thanks to Aliostad i've solved it by first grouping and then joining(actual TVF, reduced to a minimum):
WITH Report AS(
SELECT Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,SP.Inventory
,SP.GoodsIn
,SP.Ordered
,Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1) AS ClaimedReused
,Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus) AS Costsaving
FROM (
SELECT GSP.fiTabSparePart
,SUM(GSP.Inventory)AS Inventory
,SUM(Gambio.getGoodsIn(GSP.idSparePart,#FromDate,#ToDate))AS GoodsIn
,SUM(Gambio.getOrdered(GSP.idSparePart,#FromDate,#ToDate))AS Ordered
FROM Gambio.SparePart GSP
GROUP BY GSP.fiTabSparePart
)As SP
INNER JOIN tabSparePart TSP ON SP.fiTabSparePart = TSP.idSparePart
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
)
SELECT Category
, SUM(Inventory)As InventoryCount
, SUM(GoodsIn) As GoodsIn
, SUM(Ordered) As Ordered
, SUM(ClaimedReused)AS ClaimedReused
, SUM(Costsaving) As Costsaving
, Count(*) AS PartCount
FROM Report
GROUP BY Category
You are JOINing first and then GROUPing by. You need to reverse it, GROUP BY first and then JOIN.
So here in my subquery, I group by first and then join:
select
claimed,
ordered
from
#tsp
inner JOIN
(select
fitsp,
SUM(ordered) as ordered
from
#sp
group by
fitsp) as SUMS
on
SUMS.fiTsp = id;
I think you just need to select Claimed and add it to the Group By in order to get what you are looking for.
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id )
SELECT TspName, Claimed, Sum(Ordered) As Ordered
FROM cte
Group By TspName, Claimed
Your cte is an inner join between tsp and sp, which means that the data you're querying looks like this:
SpID Ordered TspID TspName Claimed
1 30 1 1235-6044 300
2 40 1 1235-6044 300
3 50 1 1235-6044 300
4 60 2 1234-5678 400
Notice how TspID, TspName and Claimed all get repeated. Grouping by TspName means that the data gets grouped in two groups, one for 1235-6044 and one for 1234-5678. The first group has 3 rows on which to run the aggregate functions, the second group only one. That's why your sum(Claimed) will get you 300*3=900.
As Aliostad suggested, you should first group by TspID and do the sum of Ordered and then join to tsp.
No need to join, just subselect:
create table #tsp(id int, name varchar(20),claimed int);
create table #sp(id int, name varchar(20),fiTsp int,ordered int);
insert into #tsp values(1,'1235-6044',300);
insert into #tsp values(2,'1234-5678',400);
insert into #sp values(1,'1235-6044',1,30);
insert into #sp values(2,'1235-6044',1,40);
insert into #sp values(3,'1235-6044',1,50);
insert into #sp values(4,'1234-5678',2,60);
WITH cte AS(
select tsp.id As TspID,tsp.name as TspName,tsp.claimed As Claimed
,sp.id As SpID,sp.name As SpName,sp.ordered As Ordered
from #sp sp inner join #tsp tsp
on sp.fiTsp=tsp.id
)
SELECT id, name, SUM(claimed) as Claimed, (SELECT SUM(ordered) FROM #sp WHERE #sp.fiTsp = #tsp.id GROUP BY #sp.fiTsp) AS Ordered
FROM #tsp
GROUP BY id, name
drop table #tsp;
drop table #sp;
Produces:
id name Claimed Ordered
1 1235-6044 300 120
2 1234-5678 400 60
-- EDIT --
Based on the additional info, this is how I might try to split the CTE to form the data as per the example. I fully admit that Aliostad's approach may yield a cleaner query but here's an attempt (completely blind) using the subselect:
CREATE FUNCTION [Gambio].[rptReusedStatistics](
#fromDate datetime
,#toDate datetime
,#fromInvoiceDate datetime
,#toInvoiceDate datetime
,#idClaimStatus varchar(50)
,#idSparePartCategories varchar(1000)
,#idSpareParts varchar(1000)
)
RETURNS TABLE AS
RETURN(
WITH ExclusionCat AS (
SELECT idSparePartCategory AS ID From tabSparePartCategory
WHERE idSparePartCategory IN(- 3, - 1, 6, 172,168)
), ReportSP AS (
SELECT fiTabSparePart
,Inventory
,Gambio.getGoodsIn(idSparePart,#FromDate,#ToDate) GoodsIn
,Gambio.getOrdered(idSparePart,#FromDate,#ToDate) Ordered
FROM Gambio.SparePart
), ReportTSP AS (
SELECT TSP.idSparePart
,Cat.SparePartCategoryName AS Category
,TSP.SparePartDescription AS Part
,TSP.SparePartName AS PartNumber
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getClaimed(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus,1)END AS ClaimedReused
,CASE WHEN TSP.idSparePart IS NULL THEN 0 ELSE
Gambio.getCostSaving(TSP.idSparePart,#FromInvoiceDate,#ToInvoiceDate,#idClaimStatus)END AS Costsaving
FROM tabSparePart AS TSP
INNER JOIN tabSparePartCategory AS Cat
ON Cat.idSparePartCategory=TSP.fiSparePartCategory
WHERE Cat.idSparePartCategory NOT IN(SELECT ID FROM ExclusionCat)
AND (#idSparePartCategories IS NULL
OR TSP.fiSparePartCategory IN(
SELECT Item From dbo.Split(#idSparePartCategories,',')
)
)
AND (#idSpareParts IS NULL
OR TSP.idSparePart IN(
SELECT Item From dbo.Split(#idSpareParts,',')
)
)
)
SELECT Category
--, Part
--, PartNumber
, (SELECT SUM(Inventory) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS Inventory
, (SELECT SUM(GoodsIn) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS GoodsIn
, (SELECT SUM(Ordered) FROM ReportSP WHERE ReportSP.fiTabSparePart = idSparePart GROUP BY fiTabSparePart) AS Ordered
, Claimed
, ClaimedReused
, Costsaving
, Count(*) AS PartCount
FROM ReportTSP
GROUP BY Category
)
Without a better understanding of the whole schema it's difficult to cover for all the eventualities but whether this works or not (I suspect PartCount will be 1 for all instances) hopefully it'll give you some fresh thoughts for alternate approaches.
SELECT
tsp.name
,max(tsp.claimed) as claimed
,sum(sp.ordered) as ordered
from #sp sp
inner join #tsp tsp
on sp.fiTsp=tsp.id
GROUP BY tsp.name