Add conditioned AND operator in Postgres routine - database

I have a Postgres routine with optional parameters and I want to add the AND operator at the end only if the optional parameters are passed. I think this could be easy, but couldn't figure it out by myself.
create or replace function bincard_all(item_unit_id integer, _institution_id integer, account_id integer, _from_date timestamp default null, _to_date timestamp default null)
-- ...
-- a bunch of SQL queries
-- ...
WHERE (a.institution_id = bincard_all._institution_id OR
(bincard_all._institution_id is null and it.code = 'STO'))
AND a.item_unit_id = bincard_all.item_unit_id
AND a.account_id = bincard_all.account_id
AND a.date BETWEEN _from_date::date AND _to_date::date; -- execute only when the last two parameters are given
end;
$$;

A typical solution is a predicate like <parameter> IS NULL OR <operation on parameter>.
...
_from_date IS NULL
OR _to_date IS NULL
OR a.date >= _from_date::date
AND a.date < _to_date::date + '1 day'::interval
...

Related

IIF with Multiple Case Statements for Computed Column

I'm trying to add this as a Formula (Computed Column) but I'm getting an error message saying it is not valid.
Can anyone see what is wrong with the below formula?
IIF
(
select * from Config where Property = 'AutomaticExpiry' and Value = 1,
case when [ExpiryDate] IS NULL OR sysdatetimeoffset()<[ExpiryDate] then 1 else 0 end,
case when [ExpiryDate] IS NULL then 1 else 0 end
)
From BOL: ALTER TABLE computed_column_definition
computed_column_expression Is an expression that defines the value of
a computed column. A computed column is a virtual column that is not
physically stored in the table but is computed from an expression that
uses other columns in the same table. For example, a computed column
could have the definition: cost AS price * qty. The expression can be
a noncomputed column name, constant, function, variable, and any
combination of these connected by one or more operators. The
expression cannot be a subquery or include an alias data type.
Wrap the login in function. Something like this:
CREATE FUNCTION [dbo].[fn_CustomFunction]
(
#ExpireDate DATETIME2
)
RETURNS BIT
AS
BEGIN;
DECLARE #Value BIT = 0;
IF EXISTS(select * from Config where Property = 'AutomaticExpiry' and Value = 1)
BEGIN;
SET #Value = IIF (sysdatetimeoffset()< #ExpireDate, 1, 0)
RETURN #value;
END;
RETURN IIF(#ExpireDate IS NULL, 1, 0);
END;
GO
--DROP TABLE IF EXISTS dbo.TEST;
CREATE TABLE dbo.TEST
(
[ID] INT IDENTITY(1,1)
,[ExpireDate] DATETIME2
,ComputeColumn AS [dbo].[fn_CustomFunction] ([ExpireDate])
)
GO
INSERT INTO dbo.TEst (ExpireDate)
VALUES ('2019-01-01')
,('2018-01-01')
,(NULL);
SELECT *
FROM dbo.Test;
Youre trying to do something, what we're not quite sure - you've made a classic XY problem mistake.. You have some task, like "implement auto login expiry if it's on in the prefs table" and you've devised this broken solution (use a computed column/IIF) and have sought help to know why it's broken.. It's not solving the actual core problem.
In transitioning from your current state to one where you're solving the problem, you can consider:
As a view:
CREATE VIEW yourtable_withexpiry AS
SELECT
*,
CASE WHEN [ExpiryDate] IS NULL OR config.[Value] = 1 AND SysDateTimeOffset() < [ExpiryDate] THEN 1 ELSE 0 END AS IsValid
FROM
yourtable
LEFT JOIN
config
ON config.property = 'AutomaticExpiry'
As a trigger:
CREATE TRIGGER trg_withexpiry ON yourtable
AFTER INSERT OR UPDATE
AS
IF NOT EXISTS(select * from Config where Property = 'AutomaticExpiry' and Value = 1)
RETURN;
UPDATE yourtable SET [ExpiryDate] = DATE_ADD(..some current time and suitable offset here..)
FROM yourtable y INNER JOIN inserted i ON y.pk = i.pk;
END;
But honestly, you should be doing this in your front end app. It should be responsible for reading/writing session data and keeping things up to date and kicking users out if they're over time etc.. Using the database for this is, to a large extent, putting business logic/decision processing into a system that shouldn't be concerned with it..
Have your front end language implement a code that looks up user info upon some regular event (like page navigation or other activity) and refreshes the expiry date as a consequence of the activity, only if the expiry date isn't passed. For sure too keep the thing valid if the expiry is set to null if you want a way to have people active forever (or whatever)

SQL Nested Case Statements Within Stored Proc

I'm having problems getting back the data I'd expect from a stored procedure. The procedure is used to both insert and update record, and this determines which parameters are set when called. My example here is assuming the DATE type parameter has the default value of NULL, i.e. they have not been passed into the sp. I have broken the code down into a small section to fix, rather than include the entire procedure code, as follows:
-- these would be sp parameters
declare #CustomerId int = 15
declare #Indicator varchar(5) = 'Yes'
declare #ProjectTypeId tinyint = 1
declare #FutureEffectiveDate as date = null
SELECT
CASE #FutureEffectiveDate
WHEN NULL THEN
CASE #Indicator
WHEN 'Yes' THEN
-- can only be 1, 2 or 3 to return relevant date
CASE #ProjectTypeId
WHEN 1 THEN DI.[NextFormalEffectiveDate]
WHEN 2 THEN DI.[NextInterimEffectiveDate]
WHEN 3 THEN DI.[NextAccountingEffectiveDate]
END
-- data should be NULL if #Indicator not 'Yes'
ELSE NULL
END
ELSE #FutureEffectiveDate
END AS [FutureEffectiveDate]
FROM
[_Staging].[DataImport_2] AS DI
JOIN
[CustomerView] AS CV ON CV.[CustomerNumber] = DI.[BillingInvoiced]
JOIN
[ProjectType] AS PT ON PT.[ProjectType] = DI.[ProjectType]
WHERE
CV.[CustomerID] = #CustomerId AND
PT.[ProjectTypeID] = #ProjectTypeId
So the idea is that, for records where a field contains the text 'Yes', and based on the project type for that record, it selects one of three dates. If the field is not 'Yes' then it should return NULL, ignoring the project type. If the date parameter is NOT null, then it should simply return the parameter passed in. The result is returned as the column 'FutureEffectiveDate'. With the example data I have, I would expect a date to be returned as the relevant field is 'Yes', and the column NextFormalEffectiveDate has a value (as project type is 1).
Oddly enough, if you exclude the outer CASE statement, it works. So the issue is around determining what to do based on the DATE parameter, but i cannot see why the outer CASE statement is breaking the result.
The way you checked #FutureEffectiveDate for NULL in CASE statement is wrong. Here is a small demo
declare #FutureEffectiveDate as date = null
Select Case #FutureEffectiveDate when NULL then 1 else 0 end
The above query will result 0. Because the above CASE statement validates the input expression like #FutureEffectiveDate = NULL which will fail. NULL should be compared using IS operator
Here is the correct way to compare NULL
SELECT CASE
WHEN #FutureEffectiveDate IS NULL THEN
CASE ..

Shift input parameters

Given the following stored procedure, I'd like to be able to shift my input parameters so if the first parameter isn't a valid date, the 2 other parameters that are dates are shifted as the input. I also want to have the current day be used if there are no input parameters to my stored procedure. What is the best way to do it? I'm using SQL Server 2008 r2.
Create PROCEDURE [dbo].[p_qIMO_TEST_2]
#i_InstrumentID VARCHAR(15) = NULL,
#i_DateLow DATETIME = '20090101',
#i_DateHigh DATETIME = '20291231'
AS
IF #i_DateLow IS NULL SET #i_DateLow = CONVERT(DATETIME,CONVERT(DATE,GETDATE()))
IF #i_DateHigh IS NULL SET #i_DateHigh = CONVERT(DATETIME,CONVERT(DATE,GETDATE()))
SELECT * FROM
(
SELECT
out_interface_id,
msg_id,
CAST(xml_msg as XML).value(
'(//InstrumentID)[1]','nvarchar(15)') AS InstrumentID,
msg_type,
xml_msg,
CAST(xml_msg AS XML) as [Quick_XML],
date_received,
status,
last_modified,
environment,
transaction_closed_date
FROM MyTable
WHERE msg_type IN ('ABC','DEF')
AND date_received >= #i_DateLow
AND date_received < DATEADD(DAY,1,#i_DateHigh) -- Need to add 1 to the DateHigh for
-- date range criteria to work properly (>= and <)
) x
WHERE (x.InstrumentID = #i_InstrumentID OR x.InstrumentID = NULL)
ORDER BY date_received DESC
RETURN
GO
Updated for more clarity
Basically, I want it to check if the first argument is a valid date, probably using IsDate()and if it isn't a valid date, then I know it is an InstrumentID. If it is an InstrumentID, I want to check if the next argument is there. If it is there, check if there is a 3rd argument. That would indicate that all 3 arguments are there so I know it is a valid InstrumentID with start and end dates. If there is only a valid first argument, I want it to use the current date for the 2nd and 3rd arguments. I know it's convoluted but that's what I've been asked to do. There is no front end app, so I have to do it in a T-SQL stored procedure.
you can use the ISDATE function to check if the first parameter is a valid date. If it is not use it as a InstrumentId. For second requirement, Make the date parameter defaulted to NULL and in the SP check ISNULL(2ndpara) , ISNULL(3rdPara) That should work.

Stored procedure with optional parameters not showing null values for joined table

I've looked around and found various approaches to this problem, but none that worked in my specific situation. I wrote a stored procedure which I'm using for an SSRS report which accommodates for optional parameters (in SSRS, I'm specifying a list of available parameters, along with an "All" option where the value is set to "=Nothing"). This works for accommodating for multiple optional parameters when, if nothing is selected, all records are shown... except for those with null ProjectID values.
I'd like to be able to run the stored procedure and specify "Null" for the #Project parameter and be shown those values with null ProjectID fields, and ideally, add a "None" option to my SSRS report for this parameter, which would also show those values.
I'm not quite sure how to modify the SP to achieve this:
ALTER PROCEDURE [dbo].[TasksByStatus]
#Status AS Integer = NULL,
#Project AS Integer = NULL
AS
BEGIN
SELECT Task, CONVERT(VARCHAR, StartDate, 101) AS StartDate,
(CASE WHEN CompleteDate IS NULL THEN 'Not complete yet'
ELSE CONVERT(VARCHAR, CompleteDate, 101) END) AS CompleteDate,
(CASE WHEN Notes IS NULL THEN 'No notes provided' ELSE Notes END) AS Notes,
ProjectName, StatusName
FROM Tasks
INNER JOIN Status ON Tasks.StatusID = Status.ID
LEFT JOIN Projects ON Tasks.ProjectID = Projects.ID
AND Projects.ID IS NULL
WHERE Status.ID = ISNULL(#Status, Status.ID)
AND Projects.ID = ISNULL(#Project, Projects.ID)
ORDER BY StartDate, StatusName
END
Results of query without specifying parameters:
I intent, when specifying NULL for #Project to see only that one record with a NULL ProjectID value.
Edit to further clarify
To OP, please let us know what the exact results should be for each row in given table. Also, when you mention 'None', how would you like to pass that to the stored procedure? (The #Project variable is defined as integer)
Parameter Projects.ID In Result
-----------------------------------------
1 1 Yes
1 2 No
1 NULL No
'None' 1 No
'None' 2 No
'None' NULL Yes
NULL 1 Yes
NULL 2 Yes
NULL NULL Yes
If you want to LEFT JOIN on Products, then you need to include that clause in ON not WHERE. Putting it in WHERE makes it an INNER JOIN.
ALTER PROCEDURE [dbo].[TasksByStatus]
#Status INT = NULL,
#Project INT = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT
Task,
CONVERT(VARCHAR, StartDate, 101) AS StartDate,
(CASE WHEN CompleteDate IS NULL THEN 'Not complete yet'
ELSE CONVERT(VARCHAR, CompleteDate, 101) END) AS CompleteDate,
(CASE WHEN Notes IS NULL THEN 'No notes provided' ELSE Notes END) AS Notes,
ProjectName, StatusName
FROM dbo.Tasks
INNER JOIN dbo.Status ON Tasks.StatusID = Status.ID
LEFT JOIN dbo.Projects ON Tasks.ProjectID = Projects.ID
WHERE Status.ID = COALESCE(#Status, Status.ID)
AND COALESCE(Projects.ID, -1) = COALESCE(#Project, Projects.ID, -1)
ORDER BY StartDate, StatusName;
END
GO
This kind of query is the shortcut for disaster. Remember, SQL must compile a query plan that work for any value of the parameters. Therefore it will be pretty much forced to do a table scan on both Status and Projects tables even when #Project and/or #Status were specified! Erland Sommarskog covers this topic in detail at Dynamic Search Conditions in T-SQL
Separate this into 3 different queries:
if (#Status is null and #Project is null)
select ...;
else if (#status is null)
select ... where ProjectID = #Project;
else if (#project is null)
select ... where StatusID = #status;
else
select ... where StatusID = #status and ProjectID = #Project;

SQL if statement in where clause for searching database

I'm creating a stored procedure to return search results where some of the parameters are optional.
I want an "if statement" in my where clause but can't get it working. The where clause should filter by only the non-null parameters.
Here's the sp
ALTER PROCEDURE spVillaGet
-- Add the parameters for the stored procedure here
#accomodationFK int = null,
#regionFK int = null,
#arrivalDate datetime,
#numberOfNights int,
#sleeps int = null,
#priceFloor money = null,
#priceCeil money = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
select tblVillas.*, tblWeeklyPrices.price from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where
If #accomodationFK <> null then
accomodationTypeFK = #accomodationFK
#regionFK <> null Then
And regionFK = #regionFK
IF #sleeps <> null Then
And sleeps = #sleeps
IF #priceFloor <> null Then
And price >= #priceFloor And price <= #priceCeil
END
Any ideas how to do this?
select tblVillas.*, tblWeeklyPrices.price
from tblVillas
INNER JOIN tblWeeklyPrices on tblVillas.villaId = tblWeeklyPrices.villaFK
where (#accomodationFK IS null OR accomodationTypeFK = #accomodationFK)
AND (#regionFK IS null or regionFK = #regionFK)
AND (#sleeps IS null OR sleeps = #sleeps)
AND (#priceFloor IS null OR (price BETWEEN #priceFloor And #priceCeil))
We've used a lot of COALESCE here in the past for "dynamic WHERE clauses" like you're talking about.
SELECT *
FROM vehicles
WHERE ([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
AND ([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
AND ([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
AND ([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
A big problem arises though when you want to optionally filter for a column that is also nullable... if the data in the column is null for a given row AND the user didn't enter anything to search by for that column (so the user input is also null), then that row won't even show up in the results (which, if your filters are optional, is incorrect exclusionary behavior).
In order to compensate for nullable fields, you end up having to do messier looking SQL like so:
SELECT *
FROM vehicles
WHERE (([vin] LIKE COALESCE(#vin, [vin]) + '%' ESCAPE '\')
OR (#vin IS NULL AND [vin] IS NULL))
AND (([year] LIKE COALESCE(#year, [year]) + '%' ESCAPE '\')
OR (#year IS NULL AND [year] IS NULL))
AND (([make] LIKE COALESCE(#make, [make]) + '%' ESCAPE '\')
OR (#make IS NULL AND [make] IS NULL))
AND (([model] LIKE COALESCE(#model, [model]) + '%' ESCAPE '\')
OR (#model IS NULL AND [model] IS NULL))
Just so you understand, IF is procedural code in T-SQl. It canot be used in an insert/update/delete/select statement it can only be used to determine which of two statements you want to run. When you need different possibilities within a statement, you can do as above or use a CASE statement.
You can also use IsNull or Coalesce function
Where accomodationTypeFK = IsNull(#accomodationFK, accomodationTypeFK)
And regionFK = Coalesce(#regionFK,regionFK)
And sleeps = IsNull(#sleeps,sleeps )
And price Between IsNull(#priceFloor, Price) And IsNull(priceCeil, Price)
This does the same thing as Michael's suggestion above...
IsNull(), and Coalesce() work more or less the same way, they return the first non-Null argument in the list, except iSNull only allows 2 arguments, and Coalesce can take any number...
http://blogs.msdn.com/sqltips/archive/2008/06/26/differences-between-isnull-and-coalesce.aspx
Try putting your IF statement around the entire SQL statement. That means will have one SQL statement for each condition. That worked for me.

Resources