SQL Server Percent Difference is Greater than Value - sql

I have the following table structures:
table c_alert:
|dynamic|symbol|price_usd|
--------------------------
|5 |BTC |13000 |
table c_current:
|symbol|price_usd|
------------------
|BTC |13600 |
I have this query:
SELECT dbo.c_alert.symbol, dbo.c_alert.price_usd AS alert_price, dbo.c_current.price_usd AS current_price, (dbo.c_current.price_usd - dbo.c_alert.price_usd) * 100.0 / dbo.c_alert.price_usd AS pct_diff, dbo.c_alert.dynamic AS pct
FROM dbo.c_alert INNER JOIN
dbo.c_current
ON dbo.c_alert.symbol = dbo.c_current.symbol AND
dbo.c_alert.dynamic > (dbo.c_current.price_usd - dbo.c_alert.price_usd) * 100.0 / dbo.c_alert.price_usd
Which returns this:
|symbol|alert_price|current_price|pct_diff|dynamic|
-----------------------------------------------
|BTC |13000 |13613.3000 |4.7 |5 |
Not very strong with financial queries...Basically I would like to know when the price difference between alert_price and current_price are equal to or greater than value in the dynamic column as a boolean. So where the difference is equal or greater than 5% show True, else False. That dynamic value (integer) could change for each row in the c_alert table. Hope someone can provide a solution to the query.

Because the same percent difference term is required in multiple places in the query, I might go with using a CTE first, which calculates this term. Then, do a straightforward query on the CTE to get the output you want.
WITH cte AS (
SELECT
t2.symbol,
t2.dynamic,
t2.price_usd AS alert_price,
t1.price_usd AS current_price,
100.0*(t1.price_usd - COALESCE(t2.price_usd, 0.0)) / t2.price_usd AS pct_diff
FROM dbo.c_current t1
LEFT JOIN dbo.c_alert t2
ON t1.symbol = t2.symbol
)
SELECT
symbol,
alert_price,
current_price,
pct_diff,
dynamic,
CASE WHEN pct_diff > dynamic THEN 'TRUE' ELSE 'FALSE' END AS result
FROM cte;
Edit:
The logic seems to be working in the demo below. If you still have issues, then edit the demo and paste the link somewhere as a comment.
Demo

Use table aliases so your query is easier to write and to read. Then just use a case:
SELECT a.symbol, a.price_usd AS alert_price,
c.price_usd AS current_price,
(c.price_usd - a.price_usd) * 100.0 / a.price_usd AS pct_diff,
a.dynamic AS pct,
(case when (a.price_usd - c.price_used) > a.dynamic
then 'true' else 'false'
end) as flag
FROM dbo.c_alert a INNER JOIN
dbo.c_current c
ON a.symbol = c.symbol AND
a.dynamic > (c.price_usd - a.price_usd) * 100.0 / a.price_usd;
SQL Server doesn't have a boolean type, so this uses a string. You can use 0 and 1 instead.

Related

How to call a calculated column in "from" in sql server

I'm trying to call this calculated column 'RelativeEffectiveSpreadAbsoluteValue' in SQL servers' FROM part.
, case when cast(sa.Mid_Price as float) = 0
then 0
else ((CAST(sa.Ask_Price as float)-cast(sa.Bid_Price as float))/CAST(sa.Mid_Price as float))/(0.01/100)
end As RelativeEffectiveSpreadAbsoluteValue
like this, but the SQL server won't recognize it
left join [RISK].[dbo].[FILiquidityBuckets] FB6
ON FB6.Metric = 'Relative spread ' AND (
((CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)>= 0 AND CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)< 1000000) AND
FB6.LiquidityScore = 5) OR
((CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)>= 1000000 AND CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)<10000000) AND
FB6.LiquidityScore = 4) OR
((CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)>= 10000000 AND CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)< 100000000) AND
FB6.LiquidityScore = 3) OR
((CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)>= 100000000 AND CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT)<1000000000) AND
FB6.LiquidityScore = 2) OR
(CAST(RelativeEffectiveSpreadAbsoluteValue AS FLOAT) >= 1000000000 AND F65.LiquidityScore = 1)
)
So far I know by using 'Cross Apply' a calculated column can calculate another column in the same view,
like this example
Select
ColumnA,
ColumnB,
c.calccolumn1 As calccolumn1,
c.calccolumn1 / ColumnC As calccolumn2
from t42
cross apply (select (ColumnA + ColumnB) as calccolumn1) as c
but this is only for the select part, can we use it in the From part?
Please help thank you!
Put the apply operation which does the calculation prior to the join in your query:
create table t(a int);
create table u(b int);
select t.a,
t2.calculatedColumn,
u.b
from t
cross apply (select t.a * 2) as t2 (calculatedColumn)
left join u on u.b = t2.calculatedColumn
As Panagiotis observed, this may result in a slow join because the join predicate will not be able to use an index. But if the nature of your query demands it, the language supports it.
If you need this to be fast, create an indexed computed column on the table you have aliased as sa instead of calculating it in the query. Since your column will be of type float, you will need to mark the computed column as persisted before you can index it.

Out of range integer: infinity

So I'm trying to work through a problem thats a bit hard to explain and I can't expose any of the data I'm working with but what Im trying to get my head around is the error below when running the query below - I've renamed some of the tables / columns for sensitivity issues but the structure should be the same
"Error from Query Engine - Out of range for integer: Infinity"
WITH accounts AS (
SELECT t.user_id
FROM table_a t
WHERE t.type like '%Something%'
),
CTE AS (
SELECT
st.x_user_id,
ad.name as client_name,
sum(case when st.score_type = 'Agility' then st.score_value else 0 end) as score,
st.obs_date,
ROW_NUMBER() OVER (PARTITION BY st.x_user_id,ad.name ORDER BY st.obs_date) AS rn
FROM client_scores st
LEFT JOIN account_details ad on ad.client_id = st.x_user_id
INNER JOIN accounts on st.x_user_id = accounts.user_id
--WHERE st.x_user_id IN (101011115,101012219)
WHERE st.obs_date >= '2020-05-18'
group by 1,2,4
)
SELECT
c1.x_user_id,
c1.client_name,
c1.score,
c1.obs_date,
CAST(COALESCE (((c1.score - c2.score) * 1.0 / c2.score) * 100, 0) AS INT) AS score_diff
FROM CTE c1
LEFT JOIN CTE c2 on c1.x_user_id = c2.x_user_id and c1.client_name = c2.client_name and c1.rn = c2.rn +2
I know the query works for sure because when I get rid of the first CTE and hard code 2 id's into a where clause i commented out it returns the data I want. But I also need it to run based on the 1st CTE which has ~5k unique id's
Here is a sample output if i try with 2 id's:
Based on the above number of row returned per id I would expect it should return 5000 * 3 rows = 150000.
What could be causing the out of range for integer error?
This line is likely your problem:
CAST(COALESCE (((c1.score - c2.score) * 1.0 / c2.score) * 100, 0) AS INT) AS score_diff
When the value of c2.score is 0, 1.0/c2.score will be infinity and will not fit into an integer type that you’re trying to cast it into.
The reason it’s working for the two users in your example is that they don’t have a 0 value for c2.score.
You might be able to fix this by changing to:
CAST(COALESCE (((c1.score - c2.score) * 1.0 / NULLIF(c2.score, 0)) * 100, 0) AS INT) AS score_diff

Why is my output not exactly 2.39?

I need the output with 2 digit like 2.39 but I got the output 2.3980815347721822541966.
That is the problem I don't understand.
Here is my Query.
SELECT M.Description,A.Target,A.Actual,
CASE
WHEN A.Target > 0 THEN A.Actual / CONVERT(DECIMAL(18,2),A.Target)
ELSE 0
END Achievement
FROM (
SELECT * FROM ProcessData PD
WHERE
ProcessYear = 2015 AND ProcessMonth = 1
AND DepotID = 6 AND ModuleID =1
) A
LEFT OUTER JOIN Measure M
ON M.ID = A.MeasureID
You need to format the whole expression rather than just the divisor:
So rather than
THEN A.Actual / CONVERT(DECIMAL(18,2),A.Target)
Change it to:
THEN CONVERT(DECIMAL(18,2), A.Actual / A.Target)
Note that I would expect 2.398 to round to 2.40 (which is what the code I posted will do) - are you sure you want to round down always? If so, look at the ROUND function as well.
THEN CONVERT(DECIMAL(18,2), ROUND(A.Actual / A.Target, 2, 1)

sql sum group by

I have following sql query
DECLARE #frameInt INT
SELECT TOP 1 #frameInt = gs.FrameGenerationInterval
FROM dbo.GlobalSettings gs
SELECT mti.InternalId,
b.InternalId AS BrandId,
CASE
WHEN DATEDIFF(second, mti.StartTime, mti.EndTime) / #frameInt > 0 THEN DATEDIFF(second, mti.StartTime, mti.EndTime) / #frameInt
ELSE 1
END AS ExposureAmount,
c.InternalId AS ChannelId,
c.Name AS ChannelName,
COALESCE( (p.Rating *
CASE
WHEN DATEDIFF(second, mti.StartTime, mti.EndTime) / #frameInt > 0 THEN DATEDIFF(second, mti.StartTime, mti.EndTime) / #frameInt
ELSE 1
END * CAST (17.5 AS decimal(8,2))
),CAST( 0 as decimal(8,2)) ) AS Equivalent
FROM dbo.MonitorTelevisionItems mti
LEFT JOIN dbo.Brands b ON mti.BrandId = b.InternalId
LEFT JOIN dbo.Channels c ON mti.ChannelId = c.InternalId
LEFT JOIN dbo.Programs p ON mti.ProgramId = p.InternalId
--WHERE mti.Date >= #dateFromLocal AND mti.Date <= #dateToLocal
GROUP BY mti.InternalId, mti.EndTime, mti.StartTime,
c.Name, p.Name, p.Rating,b.InternalId, c.InternalId
It gives following result
I would like it to return 1 row with sums of exposure amount and equivalent from all rows. Rest of the cells are the same apart from InternalId that I dont really need (i can remove it from query)
I am not very good at sql. Thank for help.
For the sake of posterity (and because it's cool to learn something new), here's the general solution to your problem (instead of a copy-and-paste ready solution for your specific case):
SELECT group_field1, group_field2, ...,
SUM(sum_field1), SUM(sum_field2), ...
FROM (...your original SQL...) AS someArbitraryAlias
GROUP BY group_field1, group_field2, ...
In your specific case, the group fields would be BrandId, ChannelId and ChannelName; the sum fields would be ExposureAmount and Equivalent.
Note: To ease readability (since your original SQL is quite complex), you can use a common table expression:
WITH someArbitraryAlias AS (
...your original SQL...
)
SELECT group_field1, group_field2, ...,
SUM(sum_field1), SUM(sum_field2), ...
FROM someArbitraryAlias
GROUP BY group_field1, group_field2, ...
Note that, when using common table expressions, the immediately preceding statement must be terminated with a semicolon.

Find closest numeric value in database

I need to find a select statement that will return either a record that matches my input exactly, or the closest match if an exact match is not found.
Here is my select statement so far.
SELECT * FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p'
ORDER BY Area DESC
What I need to do is find the closest match to the 'Area' field, so if my input is 1.125 and the database contains 2, 1.5, 1 and .5 the query will return the record containing 1.
My SQL skills are very limited so any help would be appreciated.
get the difference between the area and your input, take absolute value so always positive, then order ascending and take the first one
SELECT TOP 1 * FROM [myTable]
WHERE Name = 'Test' and Size = 2 and PType = 'p'
ORDER BY ABS( Area - #input )
something horrible, along the lines of:
ORDER BY ABS( Area - 1.125 ) ASC LIMIT 1
Maybe?
If you have many rows that satisfy the equality predicates on Name, Size, and PType columns then you may want to include range predicates on the Area column in your query. If the Area column is indexed this could allow efficient index-based access.
The following query (written using Oracle syntax) uses one branch of a UNION ALL to find the record with minimal Area >= your target, while the other branch finds the record with maximal Area < your target. One of these two records will be the record that you are looking for. Then you can ORDER BY ABS(Area - ?input) to pick the winner out of those two candidates. Unfortunately the query is complex due to nested SELECTS that are needed to enforce the desired ROWNUM / ORDER BY precedence.
SELECT *
FROM
(SELECT * FROM
(SELECT * FROM
(SELECT * FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p' AND Area >= ?target
ORDER BY Area)
WHERE ROWNUM < 2
UNION ALL
SELECT * FROM
(SELECT * FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p' AND Area < ?target
ORDER BY Area DESC)
WHERE ROWNUM < 2)
ORDER BY ABS(Area - ?target))
WHERE rownum < 2
A good index for this query would be (Name, Size, PType, Area), in which case the expected query execution plan would be based on two index range scans that each returned a single row.
SELECT *
FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p'
ORDER BY ABS(Area - 1.125)
LIMIT 1
-- MarkusQ
How about ordering by the difference between your input and [Area], such as:
DECLARE #InputValue DECIMAL(7, 3)
SET #InputValue = 1.125
SELECT TOP 1 * FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p'
ORDER BY ABS(#InputValue - Area)
Note that although ABS() is supported by pretty much everything, it's not technically standard (in SQL99 at least). If you must write ANSI standard SQL for some reason, you'd have to work around the problem with a CASE operator:
SELECT * FROM myTable
WHERE Name='Test' AND Size=2 AND PType='p'
ORDER BY CASE Area>1.125 WHEN 1 THEN Area-1.125 ELSE 1.125-Area END
If using MySQL
SELECT * FROM [myTable] ... ORDER BY ABS(Area - SuppliedValue) LIMIT 1