Multiply value, round down and CAST Select in SQL - sql

I have a SQL function that gathers labour times from multiple records, and uses STUFF(CAST AS VARCHAR(20)) to concatenate them into a single column in a temp table.
My issue is the labour values in my table are in hours and rounded down to 7 decimals, I want to convert those values to minutes and round them down to 2 decimals before stuffing them into my temp table.
Update: Forgot to mention, I'm using SQL Server 2008 R2
Here is my code.
#site nvarchar(20)
,#item nvarchar(30)
,#enviro nvarchar(30))
RETURNS nvarchar(MAX)
AS
BEGIN
DECLARE #LbrHours AS nvarchar(MAX) = ''
IF #enviro = 'Test'
BEGIN
IF #site = 'Arborg'
BEGIN
SET #LbrHours = STUFF((SELECT ',' + CAST(jrt.run_lbr_hrs AS VARCHAR(20))
FROM Arborg_Test_App.dbo.jobroute AS jbr
INNER JOIN Arborg_Test_App.dbo.job AS job
ON job.job = jbr.job
AND job.suffix = jbr.suffix
INNER JOIN Arborg_Test_App.dbo.item AS itm
ON itm.job = job.job
INNER JOIN Arborg_Test_App.dbo.jrt_sch AS jsh
ON jbr.job = jsh.job
AND jbr.suffix = jsh.suffix
AND jbr.oper_num = jsh.oper_num
LEFT OUTER JOIN Arborg_Test_App.dbo.jrt_sch AS jrt
ON jbr.job = jrt.job
AND jbr.suffix = jrt.suffix
AND jbr.oper_num = jrt.oper_num
WHERE job.suffix = '0' and job.type = 'S' AND itm.item IS NOT NULL
AND itm.item = #item
AND jbr.suffix = CASE -- Return Standard cost if Standard Operation exist, else return current cost
WHEN itm.cost_type = 'S'
THEN '1' -- '1' for standard operation
ELSE '0' -- '0' for current operations
END
ORDER BY itm.item, jbr.oper_num
FOR XML PATH('')), 1, 1, '')
END
END
RETURN #LbrHours
END
jrt.run_lbr_hrs is the column that contains the labour times in hours in our ERP's table. How can I multiply that by 60 and round it down to 2 decimals with in my existing STUFF(CASE AS NVARCHAR)?

CAST to decimal with 2 value after the point and then to varchar
CAST(CAST(jrt.run_lbr_hrs * 60 AS DECIMAL(10, 2)) AS VARCHAR(20))
Change the decimal dimension to what you need

Try this Str (jrt.run_lbr_hrs * 60, 2)

Related

SQL How to count all remains for each date

I have the following SQL function
CREATE FUNCTION [dbo].[GetCardDepartRemains]
(
#CardId INT,
#DepartId INT,
#Date DATETIME = NULL,
#DocumentId INT = NULL
)
RETURNS INT
AS
BEGIN
DECLARE #Res INT
SELECT
#Res = ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = #DepartId THEN 1 ELSE -1 END), 0)
FROM dbo.Operations
WHERE Operations.CardId = #CardId
AND (Operations.[Output] = 0 AND Operations.RecipientId = #DepartId OR Operations.Input = 0 AND Operations.SenderId = #DepartId)
AND (#Date IS NULL OR Operations.[Date] <= #Date)
RETURN #Res
END
GO
It counts remains for certain product on certain department on certain date.
If it is less then zero it means something's wrong with database
Now I need to find all remains for each card, for each department for all dates in database where result is wrong.
Theoretically speaking we can fing this by calling this procedure in a query like this
SELECT DISTINCT Operations.[Date] as [Date],
Departments.Id as Depart,
Cards.Id as [Card],
[dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) as Res
FROM [jewellery].[dbo].[Cards]
CROSS JOIN [jewellery].[dbo].[Departments]
CROSS JOIN [jewellery].[dbo].[Operations]
WHERE [dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) = -1
But this query executes more than 2 minutes, so we need to write a new query.
My query can find all remains for each card on each department on certain date (ex. '2016-10-04')
SELECT
[Card],
Depart,
Res
FROM
(SELECT
Cards.Id as [Card],
Departments.Id as Depart,
ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id THEN 1 ELSE -1 END), 0) as Res
FROM Operations
CROSS JOIN Cards
CROSS JOIN Departments
WHERE Operations.CardId = Cards.Id
AND (Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id OR Operations.Input = 0 AND Operations.SenderId = Departments.Id)
AND (Operations.[Date] <= '2016-10-04')
GROUP BY Cards.Id, Departments.Id
) as X
WHERE Res = -1
Can you help to re-write this query to find remains for all dates?
Assuming SQL Server is 2008 or above:
To find all dates, just comment out the date filter like this:
-- AND (Operations.[Date] <= '2016-10-04')
If you need to filter on a date range:
AND (Operations.[Date] between between getDate()-30 and getDate()
Changing -30 to however many days in the past. So a year ago would be -364.

Using CTE to find recursive records failed

I write a CTE in the procedure that to find the recursive records. If works fine before, but suddenly it stops working when I try to run it again. Here is what two table structure looks like:
Here is my code:
DELETE FROM [dbo].[NVA_Work_Rollup_BOM_Materials]
WHERE Product_Item_No = Ingredient_Item_No;
IF OBJECT_ID(N'tempdb..#temp', N'U') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp (Product nvarchar(32),
Ingredient nvarchar(32),
Trail nvarchar(max),
Loopcheck BIT
)
TRUNCATE TABLE #temp
DECLARE #False AS BIT,
#True AS BIT
SET #False = 0
SET #True = 1;
WITH C
as (
select
Product_Item_No,
Ingredient_Item_No,
CAST(('|' + Product_Item_No + '|') as nvarchar(max)) as Trail,
#False as LP
from NVA_Work_Rollup_BOM_Materials
where
Product_Item_No in (select Item_No from NVA_Work_Rollup_Level_Codes where Level_Code = 0 and Rpt_Period = #P_CurrentPeriod)
AND Rpt_Period = #P_CurrentPeriod
union all
select
Matl.Product_Item_No,
Matl.Ingredient_Item_No,
CAST((Trail + (Matl.Product_Item_No +'|')) as nvarchar(max)),
case
when Trail like '%|' + CAST((Matl.Ingredient_Item_No) as nvarchar(max)) + '|%'
then #True
else #False
end as LP
from NVA_Work_Rollup_BOM_Materials as Matl
inner join C
on C.Ingredient_Item_No = Matl.Product_Item_No
where C.LP = 0
and Matl.Rpt_Period = #P_CurrentPeriod
)
INSERT INTO #temp
SELECT * FROM C
WHERE LP = 1
OPTION (maxrecursion 0)
The procedure stuck on there and won't populate the #temp table. I did execution plan check, and the only thing i could see is the eager spool. Any help will be appreciate, also how should i debug here.

how to get results of function with datatype nvarchar

I have a database table like this
Id Code Amount Formula
-------------------------------------
1 A01 20.00
2 A08 0.00 dbo.ufn_Test(40)
3 A03 0.00 dbo.ufn_Test(60)
My Formula column is a string with name as a function in my database, how can I return the result into the Amount column?
My table has about 100000 rows so when I used while() it takes a lot of time.
I'm using SQL Server 2012
I've used dynamic SQL like this:
DECLARE #_j INT = 1
WHILE (#_j<=(SELECT MAX(Id) FROM #Ct_Lv))
BEGIN
SET #_CtLv = (SELECT Formula FROM #Ct_Lv WHERE Id = #_j)
DECLARE #sql NVARCHAR(MAX)
DECLARE #result NUMERIC(18, 2) = 0
SET #sql = N'set #result = N''''SELECT''' + #_CtLv
EXEC sp_executesql #sql, N'#result float output', #result out
UPDATE #Ct_Lv
SET Amount = #result
WHERE Id = #_j
SET #_j = #_j + 1
END
but my max #_j = 100000, I've run my code for 3 hours and it's still running
one thing, i would like know here is, does id attribute is identity column ?
2nd most important part is, you are declaring variables #sql and #result for each row and you are taking max at every row iterate, which might decrease the performance. I am not sure, how much faster the solution i have been given here, but you can try it once.
Set Nocount On;
Declare #_count Int
,#_j Int
,#_cnt Int
,#_dynamicSql Varchar(Max)
,#_formula Varchar(Max)
,#_row25Cnt Int
Select #_count = Count(1)
,#_j = 0
,#_cnt = 0
,#_dynamicSql = ''
,#_formula = ''
,#_row25Cnt = 1
From #Ct_Lv As ct With (Nolock)
While (#_cnt < #_count)
Begin
Select Top 1
#_j = ct.Id
,#_formula = ct.Formula
From #Ct_Lv As ct With (Nolock)
Where ct.Id > #_j
Order By ct.Id Asc
Select #_dynamicSql = 'Update ct Set ct.Amount = f.result From #Ct_Lv As ct Join ( Select ' + Cast(#_j As Varchar(20)) + ' As Id, [fuctionResultAttribute] As result From ' + #_formula + ' ) As f On ct.Id = f.Id; '
If (#_row25Cnt = 25)
Begin
Exec (#_dynamicSql)
Select #_dynamicSql = ''
,#_row25Cnt = 0
End
Else If ((#_cnt + 1) = #_count)
Begin
Exec (#_dynamicSql)
Select #_dynamicSql = ''
,#_row25Cnt = 0
End
Select #_cnt = #_cnt + 1
,#_row25Cnt = #_row25Cnt + 1
End
Here, what i have done so far is, I am looping Id by Id and generating dynamic sql for each 25 rows, once count is reach to 25, that dynamic sql will be executed which will update your amount. and again start generating dynamic sql for next 25 rows, and when count is about to end and there would be no 25 rows as end then dynamic sql will be executed when loop about to end in 'else if' condition.
above my solution will work only in that case when there would be only one formula in formula column for each row.
I just suggest one thing if Formula field calling the same function each time then better to store only parameter that you want to pass to the function, then you can easily process over huge data.
Else looping over huge data is not preferable way to perform any operation. So it's advisable to use some other trick over there in table structure and storing data.

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.

Convert Comma Delimited String to bigint in SQL Server

I have a varchar string of delimited numbers separated by commas that I want to use in my SQL script but I need to compare with a bigint field in the database. Need to know to convert it:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = 'CPN'
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN (#RegionID)
AND Viewable = 'Y'
This fails with the following error:
Error converting data type varchar to bigint.
RegionID In the database is a bigint field.. need to convert the varchar to bigint.. any ideas..?
Many thanks in advance,
neojakey
create this function:
CREATE function [dbo].[f_split]
(
#param nvarchar(max),
#delimiter char(1)
)
returns #t table (val nvarchar(max), seq int)
as
begin
set #param += #delimiter
;with a as
(
select cast(1 as bigint) f, charindex(#delimiter, #param) t, 1 seq
union all
select t + 1, charindex(#delimiter, #param, t + 1), seq + 1
from a
where charindex(#delimiter, #param, t + 1) > 0
)
insert #t
select substring(#param, f, t - f), seq from a
option (maxrecursion 0)
return
end
change this part:
AND a.RegionID IN (select val from dbo.f_split(#regionID, ','))
Change this for better overall performance:
AND DATEDIFF(d, 0, GETDATE()) <= ExpirationDate
Your query does not know that those are separate values, you can use dynamic sql for this:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #sql nvarchar(Max)
set #sql = 'SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = ''CPN''
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN ('+#RegionID+')
AND Viewable = ''Y'''
exec sp_executesql #sql
I use this apporach sometimes and find it very good.
It transfors your comma-separated string into an AUX table (called #ARRAY) and then query the main table based on the AUX table:
declare #RegionID varchar(50)
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #S varchar(20)
if LEN(#RegionID) > 0 SET #RegionID = #RegionID + ','
CREATE TABLE #ARRAY(region_ID VARCHAR(20))
WHILE LEN(#RegionID) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#RegionID, 1, CHARINDEX(',', #RegionID) - 1))
INSERT INTO #ARRAY (region_ID) VALUES (#S)
SELECT #RegionID = SUBSTRING(#RegionID, CHARINDEX(',', #RegionID) + 1, LEN(#RegionID))
END
select * from your_table
where regionID IN (select region_ID from #ARRAY)
It avoids you from ahving to concatenate the query string and then use EXEC to execute it, which I dont think it is a very good approach.
if you need to run the code twice you will need to drop the temp table
I think the answer should be kept simple.
Try using CHARINDEX like this:
DECLARE #RegionID VARCHAR(200) = NULL
SET #RegionID =
'853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT 1
WHERE Charindex('834', #RegionID) > 0
SELECT 1
WHERE Charindex('999', #RegionID) > 0
When CHARINDEX finds the value in the large string variable, it will return it's position, otherwise it return 0.
Use this as a search tool.
The easiest way to change this query is to replace the IN function with a string function. Here is what I consider the safest approach using LIKE (which is portable among databases):
AND ','+#RegionID+',' like '%,'+cast(a.RegionID as varchar(255))+',%'
Or CHARINDEX:
AND charindex(','+cast(a.RegionID as varchar(255))+',', ','+#RegionID+',') > 0
However, if you are explicitly putting the list in your code, why not use a temporary table?
declare #RegionIds table (RegionId int);
insert into #RegionIds
select 853 union all
select 834 union all
. . .
select 303
Then you can use the table in the IN clause:
AND a.RegionId in (select RegionId from #RegionIds)
or in a JOIN clause.
I like Diego's answer some, but I think my modification is a little better because you are declaring a table variable and not creating an actual table. I know the "in" statement can be a little slow, so I did an inner join since I needed some info from the Company table anyway.
declare #companyIdList varchar(1000)
set #companyIdList = '1,2,3'
if LEN(#companyIdList) > 0 SET #companyIdList = #companyIdList + ','
declare #CompanyIds TABLE (CompanyId bigint)
declare #S varchar(20)
WHILE LEN(#companyIdList) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#companyIdList, 1, CHARINDEX(',', #companyIdList) - 1))
INSERT INTO #CompanyIds (CompanyId) VALUES (#S)
SELECT #companyIdList = SUBSTRING(#companyIdList, CHARINDEX(',', #companyIdList) + 1, LEN(#companyIdList))
END
select d.Id, d.Name, c.Id, c.Name
from [Division] d
inner join [Company] c on d.CompanyId = c.Id
inner join #CompanyIds cids on c.Id = cids.CompanyId