I have a procedure with a (slightly more complex) version of the below:
CREATE PROC sp_Find_ID (
#Match1 varchar(10),
#Match2 varchar(10)
) AS
DECLARE #ID int
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND Coalesce(Match2,#Match2,'') = Coalesce(#Match2,Match2,'')
SELECT #ID ID
Essentially Match1 is a mandatory match, but Match2 is both optional on the input to the procedure, and on the table being searched. The 2nd match succeeds where the input and/or the table Match2 values are null, or where they're both the same (not null) value.
My question is: Is there a more efficient (or even more readable) way of doing this?
I've used this method a few times, and I feel slightly sullied each time (subjective dirtiness admittedly).
Is there a more efficient (or even more readable) way of doing this?
The example you provided, using COALESCE/etc is non-sargable. You need to separate things so only what needs to be present in the query is run:
DECLARE #ID int
IF #Match2 IS NOT NULL
BEGIN
SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1
AND (t.match2 = #Match2 OR t.match2 IS NULL)
END
ELSE
BEGIN
SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1
END
SELECT #ID ID
If you want this to occur in a single SQL statement, dynamic SQL is the only real alternative. I highly recommend reading The curse and blessing of dynamic SQL before reading further:
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N' SELECT #ID = t.id
FROM TABLE1 t
WHERE t.match1 = #Match1 '
SET #SQL = #SQL + CASE
WHEN #Match2 IS NOT NULL THEN
' AND (t.match2 = #Match2 OR t.match2 IS NULL) '
ELSE
' '
END
BEGIN
EXEC sp_executesql #SQL,
N'#ID INT OUTPUT, #Match1 VARCHAR(10), #Match2 VARCHAR(10)',
#ID, #Match1, #Match2
END
Avoiding OR and ISNULL etc
The EXCEPT bit returns no rows if either side IS NULL
Match2 <> #Match2 means exclude non-NULL non-matching
Something like this
DROP TABLE dbo.Table1
CREATE TABLE dbo.Table1 (ID int NOT NULL, Match1 int NOT NULL, Match2 int NULL)
INSERT dbo.Table1 VALUES (1, 55, 99), (2, 55, NULL)
DECLARE #Match1 int = 55, #Match2 int
SELECT ID
FROM
(
SELECT ID FROM Table1 WHERE Match1 = #Match1
EXCEPT -- #Match2 = NULL, match both rows (99, NULL)
SELECT ID FROM Table1 WHERE Match2 <> #Match2
) foo
SET #Match2 = -1
SELECT ID
FROM
(
SELECT ID FROM Table1 WHERE Match1 = #Match1
EXCEPT -- #Match2 = -1, match ID = 2 only where Match2 IS NULL
SELECT ID FROM Table1 WHERE Match2 <> #Match2
) foo
Don't know if this is any more preferable.
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND ((Match2 = #Match2) OR Coalesce(Match2,#Match2) IS NULL)
I would have thought this should do it - providing that the #Match2 value will be NULL if it is optional.
CREATE PROC sp_Find_ID (
#Match1 varchar(10),
#Match2 varchar(10)
) AS
DECLARE #ID int
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND Match2 = IsNull(#Match2, Match2)
SELECT #ID ID
Seems simple to me? I must be missing something.. you dont need the Coalesce
SELECT #ID = ID
FROM Table1
WHERE Match1 = #Match1
AND (
(Match2 is null and #Match2 is null)
or
#Match2=Match2
)
SELECT #ID ID
Related
select * from (values
('dept1','user1'),
('dept2','user2'),
('dept3','user3'),
('dept4','user4')
)table1([department],[user])
where [user] = #id
scenario1:
#id = 'user1'
dept1
scenario2:
#id = 'user5'
dept1
dept2
dept3
dept4
this is what it looks like from a noobish query
declare #id varchar(12) = 'user1'
declare #var int = (select count(*) from table1 where [user] = #id)
select * from table1 where [user] = #id or #var = 0
DECLARE #id VARCHAR(5) = 'user1';
--DECLARE #id VARCHAR(5) = 'user5';
WITH UsersAndDepartments
AS ( SELECT *
FROM ( VALUES ( 'dept1', 'user1'), ( 'dept2', 'user2'),
( 'dept3', 'user3'), ( 'dept4', 'user4') ) x ( [department], [user] )
)
SELECT *
FROM UsersAndDepartments ud1
WHERE ud1.[user] =
CASE
WHEN EXISTS ( SELECT 1 FROM UsersAndDepartments ud2 WHERE ud2.[user] = #id ) THEN #id
ELSE ud1.[user]
END
The above just checks on user column if any row exists for an id, else matches on all.
declare #tab table (id int , value varchar(10))
declare #id int = 4
insert into #tab
select 1,'Ajay'
union all
select 2,'Ajay1'
union all
select 3,'Ajay2'
union all
select 4,'Ajay3'
union all
select 5,'Ajay4'
select * from #tab
where id = case when exists (select * from #tab where id = #id) then #id else id end
I would do this with a simple OR, not a CASE expression in the WHERE.
In general, you want to avoid CASE expressions in the WHERE clause for several reasons:
The logic can almost be written concisely using basic boolean operations.
Adding additional constructs (in addition to AND, OR, and NOT) just makes the logic harder for people to follow.
It pretty much kills any optimization paths.
I would suggest:
with table1 as
select v.*
from (values ('dept1', 'user1'),
('dept2', 'user2'),
('dept3', 'user3'),
('dept4', 'user4')
) v([department], [user])
)
select t1.*
from table1 t1
where t1.[user] = #id or
not exists (select 1 from table1 t1 where t1.user = #id);
You may have to do a check first something like this
Declare #RowCount int
Select #RowCount = (select count(*) from [Table] Where [Column] = 'xxx')
If #RowCount > 0
begin
Select 1 -- put code here if records
end
else
begin
Select 2 -- put code here if no records
end
you can try this:
DECLARE #id varchar(12) = 'user1'
IF EXISTS(SELECT COUNT(*) FROM table1 WHERE [user] = #id)
BEGIN
SELECT * FROM table1 WHERE [user] = #id
END
ELSE
BEGIN
SELECT * FROM table1
END
you can also read more about "EXISTS" syntax on:
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/exists-transact-sql?view=sql-server-2017
You can have a slightly better execution plan if you separate the "if exist" logic from the actual query:
DECLARE #id varchar(10) = 'user5'
DECLARE #table TABLE ([department] varchar(10), [user] varchar(10))
insert into #table values
('dept1','user1'),
('dept2','user2'),
('dept3','user3'),
('dept4','user4')
DECLARE #exists BIT =
(SELECT 1 FROM #table WHERE [user] = #id)
SELECT department FROM #table
WHERE [user] = CASE #exists WHEN 1 THEN #id ELSE [user] END
I created a temp table #test containing 3 fields: ColumnName, TableName, and Id.
I would like to see which rows in the #test table (columns in their respective tables) are not empty? I.e., for every column name that i have in the ColumnName field, and for the corresponding table found in the TableName field, i would like to see whether the column is empty or not. Tried some things (see below) but didn't get anywhere. Help, please.
declare #LoopCounter INT = 1, #maxloopcounter int, #test varchar(100),
#test2 varchar(100), #check int
set #maxloopcounter = (select count(TableName) from #test)
while #LoopCounter <= #maxloopcounter
begin
DECLARE #PropIDs TABLE (tablename varchar(max), id int )
Insert into #PropIDs (tablename, id)
SELECT [tableName], id FROM #test
where id = #LoopCounter
set #test2 = (select columnname from #test where id = #LoopCounter)
declare #sss varchar(max)
set #sss = (select tablename from #PropIDs where id = #LoopCounter)
set #check = (select count(#test2)
from (select tablename
from #PropIDs
where id = #LoopCounter) A
)
print #test2
print #sss
print #check
set #LoopCounter = #LoopCounter + 1
end
In order to use variables as column names and table names in your #Check= query, you will need to use Dynamic SQL.
There is most likely a better way to do this but I cant think of one off hand. Here is what I would do.
Use the select and declare a cursor rather than a while loop as you have it. That way you dont have to count on sequential id's. The cursor would fetch fields columnname, id and tablename
In the loop build a dynamic sql statement
Set #Sql = 'Select Count(*) Cnt Into #Temp2 From ' + TableName + ' Where ' + #columnname + ' Is not null And ' + #columnname <> '''''
Exec(#Sql)
Then check #Temp2 for a value greater than 0 and if this is what you desire you can use the #id that was fetched to update your #Temp table. Putting the result into a scalar variable rather than a temp table would be preferred but cant remember the best way to do that and using a temp table allows you to use an update join so it would well in my opinion.
https://www.mssqltips.com/sqlservertip/1599/sql-server-cursor-example/
http://www.sommarskog.se/dynamic_sql.html
Found a way to extract all non-empty tables from the schema, then just joined with the initial temp table that I had created.
select A.tablename, B.[row_count]
from (select * from #test) A
left join
(SELECT r.table_name, r.row_count, r.[object_id]
FROM sys.tables t
INNER JOIN (
SELECT OBJECT_NAME(s.[object_id]) table_name, SUM(s.row_count) row_count, s.[object_id]
FROM sys.dm_db_partition_stats s
WHERE s.index_id in (0,1)
GROUP BY s.[object_id]
) r on t.[object_id] = r.[object_id]
WHERE r.row_count > 0 ) B
on A.[TableName] = B.[table_name]
WHERE ROW_COUNT > 0
order by b.row_count desc
How about this one - bitmask computed column checks for NULLability. Value in the bitmask tells you if a column is NULL or not. Counting base 2.
CREATE TABLE FindNullComputedMask
(ID int
,val int
,valstr varchar(3)
,NotEmpty as
CASE WHEN ID IS NULL THEN 0 ELSE 1 END
|
CASE WHEN val IS NULL THEN 0 ELSE 2 END
|
CASE WHEN valstr IS NULL THEN 0 ELSE 4 END
)
INSERT FindNullComputedMask
SELECT 1,1,NULL
INSERT FindNullComputedMask
SELECT NULL,2,NULL
INSERT FindNullComputedMask
SELECT 2,NULL, NULL
INSERT FindNullComputedMask
SELECT 3,3,3
SELECT *
FROM FindNullComputedMask
Subquery that is give as a parameter in ISNULL or IIF or CASE gets executed irrespective of the condition
To explain what I mean, consider the below example. When I run the below query and look in the execution plan, I find that even if the variable #Id not NULL, the second condition gets executed always.
Can anyone explain Why does the subquery gets executed in Query 1,2,3?
--Create a Temp table
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL DROP TABLE #TempTable
CREATE TABLE #TempTable
(
ID INT
)
--Insert some data
INSERT INTO #TempTable VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
DECLARE #Id INT = 1
--Query 1: ISNULL
SET #Id= ISNULL(#Id, (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))
SELECT #Id AS ID
--Query 2: IIF
SET #Id= IIF(#Id IS NULL,(SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1),#Id)
SET #Id= IIF(#Id IS NOT NULL,#Id,(SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))
SELECT #Id AS ID
--Query 3: CASE
SET #Id= CASE WHEN #Id IS NULL THEN (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1) ELSE #Id END
SET #Id= CASE WHEN #Id IS NOT NULL THEN #Id ELSE (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1) END
SELECT #Id AS ID
---Query 4: IF
IF #Id IS NULL
BEGIN
SET #Id = (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1)
SELECT #Id AS ID
END
ELSE
BEGIN
SELECT #Id AS ID
END
If you want the subquery to execute only when the null condition is met, use an IF instead of ISNULL
if #Id is null
set #id = (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1)
instead of
SET #Id= ISNULL(#Id, (SELECT TOP 1 ID FROM #TempTable TT WHERE TT.ID = 1))
I have a query that returns a reasonable number of records from a table. I need to include a comma delimited string as an output column. Something like this
SELECT
column1,
column2,
column3,
[Delimited string based on the id]
FROM
sometable
WHERE
id = someid
I know you can use the coalesce function which i have used in the past but im not sure how to integrate it into a select statement, also not really sure on the performance?
Any ideas?
I'd loop through the items to build the string with, then add it to the result set.
-- Minor performance tweak.
SET NOCOUNT ON
GO
-- ID of the item or widget or whatever
DECLARE #someid int
DECLARE #LookupItems TABLE
(
ID int IDENTITY (1, 1) PRIMARY KEY NOT NULL,
LookupTableID int, -- Primary key of the lookup table.
LookupField varchar(30) -- Text of the lookup value.
)
-- Update to the desired id.
SET #someid = 1234
INSERT INTO #LookupItems (ID, LookupField)
SELECT ID, Value
FROM dbo.somelookuptable
WHERE ID IN (
SELECT lookupid
FROM dbo.sometable
WHERE ID = #someid
)
DECLARE
#Count int,
#Max int,
#DelimitedString varchar(1000)
-- Initialize with a non-NULL value to facilitate concatenation.
SET #DelimitedString = ''
SET #Count = 1
SELECT #Max = MAX(ID) FROM #LookupItems
WHILE (#Count <= #Max)
BEGIN
SELECT #DelimitedString = #DelimitedString + IsNull(somefield, '') + ','
FROM #LookupItems
WHERE ID = #Count
SET #Count = #Count + 1
END
SELECT
column1,
column2,
column3,
#DelimitedString
FROM dbo.sometable
WHERE id = someid
Can someone please tell me why this wont work. I want to loop inside a loop .....
BEGIN
SET NOCOUNT ON;
Declare #TempLocations Table (PK int Identity(1,1) not null Primary key, LocationID Int)
Declare #TempItems Table (PK1 int Identity(1,1) not null Primary key, ItemID int)
Declare #TempTable Table (ID Int Identity(1,1), LocationID int, ItemID int)
Declare #MaxLocationID int,
#MaxItemID Int,
#LocationID int,
#ItemID int
-- Load "Can be sold from" Locations into Temp Table
Insert Into #TempLocations (LocationID)
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
Set #MaxItemID = (Select MAX(PK1) From #TempItems)
Set #LocationID = 1
-- Load "IsActive" Items into Temp Table
Insert Into #TempItems (ItemID)
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
Set #MaxLocationID = (Select MAX(PK) From #TempLocations)
Set #ItemID = 1
--Main Code
While #LocationID <= #MaxLocationID
Begin
While #ItemID <= #MaxItemID
Begin
Insert into #TempTable (LocationID, ItemID)
Values (#LocationID, #ItemID)
Set #ItemID = #ItemID + 1
end
Set #LocationID = #LocationID + 1
End
Select * from #TempTable
END
The result I am Tryinig to get is this
#tempTable =
LocationID = 1
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
LocationID = 2
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
and so on ......
This shouldn't be done in a procedural code at all. Use pure SQL and let the DB engine do it's job, it will perform much better, and less code = less bugs. I'm not sure I completely understand what results you want, but I think this does it:
select
LocationID,
ItemID
from
(
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
)
cross join
(
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
)
order by
LocationID,
ItemID
Your query selects the #MaxItemID before anything is filled into #TempItems. Therefor #MaxItemID is null. You have to switch the Statements Set #MaxLocationID = (Select MAX(PK) From #TempLocations) and Set #MaxItemID = (Select MAX(PK1) From #TempItems).
I agree with Jeremy though, it would be better to do that with set-based-programming.