SQL Server window function for running percentage - sql

I know there are several examples of recursion with CTE and so on, but how can this be accomplished just by using window functions in SQL Server 2012:
CREATE TABLE #temp
(
ID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
Percentage INT NOT NULL
)
DECLARE #Calculated MONEY = 1000
INSERT INTO #temp ( Percentage ) VALUES ( 100 )
INSERT INTO #temp ( Percentage ) VALUES ( 90)
INSERT INTO #temp ( Percentage ) VALUES ( 60)
INSERT INTO #temp ( Percentage ) VALUES ( 50)
INSERT INTO #temp ( Percentage ) VALUES ( 100)
And the result would be a running percentage like so (we are starting with $1000)
id percentage calculated
-- -------- ---------
1 100 1000
2 50 500
3 90 450
4 80 360
5 100 360
So the value for the next row is the percentage multiplied by the calculated value above that row. Can LAG be used on a computed alias?
Thanks,

You need a running product of the percentages instead of always comparing 2 consecutive rows, which is why LEAD and LAG won't work here.
You can use a windowed sum to keep a running product of the percentages against your variable to get your desired calculation:
SELECT
ID,
Expected,
EXP(SUM(LOG(CONVERT(FLOAT, Percentage) / 100)) OVER (ORDER BY ID)) * #Calculated AS Actual
FROM #Temp
Adding this to your sample code (with a column I added for your expected output):
CREATE TABLE #temp
(
ID INT PRIMARY KEY IDENTITY(1,1) NOT NULL,
Percentage INT NOT NULL,
Expected MONEY NOT NULL
)
DECLARE #Calculated MONEY = 1000
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 100 , 1000)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 50, 500)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 90, 450)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 80, 360)
INSERT INTO #temp ( Percentage, Expected ) VALUES ( 100, 360)
SELECT
ID,
Expected,
EXP(SUM(LOG(CONVERT(FLOAT, Percentage) / 100)) OVER (ORDER BY ID)) * #Calculated AS Actual
FROM #Temp
This will yield your expected output:
ID Expected Actual
----------- --------------------- ----------------------
1 1000.00 1000
2 500.00 500
3 450.00 450
4 360.00 360
5 360.00 360

you can use recursive cte to get the desired result
with cte
as
(
select id, percentage, 1000 as calculated
from #temp
where id =1
union all
select t.id, t.percentage, t.percentage*cte.calculated/100 as calculated
from #temp t
join cte
on t.id = cte.id+1
)
select * from cte

I'm afraid, widow functions won't help here (at least they won't make it simple). The easiest way to achieve your goal is update statement with double assignment:
alter table #temp add VAL decimal
declare #val decimal = 1000
update t set
#val = VAL = #val * Percentage / 100
from (select top 100 percent * from #temp order by id) as t
select * from #temp

Related

Generate Row Count, increasing in 10,000 per row

I'm needing to return a generated ID where for each row it increases by 10,000.
For example, the ExpectedResult column in the below, and if there are more rows, it would increase by 10,000 each time.
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
)
insert into #temp
(
ID,
ExpectedResult
)
select
NEWID(),
10000
union
select
NEWID(),
20000
union
select
NEWID(),
30000
union
select
NEWID(),
40000
union
select
NEWID(),
50000
select * from #temp
order by ExpectedResult
drop table #temp
I've found the example below, but I'm not sure how to increase the count by 10,000 each time
ROW_NUMBER() OVER (ORDER BY (SELECT 100))
If you are using SQL Server 2012 or later (including SQL Server 2017), you could create a numbering sequence using CREATE SEQUENCE.
To create SEQUENCE with increment of 10000, add the clause INCREMENT BY.
For example:
CREATE SEQUENCE Test.CountBy1
START WITH 10000
INCREMENT BY 10000
For more information, please consult this SQL Server documentation on CREATE SEQUENCE:
https://learn.microsoft.com/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-2017
you can use cte to generate N number for guids. Below is a sample cte to generate 100 rows for your guid.
Drop table #temp
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
);
with cte as(
select newid() as new_id, 10000 as ctr
union all
select new_id, ctr + 10000 from cte where ctr/10000 < 100
)
insert into #temp
select * from cte option (MaxRecursion 0 );
select * from #temp;
You need to divide ROW_NUMBER() by 10000 and then multiply by 10000
You can write something like this
select *, rowNum10K = 10000 * (1 + (row_number() over (order by object_id)) / 10000)
from #temp
As suggested by Nick in the original question comments, have done this:
ROW_NUMBER() OVER (ORDER BY (SELECT 100))*10000
You can define the column as an identity to do this for you:
Create Table temp (
ID uniqueidentifier,
ExpectedResult int identity (10000, 10000)
);
insert into temp (ID)
select v.id
from (values (NEWID()), (NEWID()), (NEWID()), (NEWID()), (NEWID())) v(id);
Here is a db<>fiddle.

Adding Random Id for each unique value in table

I have the table like
ID RANDOM_ID
1 123
10 456
25 789
1 1112
55 1314
10 1516
I want the result to be like :
ID RANDOM_ID
1 123
10 456
25 789
1 123
55 1314
10 456
The same ID should have same random_ids. I'm using the update statement to generate the Random_IDs after creating the table.
CREATE TABLE [RANDOMID_TABLE]([ID] [int] NULL, [RANDOM_ID] [int] NULL)
GO
INSERT INTO [RANDOMID_TABLE] ([ID])
select distinct ABC_ID from RANDOMID_ABC
GO
******** This is the update statement for the RANDOM_ID column in
[RANDOMID_TABLE] table ************
UPDATE [RANDOMID_TABLE]
SET RANDOM_ID = abs(checksum(NewId()) % 1000000)
Is there something else that I need to add to the update statement?
Please advise.
Why would you use update for this? Just generate the values when you insert them:
insert into [RANDOMID_TABLE] (ID, RANDOM_ID)
select ABC_ID, abs(checksum(NewId()) % 1000000)
from RANDOMID_ABC
group by ABC_ID;
EDIT:
If your problem is collisions, then fix how you do the assignment. Just assign a number . . . randomly:
insert into [RANDOMID_TABLE] (ID, RANDOM_ID)
select ABC_ID, row_number() over (order by newid())
from RANDOMID_ABC
group by ABC_ID;
This is guaranteed to not return duplicates.
At a total guess, are you simpling wanting to UPDATE the table so that all the values of a specific ID to have the same value for Random_ID? Like this?
CREATE TABLE YourTable (ID int, Random_ID int);
INSERT INTO YourTable
VALUES(1 ,123),
(10,456),
(25,789),
(1 ,1112),
(55,1314),
(10,1516);
GO
WITH CTE AS(
SELECT ID,
Random_ID,
MIN(Random_ID) OVER (PARTITION BY ID) AS Min_Random_ID
FROM YourTable)
UPDATE CTE
SET Random_ID = Min_Random_ID;
GO
SELECT *
FROM YourTable;
GO
DROP TABLE YourTable;
Here is the script you need with use of temporary table (you need it to persist your random results for each unique ID):
DECLARE #Tbl TABLE (ID INT, RANDOM_ID INT)
INSERT #Tbl (Id) VALUES(1), (10), (25), (1), (55), (10)
SELECT Id, abs(checksum(NewId()) % 1000000) AS Random_Id INTO #distinctData FROM #Tbl GROUP BY Id
SELECT D.* FROM #Tbl T JOIN #distinctData D ON D.ID = T.ID
DROP TABLE #distinctData
Obviously, you don't need the first two rows where I create and initialize data table
Result:
Id Random_Id
1 354317
1 62026
10 532304
10 604768
25 874209
55 718643
You want one random value per ID. So one should think that the following would work:
with ids as
(
select distinct id
from randomid_table
)
, ids_with_rnd as
(
select id, abs(checksum(NewId()) % 1000000) as rnd
from ids
)
update randomid_table
set random_id =
(
select rnd
from ids_with_rnd
where ids_with_rnd.id = randomid_table.id
);
It doesn't however. SQL Server is somewhat buggy here and still creates different numbers for the same ID.
So, your best bet may be: do your update that does create different values (your original update statement). Then correct the data as follows:
update randomid_table
set random_id =
(
select min(random_id)
from randomid_table rt2
where rt2.id = randomid_table.id
);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=504236db66fba0f12dc7e407a51451f8

Find missing numbers in a sequence in MS SQL

Say i have a table with an integer column Id. I need to find the missing numbers in a sequence with a maximum returned amount.
If the table is empty and i'm asking for 10, it should return the numbers 1-10.
If the table has 1-5 and i'm asking for 10, it should return the numbers 6,7,8,9,10,11,12,13,14,15.
If the table has 1,2,4,6,9 and im asking for 10, it should return the numbers 3,5,7,8,10,11,12,13,14,15
How can i achive this in one single query using MS SQL?
Thanks in advance!
Try this:
If you need to get more numbers, just increase the WHERE Number<=100.
DECLARE #Tab1 TABLE (ID INT)
INSERT INTO #Tab1 VALUES(1)
INSERT INTO #Tab1 VALUES(3)
INSERT INTO #Tab1 VALUES(5)
INSERT INTO #Tab1 VALUES(7)
INSERT INTO #Tab1 VALUES(9)
;WITH CTE AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1 FROM CTE
WHERE Number<=100
)
SELECT TOP 5 *
FROM CTE
WHERE Number NOT IN(SELECT ID FROM #Tab1)
ORDER BY Number
OPTION (maxrecursion 0);
Existing values:
Number
1
3
5
7
9
OutPut:
Number
2
4
6
8
10
Hope this helps you.
This should work
There are also a system table with numbers
declare #T table (i int primary key);
insert into #T values (1), (2), (4), (6), (9);
declare #count int = 10;
declare #size int = (select count(*) from #T);
with cte as
( select 1 as num
union all
select num + 1
from cte
where num + 1 <= (#count + #size)
)
select top (#count) cte.num
from cte
left join #T t
on t.i = cte.num
where t.i is null
order by cte.num
option ( MaxRecursion 0 );

SQL rounding to decimal places

I have a dataset which i need to do a calculation on price and round to decimal places. But the results aren't quite what is expected. The calculation is in the case statement
CREATE TABLE #Temp ( ID INT IDENTITY(1,1), Price DECIMAL(7,2) )
INSERT INTO #TEMP ( Price )
VALUES ( 119.99 )
, ( 48.99 )
SELECT
ID
, Price
, CASE WHEN Price > 10 THEN CONVERT( DECIMAL(7,2), Price * 1.08 - 0.05 ) END AS RRP
FROM #Temp
DROP TABLE #Temp
with the results
ID Price RRP
1 119.99 129.54
2 48.99 52.86
I need to get the 129.54 to 129.55 and the 52.86 to 52.85 within the same case statement if that is possible to match up with another data set
Are you rounding to the nearest 5 cents because your country has gotten rid of the penny? This is the only logic that we can seem to follow with your expected results.
SELECT ID
,Price
,CASE
WHEN Price > 10
THEN ROUND(CONVERT(DECIMAL(7, 2), Price * 1.08 - 0.05) * 20, 0) / 20
END AS RRP
FROM #Temp
Assuming you want to round to the nearest 5 cents or nearst 5 in 100ths decimal position.
INSERT INTO #TEMP ( Price )
VALUES ( 119.99 )
, ( 48.99 )
SELECT
ID
, Price
, Price * 1.08 - 0.05
, CASE WHEN Price > 10 THEN CONVERT( DECIMAL(7,2), round((Price * 1.08 - 0.05)*20,0)/20) END AS RRP
FROM #Temp
DEMO:http://rextester.com/QIM12944

transact-sql question

Assume there were 100 records in tableA and tableA contained a column named 'price'.
How do I select the first-n record if where sum of price > a certain amount (e.g. 1000) without using cursor?
thanks
Top N implies some kind of order, which you did not supply, so I assumed any random order.
You can change this on the OVER clause of the ROW_NUMBER().
Try something like
DECLARE #Table TABLE(
Price FLOAT
)
INSERT INTO #Table SELECT 1
INSERT INTO #Table SELECT 11
INSERT INTO #Table SELECT 12
INSERT INTO #Table SELECT 15
INSERT INTO #Table SELECT 10
INSERT INTO #Table SELECT 65
INSERT INTO #Table SELECT 100
DECLARE #TotalPrice FLOAT
SELECT #TotalPrice = 100
;WITH Vals AS (
SELECT *,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) RNR
FROM #Table
)
, Totals AS (
SELECT v.RNR,
SUM(vP.Price) TotalPrice
FROM Vals v LEFT JOIN
Vals vP ON v.RNR >= vP.RNR
GROUP BY v.RNR
)
, LimitValue AS (
SELECT TOP 1
RNR
FROM Totals
WHERE TotalPrice >= #TotalPrice
ORDER BY RNR
)
SELECT *
FROM Vals
WHERE RNR <= (
SELECT RNR
FROM LimitValue
)
select price from tableA
where price > 1000
limit n;
n - no. of records you want in result set
--
Cheers