How to reduce 10+ union select statements - sql-server

I need to extract a single column of data from 10+ columns of a single table. Instead of stacking up 10+ union select statements, is there another way of doing this without repeating the union select statements for each column?
I can get it by stacking up 10+ select statements like below:
select 'column_5' from table_a
union
select 'column_6' from table_a
union
select 'column_7' from table_a
union
.
.
.
union
select 'column_18' from table_a
Thanks for your time in advance :)

Using Cross Apply or UnPivot you can avoid multiple UNION statements
select Distinct COL
from table
Cross apply
(
values
(column_1),
(column_2),
..
..
(column_18)
)
CS (COL)
Note : Since you have used UNION i have kept Distinct in select. If you don't want to remove duplicates then remove Distinct from select

A query with UNPIVOT would look something like this....
SELECT *
FROM TableName t
UNPIVOT (Vals FOR N IN (Column1, Column2, Column3,....,Column10))up
Important note all the columns in IN clause must be of the same data type, if they are not use a sub-query to convert them to a uniform data type and then unpivot them something like...
SELECT *
FROM (
SELECT CAST(Column1 AS VARCHAR(200)) AS Column1
,CAST(Column2 AS VARCHAR(200)) AS Column2
,CAST(Column3 AS VARCHAR(200)) AS Column3
,.....
,CAST(Column10 AS VARCHAR(200)) AS Column10
FROM TableName) t
UNPIVOT (Vals FOR N IN (Column1, Column2, Column3,....,Column10))up

Related

How can I find different elements between two columns (generated by two subqueries)?

My subqueries produce a result like this:
coulmn1 column2
a d
b z1000
c c
d
1
2
z1000 k
I want to know the different elements in both sets. column1 ={ a,b,c, 1,2,d, z1000,.....} column 2 ={ d,c,z1000,k......} The result I want is ={ a,k,1,2,....} hope I made it clear ..please let me know how could I do that..?
One method is full outer join:
select coalesce(t1.col1, t2.col2)
from t t1 full join
t t2
on t1.col1 = t2.col2
where t1.col1 is null or t2.col2 is null;
Another method doesn't require running the subquery twice;
select v.col
from t cross apply
(values (t.col1, 1), (t.col2, 2)) v(col, which)
group by v.col
having min(v.which) = max(v.which);
--Test Data
with temp_table as (
select 'a' coulmn1,'b' column2 union all
select 'b' coulmn1,'z1000' column2 union all
select 'c' coulmn1,'c' column2 union all
select 'd' coulmn1,'' column2 union all
select 'z1000' coulmn1,'k' column2
)
--use cross join and union to distinct data
--you have to change temp_table to your own table
select * into #temp_table from (
select T.coulmn1,T2.column2 as column2
from temp_table T,temp_table T2
where T.coulmn1 <> T2.column2
) T;
select coulmn1 from #temp_table
union
select column2 from #temp_table;
Test Link
Use EXCEPT for this.
SELECT column1
FROM your_subquery
EXCEPT
SELECT column2
FROM your_subquery
UNION
SELECT column2
FROM your_subquery
EXCEPT
SELECT column1
FROM your_subquery

TSQL Conditional Select Statement

Is it possible to write a SQL query that does the following:
Select * From Table1
if there are results, return them. Otherwise, use an alternative query and return it's results:
Select * From Table2
I came ups with the following, but it does not seem to work:
IF EXISTS(select * From TableA)
begin
Select * from TableA
end
else
Select * from TableB
Is there a simple elegant way of accomplishing this?
You can do a UNION query with NOT EXISTS:
SELECT * FROM TABLE1
UNION ALL
SELECT * FROM TABLE2 WHERE NOT EXISTS (SELECT * FROM TABLE1)
*Assuming the columns and types are the same
If both tables have same number of columns and the column names are same then you use Union, like below.
Select * from TableA
Union
Select * from TableB
If they have different column names or different number of columns then you can use below code.
Select Col_1 as col1, col_2 as col2.... From TableA
Union
Select col_a as Col1, col_b as Col2..... From TableB

SQL IN clause multiple columns and multiple value

This query is fine works.
SELECT * FROM TABLE WHERE 330110042 IN (iItem01,iItem02,iItem03,iItem04,iItem05,iItem_1,iItem_2,iItem_3,iItem_4,iItem_5,iItem_6,iItem_7,iItem_8,iItem_9,iItem_10,iItem_11,iItem_12,iItem_13,iItem_14,iItem_15,iItem_16,iItem_17,iItem_18,iItem_19,iItem_20,iItem_21,iItem_22,iItem_23,iItem_24,iItem_25,iItem_26,iItem_27,iItem_28,iItem_29,iItem_30)
But this query didnt work.
SELECT * FROM TABLE WHERE 330110042, 330110002, 330110002 IN (iItem01,iItem02,iItem03,iItem04,iItem05,iItem_1,iItem_2,iItem_3,iItem_4,iItem_5,iItem_6,iItem_7,iItem_8,iItem_9,iItem_10,iItem_11,iItem_12,iItem_13,iItem_14,iItem_15,iItem_16,iItem_17,iItem_18,iItem_19,iItem_20,iItem_21,iItem_22,iItem_23,iItem_24,iItem_25,iItem_26,iItem_27,iItem_28,iItem_29,iItem_30)
How i work in SQL Server?
It's difficult to tell your exact goal here, but one possibility would be to turn the list of values into a table structure of its own. A Common Table Expression might work:
;WITH Ids AS
(
SELECT 330110042 AS Id
UNION ALL
SELECT 330110002
)
SELECT t.*
FROM [Table] t
INNER JOIN Ids i ON t.iItem01 = i.Id OR t.iItem02 = i.Id OR...
But, maybe a solution with UNPIVOT would be more elegant. I presume that your table has a primary key column called Id:
;WITH Unpivoted AS
(
SELECT Id, ColName, ColValue
FROM (SELECT Id, iItem01, iItem02, iItem03
FROM [Table] t) p
UNPIVOT
(ColValue FOR ColName IN (iItem01, iItem02, iItem03)) AS unpvt
)
SELECT t.*
FROM [Table] t
WHERE EXISTS (SELECT 1 FROM Unpivoted u
WHERE t.Id = u.Id
AND u.ColValue IN (330110042, 330110002))
Of course, you would add all the necessary columns. I added only the first three for this example.

Why doesn't this union give me two columns?

I want two columns in the output of the join. I only get one, the storeID. The StoreComponentID is not there.
if you want two column you need to declare two columns
SELECT column1, NULL as column2 -- even when Table1 doesnt have column2
FROM Table1
UNION
SELECT NULL as column1, column2 -- even when Table2 doesnt have column1
FROM Table2
Now if you want some kind of merge side by side.
WITH idA as (
SELECT StoreComponentID,
ROW_NUMBER() OVER (ORDER BY StoreComponentID) as rn
FROM StoreComponent
), idB as (
SELECT StoreID
ROW_NUMBER() OVER (ORDER BY StoreID) as rn
FROM Store
)
SELECT idA.StoreComponentID,
idB.StoreID
FROM idA
FULL JOIN idB
ON idA.rn = idB.rn
I figured out a simple solution:
select S.storeid as sID, SC.storecomponentid as SCID from tstore as S, tstorecomponent as SC

Is it possible to improve sort perfomance of ID-based list of rows?

Consider following example:
SET NOCOUNT ON;
CREATE TABLE #Users
(
ID INT IDENTITY(1,1),
Name VARCHAR(50)
);
CREATE CLUSTERED INDEX IDX_C_Users_UserID ON #Users(ID);
-- CREATE INDEX IDX_Users_Name ON #Users(Name); -- It doesn't work.
CREATE TABLE #Towns
(
ID INT IDENTITY(1,1),
Name VARCHAR(50)
);
CREATE CLUSTERED INDEX IDX_C_Towns_UserID ON #Towns(ID)
CREATE TABLE #BeenHere
(
ID INT IDENTITY(1,1), -- for some business reason we can't use clustered index on them
UserID INT,
TownID INT
);
CREATE UNIQUE INDEX IDX_BEEN_THERE ON #BeenHere(TownID, UserID);
INSERT INTO #Towns
SELECT Prefix+Suffix FROM (
SELECT Prefix, Suffix FROM
(SELECT 'China' UNION ALL
SELECT 'Ham' UNION ALL
SELECT 'Chicken' UNION ALL
SELECT 'Great' UNION ALL
SELECT 'Loud'
) as A(Prefix)
CROSS JOIN
(SELECT 'town' UNION ALL
SELECT 'water' UNION ALL
SELECT ' City' UNION ALL
SELECT 'burg' UNION ALL
SELECT 'berg') AS B(Suffix)
) Q
ORDER BY NEWID()
;
INSERT INTO #Users(Name)
SELECT Name + ' ' + Surname FROM (
SELECT Name, Surname FROM
(SELECT 'John' UNION ALL
SELECT 'Mary' UNION ALL
SELECT 'Ann' UNION ALL
SELECT 'Salomon' UNION ALL
SELECT 'Lisa' UNION ALL
SELECT 'Patricia' UNION ALL
SELECT 'David' UNION ALL
SELECT 'Patrick' UNION ALL
SELECT 'John' UNION ALL
SELECT 'Harry' UNION ALL
SELECT 'Richard' UNION ALL
SELECT 'George'
) as A(Name)
CROSS JOIN
(SELECT 'Smith' UNION ALL
SELECT 'Kowalski' UNION ALL
SELECT 'Bush' UNION ALL
SELECT 'Truman' UNION ALL
SELECT 'Clinton' UNION ALL
SELECT 'Reagan' UNION ALL
SELECT 'Lincoln' UNION ALL
SELECT 'Goldberg' UNION ALL
SELECT 'Adams' UNION ALL
SELECT 'Wilson' UNION ALL
SELECT 'Carter') as B(Surname)
) P
ORDER BY NEWID();
INSERT INTO #BeenHere(UserID, TownID)
SELECT
TOP 10 PERCENT
#Users.ID,
#Towns.ID
FROM
#Users
CROSS JOIN
#Towns
ORDER BY NEWID();
SET NOCOUNT OFF;
SELECT
Towns.Name,
(SELECT Users.ID, Users.Name FROM #Users Users INNER JOIN #BeenHere BH ON Users.ID = BH.UserID WHERE BH.TownID = Towns.ID ORDER BY Users.Name FOR XML PATH('User'), ROOT('Users'), TYPE) as BeenThere
FROM #Towns Towns
ORDER BY Towns.Name;
DROP TABLE #BeenHere;
DROP TABLE #Users;
DROP TABLE #Towns;
As we can see in execution plan, sorting users cost 78% of resources consumed by last query.
Is it possible to place some index on these tables to improve sorting perfomance? I can't introduce backward incompatible changes to database, like providing clustered index on #BeenHere(UserID, TownID).
I simply replaced your clustered index into this one:
CREATE CLUSTERED INDEX IDX_C_Users_Name_UserID ON #Users(Name, ID);
So now your table is sorted by Name, not by ID.
SORT operator is gone from execution plan.
UPDATE
As you said, you cannot change clustered index. There's one way to do that if you want. Your NONCLUSTERED INDEX on Name only column is fine, but SQL Server decides not to use it. What you can do is to add a HINT to your table to use this index:
SELECT Towns.Name
, (
SELECT Users.ID, Users.Name
FROM #Users Users WITH (INDEX (IDX_Users_Name))
INNER JOIN #BeenHere BH
ON Users.ID = BH.UserID
WHERE BH.TownID = Towns.ID
ORDER BY Users.Name
FOR XML PATH('User'), ROOT('Users'), TYPE
) AS BeenThere
FROM #Towns Towns
ORDER BY Towns.Name;
Then your query will use this index and sort operator will no longer be there. However, I'm not sure if it's the most efficient way. SQL Server has to Scan index then, instead of seeking it.
Your problem is the use of a correlated subquery. Stop using those and use joins (including derived tables if you must) instead. Correlated subqueries run row-by-row and not against the whole set and thus are preformance hogs. one.

Resources