SQL query with "not in" is very slow - sql

I have the following SQL query which is very slow. How can I write the script differently?
select
pws_name
from
pws_asset ass
join
Account acc on acc.AccountId = ass.pws_AccountId
where
acc.AccountNumber in ('188012', '172146', '214727', '13636', '201194', '280294', '34328')
and ass.pws_name not in ('1018684', '1018784', '1019584', '1019784', '1019884', '1070838', '1277139', '1277339'.........)

Kindly follow below steps, that will help in query performance.
Step 1: DECLARE two variables
DECLARE #AccNumList VARCHAR(4000)
DECLARE #PwsNameList VARCHAR(4000)
SET #AccNumList = '188012,172146,214727,13636,201194,280294,34328'
SET #PwsNameList = '1018684,1018784,1019584,1019784,1019884,1070838,1277139,1277339'
Step 2: Create two different temporary tables.
1 for account numbers
Create table #tblAcNum(AccountNumber VARCHAR(50))
2 for pws_name not needed
Create table #tblPwsNameNotNeeded(pws_name VARCHAR(50))
Step 3: Add records in above two tables which are used in IN and NOT IN.
Please check this Split csv string using XML in SQL Server for reference.
INSERT INTO #tblAcNum(AccountNumber)
SELECT
l.value('.','VARCHAR(50)') AcNum
FROM
(
SELECT CAST('<a>' + REPLACE(#AccNumList,',','</a><a>') + '</a>' AS XML) AcNumXML
) x
CROSS APPLY x.AcNumXML.nodes('a') Split(l)
INSERT INTO #tblPwsNameNotNeeded(pws_name)
SELECT
l.value('.','VARCHAR(50)') pws_name
FROM
(
SELECT CAST('<a>' + REPLACE(#PwsNameList,',','</a><a>') + '</a>' AS XML) PwsNameXML
) x
CROSS APPLY x.PwsNameXML.nodes('a') Split(l)
Step 3: INNER JOIN #tblAcNum table with account table with accountnumber column
Step 4: Use NOT EXISTS() function for pws_name not needed like below
WHERE NOT EXISTS
(
SELECT 1
FROM #tblPwsNameNotNeeded pn
Where pn.pws_name = ass.pws_name
)
Step 5: Drop temporary tables after your select query.
DROP TABLE tblAcNum;
DROP TABLE #tblPwsNameNotNeeded;
Please check below query.
DECLARE #AccNumList VARCHAR(4000)
DECLARE #PwsNameList VARCHAR(4000)
SET #AccNumList = '188012,172146,214727,13636,201194,280294,34328'
SET #PwsNameList = '1018684,1018784,1019584,1019784,1019884,1070838,1277139,1277339'
Create table #tblAcNum(AccountNumber VARCHAR(50))
Create table #tblPwsNameNotNeeded(pws_name VARCHAR(50))
INSERT INTO #tblAcNum(AccountNumber)
SELECT
l.value('.','VARCHAR(50)') AcNum
FROM
(
SELECT CAST('<a>' + REPLACE(#AccNumList,',','</a><a>') + '</a>' AS XML) AcNumXML
) x
CROSS APPLY x.AcNumXML.nodes('a') Split(l)
INSERT INTO #tblPwsNameNotNeeded(pws_name)
SELECT
l.value('.','VARCHAR(50)') pws_name
FROM
(
SELECT CAST('<a>' + REPLACE(#PwsNameList,',','</a><a>') + '</a>' AS XML) PwsNameXML
) x
CROSS APPLY x.PwsNameXML.nodes('a') Split(l)
select
ass.pws_name
from pws_asset ass
join Account acc on acc.AccountId = ass.pws_AccountId
INNER JOIN #tblAcNum an ON an.AccountNumber = acc.AccountNumber
WHERE NOT EXISTS
(
SELECT 1
FROM #tblPwsNameNotNeeded pn
Where pn.pws_name = ass.pws_name
)
DROP TABLE tblAcNum;
DROP TABLE #tblPwsNameNotNeeded;

Try this:
; with cte_excludepws as
(select AccountId from pws_asset where pws_name not in ('1018684', '1018784', '1019584', '1019784', '1019884', '1070838', '1277139', '1277339'.........))
select
pws_name
from
pws_asset ass
join
Account acc on acc.AccountId = ass.pws_AccountId
where ass.AccountId not in (select AccountId from cte_excludepws)
and acc.AccountNumber in ('188012', '172146', '214727', '13636', '201194', '280294', '34328')
Alternatively if you can - take the AccountID's into a temporary table instead of cte and create an index on it.

First, be sure that the account numbers are really strings. If they are numbers, drop the single quotes!
Then, for this query
select a.pws_name
from pws_asset a join
Account ac
on ac.AccountId = a.pws_AccountId
where ac.AccountNumber in ('188012', '172146', '214727', '13636', '201194', '280294', '34328') and
a.pws_name not in ('1018684', '1018784', '1019584', '1019784', '1019884', '1070838', '1277139', '1277339'.........);
I would recommend indexes on: account(accountNumber, AccountId) and pws_asset(pws_AccountId, pws_name).

Related

Simple query that should be working is not

I have a simple query:
declare #manual varchar(80) = '''Discount'',''Misc Charges'''
select *
from #Final
where charge_type in (#manual)
Now I've gone as far as verifying my declared variable is setup correctly by using the PRINT command as follows: PRINT '''Discount'',''Misc Charges''' and it in fact returns as I would expect: 'Discount','Misc Charges'.
However, when I run this query, I get no results.
If I instead simply use:
select *
from #Final
where charge_type in ('Discount','Misc Charges')
Then no problem, I get my results. I'm sure I'll kick myself once I get the answer, but as of right now, this is just not making sense. No errors, it's just not giving me my columns without any rows as if there's no data. What am I missing?
Because
IN ('''Discount'',''Misc Charges''')
is the same as
= '''Discount'',''Misc Charges'''
In other words, that is one single string that contains a bunch of escaped string delimiters, not a comma-separated list of individual string values. Which is why you can do this without SQL Server barfing:
PRINT '''Discount'',''Misc Charges''';
What you want is:
declare #manual varchar(80) = 'Discount,Misc Charges';
select f.*
from #Final AS f
INNER JOIN STRING_SPLIT(#manual, ',') AS s
ON f.charge_type = s.value;
However that can fail on compatibility_level < 130, in which case:
declare #manual varchar(80) = 'Discount,Misc Charges';
select f.*
from #Final AS f
INNER JOIN
OPENJSON('["' + REPLACE(#manual, ',', '","') + '"]') AS s
ON f.charge_type = s.value;
In the latter case you can make the query itself a little nicer by using slightly different jacked-up strings in the variable declaration:
declare #manual varchar(80) = '["Discount","Misc Charges"]';
select f.*
from #Final AS f
INNER JOIN
OPENJSON(#manual) AS s ON f.charge_type = s.value;
Or if you are on an older version and you really are hand-crafting these strings inline, you can use a table variable or CTE like #SMor suggested.
Table variable:
DECLARE #d table(str varchar(32));
INSERT #d VALUES('Discount'),('Misc Charges');
SELECT f.*
from #Final AS f
INNER JOIN #d AS d
ON f.charge_type = d.str;
CTE:
;WITH cte AS
(
SELECT str = 'Discount'
UNION ALL
SELECT str = 'Misc Charges'
)
SELECT f.*
from #Final AS f
INNER JOIN cte
ON f.charge_type = cte.str;
If you'll have more values at some point, it tips to writing a table constructor instead of multiple UNION ALLs, e.g.
;WITH cte AS
(
SELECT str FROM
(
VALUES('Discount','Misc Charges')
) AS s(str)
)
SELECT f.*
from #Final AS f
INNER JOIN cte
ON f.charge_type = cte.str;
You can use just use your list of values as comma seperated string & then use STRING_SPLIT.
declare #manual varchar(80) = 'Discount,Misc Charges'
select *from #Final
where charge_type in (SELECT * from STRING_SPLIT(#manual,',))
Here is to to do in SQL Server 2016 onwards.
SQL
DECLARE #manual VARCHAR(80) = 'Discount,Misc Charges';
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, charge_type VARCHAR(30));
INSERT INTO #tbl (charge_type) VALUES
('Discount'),
('No Discount'),
('Misc Charges');
SELECT *
FROM #tbl
WHERE charge_type in (SELECT value FROM STRING_SPLIT(#manual, ','))

Check if a temp table exists when I only know part of the name?

I have a function for checking if certain tables exist in my database, using part of the table name as a key to match (my table naming conventions include unique table name prefixes). It uses a select statement as below, where #TablePrefix is a parameter to the function and contains the first few characters of the table name:
DECLARE #R bit;
SELECT #R = COUNT(X.X)
FROM (
SELECT TOP(1) 1 X FROM sys.tables WHERE [name] LIKE #TablePrefix + '%'
) AS X;
RETURN #R;
My question is, how can I extend this function to work for #temp tables too?
I have tried checking the first char of the name for # then using the same logic to select from tempdb.sys.tables, but this seems to have a fatal flaw - it returns a positive result when any temp table exists with a matching name, even if not created by the current session - and even if created by SPs in a different database. There does not seem to be any straightforward way to narrow the selection down to only those temp tables that exist in the context of the current session.
I cannot use the other method that seems universally to be suggested for checking temp tables - IF OBJECT('tempdb..#temp1') IS NOT NULL - because that requires me to know the full name of the table, not just a prefix.
create table #abc(id bit);
create table #abc_(id bit);
create table #def__(id bit);
create table #xyz___________(id bit);
go
select distinct (left(t.name, n.r)) as tblname
from tempdb.sys.tables as t with(nolock)
cross join (select top(116) row_number() over(order by(select null)) as r from sys.all_objects with(nolock)) as n
where t.name like '#%'
and object_id('tempdb..'+left(t.name, n.r)) is not null;
drop table #abc;
drop table #abc_;
drop table #def__;
drop table #xyz___________;
Try something like this:
DECLARE #TablePrefix VARCHAR(50) = '#temp';
DECLARE #R BIT, #pre VARCHAR(50) = #TablePrefix + '%';
SELECT #R = CASE LEFT ( #pre, 1 )
WHEN '#' THEN (
SELECT CASE WHEN EXISTS ( SELECT * FROM tempdb.sys.tables WHERE [name] LIKE #pre ) THEN 1
ELSE 0
END )
ELSE (
SELECT CASE WHEN EXISTS ( SELECT * FROM sys.tables WHERE [name] LIKE #pre ) THEN 1
ELSE 0
END )
END;
SELECT #R AS TableExists;

SQL: How Do you Declare multiple paramaters as one?

I am attempting to do the following
1. Link two tables via a join on the same database
2. Take a column that exists in both FK_APPLICATIONID(with a slight difference,
where one = +1 of the other I.e. Column 1 =1375 and column 2 = 1376
3. In one of the tables exist a reference number (QREF1234) and the other
contains 11 phonenumbers
4. I want to be able to enter the Reference number, and it returns all 11
phonenumbers as a single declarable value.
5. use "Select * from TableD where phonenum in (#Declared variable)
Here is what I have so far,
Use Database 1
DECLARE #Result INT;
SELECT #Result = D.PhoneNum1,
FROM Table1
JOIN TABLE2 D on D.FK_ApplicationID= D.FK_ApplicationID
where TABLE1.FK_ApplicationID = D.FK_ApplicationID + 1
and QREF = 'Q045569/2'
Use Database2
Select * from Table3 where PhoneNum = '#result'
I apologise to the people below who didn't understand what I was trying to achieve, and I hope this clears it up.
Thanks
There are a few options but the best answer depends on what you are really trying to achieve.
There is a SQL trick whereby you can concatenate values into a variable, for example;
create table dbo.t (i int, s varchar(10))
insert dbo.t values (1, 'one')
insert dbo.t values (2, 'two')
insert dbo.t values (3, 'three')
go
declare #s varchar(255)
select #s = isnull(#s + ', ', '') + s from t order by i
select #s
set #s = null
select #s = isnull(#s + ', ', '') + s from t order by i desc
select #s
Alternatively, if you just want one value then you can use the TOP keyword, for example;
select top 1 #s = s from t order by i
select #s
select top 1 #s = s from t order by i desc
select #s
Alternatively, you can use three-part-naming and just join across the databases, something like;
SELECT T.*
FROM DB1.dbo.Table1
JOIN DB1.dbo.Table2 D
ON D.FK_ApplicationID = D.FK_ApplicationID
JOIN DB2.dbo.Table T
ON T.PhoneNum = RIGHT(D.PhoneNum1, 11)
WHERE DB1.dbo.FK_ApplicationID = D.dbo.FK_ApplicationID + 1
AND Hidden = 'VALUE'
Hope this helps,
Rhys

find circular transactions in database table

I have a table in sql server database in which records of transactions are stored. Table consists of user id of buyer and user id of seller of product. I have to find the circles in the table for example-
I have to get the records of type- A sells to B, B sells to C, C sells to D AND D sells to A.
Please help.
Use following function:
CREATE FUNCTION dbo.CheckIsCircular(#SellerId INT)
RETURNS BIT
AS BEGIN
DECLARE #IsCircular BIT = 0
DECLARE #Sellers TABLE(Id INT)
DECLARE #TempSellers TABLE(Id INT)
DECLARE #Buyers TABLE(Id INT)
INSERT INTO #TempSellers(Id)VALUES(#SellerId)
WHILE EXISTS(SELECT * FROM #TempSellers)BEGIN
IF EXISTS(SELECT *
FROM #Sellers s
INNER JOIN #TempSellers t ON t.Id = s.Id)BEGIN
SET #IsCircular = 1
BREAK;
END
INSERT INTO #Sellers(Id)
SELECT Id FROM #TempSellers
INSERT INTO #Buyers(Id) SELECT BuyerId FROM YourTable
DELETE #TempSellers
INSERT Into #TempSellers(Id)
SELECT YourTable.SellerId
FROM YourTable
INNER JOIN #Buyers ON [#Buyers].Id = YourTable.SellerId
END
RETURN #IsCircular
END
Your problem is a graph traversal challenge; this is not natively supported in TSQL, but you can simulate it.
This is a skeleton how I do it in Teradata, so syntax must be slightly modified for SQL Server:
WITH RECURSIVE cte (..., Path, isCycle) AS
(
SELECT
...
,',' || CAST(seller AS VARCHAR(1000)) || ',' AS path
,0 AS isCycle
FROM tab
UNION ALL
SELECT
...
,cte.Path || cte.buyer || ',',
,case when cte.Path LIKE '%,' || TRIM(tab.buyer) || ',%' then 1 else 0 end
FROM cte, tab
WHERE cte.buyer = tab.seller
AND cte.isCycle <> 1
)
SELECT ...
,Path || Destination
,isCycle
FROM cte
WHERE isCycle = 1
Build a materialized path of the graph while traversing and check if the next buyer is already in this path.
With a recursive cte
declare #trans table (seller int, buyer int)
insert #trans
values (1,2),(2,3),(3,4),(4,1),(1,5),(2,6),(3,5)
begin try
;with cte as
(
select *, convert(varchar(500),'') as route from #trans
union all
select cte.seller, t1.buyer, convert(varchar(500),route + CONVERT(varchar(5),t1.seller)) from cte
inner join #trans t1 on cte.buyer = t1.seller
)
select * from cte
where seller=buyer
option (maxrecursion 50)
end try
begin catch
print 'loops'
end catch

SQL - Joining tables where one of the columns is a list

I'm tryin to join two tables. The problem i'm having is that one of the columns i'm trying to join on is a list.
So is it possible to join two tables using "IN" rather than "=". Along the lines of
SELECT ID
FROM tableA INNER JOIN
tableB ON tableB.misc IN tableA.misc
WHERE tableB.miscTitle = 'help me please'
tableB.misc = 1
tableA.misc = 1,2,3
Thanks in advance
No what you want is not possible without a major workaround. DO NOT STORE ITEMS YOU WANT TO JOIN TO IN A LIST! In fact a comma delimited list should almost never be stored in a database. It is only acceptable if this is note type information that will never need to be used in a query where clasue or join.
If you are stuck with this horrible design, then you will have to parse out the list to a temp table or table variable and then join through that.
Try this:
SELECT ID
FROM tableA INNER JOIN
tableB ON ',' + TableA.misc + ',' like '%,' + cast(tableB.misc as varchar) + ',%'
WHERE tableB.miscTitle = 'help me please'
A string parsing function like the one found here together with a CROSS APPLY should do the trick.
CREATE FUNCTION [dbo].[fnParseStringTSQL] (#string NVARCHAR(MAX),#separator NCHAR(1))
RETURNS #parsedString TABLE (string NVARCHAR(MAX))
AS
BEGIN
DECLARE #position int
SET #position = 1
SET #string = #string + #separator
WHILE charindex(#separator,#string,#position) <> 0
BEGIN
INSERT into #parsedString
SELECT substring(#string, #position, charindex(#separator,#string,#position) - #position)
SET #position = charindex(#separator,#string,#position) + 1
END
RETURN
END
go
declare #tableA table (
id int,
misc char(1)
)
declare #tableB table (
misc varchar(10),
miscTitle varchar(20)
)
insert into #tableA
(id, misc)
values
(1, '1')
insert into #tableB
(misc, miscTitle)
values
('1,2,3','help me please')
select id
from #tableB b
cross apply dbo.fnParseStringTSQL(b.misc,',') p
inner join #tableA a
on a.misc = p.string
where b.miscTitle = 'help me please'
drop function dbo.fnParseStringTSQL
Is ID also in tableB? If so, you can reverse the tables, and run the IN backwards, in the WHERE section, like so:
SELECT ID
FROM tableB
WHERE tableB.miscTitle = 'help me please'
AND tableB.misc IN (SELECT tableA.misc FROM tableA)
If it's not, you could use a cross join to get all combinations of rows between the tables, then remove the rows that don't obey the IN. WARNING: This will become a huge join if the tables are large. Example:
SELECT ID
FROM tableA
CROSS JOIN tableB
WHERE tableB.miscTitle = 'help me please'
AND tableB.misc IN tableA.misc
EDIT: didn't realize "in a list" meant a comma-delimited VARCHAR. SQL's IN won't work for that, nor should you ever store joinable data that way in a database.