SQL - update each row of a column using while loop - sql-server

My original table looks like:
week volume cost
1 11 null
2 32 null
3 80 null
4 75 null
5 50 null
...
51 28 null
I want to update the cost field by applying a more intelligent rule as follows:
if volume < 13, then use a rateA (loose shipment price)
if volume >= 13 and < 25, then use a rateB (20' container price)
if volume >= 25 and < 45, then use a rateC (40' container price)
I want to get the lowest cost by flexibly using above 3 different rates according to the "volume". For instance, in week 4, the "REMAINING VOLUME" initially is 75, I should apply one 40'container cost to load a portion of the volume. Then the "REMAINING VOLUME" is 30, I should apply a 20'container cost to load a portion of the rest. Then the "REMAINING VOLUME" is 5, I should apply the loose shipment price, keep doing in such way until "REMAINING VOLUME" = 0. By doing so it will give me the best combination to minimise the cost per week. Therefore, a while loop needs to be applied to the "REMAINING VOLUME" which can give indication about how to choose the different rates.
the final updated table should look like:
week volume cost
1 11 rateA
2 32 rateB + rateA
3 80 rateC + rateB + rateA
4 75 rateC + rateB + rateA
5 50 rateC + rateA
...
51 28 rateB + rateA

If the cases are fixed and the number of them is small, the following approach works perfectly fine (with SQL Server, anyway; I don't know about other DB's -- see note below) ...
UPDATE #Orders
SET [Cost] = CASE
WHEN 0 * 44 + 0 * 24 + 1 * 12 >= [Volume] THEN 'rateA'
WHEN 0 * 44 + 1 * 24 + 0 * 12 >= [Volume] THEN 'rateB'
WHEN 0 * 44 + 1 * 24 + 1 * 12 >= [Volume] THEN 'rateB + rateA'
WHEN 1 * 44 + 0 + 24 + 0 * 12 >= [Volume] THEN 'rateC'
WHEN 1 * 44 + 0 + 24 + 1 * 12 >= [Volume] THEN 'rateC + rateA'
WHEN 1 * 44 + 1 + 24 + 0 * 12 >= [Volume] THEN 'rateC + rateB'
WHEN 1 * 44 + 1 + 24 + 1 * 12 >= [Volume] THEN 'rateC + rateB + rateA'
ELSE 'too big'
END
FROM #Orders
;
No looping needed!
Notice the straightforward progression of the cases; you can extend the pattern to include larger weights if you want.
This works with SQL Server because you are guaranteed that the first matching condition is used, so if it matches the third WHEN case it won't try to evaluate the fourth, fifth, etc. I don't know if this is true for other databases.

Related

Autoincrement value

I'm working on a local project and I would like to setup a column in a SQL Server table that I called serial. This value should have the next format/validation [A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]. Four characters that will have numbers or capital letters.
For example, the first record would be AAA0, and then start incrementing from right to left, but the interesting part is that once it reaches AAA9, I want to continue but with letters, so once 9 is reached, continue with A: AAAA. My last combination would be AAAZ, and then continue with the second position and so on, until I could complete ZZZZ.
Serial
------
AAA0
…
AAAZ
AAB0
…
AABZ
AAC0
…
AACZ
…
ZZZZ
I've been able to do something similar, but for example only incrementing numbers 0000 until 9999 or with just letters AAAA to ZZZZ (see below dummy data).
Please let me know if you have any comments or if you have seen this scenario before. I will appreciate any help.
I've reviewed some data on the internet and did this small test, but once again, this is changing letters.
CREATE TABLE #MyTable
(
MyHeadID INT IDENTITY(0,1) NOT NULL,
Consecutive AS
CHAR(MyHeadID/17576%26+65) + --26^3
CHAR(MyHeadID/676%26+65) + --26^2
CHAR(MyHeadID/26%26+65) + --26^1
CHAR(MyHeadID%26+65) --26^0
PERSISTED NOT NULL,
UniqueID VARCHAR(36) NOT NULL,
CreatedDate datetime DEFAULT GETDATE()
)
INSERT INTO #MyTable (UniqueID)
(SELECT NEWID())
SELECT Consecutive FROM #MyTable
Let's not talk about the UniqueID and CreatedDate columns, those are just for testing purposes. I created an identity column and then the code for the autoincrement on letters.
I also found this reply here but it's not my case since in that reply they are dividing 4 letters and 4 numbers with a substring it can be resolved. In my case, I need to intercalculate numbers and letters from right to left.
Here's a possible solution, counting in base 36 AAA0 starts at decimial 479879.
Ideally you'd have an identity incrementing number on your table starting at 479879. Simulating that with a numbers table generated using a CTE, the following gives base36 counting:
with numbers as (
select 479879 + Row_Number() over (order by a.object_id)n
from sys.all_objects a cross join sys.all_objects b
)
select top 10000
Concat(Char(((n/36/36/36) % 36) + case when (n/36/36/36) % 36 between 0 and 9 then 48 else 55 end),
Char(((n/36/36) % 36) + case when (n/36/36) % 36 between 0 and 9 then 48 else 55 end),
Char(((n/36) % 36) + case when (n/36) % 36 between 0 and 9 then 48 else 55 end),
Char((n % 36) + case when n % 36 between 0 and 9 then 48 else 55 end)) Base36
from numbers
order by n
See Fiddle
The previous answer is good but you can do this and cover all numbers with simple math. Also, let's assume your IDENTITY starts at one. There are 36 values for each digit, so those are base 36 encoded as pointed out. The encoding goes 0 - 9 then A - Z as you mentioned in your post. The get the individual digits of some number n from right to left, the algorithm goes:
n mod 36 is the right most digit.
n / 36 mod 36 gives the second-to-right digit.
n / (36 * 36) mod 36 gives the second-to-left digit.
n / (36 * 36 * 36) mod 36 gives left digit.
To test this logic, we can write a function:
CREATE FUNCTION CustomNumber(#id INT)
RETURNS CHAR(4)
AS BEGIN
RETURN CHAR((#id-1)/ POWER(36, 3)% 36+CASE WHEN (#id-1)/ POWER(36, 3)% 36 BETWEEN 0 AND 9 THEN 48 ELSE 55 END)
+CHAR((#id-1)/ POWER(36, 2)% 36+CASE WHEN (#id-1)/ POWER(36, 2)% 36 BETWEEN 0 AND 9 THEN 48 ELSE 55 END)
+CHAR((#id-1)/ 36% 36+CASE WHEN (#id-1)/ 36% 36 BETWEEN 0 AND 9 THEN 48 ELSE 55 END)
+CHAR((#id-1)% 36+CASE WHEN (#id-1)% 36 BETWEEN 0 AND 9 THEN 48 ELSE 55 END);
END;
We can then test this function by calling it to make sure it works. To call it, lets create some numbers. This is complete overkill, but let's pass it 1 through 1,000,000 so we can see it in action:
;WITH digits (I)
AS (
SELECT I
FROM (VALUES (0), (1),(2),(3),(4),(5),(6),(7),(8),(9)) AS digits (I) ),
integers (I)
AS (SELECT D1.I + (10 * D2.I) + (100 * D3.I) + (1000 * D4.I) + (10000*D5.I) + (100000*D6.I)
FROM digits AS D1
CROSS JOIN digits AS D2
CROSS JOIN digits AS D3
CROSS JOIN digits AS D4
CROSS JOIN digits AS D5 CROSS JOIN digits AS D6
)
SELECT I, dbo.CustomNumber(I)
FROM integers
WHERE I > 0
ORDER BY I;
If you run that and patiently wait (it takes about 20 seconds on my weak laptop, but you don't have to use a million numbers if you don't like) you will see that it does produce the result that you want. At this point we know the formula is correct, so you have options to add it to your table.
One option is to use the formula as a PERSISTED column as you did. Another option is to use a trigger. You can leave the formula as a function or you can just put the code in directly. If you need more characters than 4, you can easily add another by following the pattern (just change the POWER to which you raise).
As I mentioned, the previous answer is good, I just wanted to show another method and its derivation. I have had to implement custom sequences several times with varying formats and you can use this general technique.

Writing formulation in SAS

I have the following SAS dataset:
A B C1 C2 C2
Part1 100 50 20 2 0.1
Part2 100 10 30 5 0.5
Part3 100 80 15 9 0.7
Part4 100 60 58 3 0.9
I have, for each Part, a cost function defined as such:
F1(Part1) = C1,part1 + C2,part1*P1 + C3,part1*P1^2
F2(Part2) = C1,part2 + C2,part2*P2 + C3,part2*P2^2
F3(Part3) = C1,part3 + C2,part3*P3 + C3,part3*P3^2
F4(Part4) = C1,part4 + C2,part4*P4 + C3,part4*P4^2
I defined/declared the following parameters:
set <str> Parts; *set for all parts
num nseg{Parts}; *number of segments for each part cost function
num coef{nseg,Parts} *the C values from the dataset
I'm trying to write a formulation to represent the sum of the cost functions
F1(Part1)+ ... +FN(PartN), and came up with this:
TotalCost = sum{p in Parts, j in 1..nseg[p]} (Coef[n,p] + Coef[n,p]*P[p] + Coef[n,p]*P[p]^2).
Unfortunately I'm not getting it quite right. Any suggestions on best way to do this?
Thank you very much!

Calculating a column in SQL using the column's own output as input

I have problem that I find very hard to solve:
I need to calculate a column R_t in SQL where for each row, the sum of the "previous" calculated values SUM(R_t-1) is required as input. The calculation is done grouped over a ProjectID column. I have no clue how to proceed.
The formula for the calculation I am trying to achieve is R_t = ([Contract value]t - SUM(R{t-1})) / [Remaining Hours]_t * [HoursRegistered]t where "t" denotes time and SUM(R{t-1}) is the sum of R_t from t = 0 to t-1.
Time is always consecutive and always begin in t = 0. But number of time periods may differ across [ProjectID], i.e. one project having t = {0,1,2} and another t = {0,1,2,3,4,5}. The time period will never "jump" from 5 to 7
The expected output (using the data from below is) for ProjectID 101 is
R_0 = (500,000 - 0) / 500 * 65 = 65,000
R_1 = (500,000 - (65,000)) / 435 * 100 = 100,000
R_2 = (500,000 - (65,000 + 100,000)) / 335 * 85 = 85,000
R_3 = (500,000 - (65,000 + 100,000 + 85,000)) / 250 * 69 = 69,000
etc...
This calculation is done for each ProjectID.
My question is how to formulate this in a SQL query? My first thought was to create a recursive CTE, but I am actually not sure it is the right way proceed. Recursive CTE is (from my understanding) made for handling more of hierarchical like structure, which this isn't really.
My other thought was to calculate the SUM(R_t-1) using windowed functions, ie SUM OVER (PARITION BY ORDER BY) with a LAG, but the recursiveness really gives me trouble and I run my head against the wall when I am trying.
Below a query for creating the input data
CREATE TABLE [dbo].[InputForRecursiveCalculation]
(
[Time] int NULL,
ProjectID [int],
ContractValue float,
ContractHours float,
HoursRegistered float,
RemainingHours float
)
GO
INSERT INTO [dbo].[InputForRecursiveCalculation]
(
[Time]
,[ProjectID]
,[ContractValue]
,[ContractHours]
,[HoursRegistered]
,[RemainingHours]
)
VALUES
(0,101,500000,500,65,500),
(1,101,500000,500,100,435),
(2,101,500000,500,85,335),
(3,101,500000,500,69,250),
(4,101,450000,650,100,331),
(5,101,450000,650,80,231),
(6,101,450000,650,90,151),
(7,101,450000,650,45,61),
(8,101,450000,650,16,16),
(0,110,120000,90,10,90),
(1,110,120000,90,10,80),
(2,110,130000,90,10,70),
(3,110,130000,90,10,60),
(4,110,130000,90,10,50),
(5,110,130000,90,10,40),
(6,110,130000,90,10,30),
(7,110,130000,90,10,20),
(8,110,130000,90,10,10)
GO
For those of you who dare downloading something from a complete stranger, I have created an Excel file demonstrating the calculation (please download the file as you will not be to see the actual formula in the HTML representation shown when first clicking the link):
https://www.dropbox.com/s/3rxz72lbvooyc4y/Calculation%20example.xlsx?dl=0
Best regards,
Victor
I think it will be usefull for you. There is additional column SumR that stands for sumarry of previest rows (for ProjectID)
;with recu as
(
select
Time,
ProjectId,
ContractValue,
ContractHours,
HoursRegistered,
RemainingHours,
cast((ContractValue - 0)*HoursRegistered/RemainingHours as numeric(15,0)) as R,
cast((ContractValue - 0)*HoursRegistered/RemainingHours as numeric(15,0)) as SumR
from
InputForRecursiveCalculation
where
Time=0
union all
select
input.Time,
input.ProjectId,
input.ContractValue,
input.ContractHours,
input.HoursRegistered,
input.RemainingHours,
cast((input.ContractValue - prev.SumR)*input.HoursRegistered/input.RemainingHours as numeric(15,0)),
cast((input.ContractValue - prev.SumR)*input.HoursRegistered/input.RemainingHours + prev.SumR as numeric(15,0))
from
recu prev
inner join
InputForRecursiveCalculation input
on input.ProjectId = prev.ProjectId
and input.Time = prev.Time + 1
)
select
*
from
recu
order by
ProjectID,
Time
RESULTS:
Time ProjectId ContractValue ContractHours HoursRegistered RemainingHours R SumR
----------- ----------- ---------------------- ---------------------- ---------------------- ---------------------- --------------------------------------- ---------------------------------------
0 101 500000 500 65 500 65000 65000
1 101 500000 500 100 435 100000 165000
2 101 500000 500 85 335 85000 250000
3 101 500000 500 69 250 69000 319000
4 101 450000 650 100 331 39577 358577
5 101 450000 650 80 231 31662 390239
6 101 450000 650 90 151 35619 425858
7 101 450000 650 45 61 17810 443668
8 101 450000 650 16 16 6332 450000
0 110 120000 90 10 90 13333 13333
1 110 120000 90 10 80 13333 26666
2 110 130000 90 10 70 14762 41428
3 110 130000 90 10 60 14762 56190
4 110 130000 90 10 50 14762 70952
5 110 130000 90 10 40 14762 85714
6 110 130000 90 10 30 14762 100476
7 110 130000 90 10 20 14762 115238
8 110 130000 90 10 10 14762 130000

Working with an upper-triangular array in SAS (challenge +2 Points)

I'm looking to improve my code efficiency by turning my code into arrays and loops. The data i'm working with starts off like this:
ID Mapping Asset Fixed Performing Payment 2017 Payment2018 Payment2019 Payment2020
1 Loan1 1 1 1 90 30 30 30
2 Loan1 1 1 0 80 20 40 20
3 Loan1 1 0 1 60 40 10 10
4 Loan1 1 0 0 120 60 30 30
5 Loan2 ... ... ... ... ... ... ...
So For each ID (essentially the data sorted by Mapping, Asset, Fixed and then Performing) I'm looking to build a profile for the Payment Scheme.
The Payment Vector for the first ID looks like this:
PaymentVector1 PaymentVector2 PaymentVector3 PaymentVector4
1 0.33 0.33 0.33
It is represented by the formula
PaymentVector(I)=Payment(I)/Payment(1)
The above is fine to create in an array, example code can be given if you wish.
Next, under the assumption that every payment made is replaced i.e. when 30 is paid in 2018, it must be replaced, and so on.
I'm looking to make a profile that shows the outflows (and for illustration, but not required in code, in brackets inflows) for the movement of the payments as such - For ID=1:
Payment2017 Payment2018 Payment2019 Payment2020
17 (+90) -30 -30 -30
18 N/A (+30) -10 -10
19 N/A N/A (+40) -13.3
20 N/A N/A N/A (+53.3)
so if you're looking forwards, the rows can be thought of what year it is and the columns representing what years are coming up.
Hence, in year 2019, looking at what is to be paid in 2017 and 2018 is N/A because those payments are in the past / cannot be paid now.
As for in year 2018, looking at what has to be paid in 2019, you have to pay one-third of the money you have now, so -10.
I've been working to turn this dataset row by row into the array but there surely has to be a quicker way using an array:
The Code I've used so far looks like:
Data Want;
Set Have;
Array Vintage(2017:2020) Vintage2017-Vintage2020;
Array PaymentSchedule(2017:2020) PaymentSchedule2017-PaymentSchedule2020;
Array PaymentVector(2017:2020) PaymentVector2017-PaymentVector2020;
Array PaymentVolume(2017:2020) PaymentVolume2017-PaymentVolume2020;
do i=1 to 4;
PaymentVector(i)=PaymentSchedule(i)/PaymentSchedule(1);
end;
I'll add code tomorrow... but the code doesn't work regardless.
data have;
input
ID Mapping $ Asset Fixed Performing Payment2017 Payment2018 Payment2019 Payment2020; datalines;
1 Loan1 1 1 1 90 30 30 30
2 Loan1 1 1 0 80 20 40 20
3 Loan1 1 0 1 60 40 10 10
4 Loan1 1 0 0 120 60 30 30
data want(keep=id payment: fraction:);
set have;
array p payment:;
array fraction(4); * track constant fraction determined at start of profile;
array out(4); * track outlay for ith iteration;
* compute constant (over iterations) fraction for row;
do i = dim(p) to 1 by -1;
fraction(i) = p(i) / p(1);
end;
* reset to missing to allow for sum statement, which is <variable> + <expression>;
call missing(of out(*));
out(1) = p(1);
do iter = 1 to 4;
p(iter) = out(iter);
do i = iter+1 to dim(p);
p(i) = -fraction(i) * p(iter);
out(i) + (-p(i)); * <--- compute next iteration outlay with ye olde sum statement ;
end;
output;
p(iter) = .;
end;
format fract: best4. payment: 7.2;
run;
You've indexed your arrays with 2017:2020 but then try and use them using the 1 to 4 index. That won't work, you need to be consistent.
Array PaymentSchedule(2017:2020) PaymentSchedule2017-PaymentSchedule2020;
Array PaymentVector(2017:2020) PaymentVector2017-PaymentVector2020;
do i=2017 to 2020;
PaymentVector(i)=PaymentSchedule(i)/PaymentSchedule(2017);
end;

transact SQL, sum each row and insert into another table

for a table on ms-sql2000 containing the following columns and numbers:
S_idJ_id Se_id B_id Status Count multiply
63 1000 16 12 1 10 2
64 1001 12 16 1 9 3
65 1002 17 12 1 10 2
66 1003 16 12 1 6 3
67 1004 12 16 1 10 2
I want to generate an classic asp script which will do the following for each row
where status=1 :
-multiply -> answer= multiply column 'count' with column 'multiply'
Then:
count the total answer and sum for each se_id like :
se_id total
12 47
16 38
17 20
and display on screen like
Rank se_id total
1 12 47
2 16 38
3 17 20
Condition:
if there are multiple equal total values then give the lower numbered se_id a priority for
getting a ranking and give the next higher numbered se_id the next number in rank
Any sample code in classic asp or advice is welcome on how to get this accomplished
'score' = source table.
if (EXISTS (select * from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'result_table'))
begin
drop table result_table;
end
select
rank = IDENTITY(INT,1,1),
se_id, sum(multiply * count) as total
into result_table
from score
where status = 1
group by se_id
order by total desc, se_id;
[Edit] Change query as answer on first comment

Resources