random floating point error with subquery order - sql

I am running sql server 2008 database, i am using the following query in a web app, but for the point of debugging the error i am directly running the query in management studio.
I am getting the following error - An invalid floating point operation occurred. when running this query.
select p.Id as Id, p.CatId as CatId, p.MetaName as MetaName ,p.Active as Active,p.HasChildren as HasChildren ,p.Mlevel as Mlevel ,p.ParentId as ParentId ,p.Type as Type, p.VOrder as VOrder, p.UrlOrder as UrlOrder, Count('*') as VCount
from MetaDataValues as m
left join MetaData as p on m.MetaDataId = p.Id
left join Adverts as a on m.AdvertId = a.Id
where a.Status = 1
and a.ExpDate > current_timestamp and
m.AdvertId in
(select m2.AdvertId from MetaDataValues as m2 left join MetaData as p2 on m2.MetaDataId = p2.Id where p2.MetaName = 'meta1'
and m.AdvertId in (select m3.AdvertId from MetaDataValues as m3 left join MetaData as p3 on m3.MetaDataId = p3.Id where p3.MetaName = 'meta2'
and m.AdvertId in (select m4.AdvertId from MetaDataValues as m4 left join MetaData as p4 on m4.MetaDataId = p4.Id where p4.MetaName = 'meta3'
and m.AdvertId in (select ad9.Id from Adverts as ad9 where dbo.GetDist(ad9.X,ad9.Y,ad9.Z,52.9131514,-2.9313405) < 969))))
group by p.Id, p.CatId, p.MetaName,p.Active,p.HasChildren,p.Mlevel,p.ParentId,p.Type, p.VOrder, p.UrlOrder
To explain its the GetDist function that is causing the problem, if i move this up in the subqueries to the top level the query runs fine?? This isnt an ideal as the code that builds this query is coded in a certain way and i dont want to alter it. so here is the query that works, exactly the same but a different order!
select p.Id as Id, p.CatId as CatId, p.MetaName as MetaName ,p.Active as Active,p.HasChildren as HasChildren ,p.Mlevel as Mlevel ,p.ParentId as ParentId ,p.Type as Type, p.VOrder as VOrder, p.UrlOrder as UrlOrder, Count('*') as VCount
from MetaDataValues as m
left join MetaData as p on m.MetaDataId = p.Id
left join Adverts as a on m.AdvertId = a.Id
where a.Status = 1
and a.ExpDate > current_timestamp and
m.AdvertId in
(select m2.AdvertId from MetaDataValues as m2 left join MetaData as p2 on m2.MetaDataId = p2.Id where p2.MetaName = 'meta1'
and m.AdvertId in (select ad9.Id from Adverts as ad9 where dbo.GetDist(ad9.X,ad9.Y,ad9.Z,52.9131514,-2.9313405) < 969)
and m.AdvertId in (select m3.AdvertId from MetaDataValues as m3 left join MetaData as p3 on m3.MetaDataId = p3.Id where p3.MetaName = 'meta2'
and m.AdvertId in (select m4.AdvertId from MetaDataValues as m4 left join MetaData as p4 on m4.MetaDataId = p4.Id where p4.MetaName = 'meta3' )))
group by p.Id, p.CatId, p.MetaName,p.Active,p.HasChildren,p.Mlevel,p.ParentId,p.Type, p.VOrder, p.UrlOrder
GetDist code
USE [MVC]
GO
/****** Object: UserDefinedFunction [dbo].[GetDist] Script Date: 02/20/2013 17:05:00 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[GetDist]
(
#xaxis float,
#yaxis float,
#zaxis float,
#CenterLat float,
#CenterLon float
)
RETURNS float
AS
BEGIN
declare #CntXAxis float
declare #CntYAxis float
declare #CntZAxis float
declare #EarthRadius float
set #EarthRadius = 3961
set #CntXAxis = cos(radians(#CenterLat)) * cos(radians(#CenterLon))
set #CntYAxis = cos(radians(#CenterLat)) * sin(radians(#CenterLon))
set #CntZAxis = sin(radians(#CenterLat))
return (#EarthRadius * acos( #XAxis*#CntXAxis + #YAxis*#CntYAxis + #ZAxis*#CntZAxis))
END

It looks like GetDist may be calculating the distance between a pair of latitude/longitude values. I have a lot of experience with this. Most GetDist functions like this use the Arc Cosine function "ACos". The parameter for this function is limited to the range -1 to 1. If you try to pass a value outside this range, you will get a domain error in SQL Server. If your GetDist function uses a CLR function, the error would be within your .net code and would have a slightly different message.
When dealing with floats, you have to be aware of weird rounding issues. For example, if your calculations would return a value of 1.00000000000001, and you pass that in to the ACos function, you will get an error.
There's a lot of speculation here, and I could be completely off base, but please consider this and spend a couple minutes doing some research.
Based on your GetDist function posted above, I would suggest a relatively minor change:
ALTER FUNCTION [dbo].[GetDist]
(
#xaxis float,
#yaxis float,
#zaxis float,
#CenterLat float,
#CenterLon float
)
RETURNS float
AS
BEGIN
declare #CntXAxis float
declare #CntYAxis float
declare #CntZAxis float
declare #EarthRadius float
declare #Temp float
set #EarthRadius = 3961
set #CntXAxis = cos(radians(#CenterLat)) * cos(radians(#CenterLon))
set #CntYAxis = cos(radians(#CenterLat)) * sin(radians(#CenterLon))
set #CntZAxis = sin(radians(#CenterLat))
Set #Temp = #XAxis*#CntXAxis + #YAxis*#CntYAxis + #ZAxis*#CntZAxis
If #Temp > 1
Set #Temp = 1
Else If #Temp < -1
Set #Temp = -1
return (#EarthRadius * acos(#Temp))
END
Even if this doesn't solve your original problem, it will at least protect you from weird float/precision problems.

Is the problem an invalid operation or something more like "Error converting data type varchar to numeric"? This is a fairly common problem, when numeric data is being stored as a string. It works when the string looks right, but then fails at other times.
Are all the arguments to getDist() of the correct type? Is the return value a number?
I would guess that the filtering at the higher level filters out the bad values that are causing the problem.

Related

SAP B1 Query Returns "Must declare scalar variable " error

I am attempting to create a drop down menu which will limit the query results by item buyer with the following SQL code. However, I keep getting an error stating that I need to declare the scalar variable #ItemBuyer, despite the fact that it is clearly declared and set in the code.
DECLARE #ItemBuyer VARCHAR(30)
SET #ItemBuyer= /* T3.OwnerCode */ '[%1]'
DECLARE #SQL VARCHAR(MAX)
SET #SQL = 'SELECT T3.[DocNum] AS DocNumber, T3.[CardCode] AS
VendorCode, T3.[CardName] AS VendorName, T3.OwnerCode as BuyerID, T2.
[ItemCode] AS ItemNo, T2.[U_CPM_LegItemNo] AS LegacyItemNumber, T2.
[Dscription] AS ItemDescription, T2.[U_CPM_ConfDate] AS POConfirmDate,
T2.[OpenCreQty] AS CreditMemoAmount FROM [dbo].[OITG] T0 , [dbo].
[OITM] T1 INNER JOIN [dbo].[POR1] T2 ON T2.[ItemCode] = T1.
[ItemCode] INNER JOIN [dbo].[OPOR] T3 ON T3.[DocEntry] = T2.
[DocEntry] WHERE (T2.[OpenCreQty] > (0 ) ) AND (T2.[U_CPM_ConfDate]
IS NULL ) and (T3.[OwnerCode] = #ItemBuyer)'
EXEC(#SQL)
I also tried declaring and setting the variable like below:
DECLARE #ItemBuyer VARCHAR(30) = /* T3.[OwnerCode] */ '[%0]'
But then I get an error stating that the syntax is wrong, even through the variable now returns the correct value. Im a little stuck here. Hopefully someone can help me out.
Thanks,
Krys
I would rewrite the query like this:
/*SELECT FROM [dbo].[OPOR] P1*/
declare #ItemBuyer as int
/* WHERE */
set #ItemBuyer = /* P1.OwnerCode */ '[%0]'
SELECT T3.[DocNum] AS DocNumber
, T3.[CardCode] AS VendorCode
, T3.[CardName] AS VendorName
, T3.OwnerCode as BuyerID
, T2.[ItemCode] AS ItemNo
, T2.[U_CPM_LegItemNo] AS LegacyItemNumber
, T2.[Dscription] AS ItemDescription
, T2.[U_CPM_ConfDate] AS POConfirmDate
, T2.[OpenCreQty] AS CreditMemoAmount
FROM [dbo].[POR1] T2
INNER JOIN [dbo].[OPOR] T3 ON T3.[DocEntry] = T2.[DocEntry]
WHERE T2.[OpenCreQty] > 0
AND T2.[U_CPM_ConfDate] IS NULL
and T3.[OwnerCode] = #ItemBuyer
I still can't manage to remember the dynamic syntax query, so I have to use this link every single time I want to do it: http://www.clientsfirst-us.com/blog/sap/sap-business-one/user-defined-prompts-in-sap-business-one-queries/
Notice, also, that I got rid of a couple tables that you weren't using in your select or your where statements. It's probably not going to make THAT big of a difference in the query speed, but it just makes it cleaner. (I also cleaned up the formatting to put it in my favorite style, but that's not strictly necessary)

Can we create a view after a script from a variable?

I would like to create a view at the end of the following request.
I know that 'create view' must be the first statement in a query batch. My problem is that for this query i must use a variable (#listOfIDRUB).
This variable is only fill correctly at the end of my little script.
I also have tried to create the view before my first declaration but it created a problem with "DECLARE".
So is it possible to create a view easily from the result of my script or i have to do something else ?
DECLARE #CounterResId int;
DECLARE #lePath varchar(255);
DECLARE #listOfIDRUB TABLE (EXTERNALREFERENCE uniqueidentifier, ID varchar(255), DOCID varchar(255) );
DECLARE #Max int;
SET #lePath = '';
SET #CounterResId = 1;
SET #Max = (SELECT COUNT(*) FROM SYNTHETIC..EXTRANET_PURGE WHERE TYPE_SUPPR = 'ResId')
WHILE (#CounterResId <= #Max )
BEGIN;
set #lePath =
(select tmp.lePath from
(
select row_number() over(order by path)as NumLigne, CONCAT(path, '%' ) as lePath from RUBRIQUE
WHERE MODELE = 'CAEEE64D-2B00-44EF-AA11-6B72ABD9FE38'
and CODE in (SELECT ID FROM SYNTHETIC..EXTRANET_PURGE where TYPE_SUPPR='ResId')
) tmp
WHERE tmp.NumLigne = #CounterResId)
INSERT INTO #listOfIDRUB(EXTERNALREFERENCE, ID, DOCID)
SELECT SEC.EXTERNALREFERENCE , SEC.ID, SEC.DOCUMENTID
FROM WEBACCESS_FRONT..SECTIONS sec
inner join rubrique rub ON rub.ID_RUBRIQUE = sec.EXTERNALREFERENCE
inner join template_tree_item tti ON tti.id_template_tree_item = rub.modele
inner join template t ON t.id_template = tti.template
WHERE t.CODE IN (SELECT TEMPLATE_CODE from SYNTHETIC..EasyFlowEngineListTemplateCode)
and rub.path like #lePath
print #CounterResId;
print #lePath;
set #CounterResId = #CounterResId + 1;
END;
select * from #listOfIDRUB;
Instead of select * from #listOfIDRUB
i wanted create view test as select * from listOfIDRUB
I have also tried create view test as (all my request)
Whenever you ask something about SQL please state your RDBMS (product and version). The answers are highly depending on this...
From your code I assume this is SQL Server.
So to your question: No, a VIEW must be "inlineable" (single-statement or "ad-hoc") statement.
You might think about a multi-statement UDF, but this is in almost all cases a bad thing (bad performance). Only go this way, if your result table will consist of rather few rows!
Without knowing your tables this is rather blind walking, but you might try this (add parameters, if you can transfer external operations (e.g. filtering) into the function):
CREATE FUNCTION dbo.MyFunction()
RETURNS #listOfIDRUB TABLE (EXTERNALREFERENCE uniqueidentifier, ID varchar(255), DOCID varchar(255) )
AS
BEGIN
DECLARE #CounterResId int;
DECLARE #lePath varchar(255);
DECLARE #Max int;
SET #lePath = '';
SET #CounterResId = 1;
SET #Max = (SELECT COUNT(*) FROM SYNTHETIC..EXTRANET_PURGE WHERE TYPE_SUPPR = 'ResId')
WHILE (#CounterResId <= #Max )
BEGIN;
set #lePath =
(select tmp.lePath from
(
select row_number() over(order by path)as NumLigne, CONCAT(path, '%' ) as lePath from RUBRIQUE
WHERE MODELE = 'CAEEE64D-2B00-44EF-AA11-6B72ABD9FE38'
and CODE in (SELECT ID FROM SYNTHETIC..EXTRANET_PURGE where TYPE_SUPPR='ResId')
) tmp
WHERE tmp.NumLigne = #CounterResId)
INSERT INTO #listOfIDRUB(EXTERNALREFERENCE, ID, DOCID)
SELECT SEC.EXTERNALREFERENCE , SEC.ID, SEC.DOCUMENTID
FROM WEBACCESS_FRONT..SECTIONS sec
inner join rubrique rub ON rub.ID_RUBRIQUE = sec.EXTERNALREFERENCE
inner join template_tree_item tti ON tti.id_template_tree_item = rub.modele
inner join template t ON t.id_template = tti.template
WHERE t.CODE IN (SELECT TEMPLATE_CODE from SYNTHETIC..EasyFlowEngineListTemplateCode)
and rub.path like #lePath
--print #CounterResId;
--print #lePath;
set #CounterResId = #CounterResId + 1;
END;
RETURN;
END
You can call it like this (very similar to a VIEW)
SELECT * FROM dbo.MyFunction();
And you might even use it in joins...
And last but not least I'm quite sure, that one could solve this without declares and a loop too...

converting nvarchar to int error

I have this query that I can't quite work out the error I'm getting.
Conversion failed when converting the nvarchar value '1.5' to data type int.
This is due to pvlaue for one case pulling a '1.5' string and trying to convert it to an int implicitly. I tried to rectify this by casting to a float but this doesn't seem to work for me. Any help in this regard would be very helpful.
I'm running this on SQL Server 2012 Management Studio.
BEGIN DECLARE #Item NVARCHAR(100)
SET #Item = 'Water'
SELECT
d.DESCRIPTION, d.ITEM_CODE,
el.ENUM_LABEL as Building_Block,
CAST(itpC.pvalue AS DECIMAL(22, 3)), ffi.where_used,
d.STATUS_IND
FROM
FSITEM d
LEFT JOIN
FSITEMTECHPARAM itp ON d.ITEM_CODE = itp.ITEM_CODE
AND itp.PARAM_CODE = 'BUILDING_BLOCK'
LEFT JOIN
FSVALIDENUMVALCF ev ON CAST(coalesce(itp.PVALUE, 0) as float) = CAST(ev.ENUM_VALUE AS FLOAT)
AND ev.ENUM_CODE = 'C_BUILDING_BLOCK_TYP'
LEFT JOIN
FSVALIDENUMLABELCF el ON ev.ENUM_CODE = el.ENUM_CODE
AND ev.ENUM_ORDER = el.ENUM_ORDER
AND el.LANGUAGE_CODE = 'EN-US'
LEFT JOIN
(SELECT
item_Code, max(CAST(pvalue AS FLOAT)) as pvalue
FROM
FSITEMTECHPARAM
JOIN
KC_SITE_COST_MAP ON FSITEMTECHPARAM.PARAM_CODE = 'COST_'+KC_SITE_COST_MAP.COST_CODE
GROUP BY
ITEM_CODE
UNION
SELECT
ITEM_CODE, CAST(pvalue AS FLOAT) as pvalue
FROM
FSFORMULA
JOIN
FSFORMULATECHPARAM ON FSFORMULA.FORMULA_ID = FSFORMULATECHPARAM.FORMULA_ID
AND PARAM_CODE = 'COST_TOTAL_BASE'
WHERE
FSFORMULA.FORMULA_ID IN (SELECT FORMULA_ID FROM FSITEM)) itpC ON D.item_code = itpC.item_code
LEFT JOIN
(SELECT
ffi.item_code, COUNT(distinct ffi.formula_id) where_used
FROM
fsformulaingred ffi
JOIN
fsformula ff ON ffi.formula_id = ff.formula_id
WHERE
ff.status_ind = 500 AND ff.logical_delete = 0
GROUP BY
ffi.item_code) ffi ON D.item_code = ffi.item_code
WHERE
d.LOGICAL_DELETE = 0
AND d.COMPONENT_IND <> 2
AND d.Status_IND < 600
AND (((CAST(ev.ENUM_VALUE as FLOAT) = 3 or CAST(ev.ENUM_VALUE as FLOAT) = 2)
AND d.Status_Ind < 600) OR
CAST(ev.ENUM_VALUE as FLOAT) = 1 or CAST(ev.ENUM_VALUE as FLOAT) = '0')
AND (d.Description Like #Item + '%')
--Or d.Description Like replace(#Item, '!', '%'))
ORDER BY
CAST(coalesce(ev.enum_value,0) as FLOAT) DESC, status_ind DESC, d.Item_Code
END
It's the coalesce that is doing the conversion to int, so casting the result from the coalesce is too late.
The result of an expression has to have the same type regardless of how the expression is evaluated. The expression coalesce(itp.PVALUE, 0) will be of the type int regardless of whether the value itp.PVALUE or the value 0 is used, because the type of 0 is int.
If you cast the 0 to float, the type of the expression will be float, so use coalesce(itp.PVALUE, cast(0 as float)).

SQL WHERE ... IN clause with possibly null parameter

I am having some problems with my WHERE clause (using SQL 2008) . I have to create a stored procedure that returns a list of results based on 7 parameters, some of which may be null. The ones which are problematic are #elements, #categories and #edu_id. They can be a list of ids, or they can be null. You can see in my where clause that my particular code works if the parameters are not null. I'm not sure how to code the sql if they are null. The fields are INT in the database.
I hope my question is clear enough. Here is my query below.
BEGIN
DECLARE #elements nvarchar(30)
DECLARE #jobtype_id INT
DECLARE #edu_id nvarchar(30)
DECLARE #categories nvarchar(30)
DECLARE #full_part bit
DECLARE #in_demand bit
DECLARE #lang char(2)
SET #jobtype_id = null
SET #lang = 'en'
SET #full_part = null -- full = 1, part = 0
SET #elements = '1,2,3'
SET #categories = '1,2,3'
SET #edu_id = '3,4,5'
select
jobs.name_en,
parttime.fulltime_only,
jc.cat_id category,
je.element_id elem,
jt.name_en jobtype,
jobs.edu_id minEdu,
education.name_en edu
from jobs
left join job_categories jc
on (jobs.job_id = jc.job_id)
left join job_elements je
on (jobs.job_id = je.job_id)
left join job_type jt
on (jobs.jobtype_id = jt.jobtype_id)
left join education
on (jobs.edu_id = education.edu_id)
left join
(select job_id, case when (jobs.parttime_en IS NULL OR jobs.parttime_en = '') then 1 else 0 end fulltime_only from jobs) as parttime
on jobs.job_id = parttime.job_id
where [disabled] = 0
and jobs.jobtype_id = isnull(#jobtype_id,jobs.jobtype_id)
and fulltime_only = isnull(#full_part,fulltime_only)
-- each of the following clauses should be validated to see if the parameter is null
-- if it is, the clause should not be used, or the SELECT * FROM ListToInt... should be replaced by
-- the field evaluated: ie if #elements is null, je.element_id in (je.element_id)
and je.element_id IN (SELECT * FROM ListToInt(#elements,','))
and jc.cat_id IN (SELECT * FROM ListToInt(#categories,','))
and education.edu_id IN (SELECT * FROM ListToInt(#edu_id,','))
order by case when #lang='fr' then jobs.name_fr else jobs.name_en end;
END
Something like
and (#elements IS NULL OR je.element_id IN
(SELECT * FROM ListToInt(#elements,',')))
and (#categories IS NULL OR
jc.cat_id IN (SELECT * FROM ListToInt(#categories,',')))
....
should do the trick
je.element_id IN (SELECT * FROM ListToInt(#elements,',')) OR #elements IS NULL
that way for each one
Have you tried explicitly comparing to NULL?
and (#elements is null or je.element_id IN (SELECT * FROM ListToInt(#elements,','))
And so on.

How can I roll up rate-of-return into net asset value in SQL?

Given the starting value #pStartingValue and a table which contains rorDate and ror what is the most efficient way to get the NAV at each date using just TSQL?
This mathematically trivial, and simple in code. I have a naive SQL implementation currently that relies on cursors.
On the first date, the NAV is #pStartingValue * ror
On every subsequent date, it's the previously calculated nav * ror or it's #pStartingValue * every previous ror
How would you efficiently do this only in MSSQL2005+?
DECLARE #rorDate DATE
DECLARE #getDate CURSOR
DECLARE #lastNAV as DECIMAL(19,7)
DECLARE #datedRoR as float
DECLARE #NAVTotals TABLE
(
NAV DECIMAL(19,7),
navDate DATE
)
SET #lastNAV = 100
SET #getDate = CURSOR FOR
SELECT
p.[DATE]
FROM
performance p
ORDER BY
p.[DATE]
OPEN #getDate
FETCH NEXT
FROM #getDate INTO #rorDate
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT
#datedRoR = b.finalNetReturn
FROM
performance b
WHERE
b.date = #rorDate
INSERT INTO #NAVTotals (NAV, navDate)
VALUES (#lastNAV * (1 + #datedRoR), #rorDate)
SELECT
#lastNAV = c.NAV
FROM
#NAVTotals c
WHERE
c.navDate = #rorDate
FETCH NEXT
FROM #getDate INTO #rorDate
END
CLOSE #getDate
DEALLOCATE #getDate
select * from #NAVTotals
You'll have to do some testing to see if the performance improves but this is a way to do that same thing without using a cursor. It's untested so you'll want to make sure to test it. I also cast b.finalNetReturn as a float, if it's already a float you can remove that part.
DECLARE #lastNAV as DECIMAL(19,7)
SET #lastNAV = 100
DECLARE #NAVTotals TABLE
(
NAV DECIMAL(19,7),
navDate DATE
);
INSERT INTO #NAVTotals (navDate)
SELECT [DATE]
FROM performance
ORDER BY [DATE] ASC;
UPDATE NT
SET #lastNAV = Nav = (#lastNAV * (1.0 +
(Cast((SELECT b.finalNetReturn
FROM performance b
WHERE b.date = NT.navDate) AS FLOAT))))
FROM #NAVTotals NT;
SELECT * FROM #NAVTotals ORDER BY navDate;
By dropping the lastNAV variable into the update statement you can update both. It works similar to:
a = a + 1
There is an example of this same approach here. Including some good numbers that compare the efficiency of the approach to other approaches such as cursors.
Perhaps I'm not understanding it correctly, but you don't even need a stored proc to achieve this.
SELECT p.[DATE] AS navDate
, #pStartingValue * PRODUCT(1 + b.finalNetReturn) AS NAV
FROM performance p
INNER JOIN performance b
ON b.[DATE] <= p.[DATE]
GROUP BY p.[DATE]
ORDER BY p.[DATE]
However, there are a few "wierdness" that I don't grasp.
How come there is no range limit for p.[DATE]?
Does the "performance" table really have only one asset?