Make a variable dynamic enough alter value based on month - sql-server

Is there any way to have a variable inside a variable loop through 12 numbers every time called? I may just be malnourished in SQL loop logic. Here is the variable I have, as of now I can only get it to hold a static number.
SET #startEnr = (SELECT COUNT(*)
FROM Enrollment
WHERE MONTH(startDate) = 09
AND active = 1
AND endYear = 2020
The value 09 needs to hold 12 values as if #x = 1...12. I've attempted looping this however an error returns stating that the variable can't hold more than one value. Would I need a function?

As commenters have pointed out, the only real array in SQL is a table. I think what you're looking for is something like this:
CREATE TABLE #mthEnroll (mth int, cnt int)
INSERT INTO #mthEnroll (mth, cnt)
SELECT MONTH(startdate), COUNT(*)
FROM Enrollment
WHEREactive = 1
AND endYear = 2020
GROUP BY MONTH(startdate)
DROP TABLE #mthEnroll
You may have to do some tinkering if you have the possibility of months with no entries (e.g., 0 students enrolled in July would NOT have a row), but if it's necessary to get 0's, there are other ways.

Related

SAS conditionals applied to array elements

I have a hard time wrapping my head around SAS arrays. I have a dataset that has ID and BeginDate, EndDate. I want to create 3 binary variables that =1 if either start or enddate is in a given year and I'm looking at 3 different years. When I run the code below all of the new variables I create (year1955, year1956, year1957) = 1 if any one of them is true. This is not what I want. I am using an array because I eventually will want to do this with more than 3 variables.
My code:
data temp2; set temp;
array yr(3) year1955-year1957;
do i = 1 to 3;
if year(BeginDate) =1955 or year(EndDate)=1955 then yr(i)=1;
if year(BeginDate) =1956 or year(EndDate)=1956 then yr(i)=1;
if year(BeginDate) =1957 or year(EndDate)=1957 then yr(i)=1;
end;
drop i;
run;
I would be open to a more elegant solution than the one I've devised.
Output I'm getting :
ID Begindate EndDate year1955 year1956 year1957
AA 01/01/1956 01/01/1969 1 1 1
Output I want:
ID Begindate EndDate year1955 year1956 year1957
AA 01/01/1956 01/01/1969 . 1 .
You are not use the value of your loop variable in the IF conditions.
You could just get rid of the DO loop.
if year(BeginDate) =1955 or year(EndDate)=1955 then yr(1)=1;
if year(BeginDate) =1956 or year(EndDate)=1956 then yr(2)=1;
if year(BeginDate) =1957 or year(EndDate)=1957 then yr(3)=1;
Or include the value I in the IF condition.
do i = 1 to 3;
if year(BeginDate) =1955+i-1 or year(EndDate)=1955+i-1 then yr(i)=1;
end;
Or use the year value as the index into the array by changing the range of indexes the array uses.
array yr [1955:1957] year1955-year1957;
if year(BeginDate) in (1955:1957) then yr[year(BeginDate)]=1;
if year(EndDate) in (1955:1957) then yr[year(EndDate)]=1;

Declare a local variable in named calculation expression

I would like to create a named calculation field for age, and I want to declare a local variable inside an expression like the following, but it doesn't not work :
DECLARE
#age INT;
#age=DateDiff("yyyy",DATE_NAIS,getdate());
CASE WHEN #age<=10 THEN 1
WHEN #age>10 AND #age<=20 THEN 2
WHEN #age>20 AND #age<=35 THEN 3
ELSE 4
END
Correct, you cannot DECLARE a variable in the context of an expression in a SELECT, like you are trying to do.
You have a few different options, one of which digital.aaron gave in his comments.
Another is to create it as an artificial column in a CTE, and then select from the CTE instead of the table:
WITH cte AS (SELECT *, DateDiff("yyyy",DATE_NAIS,getdate()) AS age FROM MyTable)
SELECT CASE
WHEN age<=10 THEN 1
WHEN age>10 AND age<=20 THEN 2
WHEN age>20 AND age<=35 THEN 3
ELSE 4
END AS SomeColumn
FROM cte
This question is tagged with SSAS, so I'm guessing you're doing this in a cube? The following DAX formula will work as a calculated column in your model for what was described in your post. As stated in the comments DATEDIFF alone will only return the difference between the current year and the year the person was born, which is why YEARFRAC is used to obtain the fraction of a year, then this is rounded down to the nearest integer via the INT function. For example, someone who is 50 years, 364 days old would be considered 50, not 51. Since TRUE() is given as the first argument of the SWITCH function, this will evaluate each condition until a match is found.
PersonAge:=
var Age = INT(YEARFRAC(YourTable[DATE_NAIS], TODAY()))
RETURN
SWITCH
(TRUE(),
Age < 10, 1,
AND(
Age > 10,
Age <= 20), 2,
AND(
Age > 20,
Age <= 35), 3,
4)

Unable to grab value based on MonthNumber and YearNumber SSRS 2010

I need to grab the value in a column T for Particular for YearNumber = 2018
and for MonthNumber = Month(Today())
Currently its the only value that is NOT 0
So I wrote expression for that cell:
=IIF((Fields!YearNumber.Value = 2018 AND Fields!MonthNumber.Value = Month(Today()),Fields!T.Value,0)
But for some reason it brings me 0.
This is how this number generated in T-sql:
ISNULL(case when MonthVal <= MONTH(GETDATE()) THEN SUM(T) ELSE 0 END /2019317,0) as T,
It works if I do the SUM. But I do not need a SUM.
How can I just grab the value of 0.1023 ?
My data looks like this:
In your second picture it looks like you grouped your data by Commercial Auto but your expression picks just one row from the dataset. This could be the first row where the condition is true, thus the value could be zero. I think what you need to put in your expression is something like this:
=Sum(IIF((Fields!YearNumber.Value = 2018 AND
Fields!MonthNumber.Value = Month(Today()),
Fields!T.Value,
0),
"YourCommercialAutoGroupName")

Unusual SQL Server query with "select top 1 #arastr = k"

select top 1 #arastr = k
from #m
where datalength(k) = (select max(datalength(k)) from #m)
What does this query do, and what is the point of select top 1 #arastr = k? This query is taken from a stored procedure which has been working for 7-8 years, so there is nothing wrong with the query, but I could not understand what it does.
(#m is a temp table which is created in the early part of the query.)
The query select one random value (since top is used without an order by clause) from the column k in the temporary table #m and assigns it to a variable #arastr (which has been previously declared supposedly). The string selected will be any matching the longest (measured in bytes (by the datalength function)) string in the table.
This is a quite common (but a little old fashioned) way to get the value of k into the (previously declared!) variable #arastr for later usage.
The function DATALENGTH will measure the length of e.g. a VARCHAR.
With TOP 1 you geht in any case only one result row, the one with the "longest" k, it's value is in #arastr afterwards...
EDIT: As pointed out by #jpw this will be random, if there is more than one k with the same (longest) length.
Without knowing, what #m looks like and what kind of data is in 'k' I cannot tell you any more.
probably makes more sense if it looks like this
SET #arastr = (SELECT TOP 1 k
FROM #m
WHERE DATALENGTH(k) = (SELECT MAX(DATALENGTH(k)) FROM #m))

SQL: Is it possible to SUM() fields of INTERVAL type?

I am trying to sum INTERVAL. E.g.
SELECT SUM(TIMESTAMP1 - TIMESTAMP2) FROM DUAL
Is it possible to write a query that would work both on Oracle and SQL Server? If so, how?
Edit: changed DATE to INTERVAL
I'm afraid you're going to be out of luck with a solution which works in both Oracle and MSSQL. Date arithmetic is something which is very different on the various flavours of DBMS.
Anyway, in Oracle we can use dates in straightforward arithmetic. And we have a function NUMTODSINTERVAL which turns a number into a DAY TO SECOND INTERVAL. So let's put them together.
Simple test data, two rows with pairs of dates rough twelve hours apart:
SQL> alter session set nls_date_format = 'dd-mon-yyyy hh24:mi:ss'
2 /
Session altered.
SQL> select * from t42
2 /
D1 D2
-------------------- --------------------
27-jul-2010 12:10:26 27-jul-2010 00:00:00
28-jul-2010 12:10:39 28-jul-2010 00:00:00
SQL>
Simple SQL query to find the sum of elapsed time:
SQL> select numtodsinterval(sum(d1-d2), 'DAY')
2 from t42
3 /
NUMTODSINTERVAL(SUM(D1-D2),'DAY')
-----------------------------------------------------
+000000001 00:21:04.999999999
SQL>
Just over a day, which is what we would expect.
"Edit: changed DATE to INTERVAL"
Working with TIMESTAMP columns is a little more labourious, but we can still work the same trick.
In the following sample. T42T is the same as T42 only the columns have TIMESTAMP rather than DATE for their datatype. The query extracts the various components of the DS INTERVAL and converts them into seconds, which are then summed and converted back into an INTERVAL:
SQL> select numtodsinterval(
2 sum(
3 extract (day from (t1-t2)) * 86400
4 + extract (hour from (t1-t2)) * 3600
5 + extract (minute from (t1-t2)) * 600
6 + extract (second from (t1-t2))
7 ), 'SECOND')
8 from t42t
9 /
NUMTODSINTERVAL(SUM(EXTRACT(DAYFROM(T1-T2))*86400+EXTRACT(HOURFROM(T1-T2))*
---------------------------------------------------------------------------
+000000001 03:21:05.000000000
SQL>
At least this result is in round seconds!
Ok, after a bit of hell, with the help of the stackoverflowers' answers I've found the solution that fits my needs.
SELECT
SUM(CAST((DATE1 + 0) - (DATE2 + 0) AS FLOAT) AS SUM_TURNAROUND
FROM MY_BEAUTIFUL_TABLE
GROUP BY YOUR_CHOSEN_COLUMN
This returns a float (which is totally fine for me) that represents days both on Oracle ant SQL Server.
The reason I added zero to both DATEs is because in my case date columns on Oracle DB are of TIMESTAMP type and on SQL Server are of DATETIME type (which is obviously weird). So adding zero to TIMESTAMP on Oracle works just like casting to date and it does not have any effect on SQL Server DATETIME type.
Thank you guys! You were really helpful.
You can't sum two datetimes. It wouldn't make sense - i.e. what does 15:00:00 plus 23:59:00 equal? Some time the next day? etc
But you can add a time increment by using a function like Dateadd() in SQL Server.
In SQL Server as long as your individual timespans are all less than 24 hours you can do something like
WITH TIMES AS
(
SELECT CAST('01:01:00' AS DATETIME) AS TimeSpan
UNION ALL
SELECT '00:02:00'
UNION ALL
SELECT '23:02:00'
UNION ALL
SELECT '17:02:00'
--UNION ALL SELECT '24:02:00' /*This line would fail!*/
),
SummedTimes As
(
SELECT cast(SUM(CAST(TimeSpan AS FLOAT)) as datetime) AS [Summed] FROM TIMES
)
SELECT
FLOOR(CAST(Summed AS FLOAT)) AS D,
DATEPART(HOUR,[Summed]) AS H,
DATEPART(MINUTE,[Summed]) AS M,
DATEPART(SECOND,[Summed]) AS S
FROM SummedTimes
Gives
D H M S
----------- ----------- ----------- -----------
1 17 7 0
If you wanted to handle timespans greater than 24 hours I think you'd need to look at CLR integration and the TimeSpan structure. Definitely not portable!
Edit: SQL Server 2008 has a DateTimeOffset datatype that might help but that doesn't allow either SUMming or being cast to float
I also do not think this is possible. Go with custom solutions that calculates the date value according to your preferences.
You can also use this:
select
EXTRACT (DAY FROM call_end_Date - call_start_Date)*86400 +
EXTRACT (HOUR FROM call_end_Date - call_start_Date)*3600 +
EXTRACT (MINUTE FROM call_end_Date - call_start_Date)*60 +
extract (second FROM call_end_Date - call_start_Date) as interval
from table;
You Can write you own aggregate function :-). Please read carefully http://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dciaggfns.htm
You must create object type and its body by template, and next aggregate function what using this object:
create or replace type Sum_Interval_Obj as object
(
-- Object for creating and support custom aggregate function
duration interval day to second, -- In this property You sum all interval
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number,
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number,
-- Merge parallel summed data
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number,
-- End of query, returning summary result
member function ODCIAggregateTerminate
(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
)
/
create or replace type body Sum_Interval_Obj is
-- Object Init
static function ODCIAggregateInitialize(
actx IN OUT Sum_Interval_Obj
) return number
is
begin
actx := Sum_Interval_Obj(numtodsinterval(0,'SECOND'));
return ODCIConst.Success;
end ODCIAggregateInitialize;
-- Iterate getting values from dataset
member function ODCIAggregateIterate(
self IN OUT Sum_Interval_Obj,
ad_interval IN interval day to second
) return number
is
begin
self.duration := self.duration + ad_interval;
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateIterate;
-- Merge parallel calculated intervals
member function ODCIAggregateMerge(
self IN OUT Sum_Interval_Obj,
ctx2 IN Sum_Interval_Obj
) return number
is
begin
self.duration := self.duration + ctx2.duration; -- Add two intervals
-- return = All Ok!
return ODCIConst.Success;
exception
when others then
return ODCIConst.Error;
end ODCIAggregateMerge;
-- End of query, returning summary result
member function ODCIAggregateTerminate(
self IN Sum_Interval_Obj,
returnValue OUT interval day to second,
flags IN number
) return number
is
begin
-- return = All Ok, too!
returnValue := self.duration;
return ODCIConst.Success;
end ODCIAggregateTerminate;
end;
/
-- You own new aggregate function:
CREATE OR REPLACE FUNCTION Sum_Interval(
a_Interval interval day to second
) RETURN interval day to second
PARALLEL_ENABLE AGGREGATE USING Sum_Interval_Obj;
/
Last, check your function:
select sum_interval(duration)
from (select numtodsinterval(1,'SECOND') as duration from dual union all
select numtodsinterval(1,'MINUTE') as duration from dual union all
select numtodsinterval(1,'HOUR') as duration from dual union all
select numtodsinterval(1,'DAY') as duration from dual);
Finally You can create SUM function, if you want.

Resources