Convert String to list in SQL - sql

I have a query like:
SELECT col1,col2 from table1 where col1 in (:var);
and this :var has a value like "'1234', '5678'" which is a string consisting of single quotes and commas in it. I want to convert this string to a type which can be given as input to the SQL 'in' operator, something like this:
SELECT col1, col2 from table1 where col1 in (STRING_SPLIT(:var));

This is the code as solution to achieve desired result in SQL server query.
DECLARE #var AS NVARCHAR(100) = '''1234'', ''5678''';
SELECT col1, col2 FROM table1 WHERE col1 IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(#var, ','))

You can't use STRING_SPLIT to expand a delimited literal string into multiple delimited literal strings. STRING_SPLIT('abc,def',',') doesn't result in 'abc','def', it results in a data set of 2 rows, containing the values 'abc' and 'def'.
If you want to pass a delimited string, you need to either JOIN/CROSS APPLY to STRING_SPLIT or use a subquery:
SELECT T1.Col1,
T1.Col2
FROM dbo.table1 T1
JOIN STRING_SPLIT(#YourVariable,',') SS ON T1.Col1 = SS.Value;
SELECT T1.Col1,
T1.Col2
FROM dbo.table1 T1
WHERE T1.Col1 IN (SELECT SS.Value
FROM STRING_SPLIT(#YourVariable,',') SS);
You may, however, find even better performance with an indexed temporary table, if you are dealing with large data sets:
CREATE TABLE #temp (Value varchar(30) PRIMARY KEY); --Use an appropriate data type. I assume unique values in the delimited string
INSERT INTO #temp (Value)
SELECT SS.Value
FROM STRING_SPLIT(#YourVariable,',') SS;
SELECT T1.Col1,
T1.Col2
FROM dbo.table1 T1
JOIN #Temp T ON T1.Col1 = T.Value;
Finally, which may be better again, you could use a table type parameter. Then you would, like the above, just JOIN to that or use an EXISTS.

Related

default value if a column doesnt exist in the SQL table

I have the below sample code. Assuming I do not know if a particular column exists in a table or not, how could I write the query in a way that I can default the column value to 'Not available' if the column doesn't exist in the table?
Example:
select COL1, COL2,
CASE
WHEN OBJECT_ID('COL3') IS NULL THEN 'Not Available'
ELSE COL3
END AS COL3
from TABLE1
Thanks in advance.
This is quite tricky to do (without dynamic SQL), but there is a way by playing with the scoping rules in SQL. You can do this assuming you have a unique or primary key in the table:
select t1.col1, t1.col2,
(select col3 -- no alias!
from table1 tt1
where tt1.id = t1.id -- the primary/unique key
) col3
from table1 t1 cross join
(values ('Not Available')) v(col3) -- same name
The subquery will fetch col3 from the table1 in the subquery if it exists. Otherwise it will reach out and find col3 from the values() clause.

LEFT JOIN with subquery and accessing main table columns in select clause

I have an insert statement like the following which gets sytax error of "the multi-part identifier "t2.Col1" could not be bound.". I over simplified the statement and it looks like below:
INSERT INTO dbo.T1
(
Col1,
Col2,
Col3
)
SELECT
t2.Col1,
SUBSTRING(aCase.CaseColumn, 0, CHARINDEX('%', aCase.CaseColumn)), --I expect this line gets the value "2"
SUBSTRING(aCase.CaseColumn, CHARINDEX('%', aCase.CaseColumn) + 1, LEN(aCase.CaseColumn) - CHARINDEX('%', aCase.CaseColumn)) --I expect this line gets the value "3"
FROM
dbo.T2 t2
LEFT JOIN
(
SELECT
CASE --I have hundreds of WHEN conditions below and need to access the parent T2 tables' properties
WHEN t2.Col1 = 1 THEN '2%3' --This line has a syntax error of "the multi-part identifier "t2.Col1" could not be bound."
END AS CaseColumn
)
AS aCase ON 1 = 1
The reason I use LEFT JOIN with CASE is that I have hundreds of conditions for which I need to select different values for different columns. I don't want to repeat the same CASE statements over and over again for all of the columns. Therefore, I use a single CASE which concatenates the values with a delimiter and then I parse that concatenated string and put the appropriate values in it's place.
What you could do is use OUTER APPLY, as it allows your dbo.T2 and the aCase resultset to be related, like this:
INSERT INTO dbo.T1
(
Col1,
Col2,
Col3
)
SELECT
1,
SUBSTRING(aCase.CaseColumn, 0, CHARINDEX('%', aCase.CaseColumn)), --I expect this line gets the value "2"
SUBSTRING(aCase.CaseColumn, CHARINDEX('%', aCase.CaseColumn) + 1, LEN(aCase.CaseColumn) - CHARINDEX('%', aCase.CaseColumn)) --I expect this line gets the value "3"
FROM
dbo.T2 t2
OUTER APPLY
(
SELECT
CASE --I have hundreds of WHEN conditions below and need to access the parent T2 tables' properties
WHEN t2.Col1 = 1 THEN '2%3'
END AS CaseColumn
)
AS aCase ON 1 = 1
That is because the result of the subquery is not indipendent itself, it has to be defined based on the values of the dbo.T2 table.
Read more about OUTER APPLY and CROSS APPLY on this thread.
Number 3, "Reusing a table alias" is similiar to your case and the article linked to it perfectly explains how to use cross apply/outer apply in these cases.
When using a join to a subquery, inside that subquery it doesn't know what t2 is, unless you select from a table aliased as t2 in that subquery.
And you could change that LEFT JOIN to an OUTER APPLY.
But you don't really need to JOIN or OUTER APPLY in this case.
Just select from T2 with the CASE in the subquery.
INSERT INTO dbo.T1
(
Col1,
Col2,
Col3
)
SELECT
Col1,
SUBSTRING(CaseColumn, 1, CHARINDEX('%', CaseColumn)-1),
SUBSTRING(CaseColumn, CHARINDEX('%',CaseColumn)+1, LEN(CaseColumn))
FROM
(
SELECT
Col1,
CASE Col1
WHEN 1 THEN '2%3'
-- more when's
END AS CaseColumn
FROM dbo.T2 t2
) q
Note how the CASE and the SUBSTRING's were changed a little bit.
Btw, personally I would just insert the distinct Col1 into T1, and just update Col2 and Col3 in that reference table manually. That could prove to be faster than writing those hundreds conditions. But then again, you did say this was simplified a lot.

SQL - Comparing two tables with Minus - Include/Exclude temp field

After carefully reading a lot of topics about comparing tables and using the minus function I'm posting this.
I've got my comparison between two tables running.
Select Column1,Column2.. from table 1
minus Column1,Column2.. from table 2
union all
Column1,Column2.. from table 2
Select Column1,Column2.. from table 1
order by column1
Now this gives me a list of duplicate or single values that are different in each table. This is fine. However I do not have an indicator telling me in which table the (faulty) rows are.
I tried adding a temporary column giving it an A and B field. This results in a full export of the table because this obviously gets taken in with the minus function.
Is there a way that I can tag the rows telling me what table they are in without adding a permanent column in the table,because this is not an option.
Help is much appreciated!
I would phrase this as a union of left joins:
SELECT t1.col1, t1.col2, 'table1' AS label
FROM table1 t1
LEFT JOIN table2 t2
ON t1.col1 = t2.col1 AND t1.col2 = t2.col2
WHERE t2.col1 IS NULL
UNION ALL
SELECT t2.col1, t2.col2, 'table2'
FROM table2 t2
LEFT JOIN table1 t1
ON t1.col1 = t2.col1 AND t1.col2 = t2.col2
WHERE t1.col1 IS NULL
The label column is computed during the UNION and serves to label the origin table for each record (i.e. set of values) which are unique to that particular table. Note that you can extend what I have given above by adding the necessary number of columns to fill both tables.
This is a general solution which should work across most RDBMS, and doesn't rely on any set difference operators.
Demo here:
SQLFiddle
The example query you provided isn't syntactically correct and has errors when running. But, based on the description of what you tried, I think I understand what you're trying to accomplish.
You were on the right track with adding a temporary column that provides an indicator of which table is the source of the row. The value of the temporary column should be the same for the queries before the UNION ALL and a different value for the queries after.
Here's an updated version of your example query. You can try it out at SqlFiddle
(
SELECT 'FromTable1', COLUMN1, COLUMN2, COLUMN3 FROM TABLE1
EXCEPT
SELECT 'FromTable1', COLUMN1, COLUMN2, COLUMN3 FROM TABLE2
)
UNION ALL
(
SELECT 'FromTable2', COLUMN1, COLUMN2, COLUMN3 FROM TABLE2
EXCEPT
SELECT 'FromTable2', COLUMN1, COLUMN2, COLUMN3 FROM TABLE1
)

Why doesn't the [NOT IN ] syntax work?

I try to execute the following query but it doesn't get any data although it should get one row :
select * from [DB1.table1] where col1 not in (select col2 from DB2.table2)
col1,col2 are of type varchar
why it doesn't work ?
"Doesn't work" isn't exactly a good description of your problem, but in nearly all cases this is caused by the sub-select returning NULL values.
You probably want this:
select * from [DB1.table1]
where col1 not in (select col2 from DB2.table2 where col2 is not null);
The comparison with NULL always yield "undefined" and thus if at least one row from the sub-select contains a NULL in the col2 column the whole expression is "undefined". As undefined not "true", the whole query doesn't return anything.
If you have NULLs in col2 in table2, you'll get the behaviour you describe:
create table table2 (
col2 varchar(10) null
)
insert into table2 (col2) values ('abc'),(null)
create table table1 (
col1 varchar(10) null
)
insert into table1 (col1) values ('abc'),('def')
select * from table1 where col1 not in (select col2 from table2)
Produces no rows. This is because the result of NOT IN becomes UNKNOWN once a NULL comparison occurs.
You can fix it with:
select * from table1 where col1 not in (select col2 from table2 where col2 is not null)
If that's the correct logic for your situtation.
As others have already pointed to the reason that cause this issue, you can achieve the same results, using LEFT JOIN and it safe than the predicate IN with the NULL vlaues:
select t1.*
from [DB1.table1] AS T1
LEFT JOIN DB2.table2 AS t2 ON t1.col1 = t2.col2
where t1.col2 IS NULL;

Combine different table columns with UNION and use XML to differentiate the column names

Though this works to return the right results,
SELECT r.KeyColumn as '#Key', t1.Key1 as 'Key1', t1.Col1 as 'Col1'
FROM #resultset r
INNER JOIN Table1 t1 ON t1.Col1 = 'SomeCondition'
--FOR XML PATH('Column1') -- This errors out when used with union, works as a seperate query.
UNION
SELECT r.KeyColumn as '#Key', t2.Key1 as 'Key2', t2.Col2 as 'Col2'
FROM #resultset r
INNER JOIN Table2 t2 ON t2.Col1 = 'SomeCondition2'
--FOR XML PATH('Column2') -- This errors out when used with union, works as a seperate query.
Results:
#Key Key1 Col1
1 1 Table1Col1
1 1 Table2Col
The problem is that the results together doesn't differentiate between the different naming I'm trying to use, because of the UNION. How can I get both to display under one resultset but with different names? I'm thinking XML to do this, but am unable to figure out how?
What's an optimal way to combine different queries with different results/number of rows and put everything under one big XML?
Ok, the best I could come up with is:
SELECT r.KeyColumn as '#Key', t1.Key1 as 'Key1', t1.Col1 as 'Col1', '' as 'Key2', '' as 'Col2'
FROM #resultset r
INNER JOIN Table1 t1 ON t1.Col1 = 'SomeCondition'
--FOR XML PATH('Column1') --Error
UNION
SELECT r.KeyColumn as '#Key', '' AS 'Key1', '' as 'Col1', t2.Key1 as 'Key2', t2.Col2 as 'Col2'
FROM #resultset r
INNER JOIN Table2 t2 ON t2.Col1 = 'SomeCondition2'
--FOR XML PATH('Column2') -- Error
Will give me results (again, as expected because of the Union)
#Key Key1 Col1 Key2 Col2
---------------------------------------------------------------------------
1 1 Table1Col1
1 1 Table2Col
I still want to get my results as an XML thus:
<Root>
<Column1 Key="1">
<Key1>1</Key1>
<Col1>Table1Col1</Col1>
</Column1>
<Column2 Key="1">
<Key2>1</Key2>
<Col2>Tabl2Col</Col2>
</Column2>
</Root>
or something on these lines:
<Root Key="1">
<Column1>
<Key1>1</Key1>
<Col1>Table1Col1</Col1>
</Column1>
<Column2>
<Key2>1</Key2>
<Col2>Tabl2Col</Col2>
</Column2>
</Root>
Are you looking for something like this?
declare #T table
(
KeyColumn int,
Col1 varchar(10),
Col2 varchar(10)
)
insert into #T values(1, 'Col1', 'Col2')
select (
select KeyColumn,
Col1
from #T
for xml path('Query1'), type
),
(
select KeyColumn,
Col2
from #T
for xml path('Query2'), type
)
for xml path('root')
Result:
<root>
<Query1>
<KeyColumn>1</KeyColumn>
<Col1>Col1</Col1>
</Query1>
<Query2>
<KeyColumn>1</KeyColumn>
<Col2>Col2</Col2>
</Query2>
</root>
You can't do what you want. The UNION uses the column names from the first SELECT statement and unions the columns ijn the second (and subsequent) SELECTs. In other words, the first SELECT names the columns, and the SELECTs that follow are just considered to be additional rows of those columns.
If you want separate column names, use JOINS or multiple-table SELECTs instead.