Thank you for taking your time to help me today. I am trying to use multiple if statements to control what value is displayed depending on whether each statement is true. So right now I have this below which is essentially:
IIF(expression = NULL
, CompanyAddress
, IIF(Expression='TX'
, IIF(BOOL=TRUE
,CompanyAddress
, SWITCH(DEALER ADDRESSES))
,CompanyAddress)
)
I have tested each individual IIF statements separately and I get the outcomes which I expect. Currently in the first IIF statement and the Expression = NULL is TRUE , It just outputs #Error and not the "Nothin" OR in my real case Company Address. But if Expression = NULL is FAlSE, I do get the correct output of either the companyAddress or the Dealer.
=IIF(IsNothing(Fields!CoOppId.Value)
,("nothin")
, (IIF(Fields!Addr1.Value.Contains("TX")
, IIF(Fields!UDFCustProv.Value = 1
, Fields!Addr0.Value
, Switch(
Fields!UDFMake.Value.Contains("Chevy")
, "Knapp Chevrolet" + chr(10) + "PO box " + chr(10) + "Houston TX 77210"
, Fields!UDFMake.Value.contains("Ford")
, "Sterling McCall Ford" + chr(10) + "6445 Southwest Freeway" + chr(10) + "Houston TX 77074"
, Fields!UDFMake.Value.contains("International")
, "Pliler International" + chr(10) + "2016 S. Eastman Rd" + chr(10) + "Longview TX 75602"
, Fields!UDFMake.Value.contains("Freightliner")
, "Houston Freightliner, Inc" + chr(10) +"9550 North Loop East" + chr(10) + "Houston TX 77029"
, Fields!UDFMake.Value.contains("RAM")
, "Max Haik Dodge Chrysler Jeep" +chr(10)+ "11000 I-45 North Freeway" + chr(10) + "Houston TX 77037")),Fields!Addr0.Value)))
I agree with #Daniel, the error is most likely being produce by the Fields!UDFMake.Value.Contains when the value is null, as IIF does not short-circuit.
Alternatively to the good options that #Daniel mentioned you can replace the contains method by the function InStr as:
... , Switch(
InStr(Fields!UDFMake.Value,"Chevy") > 0
, "Knapp Chevrolet" + chr(10) + "PO box " + chr(10) + "Houston TX 77210" ...
this will not produce an error even when the value of the field is Null.
I'm going to take a guess that when your CoOppId value is NULL, that your other fields in that row are also NULL. Because IIF does not utilize short circuit logic (it always evaluates both sides of the IIF), you are trying to evaluate the expression "NULL.Contains("TX")" and that will generate an #ERROR because NULL is not a string and cannot be operated on with the CONTAINS function.
There are two workarounds available for this scenario, neither of them particularly nice in my opinion, however:
1) Use nested IIFs to ensure that nothing is ever invalid.
IIF(expression is NULL
, CompanyAddress
, IIF(**IIF(expression is NULL, "", expression)** ='TX'
, IIF(BOOL=TRUE
,CompanyAddress
, SWITCH(DEALER ADDRESSES))
,CompanyAddress)
)
Look at the pseudo code above and notice the additional nested IIF around the expression that is using the CONTAINS functionality. If CoOppId doesn't exist, it substitutes in an empty string for the CONTAINS check. Even though this branch never shows it's value for the null scenario, it will at least be valid now.
2) Create a code-behind function that actually does perform short circuit logic for you:
Public Function CheckForNull(ByVal CoOppId As String, ByVal Addr1 as String, ByVal UDFMake As String, ... all fields)
If String.IsNullOrEmpty(CoOppId)
Return "Nothing"
Else
Return *** do your calculation with your fields here
End If
End Function
Which you utilize in your report like:
=Code.CheckForNull(values....)
I just roughly laid out how such a code behind function works, it's obviously not complete but should be enough to point you in the right direction.
Related
I have the following stored procedure (In MS SQL):
ALTER PROCEDURE [dbo].[proc_GetWorksWithEngineerVisits3]
#sTextSearch nvarchar(255) = NULL,
#bCompleteFlag bit = NULL,
#dExpectedStartDateTime datetime = NULL,
#dExpectedEndDateTime datetime = NULL,
#sResponsible_UserIDs nvarchar(255) = NULL,
#bEnableTextSearchFilter bit = false,
#bEnableCompleteFlagFilter bit = false,
#bEnableExpectedDateTimeRangeFilter bit = false,
#bEnableResponsible_UserIDFilter bit = false
AS
SELECT *
FROM dbo.vwWorksWithEngineerVisits
WHERE
--TextSearch Filter Start
(sCustomer LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCustomer
END
OR
sSite LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sSite
END
OR
sCallID LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCallID
END)
--TextSearch Filter End
AND
--Complete Filter Start
bIsComplete = CASE
WHEN #bEnableCompleteFlagFilter = 1
THEN #bCompleteFlag
ELSE bIsComplete
END
--Complete Filter End
AND
--Expected DateTime Range Filter Start
dExpectedStartDateTime >= CASE
WHEN #bEnableExpectedDateTimeRangeFilter = 1
THEN #dExpectedStartDateTime
ELSE dExpectedStartDateTime
END
AND
dExpectedEndDateTime <=
CASE
WHEN #bEnableExpectedDateTimeRangeFilter = 1
THEN #dExpectedEndDateTime
ELSE dExpectedEndDateTime
END
----Expected DateTime Range Filter End
AND
--Responsible_UserID Filter Start
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE (SELECT Value FROM dbo.CSVToList(#sResponsible_UserIDs) AS CSVToList_1)
END
)
--Responsible_UserID Filter End
ORDER BY dExpectedEndDateTime
The output is fine, but it is very slow (15 sec for only 5000 rows) Executing dbo.vwWorksWithEngineerVisits directly takes 1sec for the same number. When executing the SP, I am setting all enable flags = 0.
DECLARE #return_value int
EXEC #return_value = [dbo].[proc_GetWorksWithEngineerVisits3]
#sTextSearch = NULL,
#bCompleteFlag = False,
#dExpectedStartDateTime = N'01/01/1969',
#dExpectedEndDateTime = N'01/01/2021',
#sResponsible_UserIDs = NULL,
#bEnableTextSearchFilter = 0,
#bEnableCompleteFlagFilter = 0,
#bEnableExpectedDateTimeRangeFilter = 0,
#bEnableResponsible_UserIDFilter = 0
SELECT 'Return Value' = #return_value
I want to be able to only filter a column, if the corresponding flag is set. I probably could just check for NULL in the primary parameters and reduce the parameters, but I don't think it changes the problem I am having.
The first 4 Case filters are very basic, and when I comment the remaining last 3 out, the performance/result is instantaneous. As soon as I add one of last 3 back into the mix, things slow down as above. What makes these different is that they do ">=" or "in", rather than just an "=" or "like". The other thing that I noticed is that when I changed the following:
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE (SELECT Value FROM dbo.CSVToList(#sResponsible_UserIDs) AS CSVToList_1)
END
to
lResponsible_UserID in (
CASE
WHEN #bEnableResponsible_UserIDFilter = 0
THEN lResponsible_UserID
ELSE lResponsible_UserID
END
This also speed things up to 1 sec. How is this the case that changing the else part of the statement makes any difference whatsoever, when the flag is always 0, so should never run?
I need these filters, and I need them dynamic. There are a mix of operator types (including an IN that targets a function). Is there a way to refactor this stored procedure to have the same result (it does work), but in a much more optional way?
Apologies if I have missed something in my post, and I will edit if this pointed out.
Thanks
That's a big query!
SQL Server runs a compiler against the queries in your sp when you define it. Then it uses that compiled procedure, blithely ignoring any optimizations that might come from your specific parameter values. This page explains:
When SQL Server executes procedures, any parameter values that are used by the procedure when it compiles are included as part of generating the query plan. If these values represent the typical ones with which the procedure is subsequently called, then the procedure benefits from the query plan every time that it compiles and executes. If parameter values on the procedure are frequently atypical, forcing a recompile of the procedure and a new plan based on different parameter values can improve performance.
In your situation, your parameter settings dramatically simplify the search you want. But the compiled sp doesn't know that so it uses an excessively generalized search plan.
Try appending this to the query in your SP (after your ORDER BY clause) to force the generation of a new, hopefully more specific, execution plan.
OPTION (RECOMPILE)
Also, you can tidy up your filter clauses and make them a little less gnarly.
Try this for your text-search cases: Change
sCustomer LIKE CASE
WHEN #bEnableTextSearchFilter = 1
THEN '%' + #sTextSearch + '%'
ELSE sCustomer
END
to
(#bEnableTextSearchFilter <> 1 OR sCustomer LIKE '%' + #sTextSearch + '%')
This will refrain from saying column LIKE column when your filter is disabled, and may save some time.
You can apply the same principle to the rest of your CASE statements too.
Note: the filter pattern column LIKE '%value%' is inherently slow; it can't use an index range scan on column because the text-matching isn't anchored at the beginning of the pattern. Rather it must scan all the values.
I was wondering how to combine Varchar variables in a stored procedure. I want to combine email addresses into a single variable based of access level. I have tried doing a few things in my if statement.
For example I have tried both:
v_m1_email = Concat(v_m1_email, ' , ' , v_email)
and
v_m1_email = v_m1_email || ' , ' || v_email
My code:
CREATE PROCEDURE ALERTEMAIL (OUT p_m1_email VARCHAR(300),
OUT p_m2_email VARCHAR(300),
OUT p_m3_email VARCHAR(300),
OUT p_m4_email VARCHAR(300))
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE v_email VARCHAR(50);
DECLARE v_access CHAR(5);
DECLARE v_m1_email VARCHAR(300);
DECLARE v_m2_email VARCHAR(300);
DECLARE v_m3_email VARCHAR(300);
DECLARE v_m4_email VARCHAR(300);
DECLARE SQLSTATE CHAR(5);
DECLARE cursor1 CURSOR WITH RETURN for
SELECT EMAIL,JOB_ID FROM PERSONNEL;
OPEN cursor1;
FETCH cursor1 INTO v_email, v_access;
WHILE (SQLSTATE = '00000') DO
IF v_access = 'Man1' THEN
SET v_m1_email = v_m1_email + ' , ' + v_email;
ELSEIF v_access = 'Man2' THEN
SET v_m2_email = v_m2_email + ' , ' + v_email;
ELSEIF v_access = 'Man3' THEN
SET v_m3_email = v_m3_email + ' , ' + v_email;
ELSEIF v_access = 'Man4' THEN
SET v_m4_email = v_m4_email + ' , ' + v_email;
END IF;
FETCH cursor1 INTO v_email, v_access;
END WHILE;
SET p_m1_email = v_m1_email;
SET p_m2_email = v_m2_email;
SET p_m3_email = v_m3_email;
SET p_m4_email = v_m4_email;
END P1
With regard to the first of what already had been tried from the OP, just as #I_am_Batman on 23-Apr-2016 already noted, the syntax for the CONCAT scalar >>-CONCAT--(--expression1--,--expression2--)------>< is limited to just the two arguments, so the expression coded as Concat(v_m1_email, ' , ' , v_email) would fail, presumably with a sqlcode=-170 suggesting something like "Number of arguments for function CONCAT not valid."
Which variant of DB2 was not noted [not in tag nor by comment in the OP], but I offer this link to some doc DB2 for Linux UNIX and Windows 9.7.0->Database fundamentals->SQL->Functions->Scalar functions->CONCAT
However there is nothing conspicuously incorrect with the second of what already had been tried from the OP; i.e. assuming the assignment and expression shown, had been coded just as shown in the body of the CREATE PROCEDURE, with a preceding SET and a trailing ;. In that case, the statement SET v_m1_email = v_m1_email || ' , ' || v_email; should have been able to pass both syntax-checking and data-type\validity-checking. Whereas what is shown in the OP as SET v_m1_email = v_m1_email + ' , ' + v_email; is not valid except when the values of both variables always would be valid string-representations of numbers; that is because the + operator is a numeric-operator rather than the [conspicuously as-desired] string-operator used to effect concatenation [i.e. for "combining strings"].
[ed: 22-Aug-2016] I forgot there was a constant\literal ' , ' in the above expression, so that string-literal also would have to evaluate as a numeric to allow that expression with the + as addition-operator to function at run-time. But of course, that literal could never be interpreted as a numeric; so while the expression could be treated as valid for compile-time [with implicit cast in effect and data-checking not examining the literal value], that expression never would be capable of being evaluated at run-time.
Therefore, if the || operator was properly coded [as seems so, given what was claimed to have been "tried"], yet did not effect what was desired, then the OP would need to be updated to state exactly what was the problem. For example, perhaps there was an error in compile\CREATE of the routine, or perhaps a run-time error for which the effect of the concatenation was perhaps untrimmed results or some other unexpected output, or something else.?.?
Note: as I already added in a comment to a prior answer, the use of CONCAT operator vs the equivalent || in SQL source enables use of that source in\across other code pages without a possible issue due to the use of a variant character.
p.s. A CASE statement might be preferred in place of the IF\ELSE constructs
p.p.s. Might be worth review if the SP really should return both the RS and, or just, the OUT parameters
String concatenation can be done with the || operator.
set vEmail = userName || '#' || domain || '.' || tld;
Give that a try.
We've got a QVW Script failing as it can't find the table to concatenate on, or load into the QVD.
ERROR messages shown on partial reload
ERROR MESSAGE 1
Table not found
Concatenate (DATES)
LOAD
'P' & Num(period,'00') & yearcode AS #dFinYearPeriod,
Num(period,'00') as dFinYearPeriod,
Num(period,'00') as dFinPeriod,
'' as dMonthEnd,
Text(yearcode-1) & '/' & Text(yearcode-2000) AS dFinYear,
Num(yearcode) AS dFinYearOnly,
'' AS dMonth,
yearcode as dYear,
'' AS dMonthNo,
'' as dFinYearEnd_Cur,
'' as dFinYearEnd_Prev
ERROR MESSAGE 2
Table not found
STORE DATES into C:\QlikView\QVD\DATES.qvd (qvd)
We've been running back and forth through the script and can't find the cause of the error. Nothing has been changed in the QVW as far as we're aware, the OLEDB connection is fine, and the stored procedure involved is working correctly, as is the sql script.
from the error messages we're getting this looks to be the script failure point, but we can't work out why...
DATES:
LOAD
'P' & Num(dFinPeriod,'00') & Date(dFinYearEnd_Cur,'YYYY') AS #dFinYearPeriod,
if(isnull(dMonthEnd),
Num(dFinPeriod,'00'),
(if(dMonthEnd = '',
Num(dFinPeriod,'00'),
Num(dFinPeriod,'00') & ' (' & Text(Date (dMonthEnd,'MMM')) & ')'
)
)
) as dFinYearPeriod,
Num(dFinPeriod,'00') as dFinPeriod,
Date(dMonthEnd, 'DD/MM/YYYY') as dMonthEnd,
Text(Date(dFinYearEnd_Prev,'YYYY')) & '/' & Text(Date (dFinYearEnd_Cur,'YY')) AS dFinYear,
Year(Date(dFinYearEnd_Cur, 'DD/MM/YYYY')) AS dFinYearOnly, //Return integer
Text(Date(dMonthEnd,'MMM')) AS dMonth,
Text(Date(dMonthEnd,'YYYY')) as dYear,
Num(Month(dMonthEnd),'00') AS dMonthNo,
Date(dFinYearEnd_Cur,'DD/MM/YYYY') as dFinYearEnd_Cur,
Date(dFinYearEnd_Prev,'DD/MM/YYYY') as dFinYearEnd_Prev
//Filter to only financial year 2011/2 and later
WHERE Text(Date(dFinYearEnd_Cur,'YYYY'))>=2012
;
SQL EXEC
dbo.spGetMonthEnds
;
//Add on the non-date f periods ie. 13 to 16
Concatenate (DATES)
LOAD
'P' & Num(period,'00') & yearcode AS #dFinYearPeriod,
Num(period,'00') as dFinYearPeriod,
Num(period,'00') as dFinPeriod,
'' as dMonthEnd,
Text(yearcode-1) & '/' & Text(yearcode-2000) AS dFinYear,
Num(yearcode) AS dFinYearOnly,
'' AS dMonth,
yearcode as dYear,
'' AS dMonthNo,
'' as dFinYearEnd_Cur,
'' as dFinYearEnd_Prev
;
SQL Select
yearcode,
period
from
d_details
where
period <>'R' and
period >12 and period <=16
and yearcode >=2012
group by
yearcode,
period
;
STORE DATES into $(vFolder)DATES.qvd (qvd);
DROP Table DATES;
message on full reload
Connecting to Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=WRVS;Data Source=rvs-psfsql-1-a;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=WRVS-CLICK-1-A;Use Encryption for Data=False;Tag with column collation when possible=False
Connected
DATES << EXEC
dbo.spGetMonthEnds
48 lines fetched
DATES << Select
yearcode,
period
from
d_details
where
61 lines fetched
the script execution seems to work ish, it's pulling lines back, but can't seem to find the DATES table to concatenate or store into the QVD.
The date manipulation taking place was all in place preiously, and there's nothing weird coming through on the SQL scripts to break any of that.
Any ideas please?
Thanks
It turned out to be a Kerberos error unrelated to the Qlikview application itself
Error Code: 0x7 KDC_ERR_S_PRINCIPAL_UNKNOWN
I think it was related to some virus cleanup work that took place overnight. A server reboot solved the issue.
In Access, I have a field like this:
From the start until end at 01/05/2013, the XXXXXXX device was used.
I'm looking for a query that can extract the device name (in this case XXXXXXX). I was able to use mid() to get it somewhat working, but the sentence could be longer or shorter. What are some other ways to do this? Is there a find() I can use to find the location of "device"?
My SQL looks like:
SELECT Mid(Note, 68, 30)
FROM My_Log
WHERE Note LIKE "*, the*"
;
If the device name is the word before "device", and you want to find that word using only functions supported directly by the Access db engine, Mid(), InStr(), and InstrRev() can get the job done ... but it won't be pretty.
Here is an Immediate window session ...
Note = "From the start until end at 01/05/2013, the XXXXXXX device was used."
? Mid(Note, _
InstrRev(Note, " ", InStr(1, Note, " device") -1) +1, _
InStr(1, Note, " device") - InstrRev(Note, " ", InStr(1, Note, " device") -1) -1)
XXXXXXX
So you would then need to use that complex Mid() expression in a query.
However, if you can use a user-defined function in your query, the logic could be easier to manage.
Public Function FindDevice(ByVal pInput As String) As Variant
Dim astrWords() As String
Dim i As Long
Dim lngUBound As Long
Dim varReturn As Variant
varReturn = Null
astrWords = Split(pInput, " ")
lngUBound = UBound(astrWords)
For i = 0 To lngUBound
If astrWords(i) = "device" Then
varReturn = astrWords(i - 1)
Exit For
End If
Next i
FindDevice = varReturn
End Function
Then the query could be ...
SELECT FindDevice(Note) AS device_name
FROM My_Log
WHERE Note LIKE "*, the*"
If you know the name of the device, you can use Instr:
... WHERE Instr([Note], "Mydevice")>0
You can also use a table that lists devices:
SELECT Note FROM MyTable, ListTable
WHERE Note Like "*" & ListTable.Devices & "*"
Re comment:
SELECT Note
FROM MyTable
WHERE [Note] Like "*" & [enter device] & "*"
If you need to know where in notes you will find device
SELECT Note, InStr([Note],[enter device]) ...
In addition to my comments this is Oracle query that may help you. Replace dual with with your table name and query should probably work. As I mentioned all functions I'm using in this query are ANSI SQL standard. The syntax you can fix yourself. You may run all queries separately to get start, end positions and lenght of your machine name, etc...:
SELECT SUBSTR(str, start_pos, end_pos) machine_name
FROM
(
SELECT str, INSTR(str,'XXXXXXX') start_pos, Length('XXXXXXX') end_pos
FROM
(
SELECT 'From the start until end at 01/05/2013, the XXXXXXX device was used.' str FROM dual
)
)
/
SQL>
MACHINE
-------
XXXXXXX
More generic approach with the same result - eliminating Length:
SELECT SUBSTR(str_starts, 1, end_pos) machine_name FROM -- Final string
(
SELECT str_starts, INSTR(str_starts, ' ')-1 end_pos FROM -- end pos = last X pos in string starts - in 'XXXXXXX'
(
SELECT SUBSTR(str, start_pos) str_starts FROM -- strig starts from first X
(
SELECT str, INSTR(str,'XXXXXXX') start_pos FROM -- start_pos
(
SELECT 'From the start until end at 01/05/2013, the XXXXXXX device was used.' str FROM dual
))))
/
Im working on a NHibernate criteria wich i graduatly builds upp depending on input parameters.
I got some problem with the postal section of these paramters.
Since we got a 5 number digit zipcodes the input parameter is a int, but since we in database also accept foreign zipcodes the database saves it as string.
What im trying to replicate in NHibernate Criteria/Criterion is the following where clause.
WHERE
11182 <=
(case when this_.SendInformation = 0 AND dbo.IsInteger(this_.Zipcode) = 1 then
CAST(REPLACE(this_.Zipcode, ' ', '') AS int)
when this_.SendInformation = 1 AND dbo.IsInteger(this_.WorkZipcode) = 1 then
CAST(REPLACE(this_.WorkZipcode, ' ', '') AS int)
when this_.SendInformation = 2 AND dbo.IsInteger(this_.InvoiceZipcode) = 1 then
CAST(REPLACE(this_.InvoiceZipcode, ' ', '') AS int)
else
NULL
end)
What we do is to check where the member contact (this_) has preferenced to get information sent to, then we check the input zipcode as integer against three different columns depending on if the column is convertable to int (IsInteger(expr) function) if column is not convertable we mark the side as NULL
in this case we just check if the zipcode is >= input parameter (reversed in sql code since paramter is first), the goal is to do a between (2 clauses wrapped with 'AND' statement), >= or <=.
UPDATE
Got a hint of success.
Projections.SqlProjection("(CASE when SendInformation = 0 AND dbo.IsInteger(Zipcode) = 1 then CAST(REPLACE(Zipcode, ' ', '') AS int) when SendInformation = 1 AND dbo.IsInteger(WorkZipcode) = 1 then CAST(REPLACE(WorkZipcode, ' ', '') AS int) when SendInformation = 2 AND dbo.IsInteger(InvoiceZipcode) = 1 then CAST(REPLACE(InvoiceZipcode, ' ', '') AS int) else NULL END)"
, new[] { "SendInformation", "Zipcode", "WorkZipcode", "InvoiceZipcode" },
new[] { NHibernateUtil.Int32, NHibernateUtil.String, NHibernateUtil.String, NHibernateUtil.String });
Throw my whole clause in a Projections.SqlProjection, however when i run my code some of my projection is cut (" AS int) else NULL END)" is cut from the end) and makes the sql corrupt.
Is there some kind of limit on this ?
Got it working yesterday.
Projections.SqlProjection worked, however if you don't name the projection as a column it some how cuts some of the TSQL code.
(Case
when x = 1 then 'bla'
when x = 2 then 'bla_bla'
else NULL
END) as foo
when using the last part (as foo) and naming the entire case syntax it works and dont cut anything.
However i dont know why but i could not manage to use the aliases from the other part of the criteria.