Simple query that should be working is not - sql

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, ','))

Related

SQL query with "not in" is very slow

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).

XQuery [value()]: A string literal was expected

I want to execute this code:
if exists(select * from FileUploadFileTable..sysobjects where xtype='u' and name='##Tmp830963' )
drop table ##Tmp830963
CREATE table ##Tmp830963 (RowId int,Files varchar(MAX) ,Files_Name NVARCHAR(MAX), Files_Size bigint,Job_Id bigint, Files_Type VARCHAR(MAX) , User_id bigint ,User_Name NVARCHAR(MAX)) Insert into ##Tmp830963(RowId,Files,Files_Name,Files_Size , Files_Type) select A.row_num,A.Items,O.Items,B.Items,C.Items
from(
SELECT Items,row_number() over (order by (select 0)) as row_num
FROM dbo.Func_Split('/9j/AqAAAAB4CgAwAEAAABAAAAAAA', '^') ) A
join
(SELECT Items,row_number() over (order by (select 0)) as row_num
FROM dbo.Func_Split('tt^', '^') ) O on O.row_num=A.row_num
join
(SELECT Items,row_number() over (order by (select 0)) as row_num
FROM dbo.Func_Split('12^', '^'))B on A.row_num=B.row_num
join
(SELECT Items,row_number() over (order by (select 0)) as row_num
FROM dbo.Func_Split('png^', '^'))C
on C.row_num=A.row_num
update ##Tmp830963 set User_Name=100update ##Tmp830963 set Job_Id='20' update ##Tmp830963 set User_id='1' select * from ##Tmp830963 DECLARE #OutputTbl TABLE (ID uniqueidentifier) INSERT INTO [Uploads] ([file_stream],[name]) OUTPUT INSERTED.stream_id INTO #OutputTbl(ID) select cast(N'' as xml).value('xs:base64Binary(sql:variable(Files))', 'varbinary(max)') ,Files_Name from ##Tmp830963 while (select count(*) from #OutputTbl) > 0 begin INSERT INTO [dbo].[FileDescriptions] ([User_ID] ,[FileName],Stream_id,[Size],Job_Id) select [User_id] ,cast((select MAX(ID) from #OutputTbl ) as nvarchar(max) ),(select MAX(ID) from #OutputTbl) , Files_Size , Job_Id from ##Tmp830963 where RowId=(select top 1(RowId) from ##Tmp830963) delete #OutputTbl where ID =(select MAX(ID) from #OutputTbl ) end
But I get this error:
XQuery [value()]: A string literal was expected
On this line:
cast(N'''' as xml).value(''xs:base64Binary(sql:variable(Files))''
On the first sight this looks a bit weird. I'm pretty sure, that this can be solved differently.
You are trying to transfer base64 to varbinary, correct?
Well, the XML-approach is the recommended way to achieve this. However, your problem seems to be situated here:
cast(N'''' as xml).value(''xs:base64Binary(sql:variable(Files))''
This is your attempt to create the needed statement dynamically, hence the doubled single quotes. This will translate to
cast(N'' as xml).value('xs:base64Binary(sql:variable(Files))'
The problem here is: sql:variable() expects the name of a declared variable as a string literal. Furthermore, value() expects two arguments: The Xpath and the data type. You'd need something like this
cast(N'' as xml).value('sql:variable("#Files")','varbinary(max)')
Might be you need sql:column("SomeColumn"), which allows to use the value of a column.
One example to demonstrate this
--We use FOR XML to get the binary 0x1234 as base64
SELECT 0x1234 FOR XML PATH(''); --returns "EjQ="
--Now I place the corresponding base64 string in a string variable
DECLARE #base64 VARCHAR(100)='EjQ=';
--And this is, how XML is returning the binary 0x1234 from the base64-string
SELECT CAST('' AS XML).value('sql:variable("#base64")','varbinary(max)');
And this would return the same and looks a bit easier:
SELECT CAST(#base64 AS XML).value('.','varbinary(max)');
Check this for a tabular approach
The table #base64 is a declared table variable, just use your original table instead:
--Declare a table variable with one string column and insert the base64 of 0x1234
DECLARE #base64 TABLE(Files VARCHAR(100));
INSERT INTO #base64 VALUES('EjQ=');
--use the same trick as above to get the varbinary back
SELECT CAST(Files AS XML).value('.','varbinary(max)')
FROM #base64;

Write a stored procedure which takes input string and split them by characters

I am trying to create a stored procedure in SQL Server which will be used from C# Entity Framework.
My main focus is take a input of long string text then split that data by characters and return list of values of matching data.
In detail:
- is separator between name of data type and its value
: is separator between Type and ASIN
, is separator between two different value
I want to get List of data filtered by ASIN and Type from this stored procedure. I am getting full text string in the #DataString variable but I don't know how I can split my text and run SELECT to return all of data.
Any idea to do it? Ask any question you may have.
Example of long text string:
Type-1:ASIN-NsQf8,ASIN-YhQfu,ASIN-dpQf9,ASIN-rsWf3
The unfinished SQL code:
CREATE PROCEDURE dbo.lk_GetMatchingDataOfThirdparty
#DataString VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM ThirdPartyData
WHERE ASIN = '#value_get_from_string'
AND Type = '#value_get_from_string'
END
Use a split function:
CREATE FUNCTION [dbo].[split](
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
) RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','nvarchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
GO
DECLARE #DataString VARCHAR(MAX);
SET #DataString ='Type-1:ASIN-NsQf8,ASIN-YhQfu,ASIN-dpQf9,ASIN-rsWf3'
;WITH cte as (
SELECT a.id as [1], b.id as [2], c.id as [3], c.val
FROM (
SELECT * FROM dbo.split(#DataString, ':')
) a
CROSS APPLY dbo.split(a.val,',') b
CROSS APPLY dbo.split(b.val,'-') c
),
typecte as (
select b.val as [TypeValue]
from cte a
inner join cte b
ON a.[1] = b.[1]
AND a.[2] = b.[2]
AND a.[3]+1 = b.[3] -- Next value
WHERE a.val='Type'
),
asincte as (
select b.val as [ASINValue]
from cte a
inner join cte b
ON a.[1] = b.[1]
AND a.[2] = b.[2]
AND a.[3]+1 = b.[3] -- Next value
WHERE a.val='ASIN'
)
SELECT *
FROM ThirdPartyData
WHERE [ASIN] IN (SELECT [ASINValue] FROM asincte)
AND [Type] IN (SELECT [TypeValue] FROM typecte)
An oft overlooked way of doing coding that isn't really relational database related is a SQL CLR. String manipulation is a good example of something a SQL CLR could handle much better than a SQL script. What you could do in the above example is call a SQL CLR function that does the string manipulation on the long string text to return the values you need then plug those variables into your SELECT statement.

Local variable with multiple value list

I use Excel connection to connect to SQL Server to query data from SQL server to Excel.
I have below WHERE clause in the Excel connection couple times. I need to replace the WHERE multiple value list from time to time. To simply the replacement, I want to use a local parameter, #Trans. With the local parameter, I can change it only and all SQL will use it to query.
WHERE Type in ('R','D','C')
If it is single option, below code works.
DECLARE #TRans CHAR(200)= 'R';
SELECT .....
WHERE Type in (#Trans)
If it is multiple options, the below code does not works
DECLARE #TRans CHAR(200)= 'R,D,C';
SELECT .....
WHERE Type in (#Trans)
DECLARE #TRans CHAR(200)= '''R'''+','+'''D'''+','+'''C''';
SELECT .....
WHERE Type in (#Trans)
How to declare #Trans for multiple value list, for example ('R','D','C')? Thank you.
You can use dynamic sql
DECLARE #TRans VARCHAR(200)= '''R'',''D'',''C''';
DECLARE #sql VARCHAR(MAX) = '';
SET #sql = 'SELECT * FROM table WHERE Type in (' + #Trans + ');'
EXEC #sql
Take note of the quotes for the values in #TRans since these character values.
If you want to check the value of #sql which you will see the constructed sql statement, replace EXEC #sql with PRINT #sql.
Result of #sql
SELECT * FROM table WHERE Type in ('R','D','C');
As you can see by now, SQL Server does NOT support macro substition. This leaves a couple of options. One is to split the string.
If not 2016, here is a quick in-line approach which does not require a Table-Valued Function
Example
Declare #Trans varchar(max)='R,D,C' -- Notice no single quotes
Select ...
Where Type in (
Select RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(#Trans,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
)
You can create a table named LocalParameter and keep local variables there. You can only get datas by updating LocalParameter table without changing the queries.
CREATE TABLE LocalParameter (Trans VARCHAR(MAX))
INSERT INTO LocalParameter
VALUES
(
',R,'
)
With LIKE you can use it like this:
SELECT .....
WHERE (SELECT TOP 1 A.Trans FROM LocalParameter A) LIKE ',' + Type + ','
To change WHERE clause:
UPDATE LocalParameter
SET Trans = ',R,D,C,'
Queries:
SELECT .....
WHERE (SELECT TOP 1 A.Trans FROM LocalParameter A) LIKE ',' + Type + ','
Local variables are added to the beginning and end of the comma.
You can use a split method to split csv values as shown below
DECLARE #delimiter VARCHAR(10)=','
DECLARE #input_string VARCHAR(200)='R,D,C'
;WITH CTE AS
(
SELECT
SUBSTRING(#input_string,0,CHARINDEX(#delimiter,#input_string)) AS ExtractedString,
SUBSTRING(#input_string,CHARINDEX(#delimiter,#input_string) + 1,LEN(#input_string)) AS PartString
WHERE CHARINDEX(#delimiter,#input_string)>0
UNION ALL
SELECT
SUBSTRING(PartString,0,CHARINDEX(#delimiter,PartString)) AS ExtractedString,
SUBSTRING(PartString,CHARINDEX(#delimiter,PartString)+1,LEN(PartString)) AS PartString
FROM CTE WHERE CHARINDEX(#delimiter,PartString)>0
)
SELECT ExtractedString FROM CTE
UNION ALL
SELECT
CASE WHEN CHARINDEX(#delimiter,REVERSE(#input_string))>0
THEN REVERSE(SUBSTRING(REVERSE(#input_string),0,CHARINDEX(#delimiter,REVERSE(#input_string))))
ELSE #input_string END
OPTION (MAXRECURSION 0)
This split method doesnt have any loops so it will be fast. then you integrate this with your query as below mentioned
DECLARE #delimiter VARCHAR(10)=','
DECLARE #input_string VARCHAR(200)='R,D,C'
;WITH CTE AS
(
SELECT
SUBSTRING(#input_string,0,CHARINDEX(#delimiter,#input_string)) AS ExtractedString,
SUBSTRING(#input_string,CHARINDEX(#delimiter,#input_string) + 1,LEN(#input_string)) AS PartString
WHERE CHARINDEX(#delimiter,#input_string)>0
UNION ALL
SELECT
SUBSTRING(PartString,0,CHARINDEX(#delimiter,PartString)) AS ExtractedString,
SUBSTRING(PartString,CHARINDEX(#delimiter,PartString)+1,LEN(PartString)) AS PartString
FROM CTE WHERE CHARINDEX(#delimiter,PartString)>0
)
SELECT * FROM [YourTableName] WHERE Type IN
(SELECT ExtractedString FROM CTE
UNION ALL
SELECT
CASE WHEN CHARINDEX(#delimiter,REVERSE(#input_string))>0
THEN REVERSE(SUBSTRING(REVERSE(#input_string),0,CHARINDEX(#delimiter,REVERSE(#input_string))))
ELSE #input_string END
)OPTION (MAXRECURSION 0)
If possible add a new table and then join to it in all your queries:
CREATE TABLE SelectedType
(
[Type] CHAR(1) PRIMARY KEY
)
INSERT INTO SelectedType
VALUES ('R','D','C')
Then your queries become:
SELECT *
FROM MyTable MT
INNER JOIN SelectedType [ST]
ON ST.[Type] = MT.[Type]
If you need to add, update or delete types then update the rows in SelectedType table.
This has the benefit of using SET BASED queries, is easy to understand and easy to add, update or delete required types.

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