SQL selection over two rows - sql

How do I select in a table like below all objects that have a-A and b-B (as key-value pair)?
Something like:
SELECT DISTINCT(OBJECT)
FROM MYTABLE
WHERE key = 'a'
AND value = 'A'
AND key = 'b'
AND value = 'B'
...where the result would be 1 and 3.
I know that this SQL statement doesn't work, but I hope it explains a bit what I want to do.
And sorry for the diffuse title. I simply don't know how to describe the problem better.
object | key | value
---------------------
1 | a | A
1 | b | B
1 | c | C
2 | a | F
2 | b | B
3 | a | A
3 | b | B
3 | d | D

I think you want something of this form:
SELECT a.object
FROM mytable a, mytable b
WHERE a.object = b.object
AND a.key = 'a' AND a.value = 'A'
AND b.key = 'b' AND b.value = 'B'

select *
from mytable
where (key = a and value = a)
or (key = b and value = b)
or
select *
from mytable
where key = a and value = a
union
select *
from mytable
where key = b and value = b
or more generally perhaps
select *
from mytable
where key = value
and key in (a,b)

You can even try this
declare #t table(object int, keys varchar(10), value varchar(10))
insert into #t
select 1,'a','A' union all select 1,'b','B' union all
select 1,'c','C' union all
select 2,'a','F' union all select 2,'b','B' union all
select 3,'a','A' union all select 3,'b','B' union all
select 3,'d','D'
--select * from #t
Query
select object from #t
where keys = 'a' and value ='A'
or keys = 'b' and value ='B'
group by object
having COUNT(object)>1
Output:
object
1
3

Related

Getting all the values in one query that aren't in another with a group by

Given that I am using Redshift, how would I get the counts for a query that asks:
Given table A and table B, give me all the count of values in Table A for that grouping that aren't in table B;
So if table A and B look like:
Table A
Id | Value
==========
1 | "A"
1 | "B"
2 | "C"
And table B:
Id | Value
==========
1 | "A"
1 | "D"
2 | "C"
I would want:
Id | Count
==========
1 | 1
2 | 0
You can use left join and group by:
select a.id, sum( (b.id is null)::int )
from a left join
b
on a.id = b.id and a.value = b.value
group by a.id;
Use except and subquery
with a as
(
select 1 as id, 'A' as v
union all
select 1,'B'
union all
select 2,'C'
),b as
(
select 1 as id, 'A' as v
union all
select 1,'D'
union all
select 2,'C'
), c as
(
select id,v from a except select id,v from b
)
select id,sum ( (select count(*) from c where c.id=a.id and c.v=a.v))
from a group by id
output
id cnt
1 1
2 0
online demo which will work in redshift

Need help in creating a view in sql with following data

the table I am working on contains data in following format
Name | A | B | C
----------------
abc | 1 | 0 | 1
xyz | 0 | 1 | 1
pqr | 0 | 0 | 1
I need to create a view a like this
Name | Type
abc | A
abc | C
xyz | B
xyz | C
pqr | C
Will using case and when be helpful?
like
case when A=1 then 'A'
when B=1 then 'B'
when C=1 then 'C'
else ''
end as type
Thanks in advance!
Sample Table :
DECLARE #Table1 TABLE
(Name varchar(3), A int, B int, C int)
;
INSERT INTO #Table1
(Name, A, B, C)
VALUES
('abc', 1, 0, 1),
('xyz', 0, 1, 1),
('pqr', 0, 0, 1)
;
Script
Select Name,[Type] from (
select Name,CASE WHEN VAL = 1 then COL ELSE NULL END Type,VAL from #Table1
CROSS APPLY(VALUES('A',A),('B',B),('C',C))CS(COL,VAL)
)T WHERE T.Type IS NOT NULL
You can use union all
select name,'A' from t where a = 1 union all
select name,'B' from t where b = 1 union all
select name,'C' from t where c = 1;
You can also group the where condition as:
select name, col
from
(select name, 'A' col, a val from t union all
select name, 'B', b from t union all
select name, 'C', c from t)
where val = 1;
We can use union all to select all the records from the table and insert into the view. The Below query would suffice your requirement. Thank you.
create or replace view test1 as (
select name,field from (
select name,'A' as field from test where a = 1 union all
select name,'B' as field from test where b = 1 union all
select name,'C' as field from test where c = 1));

How to subtract two rows from one another if they have they share a value in another column

I am currently working in a database with the following structure:
Var | Value | ID
--------------
A | 1 | 1
B | 2 | 1
C | 3 | 1
A | 2 | 2
B | 4 | 2
C | 6 | 2
What I am trying to achieve is to subtract the value of Var C from the other Var's (B and C) sharing the same ID as Var C. In this case the output would be:
Var | Value | ID
--------------
A | -2 | 1
B | -1 | 1
C | 3 | 1
A | -4 | 2
B | -2 | 2
C | 6 | 2
To be honest I have absolutely no idea how to start on achieving this. I am familiar with many other programming languages, but SQL is still a challenge with difficult/specific queries.
Do a self join:
select t1.var,
case when t1.var = 'C' then t1.value
else t1.value - t2.value
end as value,
t1.id
from tablename t1
join tablename t2 ON t1.id = t2.id
where t2.var = 'C'
Note that value is a reserved word in ANSI SQL, so it should be delimited as "Value".
You could pre-analyse the "C" Values and then use this to remove them?
DECLARE #Data TABLE (
[Var] VARCHAR(1),
Value INT,
ID INT);
INSERT INTO #Data SELECT 'A', 1, 1;
INSERT INTO #Data SELECT 'B', 2, 1;
INSERT INTO #Data SELECT 'C', 3, 1;
INSERT INTO #Data SELECT 'A', 2, 2;
INSERT INTO #Data SELECT 'B', 4, 2;
INSERT INTO #Data SELECT 'C', 6, 2;
WITH CValues AS (
SELECT
ID,
Value
FROM
#Data
WHERE
[Var] = 'C')
SELECT
d.[Var],
CASE WHEN d.[Var] != 'C' THEN d.Value - c.Value ELSE d.Value END AS Value,
d.ID
FROM
#Data d
LEFT JOIN CValues c ON c.ID = d.ID;
...but yes, a self-join is probably a better solution:
DECLARE #Data TABLE (
[Var] VARCHAR(1),
Value INT,
ID INT);
INSERT INTO #Data SELECT 'A', 1, 1;
INSERT INTO #Data SELECT 'B', 2, 1;
INSERT INTO #Data SELECT 'C', 3, 1;
INSERT INTO #Data SELECT 'A', 2, 2;
INSERT INTO #Data SELECT 'B', 4, 2;
INSERT INTO #Data SELECT 'C', 6, 2;
SELECT
d.[Var],
CASE WHEN d.[Var] != 'C' THEN d.Value - c.Value ELSE d.Value END AS Value,
d.ID
FROM
#Data d
LEFT JOIN #Data c ON c.[Var] = 'C' AND c.ID = d.ID;

Oracle SQL -- Combining two tables, but taking duplicates from one?

I have these tables:
Table A
Num Letter
1 A
2 B
3 C
Table B
Num Letter
2 C
3 D
4 E
I want to union these two tables, but I only want each number to appear once. If the same number appears in both tables, I want it from Table B instead of table A.
Result
Num Letter
1 A
2 C
3 D
4 E
How could I accomplish this? A union will keep duplicates and an intersect would only catch the same rows -- I consider a row a duplicate when it has the same number, regardless of the letter.
Try this: http://www.sqlfiddle.com/#!4/0b796/1
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select
Num
,case when count(*) > 1 then
min(case when src = 'B' then Letter end)
else
min(Letter)
end as Letter
from a
group by Num
order by Num;
Output:
| NUM | LETTER |
----------------
| 1 | A |
| 2 | C |
| 3 | D |
| 4 | E |
And another one:
SELECT COALESCE(b.num, a.num) num, COALESCE(b.letter, a.letter) letter
FROM a FULL JOIN b ON a.num = b.num
ORDER BY 1;
With your data:
WITH a AS
(SELECT 1 num, 'A' letter FROM dual
UNION ALL SELECT 2, 'B' FROM dual
UNION ALL SELECT 3, 'C' FROM dual),
b AS
(SELECT 2 num, 'C' letter FROM dual
UNION ALL SELECT 3, 'D' FROM dual
UNION ALL SELECT 4, 'E' FROM dual)
SELECT COALESCE(b.num, a.num) num, COALESCE(b.letter, a.letter) letter
FROM a FULL JOIN b ON a.num = b.num
ORDER BY 1;
NUM L
---------- -
1 A
2 C
3 D
4 E
The efficiency might be lacking, but it produces the correct answer.
select nums.num, coalesce(b.letter, a.letter)
from
(select num from b
union
select num from a) nums
left outer join b
on (b.num = nums.num)
left outer join a
on (a.num = nums.num);
Or you can use Oracle-specific technique to make the code shorter: http://www.sqlfiddle.com/#!4/0b796/11
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select Num, min(Letter) keep(dense_rank first order by src desc) as Letter
from a
group by Num
order by Num;
Output:
| NUM | LETTER |
----------------
| 1 | A |
| 2 | C |
| 3 | D |
| 4 | E |
The code works regardless of min(letter) or max(letter), it has the same output, it gives the same output. Important is you use keep dense_rank. Another important thing is, the order matter, we use order by src desc to give priority to source table B when keeping a row.
And to really make it shorter, use keep dense_rank last, and omit the desc on order by, asc is the default anyway http://www.sqlfiddle.com/#!4/0b796/12
with a as
(
select Num, 'A' as src, Letter
from tblA
union
select Num, 'B' as src, Letter
from tblB
)
select Num, min(Letter) keep(dense_rank last order by src) as Letter
from a
group by Num
order by Num;
Again, using min or max on Letter doesn't matter, as long as your keep dense_rank get the prioritized/preferred row
Another option is to combine the UNION and MINUS commands as follows:
SELECT
NUM, LETTER
FROM
TABLE B
UNION
( SELECT
NUM, LETTER
FROM
TABLE A
WHERE
NUM IN (SELECT
NUM
FROM
TABLE A
MINUS
SELECT
NUM
FROM
TABLE B ))
SELECT A.*
FROM A
WHERE A.NUM NOT IN
(SELECT A.NUM
FROM B
WHERE A.NUM=B.NUM
AND B.NUM IS NOT NULL
AND A.NUM IS NOT NULL
)
UNION
SELECT * FROM B;

Oracle advanced union

Is there any advanced Oracle SQL methods to solve this kind of situation?
Simplified:
Two queries returns primary_key_value and other_value.
Both queries always return primary_key_value but other_value might be null.
So how I can union those two queries so that it returns always those rows which has other_value, but if both queries are having other_value = null with same primary key, then only one row should be returned.
I know this is so stupid case. But specifications were like this :)
Example:
First query:
A | B
=======
1 | X
2 |
3 |
4 | Z
Second query:
A | B
=======
1 | Y
2 |
3 | Z
4 |
So result need to be like this:
A | B
=======
1 | X
1 | Y
2 |
3 | Z
4 | Z
You could use analytics:
SQL> WITH q1 AS (
2 SELECT 1 a, 'X' b FROM DUAL UNION ALL
3 SELECT 2 a, '' b FROM DUAL UNION ALL
4 SELECT 3 a, '' b FROM DUAL UNION ALL
5 SELECT 4 a, 'Z' b FROM DUAL
6 ), q2 AS (
7 SELECT 1 a, 'Y' b FROM DUAL UNION ALL
8 SELECT 2 a, '' b FROM DUAL UNION ALL
9 SELECT 3 a, 'Z' b FROM DUAL UNION ALL
10 SELECT 4 a, '' b FROM DUAL
11 )
12 SELECT a, b
13 FROM (SELECT a, b,
14 rank() over(PARTITION BY a
15 ORDER BY decode(b, NULL, 2, 1)) rnk
16 FROM (SELECT * FROM q1
17 UNION
18 SELECT * FROM q2))
19 WHERE rnk = 1;
A B
---------- -
1 X
1 Y
2
3 Z
4 Z
If you want use something really advanced, use model clause http://rwijk.blogspot.com/2007/10/sql-model-clause-tutorial-part-one.html
But, in real life, using such things usually means bad-designed data model
Another way to look at is that you want all possible values from the union of column A then left outer outer join these with the non-null values from column B, thus only showing null in B when there is no non-null value to display.
roughly:
WITH q1 as (whatever),
q2 as (whatever)
SELECT All_A.A, BVals.B
FROM (SELECT DISTINCT A FROM (SELECT A FROM q1 UNION SELECT A FROM q2)) All_A,
(SELECT A,B FROM q1 WHERE B IS NOT NULL
UNION
SELECT A,B FROM q2 WHERE B IS NOT NULL) BVals
WHERE All_A.A = BVals.A (+)
Also pruning the unwanted nulls explicitly could do the same job:
WITH q3 AS (q1_SELECT UNION q2_SELECT)
SELECT A,B
FROM q3 main
WHERE NOT ( B IS NULL AND
EXISTS (SELECT 1 FROM q3 x WHERE main.A = x.A and x.B IS NOT NULL) )