SSRS page numbers in page footer - reporting-services

I wish to not include the page number (in the page footer) for the first 10 pages of the report (i.e. page 1-10). Page 1 should read i, page 2 should read ii and page 3 should read iii and so on (in roman numerals).... When it gets to page 11, this should reset the page numbers
Does anyone know of the expression I can use to achieve this. So if GlobalPage number = 1,2,3,4,5,6,7,8,9,10 do not display, or compensate the globals page number for something else.....Is this possible.

You'll have to manually change the value i.e. putting something similar to the following in the footer:
IIf(Globals!PageNumber=1, "i", ...
Alternativally you could use a user function try VBA for number to roman numeral

We'll do ths with some custom code to keep flexibility. Microsoft have some code to do the Roman numeral conversion so we'll adapt this.
Let's add the custom code we need: one function to convert an integer to a Roman numeral and one function to work out what sort of numeral to provide.
Function PageNumber(page As Integer, startArabic As Integer) As String
If page <= startArabic Then
PageNumber = IntegerToRoman(page)
Else
PageNumber = (page - startArabic).ToString()
End If
End Function
Function IntegerToRoman (ByVal N As Integer) As String
Const Digits = "ivxlcdm"
Dim I As Integer
Dim Digit As Integer
Dim Temp As String
I = 1
Temp = ""
Do While N > 0
Digit = N Mod 10
N = N \ 10
Select Case Digit
Case 1
Temp = Mid(Digits, I, 1) & Temp
Case 2
Temp = Mid(Digits, I, 1) & Mid(Digits, I, 1) & Temp
Case 3
Temp = Mid(Digits, I, 1) & Mid(Digits, I, 1) & Mid(Digits, I, 1) & Temp
Case 4
Temp = Mid(Digits, I, 2) & Temp
Case 5
Temp = Mid(Digits, I + 1, 1) & Temp
Case 6
Temp = Mid(Digits, I + 1, 1) & Mid(Digits, I, 1) & Temp
Case 7
Temp = Mid(Digits, I + 1, 1) & Mid(Digits, I, 1) & Mid(Digits, I, 1) & Temp
Case 8
Temp = Mid(Digits, I + 1, 1) & Mid(Digits, I, 1) & Mid(Digits, I, 1) & Mid(Digits, I, 1) & Temp
Case 9
Temp = Mid(Digits, I, 1) & Mid(Digits, I + 2, 1) & Temp
End Select
I = I + 2
Loop
IntegerToRoman = Temp
End Function
To make the report more flexible, we'll add a parameter for when to revert to Arabic numerals (in case we need more than ten Roman numerals at some stage when the report gets longer). Let's call that #StartArabic and it will be an integer with the default value of 10. So now our page number expression is simply:
="Page " & Code.PageNumber(Globals!PageNumber, Parameters!StartArabic.Value)

Related

Find matching questions from all records

I've a library of questions and answers and building an API in NodeJS which allows to search for answers based on the question passed as input. Following is my goal:
Split the question by space
Tokenize it and remove stopwords
Query database for records where question contains one or more words from the tokenized array
Ideally sort in descending order total number of matches in the question. For eg: If the question A contains 'module' and 'solution' and question B contains only 'solution', then question A should be shown before question B
I've been able to achieve 1 to 3, using the below code:
let question = req.query.question;
let arrQuestions = question.split(" ");
let tokenizedQuestion = stopwords.removeStopwords(arrQuestions);
let whereClause = tokenizedQuestion.join("%' OR answer LIKE '%");
whereClause = " answer LIKE '%" + whereClause + "%' ";
let query = "SELECT * FROM tbl_libraries WHERE " + whereClause;
I'm not able to figure out how to achieve 4. Can somebody provide pointers?
Thanks!
Are you sure that you do not want to use MySQL fulltext search for this?
If the answer is some flavour of 'No', you can continue reading...
In one of my project I was implementing something like this.
Query wise it looks like this (simplified version):
SELECT
name
FROM
table
WHERE
name REGEXP 'term1|term2|term3' -- you can use your OR + LIKE way
ORDER BY
SP_TermsWeitght(name, 'term1 term2 term3') DESC
All the magic is in my SP_TermsWieght function that returns "weight" (number) and I'm supplying a list of terms (cleaned and normalized) to the function.
The function:
CREATE FUNCTION `SP_TermsWeight`(
`sValue` TEXT,
`sTerms` VARCHAR(127)
)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE p INT DEFAULT 1;
DECLARE w INT DEFAULT 0;
DECLARE l INT;
DECLARE c CHAR(1);
DECLARE s VARCHAR(63);
DECLARE delimiters VARCHAR(15) DEFAULT ' ,';
SET sTerms = TRIM(sTerms);
SET l = LENGTH(sTerms);
IF (l > 0) THEN
-- checking is value matched terms exactly
IF (sTerms = sValue) THEN
SET w = 50000;
ELSE
-- supposing that "the terms" is one single term so it it match in full, the weight will be high
IF (l <= 63) THEN
SET w = w + SP_TermWeight(sValue, sTerms, 5000, 1000, 100);
END IF;
-- not processing it term by term if it is already matched as full
IF (w = 0) THEN
-- processing term by term using space or comma as delimiter
WHILE i <= l DO
BEGIN
SET c = SUBSTRING(sTerms, i, 1);
IF (LOCATE(c, delimiters) > 0) THEN
SET s = SUBSTRING(sTerms, p, i - p);
SET w = w + SP_TermWeight(sValue, s, 50, 10, 0);
SET p = i + 1;
END IF;
SET i = i + 1;
END;
END WHILE;
IF (p > 1 AND p < i) THEN
SET s = SUBSTRING(sTerms, p, i - 1);
SET w = w + SP_TermWeight(sValue, s, 50, 10, 0);
END IF;
END IF;
END IF;
END IF;
RETURN w;
END
Technically speaking it is 'separating' terms using delimiters and checking if the value "contains" the term.
It's a bit hard to explain everything what it does (I've added a few comments in the code for you).
Feel free to ask questions if you do not understand some bits.
In your case it can be simplified dramatically as you do not need to differentiate begin/end/middle matches.
Another helper function that used internally:
CREATE FUNCTION `SP_TermWeight`(
`sValue` TEXT,
`sTerm` VARCHAR(63),
`iWeightBegin` INT,
`iWeightEnd` INT,
`iWeightMiddle` INT
)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE r INT DEFAULT 0;
SET sTerm = TRIM(sTerm);
IF (LENGTH(sTerm) > 1) THEN
IF (iWeightBegin != 0 AND sValue REGEXP CONCAT('[[:<:]]', sTerm)) THEN
SET r = r + iWeightBegin;
END IF;
IF (iWeightEnd != 0 AND sValue REGEXP CONCAT(sTerm, '[[:>:]]')) THEN
SET r = r + iWeightEnd;
END IF;
IF (r = 0 AND iWeightMiddle != 0 AND sValue REGEXP sTerm) THEN
SET r = r + iWeightMiddle;
END IF;
END IF;
RETURN r;
END
This function used for assigning different weights if the term matched to value from the beginning of the string, at the end of the string or in the middle. It is important in my case. In your case it might be simple LIKE.
I ended up using Full Text Search. Following is the stored procedure I created to enable searching:
DROP PROCEDURE IF EXISTS SP_Search $$
CREATE PROCEDURE `SP_Search`(IN QuestionToSearch TEXT, IN TagsToSearch TEXT, IN CollectionsToSearch TEXT, IN ReturnRecordsFromIndex INT, IN TotalRecordsToReturn INT)
BEGIN
SET #MainQuery = CONCAT("SELECT *, MATCH(question, answer_content) AGAINST (", CONCAT("'", QuestionToSearch, "'"), " IN NATURAL LANGUAGE MODE) AS score ");
SET #MainQuery = CONCAT(#MainQuery, " FROM tbl_libraries ");
SET #MainQuery = CONCAT(#MainQuery, " WHERE MATCH(question, answer_content) AGAINST (", CONCAT("'", QuestionToSearch, "'"), " IN NATURAL LANGUAGE MODE) ");
IF F_IsNullOrEmpty(TagsToSearch) AND NOT F_IsNullOrEmpty(CollectionsToSearch) THEN
SET #MainQuery = CONCAT(#MainQuery, " AND collections LIKE '%", CollectionsToSearch, "%' ");
ELSEIF F_IsNullOrEmpty(CollectionsToSearch) AND NOT F_IsNullOrEmpty(TagsToSearch) THEN
SET #MainQuery = CONCAT(#MainQuery, " AND tags LIKE '", TagsToSearch, "' ");
ELSEIF NOT F_IsNullOrEmpty(TagsToSearch) AND NOT F_IsNullOrEmpty(CollectionsToSearch) THEN
SET #MainQuery = CONCAT(#MainQuery, " AND tags LIKE '", TagsToSearch, "' AND collections LIKE '", CollectionsToSearch, "' ");
END IF;
SET #MainQuery = CONCAT(#MainQuery, " ORDER BY score DESC ");
SET #MainQuery = CONCAT(#MainQuery, " LIMIT ", ReturnRecordsFromIndex, ", ", TotalRecordsToReturn);
PREPARE SqlQuery FROM #MainQuery;
EXECUTE SqlQuery;
END $$
DELIMITER ;
This uses a custom function I created F_IsNullOrEmpty, which is as shown below for completion:
CREATE FUNCTION F_IsNullOrEmpty(ValueToCheck VARCHAR(256)) RETURNS BOOL
DETERMINISTIC
BEGIN
IF((ValueToCheck IS NULL) OR (LENGTH(ValueToCheck) = 0) OR (ValueToCheck = 'null')) THEN
Return True;
ELSE
Return False;
END IF;
END;

Set field format in query to report

Is there a way for a report's field to take into account the format of a field in a query?
In example:
I have a StudentPercent field in a query. Values of the field are between 0 to 1, but since it is formatted to percent, they appear from 0% to 100% . When I run the report, it doesn't consider the format of the field and the values are between 0 to 1. Why is that?
Edit 1: I'm using Microsoft Access 2016.
Also, datas are populated dynamically, so I can't just set the format of the fields manually.
Edit 2:
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
'Exit Sub
' Place values in text boxes and hide unused text boxes.
Dim intX As Integer
' Verify that not at end of recordset.
If Not rstReport.EOF Then
' If FormatCount is 1, place values from recordset into text boxes
' in detail section.
If Me.FormatCount = 1 Then
Me("Col" + Format(intColumnCount + 1)) = 0
For intX = 1 To intColumnCount
' Convert Null values to 0.
Me("Col" + Format(intX)) = Nz(rstReport(intX - 1))
If intX < intColumnCount Then
Me("Col" + Format(intColumnCount + 1)) = _
Me("Col" + Format(intColumnCount + 1)) + Nz(rstReport(intX))
End If
Next intX
' Hide unused text boxes in detail section.
'For intX = intColumnCount + 2 To conTotalColumns
'Me("Col" + Format(intX)).Visible = False
'Next intX
For intX = 2 To intColumnCount + 1
Me("Tot" + Format(intX)) = Nz(Me("Tot" + Format(intX))) + Nz(Me("Col" + Format(intX)))
Next intX
' Move to next record in recordset.
rstReport.MoveNext
End If
End If
End Sub
^ is the code of the detail part of my report.
I'm getting the error '13' - Type mismatch when I run the report after casting my field with Format(FieldName, "Percent") and the following code is highlighted:
Me("Col" + Format(intColumnCount + 1)) = _
Me("Col" + Format(intColumnCount + 1)) + Nz(rstReport(intX))
Set the Format property of the textbox in the report to: Percent
Or, expand the source query to have a field returning the formatted value as text:
StudentPercentText: Format([StudentPercent],"Percent")
Then use this field in your report and not the StudentPercent field. However, this is text, so you cannot use such a field in a calculation in the report.

Formatting Datetime in SSRS Expression

Part of my query is like so:
SELECT * FROM TableA
WHERE ColumnA >= DATEADD(DAY, - 30, GETDATE())
With the expression at the where clause above, you can pull a rolling 30 days data without having to supply values. Now users of the report want to see it represented like:
2nd April – 1st May
when the report is ran. Knowing that I have no parameters as the requirement is to not use parameters, how do I reference ">= DATEADD(DAY, - 30, GETDATE())" to reflect the start date and the end date in the report?
SSRS doesn't have built-in support for ordinal numbers (i.e. "1st" or "2nd" instead of "1" or "2"). This page contains custom code to add this functionality to your SSRS report; however it is slightly wrong. Here is a corrected version:
Public Function FormatOrdinal(ByVal day As Integer) as String
' Starts a select case based on the odd/even of num
if(day = 11 or day = 12 or day = 13)
' If the nymber is 11,12 or 13 .. we want to add a "th" NOT a "st", "nd" or "rd"
return day.ToString() + "th"
else
' Start a new select case for the rest of the numbers
Select Case day Mod 10
Case 1
' The number is either 1 or 21 .. add a "st"
Return day.ToString() + "st"
Case 2
' The number is either a 2 or 22 .. add a "nd"
Return day.ToString() + "nd"
Case 3
' The number is either a 3 or 33 .. add a "rd"
Return day.ToString() + "rd"
Case Else
' Otherwise for everything else add a "Th"
Return day.ToString() + "th"
End Select
end if
End Function
If you add this code to the code section of your report under report properties, your textbox expression would be:
Code.FormatOrdinal(Day(Globals!ExecutionTime)) & " " & MonthName(Month(Globals!ExecutionTime), False) & " - " & Code.FormatOrdinal(Day(DateAdd("d", -30,Globals!ExecutionTime))) & " " & MonthName(Month(DateAdd("d", -30,Globals!ExecutionTime)), False)

Right Click on the Textbox, Go To Textbox Properties then, Click on Number tab, click on custom format option then click on fx button in black.
Write just one line of code will do your work in simpler way:
A form will open, copy the below text and paste there to need to change following text with your database date field.
Fields!FieldName.Value, "Dataset"
Replace FieldName with your Date Field
Replace Dataset with your Dateset Name
="d" + switch(int(Day((Fields!FieldName.Value, "Dataset"))) mod 10=1,"'st'",int(Day((Fields!FieldName.Value, "Dataset"))) mod 10 = 2,"'nd'",int(Day((Fields!FieldName.Value, "Dataset"))) mod 10 = 3,"'rd'",true,"'th'") + " MMMM, yyyy"

Use alphabet as counter instead of number in vba

I want to append an alphabet incrementally like for variable say
I pass JohnBox then it should be JohnBox_a then the next time it would be:
JohnBox_b
.
.
JohnBox_z
JohnBox_aa
.
.
JohnBox_zz
Can someone please help regarding solving this issue? This is what I have tried so far but Case 2 is where I am having problems:
Public Function fCalcNextID(strID As String) As Variant
Dim strName As String
'Extract Numeric Component
strName = Left(strID, InStr(strID, "_"))
If Len(Nz(strName, "")) = 0 Then
strName = strID
Else
strName = strName
End If
Select Case Len(Right(strID, (Len(strID) - (InStr(strID, "_")))))
Case 1 'single alpha (a)
If Right$(strID, 1) = "z" Then
fCalcNextID = strName & "aa"
Else
fCalcNextID = strName & Chr$(Asc(Right$(strID, 1)) + 1)
End If
Case 2 'double alpha (bd)
If Right$(strID, 1) = "z" Then
If Mid$(strID, 4, 1) = "z" Then
fCalcNextID = CStr(strName + 1) & "a"
Else
fCalcNextID = CStr(strName) & Chr$(Asc(Mid$(strID, 4)) + 1) & "a"
End If
Else '101bd, 102tx, etc.
'Increment last character, 101bd ==> 101be
fCalcNextID = Left$(strName, 4) & Chr$(Asc(Right$(strID, 1)) + 1)
End If
Case Else
fCalcNextID = strName & "_a"
End Select
End Function
The solution to your problem can be found in this LINK already. Credits to UtterAccess Wiki
The link presents 2 functions: Base10ToBaseLetter and BaseLetterToBase10. The functions are shown below just in case the link changes or become unavailable.
Public Function Base10ToBaseLetter(ByVal lngNumber As Long) As String
' Code courtesy of UtterAccess Wiki
' http://www.utteraccess.com/wiki/index.php/Category:FunctionLibrary
' Licensed under Creative Commons License
' http://creativecommons.org/licenses/by-sa/3.0/
'
' You are free to use this code in any application,
' provided this notice is left unchanged.
' ================================================================================
' Concept:
' Base10: Decimal 123 => (1 * 10 ^ 2) + (2 * 10 ^ 1) + (3 * 10 ^ 0)
' Base26: Decimal 123 => ( 4 * 26 ^ 1) + (19 * 26 ^ 0)
' Representing 4 and 19 with letters: "DS"
' MSD = Most Significant Digit
' LSD = Least Significant Digit
' ================================================================================
' Returns ZLS for input values less than 1
' Error handling not critical. Input limited to Long so should not normally fail.
' ================================================================================
Dim intBase26() As Integer 'Array of Base26 digits LSD (Index = 0) to MSD
Dim intMSD As Integer 'Most Significant Digit Index
Dim n As Integer 'Counter
If lngNumber > 0 Then
' Calculate MSD position (Integer part of Log to Base26 of lngNumber)
' Log of X to Base Y = Log(X) / Log(Y) for any Base used in calculation.
' (VBA Log function uses the Natural Number as the Base)
intMSD = Int(Log(lngNumber) / Log(26))
ReDim intBase26(0 To intMSD)
For n = intMSD To 0 Step -1
' Calculate value of nth digit in Base26
intBase26(n) = Int(lngNumber / 26 ^ n)
' Reduce lngNumber by value of nth digit
lngNumber = lngNumber - ((26 ^ n) * intBase26(n))
Next
' Base Letter doesn't have a zero equivalent.
' Rescale 0 to 26 (digital representation of "Z")
' and "borrow" by decrementing next higher MSD.
' Digit can be -1 from previous borrow onto an already zero digit
' Rescale to 25 (digital representation of "Y")
' Looping from LSD toward MSD
' MSD not processed because it cannot be zero and
' avoids potential out of range intBase26(n + 1)
For n = 0 To intMSD - 1
If intBase26(n) < 1 Then
intBase26(n) = 26 + intBase26(n) ' Rescale value
intBase26(n + 1) = intBase26(n + 1) - 1 ' Decrement next higher MSD
End If
Next
' Ignore MSD if reduced to zero by "borrow"
If intBase26(intMSD) = 0 Then intMSD = intMSD - 1
' Convert Base26 array to string
For n = intMSD To 0 Step -1
Base10ToBaseLetter = Base10ToBaseLetter & Chr((intBase26(n) + 64))
Next
End If
End Function
Public Function BaseLetterToBase10(ByVal strInput As String) As Long
' Upper or lower case characters accepted as input
' ZLS returns 0
' Negative return value indicates error:
' Unaceptable character or Overflow (string value exceeds "FXSHRXW")
' Digit indicates character position where error encountered
' MSD = Most Significant Digit
Dim intMSD As Integer 'MSD Position
Dim intChar As Integer 'Character Position in String
Dim intValue As Integer 'Value from single character
Dim n As Integer 'Counter
On Error GoTo ErrorHandler
' Convert String to UpperCase
strInput = UCase(strInput)
' Calculate Base26 magnitude of MSD
intMSD = Len(strInput) - 1
For n = intMSD To 0 Step -1
intChar = intMSD - n + 1
intValue = Asc(Mid(strInput, intChar, 1)) - 64
' Test for character A to Z
If intValue < 0 Or intValue > 26 Then
BaseLetterToBase10 = -intChar
Exit For
Else
' Add Base26 value to output
BaseLetterToBase10 = BaseLetterToBase10 + intValue * 26 ^ n
End If
Next
Exit Function
ErrorHandler:
BaseLetterToBase10 = -intChar: Exit Function
End Function
Now to apply it to your needs, you simple call those functions:
Public Function fCalcNextID(strID As String) As String
Dim CurIdx As String, n As Integer, x As Long
On Error Resume Next
CurIdx = UCase(Split(strID, "_")(1))
On Error GoTo 0
If CurIdx <> "" Then
x = BaseLetterToBase10(CurIdx) + 1
fCalcNextID = Split(strID, "_")(0) & "_" & LCase(Base10ToBaseLetter(x))
Else
fCalcNextID = strID & "_a"
End If
End Function
This is not me. It is them. What I did is just ask Google to find it for me.
Nonetheless hope this helps and is actually what you need.
Important: Don't remove the comments. That is the only request of the author.
What you have is essentially a base26 count. You can implement that with a modulus function instead of your current code. You will have to create the VBA code yourself, just getting you the algoritm:
For example:
Create an array with a-z
Input a value:
cde
Convert to numeric: 3*26*26+4*26+5
Add 1: 3*26*26+4*26+5+1
input=3*26*26+4*26+6
LOOP until input equals 0:
Mod(input,26) returns remnant (first loop:6, 2nd loop: 4, 3rd loop: 3) => look up in array => f (first loop) (2nd loop d, third loop c).
returnval=lookup value+returnval;
input=Divide (input - mod output (input))/26
END LOOP

Questions on SQL Server 2008 Full-Text Search

I have some questions about SQL 2K8 integrated full-text search.
Say I have the following tables:
Car with columns: id (int - pk), makeid (fk), description (nvarchar), year (int), features (int - bitwise value - 32 features only)
CarMake with columns: id (int - pk), mfgname (nvarchar)
CarFeatures with columns: id (int - 1, 2, 4, 8, etc.), featurename (nvarchar)
If someone searches "red honda civic 2002 4 doors", how would I parse the input string so that I could also search in the "CarMake" and "CarFeatures" tables?
Trying to parse search criteria like that will be a pain. A possible alternate solution would be to create a view that creates a long description of the car and create a full text index on that. So that view might look like:
Create View dbo.CarData
WITH SCHEMABINDING
As
Select dbo.Cars.Id
, dbo.CarMake.Manufactuer
+ ' ' + dbo.Cars.[Year]
+ Coalesce(' ' + dbo.Cars.Description,'')
+ ' ' + Case When Features & 1 <> 0 Then (Select Name From dbo.CarFeature Where Id = 1) Else '' End
+ ' ' + Case When Features & 2 <> 0 Then (Select Name From dbo.CarFeature Where Id = 2) Else '' End
+ ' ' + Case When Features & 4 <> 0 Then (Select Name From dbo.CarFeature Where Id = 4) Else '' End
+ ' ' + Case When Features & 8 <> 0 Then (Select Name From dbo.CarFeature Where Id = 8) Else '' End
+ ' ' + Case When Features & 16 <> 0 Then (Select Name From dbo.CarFeature Where Id = 16) Else '' End As Description
From dbo.Cars
Join dbo.CarMake
On CarMake.Id = Cars.MakeId
With a fulltext index on that view, then you might be able to take your search criteria and do:
Select ...
From CarData
Where Contains(Description, Replace('red honda civic 2002 4 doors', ' ', ' AND '))
Now, this is far from perfect. For example, it will result in '...4 AND doors' and thus find car models in 2004 with 2 doors or 4WD and 2 doors. In addition, I did not see color in your schema so I'm not sure how that would get into the mix.
It would obviously be substantially simpler to force the user to break up the search criteria into its constituent pieces instead of trying to implement a Google-like search. Thus, you would restrict the user to selecting the color from a drop list, selecting the make from another drop list and so on. If you did this, then you wouldn't need the above mentioned View and could instead query against the columns in the tables.
Btw, the features column being a bitwise value makes searches more of a pain as you will need to do a bitwise AND operation on each value to determine if it has the feature in question. It would be better to break out the Feature to Car mapping into a separate table.