Select rows using EXCEPT - SQL Server - sql

How can I return just different columns when I use except in SQL Server?
Example:
SELECT ID, NAME FROM TABLE_B
EXCEPT
SELECT ID, NAME FROM TABLE_A
In this case, if there is different name return just show name column.

Your code is correct. You won't get any repeated row (that's ID + NAME!).
But if I understand correctly, you only want to focus in names. Then remove ID from selected fields:
SELECT NAME FROM TABLE_B
EXCEPT
SELECT NAME FROM TABLE_A
[Edited, regarding a comment:]
This shows distinct rows from TABLE_B that aren’t in TABLE_A. This is the goal of using EXCEPT. For anything else, EXCEPT is not the solution.
In case you're looking for all diferent names from both tables, you can use:
select distinct NAME
from
(select NAME from TABLE_A
UNION
select NAME from TABLE_B) as T

You can get a result set which flags non-existing data in the second table in the form
ID flag_ID NAME flag_Name
1 ! A ! -- No Id=1, no NAME ='A' exists in the second table
3 NULL NULL ! -- Id=3 exists, no NAME is NULL exists
4 NULL Y NULL -- Both values exist but never in the same row
and proceed with a criteria you need.
Assuming ID is NOT NULL, NAME is nullable, NULLs should be considered "equal":
SELECT b.ID,
CASE WHEN NOT EXISTS (SELECT 1 FROM a t2 WHERE t2.ID=b.ID) THEN '!' END flag_ID,
b.NAME,
CASE WHEN NOT EXISTS (SELECT 1 FROM a t2
WHERE ISNULL(NULLIF(b.NAME, t2.NAME), NULLIF(t2.NAME, b.NAME)) IS NULL)
THEN '!' END flag_Name
FROM b
LEFT JOIN a ON a.ID = b.ID
AND ISNULL(NULLIF(a.NAME, b.NAME), NULLIF(b.NAME, a.NAME)) IS NULL
WHERE a.ID IS NULL
OR ISNULL(NULLIF(a.NAME, b.NAME), NULLIF(b.NAME, a.NAME)) IS NOT NULL

Related

insert into with two selects and compare

It looks stupid in this example but here is what I want to do:
Table_a:
id fk_b_id full_name
1 [I want '10' here] [I want 'John, Doe' here]
Table_b:
id first_name
10 John
Table_c:
id full_name date
20 John, Doe 2020-01-01
I get all the full names that is not already in c and meets the criteria like this:
select distinct full_name
from Table_c
where full_name not in (
select full_name
from Table_a ) and date > GETDATE()
The result from the query should be inserted into Table_a together with Table_b's id (fk_b_id). So I need a compare between part of Table_a's full_name and Table_b's first_name to get the correct fk. I can do the compare like this:
where Table_b.first_name = LTRIM(RTRIM(RIGHT(Table_c.full_name, CHARINDEX(',', REVERSE(Table_c.full_name))-1 )))
Can I insert all the full names from table c (that meets the criteria) and the fk from table b (where their first names match) into table a in one query?
Sure, just use your condition to JOIN between tables B and C
INSERT INTO Table_a (fk_b_id, full_name)
SELECT DISTINCT b.id, c.full_name
FROM Table_c c
INNER JOIN Table_b b ON b.first_name = LTRIM(RTRIM(RIGHT(c.full_name, CHARINDEX(',', REVERSE(c.full_name))-1 )))
WHERE c.full_name not in (
select a.full_name
from Table_a a ) and c.date > GETDATE()
EDIT:
Also note, that if your table_a.full_name column can have NULL values, NOT IN sub-query will fail to give you expected results. I suggest rewriting it to use NOT EXISTS
WHERE NOT EXISTS
(SELECT * FROM Table_a WHERE Table_a.Full_Name = Table_c.FUll_Name )

Setting a column value in the SELECT Statement based on a value existing in another table

I have 2 tables. One table lists all the records of items we track. The other table contains flags of attributes of the records in the first table.
For example, Table 1 has columns
Tab1ID, Name, Address, Phone
Table 2 has these columns
Tab2ID, Tab1ID, FlagName
There is a 1 to Many relationship between Table1 and Table2 linked by Tab1ID.
I'd like to create a query that has all the records from Table1 in it. However, if one of the records in Table2 has a Flagname=Retired (with a matching Tab1ID) then I want a "Y" to show up in the select column list otherwise an "N".
I think it might look something like this:
Select Name, Address, Phone, (select something in table2)
from Table1
where Tab1ID > 1;
It's the subquery in the column that has me stumped.
Pat
You can use exists:
Select t1.*,
(case when exists (select 1
from table2 t2
where t2.tab1id = t1.tab1id and t2.flagname = 'Retired'
)
then 'Y' else 'N'
end) as retired_flag
from Table1 t1;
I would do a normal join returning multiple records, but convert them to bits with case statements. Then use that as the subquery and pull the max value for each bit column.
select
name
,address
,phone
,max(retired_flag)
from (
select
table1.name
,table1.address
,table1.phone
,case when table2.flagname = 'retired' then 1 else 0 end as [retired_flag]
from table1
left join table2
on table1.tab1id = table2.tab1id
where tab1id > 1
) tbl
group by
name
,address
,phone

Value present in more than one table

I have 3 tables. All of them have a column - id. I want to find if there is any value that is common across the tables. Assuming that the tables are named a.b and c, if id value 3 is present is a and b, there is a problem. The query can/should exit at the first such occurrence. There is no need to probe further. What I have now is something like
( select id from a intersect select id from b )
union
( select id from b intersect select id from c )
union
( select id from a intersect select id from c )
Obviously, this is not very efficient. Database is PostgreSQL, version 9.0
id is not unique in the individual tables. It is OK to have duplicates in the same table. But if a value is present in just 2 of the 3 tables, that also needs to be flagged and there is no need to check for existence in he third table, or check if there are more such values. One value, present in more than one table, and I can stop.
Although id is not unique within any given table, it should be unique across the tables; a union of distinct id should be unique, so:
select id from (
select distinct id from a
union all
select distinct id from b
union all
select distinct id from c) x
group by id
having count(*) > 1
Note the use of union all, which preserves duplicates (plain union removes duplicates).
I would suggest a simple join:
select a.id
from a join
b
on a.id = b.id join
c
on a.id = c.id
limit 1;
If you have a query that uses union or group by (or order by, but that is not relevant here), then you need to process all the data before returning a single row. A join can start returning rows as soon as the first values are found.
An alternative, but similar method is:
select a.id
from a
where exists (select 1 from b where a.id = b.id) and
exists (select 1 from c where a.id = c.id);
If a is the smallest table and id is indexes in b and c, then this could be quite fast.
Try this
select id from
(
select distinct id, 1 as t from a
union all
select distinct id, 2 as t from b
union all
select distinct id, 3 as t from c
) as t
group by id having count(t)=3
It is OK to have duplicates in the same table.
The query can/should exit at the first such occurrence.
SELECT 'OMG!' AS danger_bill_robinson
WHERE EXISTS (SELECT 1
FROM a,b,c -- maybe there is a place for old-style joins ...
WHERE a.id = b.id
OR a.id = c.id
OR c.id = b.id
);
Update: it appears the optimiser does not like carthesian joins with 3 OR conditions. The below query is a bit faster:
SELECT 'WTF!' AS danger_bill_robinson
WHERE exists (select 1 from a JOIN b USING (id))
OR exists (select 1 from a JOIN c USING (id))
OR exists (select 1 from c JOIN b USING (id))
;

Issues with SQL Select utilizing Except and UNION All

Select *
From (
Select a
Except
Select b
) x
UNION ALL
Select *
From (
Select b
Except
Select a
) y
This sql statement returns an extremely wrong amount of data. If Select a returns a million, how does this entire statement return 100,000? In this instance, Select b contains mutually exclusive data, so there should be no elimination due to the except.
As already stated in the comment, EXCEPT does an implicit DISTINCT, according to this and the ALL in your UNION ALL cannot re-create the duplicates. Hence you cannot use your approach if you want to keep duplicates.
As you want to get the data that is contained in exactly one of the tables a and b, but not in both, a more efficient way to achieve that would be the following (I am just assuming the tables have columns id and c where id is the primary key, as you did not state any column names):
SELECT CASE WHEN a.id IS NULL THEN 'from b' ELSE 'from a' END as source_table
,coalesce(a.id, b.id) as id
,coalesce(a.c, b.c) as c
FROM a
FULL OUTER JOIN b ON a.id = b.id AND a.c = b.c -- use all columns of both tables here!
WHERE a.id IS NULL OR b.id IS NULL
This makes use of a FULL OUTER JOIN, excluding the matching records via the WHERE conditions, as the primary key cannot be null except if it comes from the OUTER side.
If your tables do not have primary keys - which is bad practice anyway - you would have to check across all columns for NULL, not just the one primary key column.
And if you have records completely consisting of NULLs, this method would not work.
Then you could use an approach similar to your original one, just using
SELECT ...
FROM a
WHERE NOT EXISTS (SELECT 1 FROM b WHERE <join by all columns>)
UNION ALL
SELECT ...
FROM b
WHERE NOT EXISTS (SELECT 1 FROM a WHERE <join by all columns>)
If you're trying to get any data that is in one table and not in the other regardless of which table, I would try something like the following:
select id, 'table a data not in b' from a where id not in (select id from b)
union
select id, 'table b data not in a' from b where id not in (select id from a)

Include table name in column from select wildcard sql

Is it possible to include table name in the returned column if I use wildcard to select all columns from tables?
To explain it further. Suppose I want to join two tables and both tables have the column name “name” and many other columns. I want to use wildcard to select all columns and not explicitly specifying each column name in the select.
Select *
From
TableA a,
TableB b
Where
a.id = b.id
Instead of seeing two column with same name "name", could I write a sql to return one column name as "a.name" (or TableA.name) and one as "b.name"(or TableB.name) without explicitly putting the column name in select?
I would prefer a solution for mssql but other database could be a reference too.
Thanks!
You can use select a.*, ' ', b.* from T1 a, T2 b to make it more visible where columns from T1 end and columns from T2 begin.
You are basically joining two tables on the ID field, so you will only see one column labeled "ID", not two, because you are asking to see only those records where the ID is the same in table a and table b: they share the same id.
Try ...
SELECT 'TableA' AS 'Table', A.* FROM TableA A
WHERE A.id IN (SELECT id FROM TableB)
UNION
SELECT 'TableB' AS 'Table', B.* FROM TableB B
WHERE B.id IN (SELECT id FROM TableA)
ORDER BY id, [Table]