SQL: EXCEPT Query - sql

Here is a basic example of what I am trying to achieve:
create table #testing (
tab varchar(max), a int, b int, c int )
insert into #testing VALUES ('x',1, 2, 3)
insert into #testing VALUES ('y',1, 2, 3)
insert into #testing VALUES ('x', 4, 5, 6)
select * from #testing
Which will Produce the table:
tab a b c
-----------------------
x 1 2 3
y 1 2 3
x 4 5 6
I then want to compare rows on 'tab' based on the values of a,b,c:
select a,b,c from #testing where tab = 'x'
except
select a,b,c from #testing where tab= 'y'
Which gives me the answer I was expecting:
a b c
------------
4 5 6
However I want to also include the Tab column in my resultset, so I want somthing like this:
Select tab,a,b,c from #testing where ????
(select a,b,c from #testing where tab = 'x'
except
select a,b,c from #testing where tab= 'y')
How would I achieve this?

Use not exists:
select a.*
from #testing a
where a.tab = 'x'
and not exists (
select *
from #testing t
where t.a = a.a and t.b = a.b and t.c = a.c and t.tab = 'y'
)
And here you get SQL Fiddle demo: DEMO

Although the answer from #gzaxx does produce a correct result for this test data, the more generalized version is below, where I left 'x' and 'y' out of the statements.
select a.*
from #testing a
where not exists (
select *
from #testing t
where t.a = a.a and t.b = a.b and t.c = a.c and t.tab <> a.tab
)

Please Try it
with cte as
(
select *,rn = ROW_NUMBER() over(PARTITION by tab order by tab)from #testing
)
select tab,a,b,c from cte where rn>1

Related

1 of these 2 request apparently equivalent is not working

I try to understand how PIVOT table works
These 2 requests with pivot table seem equivalent:
I only write
tablename.column1, ...........column2 instead of tablename.*
You can find the requests here:
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=a5c3aacdaebe599bb050295caf3512b6
with
a as
(
select
a1.column_value a, a2.column_value b , cos(a1.r) c
from
(select column_value, rownum r from table(sys.odcinumberlist(1,2,3,4,5))) a1 ,
(select column_value, rownum r from table(sys.odcivarchar2list('a','b','a','b','a'))) a2
where
a1.r = a2.r)
select a.a,a.b,a.c from a --a.a,a.b
PIVOT
(
count(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
)
ORA-00904: "A"."C": invalid identifier
with
a as
(
select
a1.column_value a, a2.column_value b , cos(a1.r) c
from
(select column_value, rownum r from table(sys.odcinumberlist(1,2,3,4,5))) a1 ,
(select column_value, rownum r from table(sys.odcivarchar2list('a','b','a','b','a'))) a2
where
a1.r = a2.r)
select * from a
PIVOT
(
COUNT(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
)
intended result
When you do a PIVOT, Oracle will name the resulting columns just like their original values.
You can see this behavior when you do your select * that is working :
with
a as
(
select
a1.column_value a, a2.column_value b , cos(a1.r) c
from
(select column_value, rownum r from table(sys.odcinumberlist(1,2,3,4,5))) a1 ,
(select column_value, rownum r from table(sys.odcivarchar2list('a','b','a','b','a'))) a2
where
a1.r = a2.r)
select * from a
PIVOT
(
COUNT(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
)
result is
C 'a' 'b'
-.65364362086361191463916818309775038145 0 1
.5403023058681397174009366074429766037354 1 0
-.98999249660044545727157279473126130238 1 0
.2836621854632262644666391715135573083265 1 0
-.41614683654714238699756822950076218977 0 1
Your columns headings have been turned by Oracle into the exact values you've got in the IN clause, including the surrounding quotes.
So to refer them in your SELECT clause, you should use double quotes like this:
select "'a'","'b'", c from a
PIVOT
(
count(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
)
An alternative is to alias your values directly in the IN clause
select val_a, val_b, c from a --a.a,a.b
PIVOT
(
count(a.a)--,sum(a.c)
FOR b IN ('a' val_a, 'b' val_b )
)
VAL_A VAL_B C
0 1 -.65364362086361191463916818309775038145
1 0 .5403023058681397174009366074429766037354
1 0 -.98999249660044545727157279473126130238
1 0 .2836621854632262644666391715135573083265
0 1 -.41614683654714238699756822950076218977
And finally, you had another mistake in your initial approach:
select a.a,a.b,a.c from a --a.a,a.b
PIVOT
(
count(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
)
in this query a refers to your initial a CTE. When you do a.a,a.b,a.c, Oracle doesn't know what you are referencing because of the PIVOT that comes afterwards.
You should properly alias the PIVOT results if you want to refer to it in the SELECT clause :
select pa."'a'",pa."'b'", pa.c from a
PIVOT
(
count(a.a)--,sum(a.c)
FOR b IN ('a', 'b')
) pa

SQL Anywhere: find rows that are +-2 compared to another row

I have the following table:
ID User Form Depth
1 A ABC 2001
1 A XYZ 1001
1 B XYZ 1003
1 B DEF 3001
1 C XYZ 1000
If ID and Form are identical, I need to identify those rows that are +-2 from User A. Using the example above, the script would return:
ID User Form Depth
1 B XYZ 1003
1 C XYZ 1000
I already have a script which identifies rows with identical ID and Form--I just need the other part, but I'm struggling with figuring out the logic. I was hoping there was some kind of DIFF function I could use, but I can't find one for SQL Anywhere.
Does anyone have any suggestions?
Thanks!
If you're looking for the depth to be exactly +/-2 from A's depth:
select t1.*
from mytab t1,
mytab t2
where t1.id = t2.id
and t1.form = t2.form
and t1.user != 'A'
and t2.user = 'A'
and abs(t1.depth - t2.depth) = 2
go
ID User Form Depth
--- ----- ----- -----
1 B XYZ 1003
If you're looking for the depth to be within 2 of A's depth (ie, diff <= 2):
select t1.*
from mytab t1,
mytab t2
where t1.id = t2.id
and t1.form = t2.form
and t1.user != 'A'
and t2.user = 'A'
and abs(t1.depth - t2.depth) <= 2
go
ID User Form Depth
--- ----- ----- -----
1 B XYZ 1003
1 C XYZ 1000
This is pretty basic SQL so while this fiddle was done with MySQL, you should find the queries work in SQLAnywhere, too: sql fiddle
I think you want exists:
select t.*
from t
where t.user <> 'A' and
exists (select 1
from t t2
where t2.form = t.form and t2.id = t.id and
t2.depth between t.depth - 2 and t.depth + 2
);
A quick and dirty generalized method.
Replace #User with whomever you would like to remove.
DECLARE #table TABLE (
ID Int
,[User] VARCHAR(2)
,Form VARCHAR(3)
,Depth INT
)
DECLARE #User VARCHAR(2) = 'A'
INSERT INTO #table (ID , [User], Form, Depth)
VALUES
(1 , 'A' , 'ABC' , 2001),
(1 , 'A' , 'XYZ' , 1001),
(1 , 'B' , 'XYZ' , 1003),
(1 , 'B' , 'DEF' , 3001),
(1 , 'C' , 'XYZ' , 1000)
SELECT t1.ID, t1.[User], t1.Form, t1.Depth , ROW_NUMBER() OVER(ORDER BY t1.ID, t1.[User], t1.Form, t1.Depth) AS [row_number]
INTO #temp
FROM #table as t1
INNER JOIN (
SELECT t.ID, t.Form, COUNT('8') as [count]
FROM #table as t
GROUP BY ID, Form
HAVING COUNT('8') > 1
) as duplicates
ON duplicates.ID = t1.ID
AND duplicates. Form = t1.Form
ORDER BY ID, User, Form, Depth
-- SELECT * FROM #temp
SELECT [row_number] - 2 as value
INTO #range
FROM #temp as t
WHERE t.[User] = #User
--SELECT * FROM #range
INSERT INTO #range
SELECT [row_number] - 1
FROM #temp as t
WHERE t.[User] = #User
INSERT INTO #range
SELECT [row_number] + 1
FROM #temp as t
WHERE t.[User] = #User
INSERT INTO #range
SELECT [row_number] + 2
FROM #temp as t
WHERE t.[User] = #User
SELECT * FROM #temp
WHERE [row_number] IN (SELECT value FROM #range)
DROP TABLE #temp
DROP TABLE #range

oracle sql pairing up elements from collections

i have created 2 types and a table that contains those:
create or replace type a_t as varray(5) of int
create or replace type b_t as varray(5) of int
create table test(
a a_t,
b b_t
)
insert into test Values (
a_t(1,2,3),
b_t(4,5,6)
)
what i want now is to select with a result of
a_t b_t
------|-----
1 | 4
2 | 5
3 | 6
using the table operator on 1 column result in :
select a.* from test t, table(t.a_t) a
a_t
------
1
2
3
but now i dont know how to get the second row and pair them up in the right order
Try something like This.
WITH tab_a
AS (SELECT ROWNUM rn,
a.*
FROM test t,
TABLE ( t.a ) a),
tab_b
AS (SELECT ROWNUM rn,
b.*
FROM test t,
TABLE ( t.b ) b)
SELECT a.column_value a_t,
b.column_value b_t
FROM tab_a a
FULL OUTER JOIN tab_b b
ON a.rn = b.rn;
One way is to use WITH FUNCTION (Oracle 12c):
WITH FUNCTION f1(i INT, s a_t) RETURN INT AS BEGIN RETURN s(i); END;
FUNCTION f2(i INT, s b_t) RETURN INT AS BEGIN RETURN s(i); END;
SELECT s.*
FROM test t
OUTER APPLY (SELECT f1(1, t.a) AS a_t ,f2(1, t.b) AS b_t FROM dual
UNION ALL SELECT f1(2, t.a),f2(2, t.b) FROM dual
UNION ALL SELECT f1(3, t.a),f2(3, t.b) FROM dual)s;
Output:
A_T B_T
1 4
2 5
3 6
db<>fiddle demo

Joining a list of values with table rows in SQL

Suppose I have a list of values, such as 1, 2, 3, 4, 5 and a table where some of those values exist in some column. Here is an example:
id name
1 Alice
3 Cindy
5 Elmore
6 Felix
I want to create a SELECT statement that will include all of the values from my list as well as the information from those rows that match the values, i.e., perform a LEFT OUTER JOIN between my list and the table, so the result would be like follows:
id name
1 Alice
2 (null)
3 Cindy
4 (null)
5 Elmore
How do I do that without creating a temp table or using multiple UNION operators?
If in Microsoft SQL Server 2008 or later, then you can use Table Value Constructor
Select v.valueId, m.name
From (values (1), (2), (3), (4), (5)) v(valueId)
left Join otherTable m
on m.id = v.valueId
Postgres also has this construction VALUES Lists:
SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter)
Also note the possible Common Table Expression syntax which can be handy to make joins:
WITH my_values(num, str) AS (
VALUES (1, 'one'), (2, 'two'), (3, 'three')
)
SELECT num, txt FROM my_values
With Oracle it's possible, though heavier From ASK TOM:
with id_list as (
select 10 id from dual union all
select 20 id from dual union all
select 25 id from dual union all
select 70 id from dual union all
select 90 id from dual
)
select * from id_list;
the following solution for oracle is adopted from this source. the basic idea is to exploit oracle's hierarchical queries. you have to specify a maximum length of the list (100 in the sample query below).
select d.lstid
, t.name
from (
select substr(
csv
, instr(csv,',',1,lev) + 1
, instr(csv,',',1,lev+1 )-instr(csv,',',1,lev)-1
) lstid
from (select ','||'1,2,3,4,5'||',' csv from dual)
, (select level lev from dual connect by level <= 100)
where lev <= length(csv)-length(replace(csv,','))-1
) d
left join test t on ( d.lstid = t.id )
;
check out this sql fiddle to see it work.
Bit late on this, but for Oracle you could do something like this to get a table of values:
SELECT rownum + 5 /*start*/ - 1 as myval
FROM dual
CONNECT BY LEVEL <= 100 /*end*/ - 5 /*start*/ + 1
... And then join that to your table:
SELECT *
FROM
(SELECT rownum + 1 /*start*/ - 1 myval
FROM dual
CONNECT BY LEVEL <= 5 /*end*/ - 1 /*start*/ + 1) mypseudotable
left outer join myothertable
on mypseudotable.myval = myothertable.correspondingval
Assuming myTable is the name of your table, following code should work.
;with x as
(
select top (select max(id) from [myTable]) number from [master]..spt_values
),
y as
(select row_number() over (order by x.number) as id
from x)
select y.id, t.name
from y left join myTable as t
on y.id = t.id;
Caution: This is SQL Server implementation.
fiddle
For getting sequential numbers as required for part of output (This method eliminates values to type for n numbers):
declare #site as int
set #site = 1
while #site<=200
begin
insert into ##table
values (#site)
set #site=#site+1
end
Final output[post above step]:
select * from ##table
select v.id,m.name from ##table as v
left outer join [source_table] m
on m.id=v.id
Suppose your table that has values 1,2,3,4,5 is named list_of_values, and suppose the table that contain some values but has the name column as some_values, you can do:
SELECT B.id,A.name
FROM [list_of_values] AS B
LEFT JOIN [some_values] AS A
ON B.ID = A.ID

Easiest way to eliminate NULLs in SELECT DISTINCT?

I am working on a query that is fairly similar the following:
CREATE TABLE #test (a char(1), b char(1))
INSERT INTO #test(a,b) VALUES
('A',NULL),
('A','B'),
('B',NULL),
('B',NULL)
SELECT DISTINCT a,b FROM #test
DROP TABLE #test
The result is, unsurprisingly,
a b
-------
A NULL
A B
B NULL
The output I would like to see in actuality is:
a b
-------
A B
B NULL
That is, if a column has a value in some records but not in others, I want to throw out the row with NULL for that column. However, if a column has a NULL value for all records, I want to preserve that NULL.
What's the simplest/most elegant way to do this in a single query?
I have a feeling that this would be simple if I weren't exhausted on a Friday afternoon.
Try this:
select distinct * from test
where b is not null or a in (
select a from test
group by a
having max(b) is null)
You can get the fiddle here.
Note if you can only have one non-null value in b, this can be simplified to:
select a, max(b) from test
group by a
Try this:
create table test(
x char(1),
y char(1)
);
insert into test(x,y) values
('a',null),
('a','b'),
('b', null),
('b', null)
Query:
with has_all_y_null as
(
select x
from test
group by x
having sum(case when y is null then 1 end) = count(x)
)
select distinct x,y from test
where
(
-- if a column has a value in some records but not in others,
x not in (select x from has_all_y_null)
-- I want to throw out the row with NULL
and y is not null
)
or
-- However, if a column has a NULL value for all records,
-- I want to preserve that NULL
(x in (select x from has_all_y_null))
order by x,y
Output:
X Y
A B
B NULL
Live test: http://sqlfiddle.com/#!3/259d6/16
EDIT
Seeing Mosty's answer, I simplified my code:
with has_all_y_null as
(
select x
from test
group by x
-- having sum(case when y is null then 1 end) = count(x)
-- should have thought of this instead of the code above. Mosty's logic is good:
having max(y) is null
)
select distinct x,y from test
where
y is not null
or
(x in (select x from has_all_y_null))
order by x,y
I just prefer CTE approach, it has a more self-documenting logic :-)
You can also put documentation on non-CTE approach, if you are conscious of doing so:
select distinct * from test
where b is not null or a in
( -- has all b null
select a from test
group by a
having max(b) is null)
;WITH CTE
AS
(
SELECT DISTINCT * FROM #test
)
SELECT a,b
FROM CTE
ORDER BY CASE WHEN b IS NULL THEN 9999 ELSE b END ;
SELECT DISTINCT t.a, t.b
FROM #test t
WHERE b IS NOT NULL
OR NOT EXISTS (SELECT 1 FROM #test u WHERE t.a = u.a AND u.b IS NOT NULL)
ORDER BY t.a, t.b
This is a really weird requirement. I wonder how you need it.
SELECT DISTINCT a, b
FROM test t
WHERE NOT ( b IS NULL
AND EXISTS
( SELECT *
FROM test ta
WHERE ta.a = t.a
AND ta.b IS NOT NULL
)
)
AND NOT ( a IS NULL
AND EXISTS
( SELECT *
FROM test tb
WHERE tb.b = t.b
AND tb.a IS NOT NULL
)
)
Well, I don't particularly like this solution, but it seems the most appropriate to me. Note that your description of what you want sounds exactly like what you get with a LEFT JOIN, so:
SELECT DISTINCT a.a, b.b
FROM #test a
LEFT JOIN #test b ON a.a = b.a
AND b.b IS NOT NULL
SELECT a,b FROM #test t where b is not null
union
SELECT a,b FROM #test t where b is null
and not exists(select 1 from #test where a=t.a and b is not null)
Result:
a b
---- ----
A B
B NULL
I'll just put here a mix of two answers that solved my issue, because my View was more complex
--IdCompe int,
--Nome varchar(30),
--IdVanBanco int,
--IdVan int
--FlagAtivo bit,
--FlagPrincipal bit
select IdCompe
, Nome
, max(IdVanBanco)
, max(IdVan)
, CAST(MAX(CAST(FlagAtivo as INT)) AS BIT) FlagAtivo
, CAST(MAX(CAST(FlagPrincipal as INT)) AS BIT) FlagPrincipal
from VwVanBanco
where IdVan = {IdVan} or IdVan is null
group by IdCompe, Nome order by IdCompe asc
Thanks to mosty mostacho and
kenwarner