Compare to all columns - sql

Is it possible to compare a vector of values with all columns of a table in SQL, more specifically in MS SQL Server?
For example, I have a table, EXAMPLE, with 3 columns:
EXAMPLE: ColA, ColB, ColC
And I want to check if its columns match a specific vector: ('val0', 'val1', 'val2')
I know I can do that with a sentence like this:
SELECT * FROM EXAMPLE WHERE ColA='val0' AND ColB = 'val1' AND ColC = 'val2'
But I'd like to know if there is some function, ALLEQUAL which could allow me to do something like:
SELECT * FROM EXAMPLE WHERE ALLEQUAL('val0', 'val1', 'val2');
I understand that if that function exists its syntax may be quite different between different RDBMSs and I now focused on Ms SQL Server. Nevertheless I will be more than happy if you can give me examples in other databases managers.

declare #Foo as Table ( ColA Int, ColB Int );
insert into #Foo ( ColA, ColB ) values ( 1, 1 ), ( 1, 2 ), ( 2, 1 );
select * from #Foo;
select *
from #Foo
intersect
select *
from ( values ( 2, 1 ) ) as Bar( ColA, ColB );

Maybe this will help you
SELECT *
FROM EXAMPLE
WHERE ColA+ColB+ColC = 'val0'+'val1'+'val2'

In a well designed database the tables will have a PRIMARY KEY, the key will be a unique identifier for the row, because of this there is no need to match the rest of the 'vector' in your scenario.
Now if you have some flat de-normalized data you could create an sproc that generates dynamic sql to do what you want. You'd have to query INFORMATION_SCHEMA.columns or sys.columns and generate something similar to the first SQL statement you wrote ~"WHERE ColA='val0' AND ColB = 'val1' AND ColC = 'val2' "
Here's the varchar column only approach using FOR XML PATH('')
BEGIN
DECLARE #tbl TABLE(c1 VARCHAR(max), c2 VARCHAR(max), c3 VARCHAR(max))
DECLARE #vec TABLE(data VARCHAR(max))
INSERT INTO #tbl VALUES ('abc', '123', 'xyz'), ('cba', '321', 'zyx')
INSERT INTO #vec VALUES ('abc'), ('123'), ('xyz')
SELECT * FROM #tbl
WHERE c1+c2+c3 = (SELECT '' + data FROM #vec FOR XML PATH(''))
END

Related

Select rows with same data in columns

I have a SQL Server database with a table with 20 columns. These columns have data as agree or disagree. Now I want to show rows in these columns which have "agree" data in them. I can use where clause but it is a time consuming task for 20 columns. I am looking for a SQL query which does this task.
You can use in:
select t.*
from t
where 'agree' in (col1, col2, ... col20);
There is no shortcut for this type of scenarios, If you want to compare all the columns, you have to explicitly mention each and every column like .
WHERE Col1='agree' AND Col2="agree"....
To avoid coding, you may go with dynamic query creation or creating a function, but ultimately it will be executed as same query comparing all the columns.
What about JOIN ?
If u have such a complex logic, best practices advise to keep data in different tables.
Here's some simplified example code that tests out several methods to return records that may or may not agree.
Just for the fun of it actually.
declare #T table (id int identity(1,1) primary key, col1 varchar(30), col2 varchar(30), col3 varchar(30));
insert into #T (col1, col2, col3) values
('agree','agree','agree'),
('agree','disagree','disagree'),
('agree','disagree',null),
('disagree','disagree','disagree'),
('disagree','disagree',null),
(null,null,null);
select 'OR' as method, * from #T
where (col1='agree' OR col2='agree' OR col3='agree');
select 'AND' as method, * from #T
where (col1='agree' AND col2='agree' AND col3='agree');
select 'IN' as method, * from #T
where 'agree' IN (col1, col2, col3);
select 'NOT IN' as method, * from #T
where 'agree' NOT IN (col1, col2, col3);
select 'LIKE' as method, * from #T
where CONCAT('-',col1,'-',col2,'-',col3,'-') LIKE '%-agree-%';
select 'NOT LIKE' as method, * from #T
where CONCAT('-',col1,'-',col2,'-',col3,'-') NOT LIKE '%-agree-%';
select 'ALL' as method, * from #T
where 'agree' = ALL(select col from (values (col1),(col2),(col3))q(col));
select 'SOME' as method, * from #T
where 'agree' = SOME(select col from (values (col1),(col2),(col3))q(col));
select 'ANY' as method, * from #T
where 'agree' = ANY(select col from (values (col1),(col2),(col3))q(col));
select 'EXISTS' as method, * from #T
where EXISTS (
select 1
from (values (col1),(col2),(col3))q(col)
where col = 'agree'
);
select 'NOT EXISTS' as method, * from #T
where NOT EXISTS (
select 1
from (values (col1),(col2),(col3))q(col)
where col = 'agree'
);

SQL Server 2008 - Replace Text Values in Column with Values from Another Table

I've tried flexing my Google-fu to no avail so here I am! Unfortunately I cannot change anything about these tables as they are coming out of an application that I have to report out of.
In SQL Server 2008, I'm trying to replace multiple values in one text string column (Table 1) with the value from another table (Table 2).
Thanks in advance!!
Table 1
id value
-------------
1 a1, a2, a3
2 a2, a3
3 a4
Table 2
id value
---------
a1 Value1
a2 Value2
a3 Value3
a4 Value4
Desired Output
id value
-----------------------------
1 Value1, Value2, Value3
2 Value2, Value3
3 Value4
I'm sorry for this solution in advance :) It does what you need though:
create table TableA(
id int,
string varchar(255)
)
create table table2(
id varchar , text varchar(255)
)
insert into tableA values(1,'a,b,c,d')
insert into tableA values(2,'e,f')
insert into table2 values('a', 'value1')
insert into table2 values('b', 'value2')
insert into table2 values('c', 'value3')
insert into table2 values('d', 'value4')
insert into table2 values('e', 'value5')
insert into table2 values('f', 'value6')
select id, left(myConcat,len(myConcat)-1) from (
select c.id, replace(replace(CAST(CAST('<i'+stuff((select * from(
SELECT A.[id] ,
Split.a.value('.', 'VARCHAR(1000)') AS String
FROM (SELECT [id],
CAST ('<M>' + REPLACE([string], ',', '</M><M>') + '</M>' AS XML) AS String
FROM TableA) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) a
inner join table2 b on a.String = b.id
where a.id = c.id
FOR XML PATH ('')
),1,2,'') AS XML).query('/text') AS VARCHAR(1000)),'<text>',''),'</text>',',') myConcat
from TableA c
group by c.id
) d
Using the DelimitedSplit8K found at http://www.sqlservercentral.com/articles/Tally+Table/72993/ as suggested by #user1221684 you might come up with something like this. Working with delimited data like this is a pain. First you have to parse the string so you can join it to the other table and then ruin by stuffing it back into a denormalized form.
Make sure that if you use this that you understand that function and what this code is doing. This is not entry level t-sql and it will be YOU supporting this at 3am when it breaks in production, not me.
if OBJECT_ID('tempdb..#table1') is not null
drop table #table1;
create table #table1
(
id int,
value varchar(50)
);
insert #table1
select 1, 'a1, a2, a3' union all
select 2, 'a2, a3' union all
select 3, 'a4';
if OBJECT_ID('tempdb..#table2') is not null
drop table #table2;
create table #table2
(
id varchar(50),
value varchar(50)
);
insert #table2
select 'a1', 'Value1' union all
select 'a2', 'Value2' union all
select 'a3', 'Value3' union all
select 'a4', 'Value4';
with parsedValues as
(
select t1.id
, t1.value
, LTRIM(x.item) as item
from #table1 t1
cross apply dbo.DelimitedSplit8K(t1.value, ',') x
)
, swappedVals as
(
select pv.id
, t2.value
from parsedValues pv
join #table2 t2 on t2.id = pv.item
)
select id
, STUFF((select ',' + value
from swappedVals sv2
where sv2.id = sv.id
order by sv2.value --need to make sure to order here so the results are in the right order
for XML path('')), 1, 1, '') as MyValues
from swappedVals sv
group by id
;
This site has a delimited text split function http://www.sqlservercentral.com/articles/Tally+Table/72993/
Use that function to split your values out into a temp table. Replace the values in your temp table with the new values. Then use STUFF..FOR XML to combine the records back together and update your table.
One query with a few cte's should be able to handle all of this after you add the function to your database.
Example using Sql Fiddle
Use this:
DECLARE #t TABLE(id int,value varchar(255))
INSERT INTO #t (id,value)
VALUES(1,'a1'),(2,'a2'),(3,'a3')....
SELECT *,STUFF((SELECT DISTINCT ','+value FROM #t WHERE id=t.id)
FOR XML PATH('')),1,2,' ')
FROM (SELECT DISTINCT ID FROM #t) t
DISTINCT in the case of same id, otherwise let it go

How do I return the column name in table where a null value exists?

I have a table of more than 2 million rows and over 100 columns. I need to run a query that checks if there are any null values in any row or column of the table and return an ID number where there is a null. I've thought about doing the following, but I was wondering if there is a more concise way of checking this?
SELECT [ID]
from [TABLE_NAME]
where
[COLUMN_1] is null
or [COLUMN_2] is null
or [COLUMN_3] is null or etc.
Your method is fine. If your challenge is writing out the where statement, then you can run a query like this:
select column_name+' is null or '
from information_schema.columns c
where c.table_name = 'table_name'
Then copy the results into a query window and use them for building the query.
I used SQL Server syntax for the query, because it looks like you are using SQL Server. Most databases support the INFORMATION_SCHEMA tables, but the syntax for string concatenation varies among databases. Remember to remove the final or at the end of the last comparison.
You can also copy the column list into Excel and use Excel formulas to create the list.
You can use something similar to the following:
declare #T table
(
ID int,
Name varchar(10),
Age int,
City varchar(10),
Zip varchar(10)
)
insert into #T values
(1, 'Alex', 32, 'Miami', NULL),
(2, NULL, 24, NULL, NULL)
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select *
from #T as T2
where T1.ID = T2.ID
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1

Convert Rows Into Columns in SQL

Table A
ID COLA
-----------------------
A value1
B value1
C value1
Table B
ID DETAIL_ID COL_X COL_Y
A 0 foo foo
A 1 bar bar
B 0 foo foo
My expected out is something like
ID COLA COL_X_0 COL_X_1 COL_Y_0 COL_Y_1
A value1 foo bar foo bar
B value1 foo NULL foo NULL
C value1 NULL NULL NULL NULL
It means the rows of table B will be column values based on DETAIL_ID column.
I tried to write queries for this , but can't succeed due to following.
Number of DetailID values will NOT be fixed-length.It means I can't hard-coded the name of the columns.
This will give the exact output you described and you can add more columns if needed
DECLARE #a table (id char, cola varchar(10))
DECLARE #b table (id char, detail_id int, colx char(3), coly char(3))
INSERT #a values('A', 'value1'),('B', 'value2'),('C','value3')
INSERT #b values('A', 0, 'foo', 'foo'),('A', 1, 'bar', 'bar'),
('B',0, 'foo','foo')--,('A', 2, 'bar', 'bar') -- add this for extra columns
CREATE TABLE ##t(id char, detail_id tinyint, colvalue char(3), col varchar(8), cola varchar(10))
DECLARE #columns varchar(max)=''
DECLARE #sqlstring varchar(1000)
;WITH a as (
SELECT a.id, a.cola, b.detail_id, colx, coly,
'col_x_' + cast(detail_id as varchar) col_a,
'col_y_' + cast(detail_id as varchar) col_b
FROM #a a LEFT JOIN #b b on a.id = b.id
)
INSERT ##t
SELECT id, detail_id, colx, col_a, cola FROM a
UNION
SELECT id, detail_id, coly, col_b, cola FROM a
ORDER BY 4,2
SELECT #columns = coalesce(#columns, '') +',[' + col + ']'
FROM (
SELECT DISTINCT col, detail_id FROM ##t where not col is null
) a
SET #columns = stuff(#columns, 1,1,'')
SET #sqlstring =
'SELECT * FROM (
SELECT id, cola, col, colvalue FROM ##t
) b
PIVOT(max(colvalue) FOR col
in(
'+#columns+'))AS p order by 1'
EXEC(#sqlstring)
DROP TABLE ##t
SQL queries must specify the columns of the result set. That's fundamental to SQL. Even PIVOT requires that your query specify the columns before you send it to the RDBMS.
For that reason, it's difficult and error-prone to create a query that returns rows as columns as you describe, and can adapt as needed to any number of columns.
Handling dynamic columns must be a two-stage procedure.
One option is to make the two stages be:
Write application code to build the SQL query dynamically, based on the distinct values found in the data. This requires an extra query to discover what values exist so you can build the query.
Execute the SQL query and retrieve the results.
The other option is to make the two stages be:
Run a more plain SQL query, that fetches rows as rows, as they are stored in the database.
Write application code to post-process the results, collecting individual values from rows into an expanding set of columns based on the values found. This does not require an extra query as the first design does.
Just join table A and B on B.DETAIL_ID == A.ID ?? Or is that too simple?

Select records with order of IN clause

I have
SELECT * FROM Table1 WHERE Col1 IN(4,2,6)
I want to select and return the records with the specified order which i indicate in the IN clause
(first display record with Col1=4, Col1=2, ...)
I can use
SELECT * FROM Table1 WHERE Col1 = 4
UNION ALL
SELECT * FROM Table1 WHERE Col1 = 6 , .....
but I don't want to use that, cause I want to use it as a stored procedure and not auto generated.
I know it's a bit late but the best way would be
SELECT *
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')
Or
SELECT CHARINDEX(CAST(Col1 AS VARCHAR), '4,2,67')s_order,
*
FROM Table1
WHERE Col1 IN( 4, 2, 6 )
ORDER BY s_order
You have a couple of options. Simplest may be to put the IN parameters (they are parameters, right) in a separate table in the order you receive them, and ORDER BY that table.
The solution is along this line:
SELECT * FROM Table1
WHERE Col1 IN(4,2,6)
ORDER BY
CASE Col1
WHEN 4 THEN 1
WHEN 2 THEN 2
WHEN 6 THEN 3
END
select top 0 0 'in', 0 'order' into #i
insert into #i values(4,1)
insert into #i values(2,2)
insert into #i values(6,3)
select t.* from Table1 t inner join #i i on t.[in]=t.[col1] order by i.[order]
Replace the IN values with a table, including a column for sort order to used in the query (and be sure to expose the sort order to the calling application):
WITH OtherTable (Col1, sort_seq)
AS
(
SELECT Col1, sort_seq
FROM (
VALUES (4, 1),
(2, 2),
(6, 3)
) AS OtherTable (Col1, sort_seq)
)
SELECT T1.Col1, O1.sort_seq
FROM Table1 AS T1
INNER JOIN OtherTable AS O1
ON T1.Col1 = O1.Col1
ORDER
BY sort_seq;
In your stored proc, rather than a CTE, split the values into table (a scratch base table, temp table, function that returns a table, etc) with the sort column populated as appropriate.
I have found another solution. It's similar to the answer from onedaywhen, but it's a little shorter.
SELECT sort.n, Table1.Col1
FROM (VALUES (4), (2), (6)) AS sort(n)
JOIN Table1
ON Table1.Col1 = sort.n
I am thinking about this problem two different ways because I can't decide if this is a programming problem or a data architecture problem. Check out the code below incorporating "famous" TV animals. Let's say that we are tracking dolphins, horses, bears, dogs and orangutans. We want to return only the horses, bears, and dogs in our query and we want bears to sort ahead of horses to sort ahead of dogs. I have a personal preference to look at this as an architecture problem, but can wrap my head around looking at it as a programming problem. Let me know if you have questions.
CREATE TABLE #AnimalType (
AnimalTypeId INT NOT NULL PRIMARY KEY
, AnimalType VARCHAR(50) NOT NULL
, SortOrder INT NOT NULL)
INSERT INTO #AnimalType VALUES (1,'Dolphin',5)
INSERT INTO #AnimalType VALUES (2,'Horse',2)
INSERT INTO #AnimalType VALUES (3,'Bear',1)
INSERT INTO #AnimalType VALUES (4,'Dog',4)
INSERT INTO #AnimalType VALUES (5,'Orangutan',3)
CREATE TABLE #Actor (
ActorId INT NOT NULL PRIMARY KEY
, ActorName VARCHAR(50) NOT NULL
, AnimalTypeId INT NOT NULL)
INSERT INTO #Actor VALUES (1,'Benji',4)
INSERT INTO #Actor VALUES (2,'Lassie',4)
INSERT INTO #Actor VALUES (3,'Rin Tin Tin',4)
INSERT INTO #Actor VALUES (4,'Gentle Ben',3)
INSERT INTO #Actor VALUES (5,'Trigger',2)
INSERT INTO #Actor VALUES (6,'Flipper',1)
INSERT INTO #Actor VALUES (7,'CJ',5)
INSERT INTO #Actor VALUES (8,'Mr. Ed',2)
INSERT INTO #Actor VALUES (9,'Tiger',4)
/* If you believe this is a programming problem then this code works */
SELECT *
FROM #Actor a
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY case when a.AnimalTypeId = 3 then 1
when a.AnimalTypeId = 2 then 2
when a.AnimalTypeId = 4 then 3 end
/* If you believe that this is a data architecture problem then this code works */
SELECT *
FROM #Actor a
JOIN #AnimalType at ON a.AnimalTypeId = at.AnimalTypeId
WHERE a.AnimalTypeId IN (2,3,4)
ORDER BY at.SortOrder
DROP TABLE #Actor
DROP TABLE #AnimalType
ORDER BY CHARINDEX(','+convert(varchar,status)+',' ,
',rejected,active,submitted,approved,')
Just put a comma before and after a string in which you are finding the substring index or you can say that second parameter.
And first parameter of CHARINDEX is also surrounded by , (comma).