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.
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 have a SQL column of type varchar that is meant to contain SQL conditions, eg (3 rows below):
WHERE Language = 1 OR Region = 10
WHERE Color= 3 OR Region = 10
WHERE Status = 1 OR Region = 10 AND Language = 1
I need to create a query to get the lookup values of these that can be sent to any non technical person. The output would be:
WHERE Language = English OR Region = Canada
WHERE Color= Red OR Region = Canada
WHERE Status = Active OR Region = Canada AND Language = English
I know I can first put all the lookups in a temp table and then use the REPLACE function, but I was wondering if there's any better (and shorter) way to do this? Any help would be appriciated.
Thanks.
I would create subquery column values ready to replace originals, f.eks for Regions:
SELECT
CONCAT('%Region = ', Region,'%') ConditionToJoin,
CONCAT('Region = ', Region) ConditionToFind,
CONCAT('Region = ', RegionName) ConditionToReplace
FROM Regions
The join this sub query with condition rows using LIKE operator and ConditionToJoin column.
There is also a question if you need to handle situations like:
Region=1
Region =1
etc...
Alternatively you could write user function to wash data and perform all replacements, something like:
CREATE FUNCTION replaceLookups(#condition VARCHAR(MAX))
RETURNS VARCHAR(MAX)
BEGIN
#string_replacement VARCHAR(MAX), #string_pattern VARCHAR(MAX);
SET #string_pattern = SELECT TOP 1 CONCAT('Region = ', Region) FROM Regions WHERE #condition LIKE CONCAT('%Region = ', Region, '%')
SET #string_replacement = SELECT TOP 1 CONCAT('Region = ', RegionName) FROM Regions WHERE #condition LIKE CONCAT('%Region = ', Region, '%')
SET #ret = REPLACE(#condition, #string_pattern, #string_replacement)
... --and so on for each lookup
RETURN #ret
END
Word of warning. Above code was written from top of my head - might need some work. Also I wouldn't expect great performance.
I have a situation where I'm trying to filter people that have credits or not. Here's an example Dapper query for reference:
var sql = #"
SELECT *
FROM Person
WHERE (Person.Credits > 0) = #hasCredits";
Connection.Query(sql, new { hasCredits });
I was pretty sure Postgres allows you to do this, hence my surprise when on SQL Server this failed with Incorrect syntax near '='.
With the sample data below, I would expect the query to return the Person with the ID of 1 when hasCredits is FALSE and the Person with the ID of 2 when hasCredits is TRUE.
INSERT INTO Person (PersonId, Credits) VALUES (1, 0), (2, 0);
In SQL Server Is there a way to evaluate whether an expression evaluates to true or false?
I've considered the following (horrible looking) options, but was hoping there was a more elegant solution:
"WHERE (Person.Credits " + (hasCredits ? ">" : "=") + " 0)"
"WHERE (#hasCredits = 1 AND Person.Credits > 0) OR (#hasCredits = 0 AND Person.Credits = 0)"
Considering that when Has credits radio button is selected #hasCredits variable will have 1 else when Doesn't have credits is selected #hasCredits variable will have 0 else #hasCredits variable will be null
SELECT *
FROM Person
WHERE (Person.Credits > 0 and #hasCredits=1) or --Has credits
(Person.Credits <1 and #hasCredits=0) or --Doesn't have credits
(#hasCredits IS NULL) --Don't care
You question was quite plain and the answer is super easy!
case when Person.Credit > 0 then 1 else 0 end = #hasCredits
I am using following script to generate objects in C# from Oracle and it works ok.
select 'yield return new Question { QuestionID = '||Q.questionid||', Qcode = "'||Q.Qcode||'", QuestionDescription = "'||Q.questiondescription||'", QuestionText = "'||Q.questiontext||'", QuestionCategoryId = '||Q.questioncategoryid||', QuestionTypeID = '||Q.QuestionTypeID||', IsThunderheadOnly = '|| case q.isthunderheadonly when 0 then 'false' else 'true' end ||', DisplayOrder = '||q.displayorder||' };'
from QUESTION q
where
questioncategoryid = 7
However again and again I run into the problem where I cannot || add columns with NULL values and solution to this point was adding those properties manually, which was ok when selecting up to 20 records.
Now I ran into a case of having to select hundreds of records and adding them manually would take substantial amount of time.
How could I modify the script to add (example) MaxValue property if column in the table is NOT NULL but skip it it if it is?
You can skip it with case ... when ... else like you figured out by yourself:
... ||case when A.NEXTQUESTIONID is not null then 'NextQuestionID = '||A.NEXTQUESTIONID||',' else '' end || ...
You can also use the nvl2 function for a shorter solution:
... || nvl2(A.NEXTQUESTIONID, 'NextQuestionID = '||A.NEXTQUESTIONID||',', '') || ...
I'm brand-new to the Oracle world so this could be a softball. In working with an SSRS report, I'm passing in a string of states to a view. The twist is that the users could also pick a selection from the state list called "[ No Selection ]" ... (that part was not by doing and I'm stuck with implementing things this way)
If they choose the No Selection option, then I just want to return all states by default, otherwise return just the list of states that are in my comma-separated list.
This really seems like it should be easy but I'm stuck. Here is the code I have so far (just trying to get a sample to work) but my eyes have finally gone crossed trying to get it going.
Could anybody give me some direction on this one please?
begin
:stateCode:='MO,FL,TX';
--:stateCode:='[ No Selection ]';
end;
/
select count(*) as StateCount, :stateCode as SelectedVal
from hcp_state vw
where
(case
when (:stateCode = '') then (1)
when (:stateCode != '') then (vw.state_cd in (:stateCode))
(else 0)
end)
;
You can write the where clause as:
where (case when (:stateCode = '') then (1)
when (:stateCode != '') and (vw.state_cd in (:stateCode)) then 1
else 0)
end = 1;
Alternatively, remove the case entirely:
where (:stateCode = '') or
((:stateCode != '') and vw.state_cd in (:stateCode));
Or, even better:
where (:stateCode = '') or vw.state_cd in (:stateCode)
We can use a CASE statement in WHERE clause as:
SELECT employee_no, name, department_no
FROM emps
WHERE (CASE
WHEN :p_dept_no = 50 THEN 0
WHEN :p_dept_no = 70 THEN 0
ELSE -1
END) = 0;