Sql Server: Alternatives for conditional expressions in where condition - sql-server

By writing client server applications typically one stumbles in cases where he needs to do a query that returns different records according to which is the logged in user.
I use this approach, but I wonder if there are alternatives, may you suggest?
(the reason I ask is that for more complx queries this becomes quite unreadable, while with some CASE statements it would be better).
declare #UserIsAdministrator bit;
declare #UserID integer;
set #UserIsAdministrator = 0 -- test value
set #UserID = 41; -- test value
SELECT * FROM employees
WHERE
(#UserIsAdministrator = 1) -- if user is admin return all records
OR
(
(#UserIsAdministrator = 0)
AND
(manager_ID = #UserID)
) -- if user is not admin return only "his" records

I think I would try to avoid embedding this type of authorization within the SQL queries altogether. Have separate queries, and resolve roles and privileges in a separate layer/component before you select which query to execute.

Options:
IF and ELSE
IF #UserIsAdministrator = 1
SELECT * FROM employees
ELSE
SELECT * FROM employees WHERE manager_ID = #UserID
Change parameters based on another parameter
IF #UserIsAdministrator = 1
SET #UserID = NULL
SELECT * FROM employees
WHERE manager_ID = ISNULL(#UserID, manager_ID)
Or different views/stored procs

Related

Dynamic T-SQL stored procedure in where clause

I need to write a sample stored procedure with 2 parameters like this:
sp_list_customers #locationid int, #category varchar
Users can pass in the #locationid = 0 to list down customers in ALL locations or specific location ID. Also they can pass null or empty string to p to slow ALL categories.
Here is my SQL code:
create procedure ...
as
select *
from customers
where locationid = (case
when isnull(#locationid, 0) = 0
then locationid
else #locationid
end)
and category = (case
when isnull(#category, '') = ''
then category
else #category
end)
However this kind of codes is running toooo slow with more parameters
I was looking for ways to fix the issue and then found out the dynamic T-SQL is the acceptable solution. But to migrate all my stored procedures is a nightmare job and error prone.
I need to write another user defined function to help me. What is the best user-defined function code for this?
Thanks
Try if the below simplified code works better...
create procedure ... as select * from customers where (nullif(#locationid,'') is null
or locationid = #locationid)
and ( nullif(#category,'') is null or category = #category)

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)

Select statement return if there is a value and if there is no value

Here's my code of the SQL Server stored procedure:
SELECT NOTES as DeletionNote
FROM STCRCHF_LOG
WHERE STHTR_ = #transferNo
IF ( ##ROWCOUNT = 0)
If there is data found, I just want to return the string of NOTES. Else if it doesn't have data, I just want to return an empty string or null.
Screenshot (executed stored procedure):
If there is data found. At my program on the web server side it gets the data.
If there is no data. In my program on the web server side it causes a NullReferenceException
If only a single record is possible then:
select coalesce((SELECT NOTES FROM STCRCHF_LOG
WHERE STHTR_ = #transferNo), '') as DeletionNote
If multiple records are possible then the following will ensure at least one row is returned:
SELECT NOTES as DeletionNote FROM STCRCHF_LOG WHERE STHTR_ = #transferNo
union all select '' /* or null if preferred */ where not exists (SELECT 1 FROM STCRCHF_LOG WHERE STHTR_ = #transferNo)
Another way which I like is to use a dummy value and OUTER APPLY like so.
-- sample data
DECLARE #table TABLE (someId INT IDENTITY, Col1 VARCHAR(100));
INSERT #table(Col1) VALUES ('record 1'),('record 2');
DECLARE #id INT = 11;
SELECT f.Col1
FROM (VALUES(NULL)) AS dummy(x)
OUTER APPLY
(
SELECT t.Col1
FROM #table AS t
WHERE t.someId = #id
) AS f;
Check If(DataTable.Rows.Count >0) check at your web programming level to avoid NullReferenceException. Based on the condition you can make the decision what to do with the further logic at web program.
It is always wise idea to handle such exception from programming level. Think of scenario where somebody else make changes in SQL query without knowing the effect of web usages of the code.

How to do a conditional JOIN with table valued parameter?

Okay so I have spent some time researching this but cannot seem to find a good solution.
I am currently creating a stored procedure that takes a set of optional parameters. The stored procedure will act as the "universal search query" for multiple tables and columns.
The stored procedure looks something like this (Keep in mind that this is just a stripped down version and the actual stored procedure has more columns etc.)
The '#ProductIdsParam IntList READONLY' is an example table valued parameter that I would like to JOIN if it is not empty. In other words, the query should only search by parameters that are not null/empty.
Calling the procedure and parsing the other parameters works just like it should. I might however have misunderstood and should not do a "universal search query" like this at all.
CREATE PROCEDURE [dbo].[usp_Search]
#ProductIdParam INT = NULL,
#CustomerNameParam NVARCHAR(100) = NULL,
#PriceParam decimal = NULL,
-- THIS IS WHAT I'D LIKE TO JOIN. BUT THE TABLE CAN BE EMPTY
#ProductIdsParam IntList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT
CustomerTransactionTable.first_name AS FirstName,
CustomerTransactionTable.last_name AS LastName,
ProductTable.description AS ProductDescription,
ProductTable.price as ProductPrice
FROM dbo.customer AS CustomerTransactionTable
-- JOINS
LEFT JOIN dbo.product AS ProductTable
ON CustomerTransactionTable.product_id = ProductTable.id
WHERE
(ProductTable.id = #ProductIdParam OR #ProductIdParam IS NULL)
AND (CustomerTransactionTable.first_name = #CustomerNameParam OR #CustomerNameParam IS NULL)
AND (CustomerTransactionTable.price = #PriceParam OR #PriceParam IS NULL)
END
You can add the int table in LEFT join and then add a where condition based on the record count in the filter table. If #ProductIdsParam is declared as table, you should first count records in it and store the result in a varaible.
AND COALESCE(#ProductIdsParam.id, 0) = (CASE WHEN #ProductIdsCount = 0 THEN 0 ELSE ProductTable.id END)
In case #ProductIdsCount = 0 then you get always 0 = 0 so you get all the records, else you select only records where the productId in the filter table equals the ProductTable.id.
There are other (maybe cleaner) approaches possible though but I think this works.

better way to assign stored procedures output values from a table select

Due to programming language restraints, my ERP system does not allow me to make advanced select queries, that´s why I need to rely on making a stored procedure on SQL Server, calling it from the ERP system and getting the result through an array.
The code belows works ok, but I think it´s not the correct way to assign the values to the output variables... I wanted to assign the output variables directly from the select, without need to make a #temp table... is it possible? or did I make it right?
If the code can be enhanced, I would gracefully accept any suggestions. The objective of the code is call a stored procedure with a RFID tag (read by a RFID card reader) and then get some employee info from another database, from another ERP, on another server (linked through SQL "linked servers")
ALTER procedure [dbo].[KSBValTag]
(
#rfid varchar(20),
#OUT_NUMCAD varchar(10) OUTPUT,
#OUT_NOMFUN varchar(50) OUTPUT,
#OUT_SIT varchar(2) OUTPUT,
#OUT_CODCCU varchar(5) OUTPUT,
#OUT_NOMCCU varchar(30) OUTPUT
) as
Begin
set #rfid = SUBSTRING(#rfid, PATINDEX('%[^0]%', #rfid+'.'), LEN(#rfid))
select fun.numcad as Numcad,
fun.nomfun as Nomefun,
fun.sitafa as Situacao,
fun.codccu as CodCCU,
ccu.nomccu as NomeCCU
into #temp
from [vetorh].vetorh.r034fun as FUN
inner join
[vetorh].vetorh.r018ccu CCU
on fun.codccu = ccu.codccu
where numcad = (select num_cartao from [ksb-app01].topacesso.dbo.Cartoes where CodigoDeBarras = #rfid)
and tipcol = '1'
set #OUT_NUMCAD = (select Numcad from #temp)
set #OUT_NOMFUN = (select Nomefun from #temp)
set #OUT_SIT = (select Situacao from #temp)
set #OUT_CODCCU = (select CodCCU from #temp)
set #OUT_NOMCCU = (select NomeCCU from #temp)
End
select
#OUT_NUMCAD = fun.numcad,
#OUT_NOMFUN = fun.nomfun,
#OUT_SIT = fun.sitafa,
#OUT_CODCCU = fun.codccu,
#OUT_NOMCCU = ccu.nomccu
from [vetorh].vetorh.r034fun as FUN
inner join
[vetorh].vetorh.r018ccu CCU
on fun.codccu = ccu.codccu
where numcad = (select num_cartao from [ksb-app01].topacesso.dbo.Cartoes where CodigoDeBarras = #rfid)
and tipcol = '1'

Resources