Unable to use variable in DATEADD(#pinterval, #pinterval, #DayPlus) - tsql

DECLARE #pinterval INT = 1, #DayPlus DATETIME = '2016-07-01', #datepart VARCHAR(20) = 'MONTH'
SET #DayPlus = DATEADD(#datepart, #pinterval, #DayPlus)
SELECT #DayPlus
Do we have any alternative to accomplish task ? I have to do it in loop so I can't define it every time based on interval value. Only date part is not acceptable as variable because if I use as
DATEADD(MONTH, #pinterval, #DayPlus)
then it's working fine. Not sure but I can understand the issue but I am seeking for the quick solution. I have visited the web but didn't get exact solution.

It's not possible to substitute a variable for the first argument to DATEADD1, since what is wanted here is a name, not a string.
About the best you can do is a CASE expression:
SET #DayPlus =
CASE #datepart
WHEN 'MONTH' THEN DATEADD(MONTH, #pinterval, #DayPlus)
WHEN 'YEAR' THEN DATEADD(YEAR, #pinterval, #DayPlus)
WHEN 'DAY' THEN DATEADD(DAY, #pinterval, #DayPlus)
--TODO - add all parts you might wish to use
END
1This is even stated in the documentation:
User-defined variable equivalents are not valid.

if this is only the way then I have to repeat this for more than 10times
No, use CROSS APPLY
#Shnugo it sounds good, can you please help me by placing the complete code as an answer. Please !!
About CROSS APPLY: generate something like a variable dynamically
CREATE TABLE dbo.Test(ID INT, SomeDate DATE);
GO
INSERT INTO dbo.Test VALUES(1,{d'2017-01-01'}),(2,{d'2017-02-02'})
GO
DECLARE #Intervall VARCHAR(100)='DAY';
DECLARE #Count INT=1
SELECT dbo.Test.*
,t.AddedDate
FROM dbo.Test
CROSS APPLY(SELECT CASE #Intervall WHEN 'MONTH' THEN DATEADD(MONTH,#Count,SomeDate)
WHEN 'DAY' THEN DATEADD(DAY,#Count,SomeDate)
ELSE SomeDate END AS AddedDate) AS t;
GO
DROP TABLE dbo.Test;

You could use dynamic SQL:
DECLARE #pinterval INT = 1,
#DayPlus DATETIME = '2016-07-01',
#datepart NVARCHAR(20) = 'MONTH'
DECLARE #sql NVARCHAR(MAX) = N'SET #DayPlus = DATEADD('+#datepart+', #pinterval, #DayPlus)',
#params NVARCHAR(MAX) = N'#DayPlus DATETIME OUTPUT, #pinterval INT'
EXEC sp_executesql #sql, #params, #pinterval = #pinterval, #DayPlus = #DayPlus OUTPUT
SELECT #DayPlus
sp_executesql is used for 2 reasons:
to minimize SQL injections risk, it will not eliminate it because of #datepart
it will make sure your dynamic query's plan is going to be cached - which might be beneficial with simple queries (where query time vs compilation time matters)

Related

Repeated use of parameter for multiple UDF's in FROM throws an invalid column name error

When using multiple table-valued functions in a query like beneath, SSMS throws an error. Also, the [Date] parameter of [PRECALCPAGES_asof] is underlined in red.
I am trying to understand why this fails. I think this might be related to the way the SQL Server engine works. Have looked into documentation on MSDN but unfortunately I do not know what to look for. Why is this caused and is there a way around it?
Query
SELECT
[Date]
, COUNT(*)
FROM
[Warehouse].[dbo].[DimDate]
CROSS APPLY
[PROJECTS_asof]([Date])
INNER JOIN
[PRECALCPAGES_asof]([Date]) ON [PRECALCPAGES_asof].[PROJECTID] = [PROJECTS_asof].[PROJECTID]
GROUP BY
[Date]
Error
Msg 207, Level 16, State 1, Line 9
Invalid column name 'Date'.
Functions
CREATE FUNCTION [ProfitManager].[PROJECTS_asof]
(
#date DATETIME
)
RETURNS TABLE AS
RETURN
(
SELECT
[PROJECTID]
, [PROJECT]
, ...
FROM
Profitmanager.[PROJECTS_HISTORY]
WHERE
[RowStartDate] <= #date
AND
[RowEndDate] > #date
)
GO
CREATE FUNCTION [ProfitManager].[PRECALCPAGES_asof]
(
#date DATETIME
)
RETURNS TABLE AS
RETURN
(
SELECT
[PAGEID]
, [PAGENAME]
, ...
FROM
Profitmanager.[PRECALCPAGES_HISTORY]
WHERE
[RowStartDate] <= #date
AND
[RowEndDate] > #date
)
GO
I think you can't use fields from tables as parameters to a function in a join. You should use cross apply.
SELECT
[Date]
, COUNT(*)
FROM
[Warehouse].[dbo].[DimDate]
CROSS APPLY
[PROJECTS_asof]([Date])
CROSS APPLY
[PRECALCPAGES_asof]([Date])
WHERE
[PRECALCPAGES_asof].[PROJECTID] = [PROJECTS_asof].[PROJECTID]
GROUP BY
[Date]

SQL Server : error "Must Declare the Scalar Variable"

Trying to insert into a table from other two tables with a loop
DECLARE #RowCount INT
SET #RowCount = (SELECT Max(FogTopicsID) FROM FSB_FogTopics )
DECLARE #I INT
SET #I = 1
WHILE (#I <= #RowCount)
BEGIN
DECLARE #FogID INT, #StudentID INT, #TopicID INT, #ProcessStudentId INT
SELECT #FogID = FogID, #StudentID = StudentID, #TopicID = TopicsID
FROM FSB_FogTopics
WHERE FogTopicsID = #I
SELECT #ProcessStudentId = ProStudentId
FROM FSB_ProcessStudents
WHERE ProcessId = #FogID AND StudentId = #StudentID
INSERT INTO FSB_ProcessTopics( [ProcessStudentId], [TopicId])
VALUES (#ProcessStudentId, #TopicID)
SET #I = #I + 1
END
but I get an error
Must Declare the Scalar Variable #ProcessStudentId
As pointed out by forklift's comment - You can use proper set based solution instead of horrible loop like so;
INSERT FSB_ProcessTopics( [ProcessStudentId], [TopicId])
SELECT
s.ProStudentId,
f.TopicsId
FROM FSB_FogTopics f
INNER JOIN FSB_ProcessStudents s
ON f.FogId = s.ProcessId
AND f.StudentId = s.StudentId
While I realise this doesn't answer your question per-say, this is a better way to do it and should eliminate the need to solve your problem...
You probably have non-continuous Ids - So you have 1,2,4 as Ids but your code is trying to dind 1,2,3,4
You don't need loops to do this (you should almost never need to use loops in SQL for anything). You can do your INSERT in a single statement:
Insert FSB_ProcessTopics
(ProcessStudentId, TopicId)
Select P.ProStudentId, T.TopicsId
From FSB_FogTopics T
Join FSB_ProcessStudents P On P.ProcessId = T.FogId
And P.StudentId = T.StudentId
Do this as a single statement:
INSERT FSB_ProcessTopics(ProcessStudentId, TopicId)
SELECT ProStudentId, TopicsID
FROM FSB_FogTopics ft JOIN
FSB_ProcessStudents ps
ON ft.StudentID = ps.StudentId AND sps.ProcessId = ft.FogiId;
This should replace the cursor, the loop, everything.

Stored Procedure Date appears long date

ALTER PROCEDURE [dbo].[SP_My_Procedured]
AS
BEGIN
SELECT Mission_Time
FROM Mission_Table
WITH (NOLOCK)
WHERE
cast(getdate() as Date)=Mission_Time
END
When i run SP_My_Procedured,
I see Mission_Time as
"2014-01-04 08:35:05.510"
"2014-01-03 10:49:00.697"
But ı want to see like below,
"2014-01-04"
"2014-01-03"
So how can i do this in stored procedure by select ?
Any help will be appreciated.
Cast the value in the SELECT list to Date:
ALTER PROCEDURE [dbo].[SP_My_Procedured]
AS
BEGIN
SELECT
Mission_Time = cast(Mission_Time as Date)
FROM
Mission_Table (NOLOCK)
WHERE
cast(Mission_Time as Date) = cast(getdate() as Date)
END
[BTW: there are dangers to using NO LOCK]
[Also, casting the column to date may not be necessary if it is already of type Date (you don't specify its type). Doing so may result in an appropriate index not being used]
Try
SELECT CONVERT(VARCHAR(10), 'date', 120) AS MissionTime
For more date formats check this

Using patterns in REPLACE

I need to find and replace an expression within a dynamic query. I have a subset of a where condition in string type like
'fieldA=23 OR field_1=300 OR fieldB=4'
What I need is to find a way to detect expression field_1=300 within the string and replace it while retaining the expression field_1=300.
I can do the detection part using CHARINDEX or PATINDEX but I'm not able to figure out how to use the patterns in the REPLACE function and how to get the value of the field_1 parameter.
Thanks in advance.
I'm not entirely clear on what you're trying to acheieve (e.g. what are you wanting to replace "field_1=300" with, and is it the exact string "field_1=300" that you're looking for, or just the field name, i.e. "field_1"?).
Also, could you paste in the code you've written so far?
Here's a simple script which will extract the current value of a given field name:
DECLARE #str VARCHAR(100),
#str_tmp VARCHAR(100),
#field_pattern VARCHAR(10),
#field_val INT;
SET #str = 'fieldA=23 OR field_1=300 OR fieldB=4';
SET #field_pattern = 'field_1='
-- This part will extract the current value assigned to the "#field_pattern" field
IF CHARINDEX(#field_pattern, #str) > 0
BEGIN
SELECT #str_tmp = SUBSTRING(#str,
CHARINDEX(#field_pattern, #str) + LEN(#field_pattern),
LEN(#str)
);
SELECT #field_val = CAST(SUBSTRING(#str_tmp, 1, CHARINDEX(' ', #str_tmp)-1) AS INT);
END
PRINT #field_val
If you want to replace the value itself (e.g. replacing "300" in this case with "600"), then you could add something like this:
DECLARE #new_val INT;
SET #new_val = 600;
SET #str = REPLACE(#str, (#field_pattern+CAST(#field_val AS VARCHAR)), (#field_pattern+CAST(#new_val AS VARCHAR)));
PRINT #str;
Which would give you "fieldA=23 OR field_1=600 OR fieldB=4".
Cheers,
Dave

DESCENDING/ASCENDING Parameter to a stored procedure

I have the following SP
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = #webRegionID)
ORDER BY
CASE UPPER(#sortBy)
when 'CASE_NO' then case_no
when 'AREA' then area
when 'FURNISHED' then furnished
when 'TYPE' then [type]
when 'SQUAREFEETS' then squarefeets
when 'BEDROOMS' then bedrooms
when 'LIVINGROOMS' then livingrooms
when 'BATHROOMS' then bathrooms
when 'LEASE_FROM' then lease_from
when 'RENT' then rent
else case_no
END
END
GO
Now everything in that SP works but I want to be able to choose whether I want to sort ASCENDING or DESCENDING.
I really can't fint no solution for that using SQL and can't find anything in google.
As you can see I have the parameter sortDirection and I have tried using it in multiple ways but always with errors... Tried Case Statements, IF statements and so on but it is complicated by the fact that I want to insert a keyword.
Help will be very much appriciated, I have tried must of the things that comes into mind but haven't been able to get it right.
You could use two order by fields:
CASE #sortDir WHEN 'ASC' THEN
CASE UPPER(#sortBy)
...
END
END ASC,
CASE #sortDir WHEN 'DESC' THEN
CASE UPPER(#sortBy)
...
END
END DESC
A CASE will evaluate as NULL if none of the WHEN clauses match, so that causes one of the two fields to evaluate to NULL for every row (not affecting the sort order) and the other has the appropriate direction.
One drawback, though, is that you'd need to duplicate your #sortBy CASE statement. You could achieve the same thing using dynamic SQL with sp_executesql and writing a 'ASC' or 'DESC' literal depending on the parameter.
That code is going to get very unmanageable very quickly as you'll need to double nest your CASE WHEN's... one set for the Column to order by, and nested set for whethers it's ASC or DESC
Might be better to consider using Dynamic SQL here...
DECLARE #sql nvarchar(max)
SET #sql = '
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = ' + #webRegionID + ') ORDER BY '
SET #sql = #sql + ' ' + #sortBy + ' ' + #sortDirection
EXEC (#sql)
You could do it with some dynamic SQL and calling it with an EXEC. Beware SQL injection though if the user has any control over the parameters.
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
DECLARE #dynamicSQL NVARCHAR(MAX)
SET #dynamicSQL =
'
SELECT
tbl_houses.*
FROM
tbl_houses
WHERE
postal
IN
(
SELECT
zipcode
FROM
crm_zipcodes
WHERE
web_region_id = ' + CONVERT(nvarchar(10), #webRegionID) + '
)
ORDER BY
' + #sortBy + ' ' + #sortDirection
EXEC(#dynamicSQL)
END
GO