(SQL) Replaced NOT IN with NOT EXISTS and results differ - sql

Trying to fix someone else's code. The NOT IN kills performance.
I took it out and replaced with Not Exists and I'm getting different results.
The commented out not in is just above my not exists.
Anyone see anything stupid I'm doing here?
IF #ProcessComplete = 1
BEGIN
-- PRINT 'Group-Complete'
INSERT INTO #ProcessIDTable
SELECT DISTINCT(ProcessID)
FROM vPortalInbox
WHERE GroupUserIDs LIKE '%,' + CAST(#UserID AS VARCHAR(MAX)) + ',%'
AND StepOwnerID IS NULL
--AND ProcessID NOT IN (SELECT ProcessID FROM #ProcessIDTable)
And not exists (SELECT ProcessID FROM #ProcessIDTable)

You could try:
And not exists (
SELECT ProcessID
FROM #ProcessIDTable
WHERE ProcessID = vPortalInbox.ProcessID)
...or possibly even better still: you could try a (left or right) outer join to vPortalInbox on ProcessID, and specify in your WHERE clause that #ProcessIDTable.ProcessID IS NULL:
...
SELECT DISTINCT(ProcessID)
FROM vPortalInbox LEFT OUTER JOIN #ProcessIDTable
ON vPortalInbox.ProcessID = #ProcessIDTable.ProcessID
WHERE GroupUserIDs LIKE '%,' + CAST(#UserID AS VARCHAR(MAX)) + ',%'
AND StepOwnerID IS NULL AND #ProcessIDTable.ProcessID IS NULL

1 --AND ProcessID NOT IN (SELECT ProcessID FROM #ProcessIDTable)
2 And not exists (SELECT ProcessID FROM #ProcessIDTable)
The above two statements are not same. The not exist will evaluate to true only when you get no rows from the subquery inside the brackets following the not exists clause.
Since you have no conditions for the sub-query following not exists clause, it will never return 0 rows unless the table is empty.
Try using this where clause:
FROM vPortalInbox P1
WHERE GroupUserIDs LIKE '%,' + CAST(#UserID AS VARCHAR(MAX)) + ',%'
AND StepOwnerID IS NULL
And not exists (SELECT 1 FROM #ProcessIDTable P2
where P1.ProcessID = P2.ProcessID )

Related

Using 'Where' OR Having for Searching select in SQL

I am trying to fetch records in SQL using a simple used submitted field. More precisely, the user inputs a keyword (name,m_name, or field_region) and the server should return matched rows.
Here my code :
Select user_sg.id as id,user_sg.name as name,id_channel,master_channel.code,master_channel.name as m_name,user_sg.id_relation,
STUFF((SELECT ', ' + region_code
FROM user_sg_region AS T3
WHERE T3.id_sg = user_sg.id
FOR XML PATH('')), 1, 2, '') as field_region
FROM user_sg
INNER JOIN master_channel ON user_sg.id_channel=master_channel.id
where user_sg.name like '%search%' OR master_channel.name like '%search%'
GROUP BY user_sg.id,user_sg.name,id_channel,master_channel.code,master_channel.name,user_sg.id_relation
ORDER BY user_sg.Id
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
That works well for now, but that (obviously) won't work when a user trying to search field_region. Is there a way to add a OR between the whole 'WHERE type conditions' and the 'HAVING type conditions'?
what should i change where it can be work like what i need ? (I need i can search for field_region)
Here my table :
Here My sample data :
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=62409eec4ca0eef171d2d862e99c98d4
If you need FOR XML to aggregate then you need to add an EXISTS clause to check the child table.
DECLARE #search varchar(255) = '%BSI%';
Select user_sg.id as id,user_sg.name as name,id_channel,master_channel.code,master_channel.name as m_name,user_sg.id_relation,
STUFF((SELECT ', ' + region_code
FROM user_sg_region AS T3
WHERE T3.id_sg = user_sg.id
FOR XML PATH('')), 1, 2, '') as field_region
FROM user_sg
INNER JOIN master_channel ON user_sg.id_channel=master_channel.id
WHERE user_sg.name like #search
OR master_channel.name like #search
OR EXISTS (SELECT 1
FROM user_sg_region AS T3
WHERE T3.id_sg = user_sg.id
AND T3.region_code LIKE #search)
ORDER BY user_sg.Id
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
In newer versions of SQL Server you can use STRING_AGG inside an APPLY, and combine it with a conditional COUNT.
DECLARE #search varchar(255) = '%BSI%';
Select user_sg.id as id,user_sg.name as name,id_channel,master_channel.code,master_channel.name as m_name,user_sg.id_relation,
T3.field_region
FROM user_sg
INNER JOIN master_channel ON user_sg.id_channel=master_channel.id
CROSS APPLY (
SELECT
STRING_AGG(T3.region_code, ', ') AS field_region,
COUNT(CASE WHEN T3.region_code LIKE #search THEN 1 END) AS match
FROM user_sg_region AS T3
WHERE T3.id_sg = user_sg.id
) T3
WHERE user_sg.name like #search
OR master_channel.name like #search
OR T3.match > 0
ORDER BY user_sg.Id
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;
db<>fiddle
I see no need for the GROUP BY as there is no aggregation in the outer scope.
In both versions, the aggregation is done inside a subquery, which does not affect the outer SELECT

If else condition in MSSQL

Suppose I have serial number, test name and few other columns, i want to write a condition if TESTNAME is null for a particular serial number then set the TESTNAME to blank else perform inner join
SELECT
(A.PTNUMBER + '-' +A.SL_NO) AS ENUMBER,
D.ENGINEER AS REQ, D.DATETIME as "DATE",
(select Value
from DROPDOWN
where B.TEST_NAME=CONVERT(VARCHAR,DropdownID)) TESTNAME,
TABLE_NAME AS TABLETD
FROM INSPECTION D
INNER JOIN TABLEA A ON D.ENGID = CONVERT(VARCHAR,A.EN_ID)
INNER JOIN TABLEB B ON B.ENGID = CONVERT(VARCHAR,A.EN_ID)
INNER JOIN TABLEC C ON C.ENGID = CONVERT(VARCHAR,A.EN_ID)
not sure what you mean by set testname to blank but if you meant to be using a SELECT query then you can do like
select *,
case when TESTNAME is null and serial_number = some_value then '' end as TESTNAME
from mytable
You could combine a case expression and coalesce() along with your join to choose the value you want to return.
select serial_number, ...
,case when coalesce(testname,'') <> ''
then t2.testname
else coalesce(testname,'') end
from t
inner join t2
on ...
You can use isnull() or coalesce() in sql server to return a different value to replace null.
select isnull(testname,'')
or
select coalesce(testname,'')
The main difference between the two is that coalesce() can support more than 2 parameters, and it selects the first one that is not null. More differences between the two are answered here.
select coalesce(testname,testname2,'')
coalesce() is also standard ANSI sql, so you will find it in most RDBMS. isnull() is specific to sql server.
Reference:
isnull() - msdn
coalesce() - msdn
SELECT (A.PTNUMBER + '-' + A.SL_NO) AS ENUMBER,
D.ENGINEER AS REQ,
D.DATETIME as "DATE",
case
when SerialNo = xxx and TESTNAME is null then ''
else (select Value from DROPDOWN where B.TEST_NAME = CONVERT(VARCHAR, DropdownID))
end AS TESTNAME,
TABLE_NAME AS TABLETD
FROM INSPECTION D
INNER JOIN TABLEA A ON D.ENGID = CONVERT(VARCHAR, A.EN_ID)
INNER JOIN TABLEB B ON B.ENGID = CONVERT(VARCHAR, A.EN_ID)
INNER JOIN TABLEC C ON C.ENGID = CONVERT(VARCHAR, A.EN_ID);

Completing my recursive SQL operation, is cte a good choice for me?

I have the following data:
TestData
Code | RowNum |
123 | 1 |
456 | 2 |
789 | 3 |
What I am trying to accomplish is a cycle through this data, checking a separate table to see if any records are LIKE these codes. Here is what I have tried, but I am returning 0 records at the end. I believe I could possibly use a cte to accomplish this, but I'm having some trouble understanding the base idea of recursive cte. Am I going the wrong direction here? Below is the query I have which is returning 0 records.
DECLARE #myCounter int = 0;
WHILE (#myCounter < #ROWCOUNT)
BEGIN
DECLARE #Code nvarchar(8) = (SELECT [Code]
FROM [MyTable]
WHERE [RowNum] = #myCounter);
SELECT *
FROM [RefTable]
WHERE [Code] LIKE '%' + #Code + '%';
END
Looks like this should be done with a single query using a LEFT JOIN:
SELECT m.RowNum, m.Code, COUNT(*) AS c
FROM MyTable m
LEFT JOIN RefTable r ON ( r.Code LIKE '%' + m.Code + '%' )
GROUP BY m.RowNum, m.Code
ORDER BY m.RowNum
Use a JOIN instead of a LEFT JOIN to ignore rows without entries instead of displaying 0.
Not sure if it'll help a whole lot, but if you're trying to match code to another table, you can always do a join with a LIKE condition and potentially avoid the recursive search.
SELECT * FROM T1
RIGHT JOIN T2
ON T1.CODE LIKE '%' + T2.CODE + '%'

Cross join on condition

I have a Stored Proc query below which involves returning partial delimited search string. E.g.searching passing a search string of 'wis,k' will return all results with ID that has 'wis' and 'k' in them. I am using a function and a cross join for this but the problem if attaching the cross join will prevent loading all my data which I will need to when I load this SPROC. I was thinking if a conditioned Cross Join is possible such that when my search string variable '#ReceiptNo' is null then I will omit the Cross Join and allow all my data to be displayed. Please kindly advice. Thanks.
Portion of my SPROC:
FROM [Transact] T
LEFT JOIN [Outlet] O On (T.Outlet_Code = O.Code)
LEFT JOIN [SystemCode] SC on (CONVERT(NVARCHAR,T.Mode) = SC.Code)
CROSS JOIN DBO.SPLIT(#ReceiptNo , ',') --SPLIT function to seperate delimited string
Where
(
CardNo In
(
Select [CardNo]
FROM [Card]
WHERE [CardNo] = #CardNo
AND [DeletedBy] IS NULL
AND [DeletedOn] IS NULL
AND [MemberID] = #MemberId
)
)
and
(
(T.TransactDate Between #TransactDateFrom And #TransactDateTo
or #TransactDateFrom is null
or #TransactDateTo is null
)
and (T.TransactDate >= #TransactDateFrom
or #TransactDateFrom is null)
and (T.TransactDate <= #TransactDateTo
or #TransactDateTo is null)
and
(
(',' + #Mode +',' LIKE '%,' + CONVERT(VARCHAR, T.Mode) + ',%')
or #Mode is null
)
and (T.ReceiptNo LIKE '%' + VAL + '%') --This is the 'LIKE' condition to return desired search string results
or (#TransactDateFrom is null
and #TransactDateTo is null
and #Mode is null
and #Outlet_Code is null
and #ReceiptNo is null
)
)
Group by T.AutoID, TransactDate,TransactTime, SC.Name, O.Name
, ReceiptNo, AmountSpent, TransactPoints, VoidOn
You need to take care of NULL and set it to any constant value. Modify CROSS JOIN to (read notes below query):
CROSS JOIN (SELECT ISNULL(Portion, 1) AS Portion FROM DBO.SPLIT(#ReceiptNo , ',')) TTT
In query above, Portion is column returned by DBO.SPLIT function. Change its name to appropriate and add more columns (with ISNULL) if needed.
Am I missing something or You can simply use LEFT JOIN instead of CROSS JOIN? Also, You might consider putting DBO.SPLIT function result into temporary table, index it and then use it in your CROSS/LEFT JOIN.
EDIT#1: I can't find any reason why You should not change CROSS JOIN to LEFT JOIN, as it will process less rows when #RecepitNo is not NULL.

Is there a way to query 135 distinct tables in one single query?

In our database, we have 135 tables that have a column named EquipmentId. I need to query each of those tables to determine if any of the them have an EquipmentId equal to a certain value. Any way to do this in a single query, instead of 135 separate queries?
Thanks very much.
You are looking at either Dynamic SQL to generate queries to all of the tables and perhaps union the results, or using something like the undocumented sp_MSforeachtable stored procedure.
sp_msforeachtable 'select * from ? where equipmentid = 5'
You could use a query to build a query:
select 'union all select * from ' + name +
' where EquipmentId = 42' + char(13) + char(10)
from sys.tables
Copy the result, strip the first union all, and run the query :)
I would dump them into a temp table or something else similar:
CREATE TABLE #TempTable (Equip NVARCHAR(50))
sp_msforeachtable 'INSERT INTO #TempTable (Equip) SELECT Equip FROM ?'
SELECT * FROM #TempTable
DROP TABLE #TempTable
I assume that not all the tables in the DB have EquipmentId column.
If this is a valid assumption then #whereand parameter of sp_msforeachtable would help to filter the tables.
The query bellow will show all table names that have specified EquipmentId.
Table name will be shown as many times as many rows from this table have the specified EquipmentId.
declare #EquipmentId int = 666
create table #Result (TableName sysname)
declare #command nvarchar(4000) =
'insert into #Result select ''?'' from ? where EquipmentId = ' + cast(#EquipmentId as varchar)
execute sp_msforeachtable
#command1 = #command,
#whereand = 'and o.id in (select object_id from sys.columns where name = ''EquipmentId'')'
select *
from #Result
drop table #Result
You're probably going to have to go Dynamic SQL on this one - query the system tables for all the tables that have columns named EquipmentId, and build a dynamic SQL statement querying each table for the presence of that particular EquipmentId you need.
EDIT: #mellamokb's seems much easier - try that.
This can be implemented using LEFT JOIN's. First we'll need a base table to hold the certain values of EquipmentID's we're looking for:
CREATE TABLE #CertainValues
(
EquipmentID int
)
INSERT INTO #CertainValues(EquipmentID) VALUES (1)
INSERT INTO #CertainValues(EquipmentID) VALUES (2)
INSERT INTO #CertainValues(EquipmentID) VALUES (3)
We can then join the 135 known tables to this base table using their respective [EquipmentID] fields. To avoid cardinality issues (duplication) due to an [EquipmentID] appearing in multiple rows of one table, it's best to use a subquery to get counts per [EquipmentID] on each of the 135 tables.
SELECT
CV.EquipmentID,
ISNULL(T001.CNT, 0) AS T001,
ISNULL(T002.CNT, 0) AS T002,
...
ISNULL(T134.CNT, 0) AS T134,
ISNULL(T135.CNT, 0) AS T135
FROM
#CertainValues AS CV
LEFT OUTER JOIN (SELECT EquipmentID, SUM(1) AS CNT FROM Table001 GROUP BY EquipmentID) AS T001 ON CV.EquipmentID = T001.EquipmentID
LEFT OUTER JOIN (SELECT EquipmentID, SUM(1) AS CNT FROM Table002 GROUP BY EquipmentID) AS T002 ON CV.EquipmentID = T002.EquipmentID
...
LEFT OUTER JOIN (SELECT EquipmentID, SUM(1) AS CNT FROM Table134 GROUP BY EquipmentID) AS T134 ON CV.EquipmentID = T134.EquipmentID
LEFT OUTER JOIN (SELECT EquipmentID, SUM(1) AS CNT FROM Table135 GROUP BY EquipmentID) AS T135 ON CV.EquipmentID = T135.EquipmentID
This also gives us a more meaningful resultset which shows the count of rows per table for each of the certain values we are looking for. Below is a sample resultset:
EquipmentID T001 T002 ... T134 T135
----------- ---- ---- ... ---- ----
1 0 1 ... 2 3
2 3 2 ... 1 0
3 0 0 ... 0 0