SQL Scenario of allocating ids to user - sql

I have an sql scenario as follows which I have been trying to improve.
There is a table 'Returns' which is having ids of the returned goods against a shop for an item. Its structure is as below.
Returns
-------------------------
Return ID | Shop | Item
-------------------------
1 Shop1 Item1
2 Shop1 Item1
3 Shop1 Item1
4 Shop1 Item1
5 Shop1 Item1
There is one more table Supplier with Shop, supplier and Item as shown below.
Supplier
---------------------------------
Supplier | Shop | Item | Volume
---------------------------------
supp1 Shop1 Item1 20%
supp2 Shop1 Item1 80%
Now as you see supp1 is supplying 20 % of total item1 volume and supp2 is supplying 80% of Item1 to shop1. And there were 5 return of items against the same Item1 for same Shop1.
Now I need to allocate any four return IDs to Supp1 and remaining one return Id to supp2. This allocation of numbers is based on the ratio of the supplied volume percentage of the supplier. This allocation varies depending on the ratio of volume of supplied items.
Now I have tried a method of using RANKs as shown below by use of temp tables.
temp table 1 will have Shop, Return Id, Item, Total count of return IDs and Rank of the return id.
temp table 2 will have shop, Supplier, Item and his proportion and rank of proportion.
Now I am facing the difficulty in allocating top return ids to top supplier as illustrated above. As SQL doesnt have loops how can I achieve this. I have been tying several ways of doing this.
My environment is Teradata (ANSI SQL is enough).

UPDATE:
You need to loop, here is some SQL code you can use as a starting point.
Basically I use temporary tabes and ROW_NUMBER().
For my sample I used SQL SERVER 2008.
Try the following:
-- gather suppliers in temp table
DECLARE #SupplierTemp table
( [RowId] int
,[Supplier] nvarchar (50)
,[ReturnCount] int )
-- gather supplier with return count
INSERT INTO #SupplierTemp
SELECT ROW_NUMBER() OVER(ORDER BY [Supplier].[Supplier] DESC, [Supplier].[Supplier])
,[Supplier].[Supplier]
, COUNT([Supplier].[Supplier])*[Supplier].[Volume]/100 AS ReturnCount
FROM [Supplier]
INNER JOIN [Returns] ON (([Returns].[Item] = [Supplier].[Item])
AND ([Returns].[Shop] = [Supplier].[Shop]))
GROUP BY [Supplier].[Supplier], [Supplier].[Volume]
ORDER BY [Supplier].[Supplier]
-- gather returns in temp table
DECLARE #ReturnsTemp table
( [RowId] int
,[Id] int)
-- gather returns
INSERT INTO #ReturnsTemp
SELECT ROW_NUMBER() OVER(ORDER BY [Returns].[Id] DESC, [Returns].[Id])
,[Returns].[Id]
FROM [Returns]
-- gather results in temp table
DECLARE #ResultsTemp table
( [Supplier] nvarchar(50)
,[Id] int)
DECLARE #rrowid as int
DECLARE #rid as int
-- loop over all suppliers
-- loop once for each [ReturnCount]
-- find the next avialable Id
DECLARE #srowid as int
DECLARE #loopCnt as int
DECLARE #supplier as nvarchar(50)
-- get first supplier
SELECT #srowid = (SELECT MIN([RowId]) FROM #SupplierTemp)
SELECT #loopCnt = [ReturnCount] FROM #SupplierTemp WHERE [RowId] = #srowid
SELECT #supplier = [Supplier] FROM #SupplierTemp WHERE [RowId] = #srowid
-- loop over suppliers
WHILE #srowid IS NOT NULL
BEGIN
-- loop of number of returns
WHILE #loopCnt > 0
BEGIN
-- find the Id to return
SELECT #rrowid = (SELECT MIN([RowId]) FROM #ReturnsTemp)
SELECT #rid = [Id] FROM #ReturnsTemp WHERE [RowId] = #rrowid
INSERT INTO #ResultsTemp VALUES (#supplier, #rid)
DELETE FROM #ReturnsTemp WHERE [RowId] = #rrowid
SELECT #loopCnt = #loopCnt - 1
END
-- delete current item from table to keep loop moving forward...
DELETE FROM #SupplierTemp WHERE [RowId] = #srowid
-- get next supplier.
SELECT #srowid = (SELECT MIN([RowId]) FROM #SupplierTemp)
SELECT #loopCnt = [ReturnCount] FROM #SupplierTemp WHERE [RowId] = #srowid
SELECT #supplier = [Supplier] FROM #SupplierTemp WHERE [RowId] = #srowid
END
SELECT * FROM #ResultsTemp

Related

Circular JOIN - Backtracking in production

How to solve something like "circular JOIN" on table to solve this example of a production order backtracking problem?
The problem is:
I need to store evidence of production orders.
During the production is usual that sometimes is need to split the order and continue in the production with the new order (with the relation on previous order).
See this example:
CREATE TABLE EventOrders
(
OrderID INT IDENTITY(1,1) PRIMARY KEY,
Code NVARCHAR(20),
OrderTypeID INT
);
CREATE TABLE EventSetOrderRelations
(
OrderRelationID INT IDENTITY(1,1) PRIMARY KEY,
OrderIDIn INT FOREIGN KEY REFERENCES EventOrders(OrderID),
OrderIDOut INT FOREIGN KEY REFERENCES EventOrders(OrderID)
);
INSERT INTO EventOrders (Code, OrderTypeID) VALUES
('221209-1',1),('221209-2',1),('221209-3-R',2),
('221209-4',1),('221209-5-R',2),('221209-6-R',2);
INSERT INTO EventSetOrderRelations (OrderIDIn, OrderIDOut) VALUES
(1,3),(2,5),(3,6);
SELECT * FROM EventOrders;
SELECT * FROM EventSetOrderRelations
OrderID
Code
OrderTypeID
1
221209-1
1
2
221209-2
1
3
221209-3-R
2
4
221209-4
1
5
221209-5-R
2
6
221209-6-R
2
OrderRelationID
OrderIDIn
OrderIDOut
1
1
3
2
2
5
3
3
6
Question:
I would like use some elegant query with parameter of the order (let's say that in the condition will be "Code" of the order) and get all previous orders in this branch...
My solution:
... I can solve this issue with this SQL script and the result is exactly what I need BUT I need to apply this solution in Stored Procedure...
DECLARE #MyCode NVARCHAR(20) = '221209-6-R'
DECLARE #CheckID INT
DECLARE #AllOrders TABLE (OrderID INT, Code NVARCHAR(20), OrderTypeID INT)
DECLARE #Count INT = 0
SET #CheckID = (SELECT OrderID FROM dbo.EventOrders WHERE Code = #MyCode)
-- --------------------------------------------------
WHILE #Count < 100
BEGIN
SET #Count = #Count + 1
INSERT INTO #AllOrders (OrderID, Code, OrderTypeID)
SELECT OrderID, Code, OrderTypeID FROM dbo.EventOrders
WHERE OrderID = #CheckID
SET #CheckID = (SELECT OrderIDIn FROM dbo.EventSetOrderRelations ESOR
JOIN dbo.EventOrders EO ON EO.OrderID = ESOR.OrderIDOut
WHERE EO.OrderID = (SELECT MIN(OrderID) FROM #AllOrders))
IF #CheckID IS NULL
BEGIN
SET #Count = 100
END
END
SELECT * FROM #AllOrders ORDER BY OrderID ASC
OrderID
Code
OrderTypeID
1
221209-1
1
3
221209-3-R
2
6
221209-6-R
2
My wonder:
I would like to ask You if is possible some easier way to get this result.
See DBFiddle:
fiddle link
Seems that what you want is a recursive Common Table Expression (rCTE) here. You can pass the parameter for the order in the first query within the rCTE, and then use that data to recurse through the table(s):
WITH rCTE AS(
SELECT EO.*
FROM dbo.EventOrders EO
WHERE EO.OrderID = #Order
UNION ALL
SELECT EO.*
FROM dbo.EventOrders EO
JOIN EventSetOrderRelations ESOR ON EO.OrderID = ESOR.OrderIDIn
JOIN rCTE r ON ESOR.OrderIDOut = r.OrderID)
SELECT *
FROM rCTE;

Insert into 2 different data to 2 table at the same time using first data's id

I have 2 tables: Order and product_order. Every order has some product in it and that's because I store products another table.
Table Order:
Id name
Table PRODUCT_ORDER:
id product_id order_id
Before I start to insert, I don't know what the Order Id is. I want to insert the data into both tables at once and I need the order id to do that.
Both id's are auto incremented. I'm using SQL Server. I can insert first order and then find the id of the order and than execute the second insert, but I want to do these both to execute at once.
The output clause is your friend here.
DECLARE #Orders TABLE (OrderID INT IDENTITY, OrderDateUTC DATETIME, CustomerID INT)
DECLARE #OrderItems TABLE (OrderItemID INT IDENTITY, OrderID INT, ProductID INT, Quantity INT, Priority TINYINT)
We'll use these table variables as demo tables with IDs to insert into. You're liking going to be passing the set of items for an order in together, but for the purpose of a demo we'll ad hoc them as a VALUES list.
DECLARE #Output TABLE (OrderID INT)
INSERT INTO #Orders (OrderDateUTC, CustomerID)
OUTPUT INSERTED.OrderID INTO #Output
VALUES (GETUTCDATE(), 1)
We inserted the Order into the Orders table, and used the OUTPUT clause to cause the inserted (and generated by the engine) into the table variable #Output. We can now use this table however we'd like:
INSERT INTO #OrderItems (OrderID, ProductID, Quantity, Priority)
SELECT OrderID, ProductID, Quantity, Priority
FROM (VALUES (5,1,1),(2,1,2),(3,1,3)) AS x(ProductID, Quantity, Priority)
CROSS APPLY #Output
We cross applied it to our items list, and inseted it as if it was any other row.
DELETE FROM #Output
INSERT INTO #Orders (OrderDateUTC, CustomerID)
OUTPUT INSERTED.OrderID INTO #Output
VALUES (GETUTCDATE(), 1)
INSERT INTO #OrderItems (OrderID, ProductID, Quantity, Priority)
SELECT OrderID, ProductID, Quantity, Priority
FROM (VALUES (1,1,1)) AS x(ProductID, Quantity, Priority)
CROSS APPLY #Output
Just to demo a little farther here's another insert. (You likely wouldn't need the DELETE normally, but we're still using the same variable here)
Now when we select that data we can see the two separate orders, with their IDs and the products that belong to them:
SELECT *
FROM #Orders o
INNER JOIN #OrderItems oi
ON o.OrderID = oi.OrderID
OrderID OrderDateUTC CustomerID OrderItemID OrderID ProductID Quantity Priority
------------------------------------------------------------------------------------------------
1 2022-12-08 23:23:21.923 1 1 1 5 1 1
1 2022-12-08 23:23:21.923 1 2 1 2 1 2
1 2022-12-08 23:23:21.923 1 3 1 3 1 3
2 2022-12-08 23:23:21.927 1 4 2 1 1 1
Dale is correct. You cannot insert into multiple tables at once, but if you use a stored procedure to handle your inserts, you can capture the ID and use it in the next insert.
-- table definitions
create table [order]([id] int identity, [name] nvarchar(100))
go
create table [product_order]([id] int identity, [product_id] nvarchar(100), [order_id] int)
go
-- stored procedure to handle inserts
create procedure InsertProductWithOrder(
#OrderName nvarchar(100),
#ProductID nvarchar(100))
as
begin
declare #orderID int
insert into [order] ([name]) values(#OrderName)
select #orderID = ##identity
insert into [product_order]([product_id], [order_id]) values(#ProductID, #orderID)
end
go
-- insert records using the stored procedure
exec InsertProductWithOrder 'Order ONE', 'AAAAA'
exec InsertProductWithOrder 'Order TWO', 'BBBBB'
-- verify the results
select * from [order]
select * from [product_order]

Calculating SQL RunningTotal column

I have a table like this:
I want RunningTotal column should be calculated as like:
Balance=Deposit+Balance and Withdraw-Balance on each row for example I want RunningTotal table must be like this:
AccountNo Deposit Withdraw RunningTotal
--------------- -------- -------- --------------
2014002 1000 0 1000
305002 0 500 500
50021 2500 100 2900
54201 6000 0 8900
Help me!
Here is one way using sum() over() works from 2012. Considering you have an id/date column to order the results
select AccountNo, Deposit, Withdraw,
RunningTotal = sum(Deposit-Withdraw)over(order by id Rows between UNBOUNDED PRECEDING and current row )
from Yourtable
For older versions
select AccountNo, Deposit, Withdraw,
cs.RunningTotal
from Yourtable a
cross apply(select sum(Deposit-Withdraw)
from Yourtable b
where a.id>=b.id) cs (RunningTotal)
Demo
Try a computed column in SQL Server, change the column RunningTotal to a computed column which calculates its value for each row based on Deposit - Withdraw. Like this:
CREATE TABLE #temp
(
AccountNo VARCHAR(50),
Deposit NUMERIC(19,2),
Withdraw NUMERIC(19,2),
RunningTotal AS (Deposit - Withdraw)
)
INSERT INTO #temp (AccountNo, Deposit, Withdraw)
SELECT '1234', 100, 50
SELECT * FROM #temp
Now the result will be like this
I'm adding 20 to the deposit and the RunningTotal will be automatically changed
UPDATE #temp
SET Deposit = Deposit + 20
SELECT * FROM #temp
If you have multiple rows for each account and want to calculate the RunningTotal for each row separately (assuming that you have a primary key/ identity column on your table - like below)
CREATE TABLE YourTable
(
SeqNo INT IDENTITY(1,1),
AccountNo VARCHAR(50),
Deposit NUMERIC(19,2),
Withdraw NUMERIC(19,2),
RunningTotal AS (Deposit - Withdraw)
)
Create an UDF to calculate the value, like this
CREATE FUNCTION dbo.fn_CalculateBalance
(#AccountNo VARCHAR(50), #Id INT)
RETURNS NUMERIC(19,2)
AS
BEGIN
DECLARE #RunningTotal NUMERIC(19,2)
SELECT #RunningTotal = SUM(Deposit) - SUM(Withdraw)
FROM YourTable
WHERE AccountNo = #AccountNo
AND SeqNo <= #Id
RETURN #RunningTotal
END
Now change the RunningTotal like this
CREATE TABLE YourTable
(
SeqNo INT IDENTITY(1,1),
AccountNo VARCHAR(50),
Deposit NUMERIC(19,2),
Withdraw NUMERIC(19,2),
RunningTotal AS (dbo.fn_CalculateBalance(AccountNo, SeqNo))
)
Or if you want to use the Date column instead on the SeqNo (identity column). Change the UDF to replace the check for SeqNo with the Date column.
When you look at the messages you can see the message "1 row(s) affected" multiple times. That's because you have this computed column and pass the date value in the computed column.

SQL query Optimization help

I have the the following SQL query
Declare #tempcalctbl Table
(
ItemId varchar(50),
ItemLocation varchar(50),
ItemNo varchar(50),
Width real,
Unit varchar(50),
date datetime
)
Insert Into #tempcalctbl
Select distinct SubId,ItemLocation,ItemNo,
(ABS((Select width From #temptbl a Where ItemProcess ='P1'and a.ItemId = c.ItemId
and a.ItemNo = c.ItemNo and a.ItemLocation = c.ItemLocation)
-(Select width From #temptbl b Where ItemProcess ='P2' and b.ItemId = c.ItemId
and b.ItemNo = c.ItemNo and b.ItemLocation = c.ItemLocation))) * 1000,
Unit,date
From #temptbl c
Group by ItemId,ItemLocation,ItemNo,Unit,date
I was wondering how to optimize this query.
The idea is to find out the different in width (p1's item - p2's item) between ItemProcess 'P1' and 'P2' according to the same ItemID, same ItemNo and same ItemLocation.
I have around 75000 and it took more then 25 minute to get the width differences for all the ItemId.
I tried to use Group by for the width different calculation but it would return multiple row instead of just a value which then would return error. By the way I am use MS SQL server 2008 and #tempcalctbl is a table that I declared in a store procedure.
Does the following help?
INSERT INTO #tempcalctbl
SELECT P1.SubId ,
P1.ItemLocation ,
P1.ItemNo ,
ABS(P1.Width - P2.Width) * 1000 AS Width ,
P1.Unit ,
P1.date
FROM #temptbl AS P1
INNER JOIN #temptbl AS P2 ON P1.ItemId = P2.ItemId
AND P1.ItemNo = P2.ItemNo
AND P1.ItemLocation = P2.ItemLocation
WHERE P1.ItemProcess = 'P1'
AND P2.ItemProcess = 'P2'
EDIT
To make use of indexes, you will need to change your table variable to a temporary table
CREATE TABLE #temptbl
(
ItemId varchar(50),
ItemLocation varchar(50),
ItemNo varchar(50),
Width real,
Unit varchar(50),
date DATETIME,
ItemProcess INT,
SubId INT
)
CREATE NONCLUSTERED INDEX Index01 ON #temptbl
(
ItemProcess ASC,
ItemId ASC,
ItemLocation ASC,
ItemNo ASC
)
INCLUDE ( SubId,Width,Unit,date)
GO
That should speed you up a little.
John Petrak's answer is the best query for this case.
If the speed is still now acceptable, maybe you can store #temptbl at a temporary real table, and create the related index on those four columns.

I need to split string in select statement and insert to table

I have a data in one table. I need to copy it to another table. One of the column is text delimited string. So what I'm thinking to select all columns insert get indentity value and with subquery to split based on delimiter and insert it to another table.
Here is the data example
ID Name City Items
1 Michael Miami item|item2|item3|item4|item5
2 Jorge Hallandale item|item2|item3|item4|item5
copy Name, City to one table get identity
and split and copy Items to another table with Identity Column Value
So output should be
Users table
UserID Name City
1 Michael Miami
2 Jorge Hallandale
...
Items table
ItemID UserID Name
1 1 Item
2 1 Item2
3 1 Item3
4 1 Item4
5 2 Item
6 2 Item2
7 2 Item3
8 2 Item4
Not really sure how to do it with T-SQL. Answers with examples would be appreciated
You may create you custom function to split the string in T-Sql. You could then use the Split function as part of a JOIN with your base table to generate the final results for your INSERT statement. Have a look at this post. Hope this help.
You can do this using xml and cross apply.
See the following:
DECLARE #t table (ID int, Name varchar(20), City varchar(20), Items varchar(max));
INSERT #t
SELECT 1,'Michael','Miami' ,'item|item2|item3|item4|item5' UNION
SELECT 2,'Jorge' ,'Hallandale','item|item2|item3|item4|item5'
DECLARE #u table (UserID int identity(1,1), Name varchar(20), City varchar(20));
INSERT #u (Name, City)
SELECT DISTINCT Name, City FROM #t
DECLARE #i table (ItemID int identity(1,1), UserID int, Name varchar(20));
WITH cte_Items (Name, Items) as (
SELECT
Name
,CAST(REPLACE('<r><i>' + Items + '</i></r>','|','</i><i>') as xml) as Items
FROM
#t
)
INSERT #i (UserID, Name)
SELECT
u.UserID
,s.Name as Name
FROM
cte_Items t
CROSS APPLY (SELECT i.value('.','varchar(20)') as Name FROM t.Items.nodes('//r/i') as x(i) ) s
INNER JOIN #u u ON t.Name = u.Name
SELECT * FROM #i
See more here:
http://www.kodyaz.com/articles/t-sql-convert-split-delimeted-string-as-rows-using-xml.aspx
Can you accomplish this with recursion? My T-SQL is rusty but this may help send you in the right direction:
WITH CteList AS (
SELECT 0 AS ItemId
, 0 AS DelimPos
, 0 AS Item_Num
, CAST('' AS VARCHAR(100)) AS Item
, Items AS Remainder
FROM Table1
UNION ALL
SELECT Row_Number() OVER(ORDER BY UserID) AS ItemId
, UserID
, CASE WHEN CHARINDEX('|', Remainder) > 0
THEN CHARINDXEX('|', Remainder)
ELSE LEN(Remainder)
END AS dpos
, Item_num + 1 as Item_Num
, REPLACE(Remainder, '|', '') AS Element
, right(Remainder, dpos+1) AS Remainder
FROM CteList
WHERE dpos > 0
AND ItemNum < 20 /* Force a MAX depth for recursion */
)
SELECT ItemId
, Item
FROM CteList
WHERE item_num > 0
ORDER BY ItemID, Item_Num