SQL - positive part function (x)^+ - sql

I want to get positive part of a number x in sql. It means that the result is x if x>0 and zero otherwise. I mean to use it after an aggregate function.
select 1 as num, 200 as weight into #table
insert into #table values
(8, 100),
(10, 200),
(11, -300),
(20, -100);
Till now I have been using the following:
select sum(num * weight)/sum(weight) as Result,
IIf(sum(num * weight)/sum(weight)>0, sum(num * weight)/sum(weight), 0) as PositivePartResult
from #table
But it is not clear as the function gets longer. Is there a built-in function to get the same result without repetition of the formula?

Another way of writing same query is:
select Result,
case when Result > 0 Then Result else 0 end as PositivePartResult
from
(
select sum(num * weight)/sum(weight) as Result
from #table
)T

You could either calculate the value inline or, if you'll be doing this frequently, create a user defined function:
create function PositiveValue( #N as Int )
returns Int as
begin
return ( Sign( #N ) + 1 ) / 2 * #N;
end;
go
declare #Samples as Table ( N Int );
insert into #Samples ( N ) values ( -42 ), ( -1 ), ( 0 ), ( 1 ), ( 42 );
select N, ( Sign( N ) + 1 ) / 2 * N as PositiveValue1, dbo.PositiveValue( N ) as PositiveValue2
from #Samples;
-- drop function dbo.PositiveValue;

Related

SQL Server - Generate 6 float values that MAX - MIN = parameter

I need to generate 6 float values with 1 decimal in the best way and performance possible where:
MAX(value) - MIN(value) = #parameter
I have this code:
BEGIN
DECLARE #parameter float = 0.6
WHILE #validated = 0
BEGIN
IF #count < 6
BEGIN -- fill table with 6 random values from 0 to 2 (with 1 decimal)
INSERT INTO #tempdata ([value])
SELECT ROUND(RAND()*(2-0),1);
SET #count = #count + 1
END
IF #count = 6 -- if temp table has 6 values then do the validation
BEGIN
SELECT #result = (MAX(value) - MIN(value)) FROM #tempdata
IF(#result = #parameter)
BEGIN
PRINT 'MATCH PARAMETER'
SET #validated = 1
END
ELSE
BEGIN
DELETE #tempdata
SET #counter = 0
END
END
END
END
This is working but sometimes it takes 10 or 20 seconds and should be faster.
For example if #parameter value is: 0.8 then we need 6 numbers between 0 and 2 where the MAX - MIN match that, for example:
0.7
1.1
0.6
0.9
1.5
1.2
MAX(1.5) - MIN(0.7) = 0.8
Any clue?
Do the following:
Generate 6 random numbers between 0 and 1
Normalize the values to be between 0 and 0.8 (or whatever)
Add something back in if you don't want them to all start at 0
In SQL:
select x,
max(x) over () - min(x) over () as starting_at_0,
min(x) over () + 0.8 * (x - min(x) over ()) / (max(x) over () - min(x) over ()) as the_value_you_want
from (values (rand(checksum(newid()))),
(rand(checksum(newid()))),
(rand(checksum(newid()))),
(rand(checksum(newid()))),
(rand(checksum(newid()))),
(rand(checksum(newid())))
) v(x);
Here is a db<>fiddle.
Cast to numeric(2, 1), if you want only one decimal point.
EDIT:
I tend to forget that this also works:
select x,
max(x) over () - min(x) over () as starting_at_0,
min(x) over () + 0.8 * (x - min(x) over ()) / (max(x) over () - min(x) over ()) as the_value_you_want
from (values (rand()),
(rand()),
(rand()),
(rand()),
(rand()),
(rand())
) v(x);
(See here.)
SQL Server treats rand() in a special way. Each call to rand() is evaluated before the query is run. So, rand() has the same value on multiple rows in the result set. However, it has different values in different columns.
with randvals(rval) as
(
select rand()
union all
select rand()
union all
select rand()
union all
select rand()
union all
select rand()
union all
select rand()
),
arandvals(rval, xrval, mrval) as
(
select rval, max(rval) over() as xrval, min(rval) over() as mrval
from randvals
)
select cast(0.8 * rval / (xrval - mrval) as numeric(3,2))
from arandvals

sql Creating a function that limits by input

Trying to return the total sum of the bottom 3 results if I call the input as 3. Currently it returns as a dataset which isn't allowed, how would I fix this?
Code looks like
DELIMITER++
CREATE FUNCTION function1 (input INT) returns INT
BEGIN
DECLARE amount int;
SET amount = input;
SELECT SUM(T.C1*Y.C2)
FROM Table T, YTable Y
WHERE T.ID=Y.TID
ORDER BY T.C1*Y.C2 ASC
LIMIT amount;
END++
Table T (C1, ID) has values
(20,1), (50,2), (100,3), (110, 4)
YTable Y (C2, TID) has values
(30, 1), (90, 2), (110, 3), (160,4)
Expected output would be 20*30 + 50*90 + 100*110
=16,100
Use ROW_NUMBER() to determinate which are the bottom results.
WITH cte as (
SELECT T.C1*Y.C2 as total,
ROW_NUMBER() OVER (ORDER BY T.C1*Y.C2) as rn
FROM Table T
JOIN YTable Y
ON T.ID = Y.TID
)
SELECT SUM(total)
FROM cte
WHERE rn <= input
Construct the query using CONCAT(), prepare it, and execute it.

How to select only armstrong numbers from the list?

I want is to select Armstrong numbers from the list below list I have searched of solution of this question bu unable to find in SQL-Server:
Numbers
121
113
423
153
541
371
I am sure most of you know what's the Armstrong number and how to calculate though I am describing is for the simplicity : sum of the cubes of its digits is equal to the number itself i.e.
1*1*1 + 5*5*5 + 3*3*3 = 153
3*3*3 + 7*7*7 + 1*1*1 = 371
Please help me on this as I am also trying but seeking for quick solution. It will be very helpful to me. Thanks in advance.
Obviously static processing during each query is not correct approach but we can create function like this and
create function dbo.IsArmstrongNumber(#n int)
returns int as
begin
declare #retValue int = 0
declare #sum int = 0
declare #num int = #n
while #num > 0
begin
set #sum += (#num%10) * (#num%10) * (#num%10)
set #num = #num/10
end
IF #sum = #n
set #retValue = 1
return #retValue
end
Pre-processing and selecting in IN clause is better
select * from #Numbers where dbo.IsArmstrongNumber(n) = 1
select 153 x into #temp;
insert #temp values(371);
insert #temp values(541);
with cte as (select x, substring(cast(x as nvarchar(40)) ,1,1) as u, 1 as N FROM #temp
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),3)) = x
drop table #temp;
here is the mark 2 - you can change the #ORDER to explore power of 4,5 etc
declare #order int = 3;
declare #limit int = 50000;
with nos as (select 1 no
union all
select no + 1 from nos where no < #limit),
cte as (select no as x, substring(cast(no as nvarchar(40)) ,1,1) as u, 1 as N FROM nos
union all
select x, substring(cast(x as nvarchar(40)),n+1,1) as u , n+1 from cte where len(cast(x as nvarchar(40))) > n
)
select x from cte group by x having SUM(POWER(cast(u as int),#order)) = x
option (maxrecursion 0);
This is a quick mod to my sum of digits UDF
Declare #Table table (Numbers int)
Insert into #Table values
(121),
(113),
(423),
(153),
(541),
(371)
Select * from #Table where [dbo].[udf-Stat-Is-Armstrong](Numbers)=1
Returns
Numbers
153
371
The UDF
CREATE Function [dbo].[udf-Stat-Is-Armstrong](#Val bigint)
Returns Bit
As
Begin
Declare #RetVal as bigint
Declare #LenInp as bigint = len(cast(#Val as varchar(25)))
;with i AS (
Select #Val / 10 n, #Val % 10 d
Union ALL
Select n / 10, n % 10
From i
Where n > 0
)
Select #RetVal = IIF(SUM(power(d,#LenInp))=#Val,1,0) FROM i;
Return #RetVal
End
You can use the following to find Armstrong numbers using Sql functions:
WITH Numbers AS(
SELECT 0 AS number UNION ALL SELECT number + 1 FROM Numbers WHERE number < 10000)
SELECT number AS ArmstrongNumber FROM Numbers
WHERE
number = POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),1,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),2,1),0),3)
+ POWER(COALESCE(SUBSTRING(CAST(number AS VARCHAR(10)),3,1),0),3)
OPTION(MAXRECURSION 0)

Incremental Group BY

How I can achieve incremental grouping in query ?
I need to group by all the non-zero values into different named groups.
Please help me write a query based on columns date and subscribers.
If you have SQL Server 2012 or newer, you can use few tricks with windows functions to get this kind of grouping without cursors, with something like this:
select
Date, Subscribers,
case when Subscribers = 0 then 'No group'
else 'Group' + convert(varchar, GRP) end as GRP
from (
select
Date, Subscribers,
sum (GRP) over (order by Date asc) as GRP
from (
select
*,
case when Subscribers > 0 and
isnull(lag(Subscribers) over (order by Date asc),0) = 0 then 1 else 0 end as GRP
from SubscribersCountByDay S
) X
) Y
Example in SQL Fiddle
In general I advocate AGAINST cursors but in this case it ill not hurt since it ill iterate, sum up and do the conditional all in one pass.
Also note I hinted it with FAST_FORWARD to not degrade performance.
I'm guessing you do want what #HABO commented.
See the working example below, it just sums up until find a ZERO, reset and starts again. Note the and #Sum > 0 handles the case where the first row is ZERO.
create table dbo.SubscribersCountByDay
(
[Date] date not null
,Subscribers int not null
)
GO
insert into dbo.SubscribersCountByDay
([Date], Subscribers)
values
('2015-10-01', 1)
,('2015-10-02', 2)
,('2015-10-03', 0)
,('2015-10-04', 4)
,('2015-10-05', 5)
,('2015-10-06', 0)
,('2015-10-07', 7)
GO
declare
#Date date
,#Subscribers int
,#Sum int = 0
,#GroupId int = 1
declare #Result as Table
(
GroupName varchar(10) not null
,[Sum] int not null
)
declare ScanIt cursor fast_forward
for
(
select [Date], Subscribers
from dbo.SubscribersCountByDay
union
select '2030-12-31', 0
) order by [Date]
open ScanIt
fetch next from ScanIt into #Date, #Subscribers
while ##FETCH_STATUS = 0
begin
if (#Subscribers = 0 and #Sum > 0)
begin
insert into #Result (GroupName, [Sum]) values ('Group ' + cast(#GroupId as varchar(6)), #Sum)
set #GroupId = #GroupId + 1
set #Sum = 0
end
else begin
set #Sum = #Sum + #Subscribers
end
fetch next from ScanIt into #Date, #Subscribers
end
close ScanIt
deallocate ScanIt
select * from #Result
GO
For the OP: Please next time write the table, just posting an image is lazy
In a version of SQL Server modern enough to support CTEs you can use the following cursorless query:
-- Sample data.
declare #SampleData as Table ( Id Int Identity, Subscribers Int );
insert into #SampleData ( Subscribers ) values
-- ( 0 ), -- Test edge case when we have a zero first row.
( 200 ), ( 100 ), ( 200 ),
( 0 ), ( 0 ), ( 0 ),
( 50 ), ( 50 ), ( 12 ),
( 0 ), ( 0 ),
( 43 ), ( 34 ), ( 34 );
select * from #SampleData;
-- Run the query.
with ZerosAndRows as (
-- Add IsZero to indicate zero/non-zero and a row number to each row.
select Id, Subscribers,
case when Subscribers = 0 then 0 else 1 end as IsZero,
Row_Number() over ( order by Id ) as RowNumber
from #SampleData ),
Groups as (
-- Add a group number to every row.
select Id, Subscribers, IsZero, RowNumber, 1 as GroupNumber
from ZerosAndRows
where RowNumber = 1
union all
select FAR.Id, FAR.Subscribers, FAR.IsZero, FAR.RowNumber,
-- Increment GroupNumber only when we move from a non-zero row to a zero row.
case when Groups.IsZero = 1 and FAR.IsZero = 0 then Groups.GroupNumber + 1 else Groups.GroupNumber end
from ZerosAndRows as FAR inner join Groups on Groups.RowNumber + 1 = FAR.RowNumber
)
-- Display the results.
select Id, Subscribers,
case when IsZero = 0 then 'no group' else 'Group' + Cast( GroupNumber as VarChar(10) ) end as Grouped
from Groups
order by Id;
To see the intermediate results just replace the final select with select * from FlagsAndRows or select * from Groups.

MS SQL - User Defined Function - Slope Intercept RSquare ; How to Group by Portfolio

Below is an example of my data, table RR_Linest:
Portfolio ---- Month_number ---- Collections
A --- --------- 1 --------------------- $100-------------------------------------------------------------------------------------
A-------------- 2 --------------------- $90
A ------------- 3 --------------------- $80--------------------------------------------------------------------------------------
A ------------- 4 --------------------- $70--------------------------------------------------------------------------------------
B ------------- 1 -------------------- $100-------------------------------------------------------------------------------------
B ---- -------- 2 ---------------------- $90 -------------------------------------------------------------------------------------
B - ------------ 3 --------------------- $80
I was able to figure out how to how to get the slope,intercept, RSquare for one portfolio by removing the portfolio column and only selecting the month_Number (x) and collections data (y) for only one selected portfolio (I removed data for portfolio B) and running the code below.
I have been trying to change the function so that when I run it; it gives me the slope, intercept, and R-square by portfolio. Does someone know how to do that? I have tried many ways and I just can't figure it out.
First I created the function:
declare #RegressionInput_A [dbo].[RegressionInput_A]
insert into #RegressionInput_A (x,y)
select
([model month]),log([collection $])
from [dbo].[RR_Linest]
select * from [dbo].LinearRegression_A
GO
drop function dbo.LinearRegression_A
CREATE FUNCTION dbo.LinearRegression_A
(
#RegressionInputs_A AS dbo.RegressionInput_A READONLY
)
RETURNS #RegressionOutput_A TABLE
(
Slope DECIMAL(18, 6),
Intercept DECIMAL(18, 6),
RSquare DECIMAL(18, 6)
)
AS
BEGIN
DECLARE #Xaverage AS DECIMAL(18, 6)
DECLARE #Yaverage AS DECIMAL(18, 6)
DECLARE #slope AS DECIMAL(18, 6)
DECLARE #intercept AS DECIMAL(18, 6)
DECLARE #rSquare AS DECIMAL(18, 6)
SELECT
#Xaverage = AVG(x),
#Yaverage = AVG(y)
FROM
#RegressionInputs_A
SELECT
#slope = SUM((x - #Xaverage) * (y - #Yaverage))/SUM(POWER(x - #Xaverage, 2))
FROM
#RegressionInputs_A
SELECT
#intercept = #Yaverage - (#slope * #Xaverage)
SELECT #rSquare = 1 - (SUM(POWER(y - (#intercept + #slope * x), 2))/(SUM(POWER(y - (#intercept + #slope * x), 2)) + SUM(POWER(((#intercept + #slope * x) - #Yaverage), 2))))
FROM
#RegressionInputs_A
INSERT INTO
#RegressionOutput_A
(
Slope,
Intercept,
RSquare
)
SELECT
#slope,
#intercept,
#rSquare
RETURN
END
GO
Then I run the function
declare #RegressionInput_A [dbo].[RegressionInput_A]
insert into #RegressionInput_A (x,y)
select
([model month]),log([collection $])
from [dbo].[RR_Linest]
select * from [dbo].[LinearRegression_A](#RegressionInput_A)
Wow, this is a real cool example of how to use nested CTE's in a In Line Table Value Function. You want to use a ITVF since they are fast. See Wayne Sheffield’s blog article that attests to this fact.
I always start with a sample database/table if it is really complicated to make sure I give the user a correct solution.
Lets create a database named [test] based on model.
--
-- Create a simple db
--
-- use master
use master;
go
-- delete existing databases
IF EXISTS (SELECT name FROM sys.databases WHERE name = N'Test')
DROP DATABASE Test
GO
-- simple db based on model
create database Test;
go
-- switch to new db
use [Test];
go
Lets create a table type named [InputToLinearReg].
--
-- Create table type to pass data
--
-- Delete the existing table type
IF EXISTS (SELECT * FROM sys.systypes WHERE name = 'InputToLinearReg')
DROP TYPE dbo.InputToLinearReg
GO
-- Create the table type
CREATE TYPE InputToLinearReg AS TABLE
(
portfolio_cd char(1),
month_num int,
collections_amt money
);
go
Okay, here is the multi-layered SELECT statement that uses CTE's. The query analyzer treats this as a SQL statement which can be executed in parallel versus a regular function that can't. See the black box section of Wayne's article.
--
-- Create in line table value function (fast)
--
-- Remove if it exists
IF OBJECT_ID('CalculateLinearReg') > 0
DROP FUNCTION CalculateLinearReg
GO
-- Create the function
CREATE FUNCTION CalculateLinearReg
(
#ParmInTable AS dbo.InputToLinearReg READONLY
)
RETURNS TABLE
AS
RETURN
(
WITH cteRawData as
(
SELECT
T.portfolio_cd,
CAST(T.month_num as decimal(18, 6)) as x,
LOG(CAST(T.collections_amt as decimal(18, 6))) as y
FROM
#ParmInTable as T
),
cteAvgByPortfolio as
(
SELECT
portfolio_cd,
AVG(x) as xavg,
AVG(y) as yavg
FROM
cteRawData
GROUP BY
portfolio_cd
),
cteSlopeByPortfolio as
(
SELECT
R.portfolio_cd,
SUM((R.x - A.xavg) * (R.y - A.yavg)) / SUM(POWER(R.x - A.xavg, 2)) as slope
FROM
cteRawData as R
INNER JOIN
cteAvgByPortfolio A
ON
R.portfolio_cd = A.portfolio_cd
GROUP BY
R.portfolio_cd
),
cteInterceptByPortfolio as
(
SELECT
A.portfolio_cd,
(A.yavg - (S.slope * A.xavg)) as intercept
FROM
cteAvgByPortfolio as A
INNER JOIN
cteSlopeByPortfolio S
ON
A.portfolio_cd = S.portfolio_cd
)
SELECT
A.portfolio_cd,
A.xavg,
A.yavg,
S.slope,
I.intercept,
1 - (SUM(POWER(R.y - (I.intercept + S.slope * R.x), 2)) /
(SUM(POWER(R.y - (I.intercept + S.slope * R.x), 2)) +
SUM(POWER(((I.intercept + S.slope * R.x) - A.yavg), 2)))) as rsquared
FROM
cteRawData as R
INNER JOIN
cteAvgByPortfolio as A ON R.portfolio_cd = A.portfolio_cd
INNER JOIN
cteSlopeByPortfolio S ON A.portfolio_cd = S.portfolio_cd
INNER JOIN
cteInterceptByPortfolio I ON S.portfolio_cd = I.portfolio_cd
GROUP BY
A.portfolio_cd,
A.xavg,
A.yavg,
S.slope,
I.intercept
);
Last but not least, setup a Table Variable and get the answers. Unlike you solution above, it groups by portfolio id.
-- Load data into variable
DECLARE #InTable AS InputToLinearReg;
-- insert data
insert into #InTable
values
('A', 1, 100.00),
('A', 2, 90.00),
('A', 3, 80.00),
('A', 4, 70.00),
('B', 1, 100.00),
('B', 2, 90.00),
('B', 3, 80.00);
-- show data
select * from CalculateLinearReg(#InTable)
go
Here is a picture of the results using your data.
CREATE FUNCTION dbo.LinearRegression
(
#RegressionInputs AS dbo.RegressionInput READONLY
)
RETURNS TABLE AS
RETURN
(
WITH
t1 AS ( --calculate averages
SELECT portfolio, x, y,
AVG(x) OVER(PARTITION BY portfolio) Xaverage,
AVG(y) OVER(PARTITION BY portfolio) Yaverage
FROM #RegressionInputs
),
t2 AS ( --calculate slopes
SELECT portfolio, Xaverage, Yaverage,
SUM((x - Xaverage) * (y - Yaverage))/SUM(POWER(x - Xaverage, 2)) slope
FROM t1
GROUP BY portfolio, Xaverage, Yaverage
),
t3 AS ( --calculate intercepts
SELECT portfolio, slope,
(Yaverage - (slope * Xaverage) ) AS intercept
FROM t2
),
t4 AS ( --calculate rSquare
SELECT t1.portfolio, slope, intercept,
1 - (SUM(POWER(y - (intercept + slope * x), 2))/(SUM(POWER(y - (intercept + slope * x), 2)) + SUM(POWER(((intercept + slope * x) - Yaverage), 2)))) AS rSquare
FROM t1
INNER JOIN t3 ON (t1.portfolio = t3.portfolio)
GROUP BY t1.portfolio
)
SELECT portfolio, slope, intercept, rSquare FROM t4
)