Comparing rows from two tables and and ouputting the result - sql

Hello guys I've been trying to compare two similar tables that have some different columns
Table 1 has columns ID_A, X, Y, Z and
Table 2 has columns ID_B, X, Y, Z
If both values from columns X or Y or Z are = 1
the result of the query would output columns
ID_A, ID_B, X, Y, Z
I thought it would be an intersect statement in there, but I'm having problems because the name of the columns and the values from ID_A and ID_B are completely different.
What would this SQL statement look like? I'd appreciate any ideas, been banging my head on the wall for this one.

To output rows that are in both tables, an inner join would work:
select *
from table1 a
inner join table2 b
on a.x = b.x and a.y = b.y and a.z = b.z
To list only rows with x=1, y=1, or z=1 in both tables, add a where clause like;
where a.x = 1 or a.y = 1 or a.z = 1

Related

what is the right way to reduce multiple update statement in oracle

I have 2 tables out of which one column/field is same in both tables, I need to update the Table A with data from table B.
Here table A.x value needs to be taken and compared with B.w and equivalant B.z value needs to be updated in A.x. x value differs from x1,x2 etc.. so each value needs to be taken and compared with w in Table B and equivalent z value need to be updated in x,x1,x2 etc in Table A.
Table A (columns j, x, x1,x2,x3..x20 and so on)
---------
j x x1 x2 ..x20 and y y1 y2 .. y20
Table B (columns w and z)
--------
w z
UPDATE TableA a SET a.x = (SELECT b.w
FROM TableB b
WHERE a.x = b.z)
WHERE a.j='somevalue';
If I write this way I need to write 40 update statement, is there any easy way to do these updates.
And the subquery might return multiple rows and I need to refine that too.
Thanks,
Ashraf
As a.x has another value as a.x1 and a.x2, etc. you need a subquery per value of course, because it is different b records you want to find.
UPDATE TableA a
SET a.x = (SELECT b.w FROM TableB b WHERE a.x = b.z)
, a.x1 = (SELECT b.w FROM TableB b WHERE a.x1 = b.z)
, a.x2 = (SELECT b.w FROM TableB b WHERE a.x2 = b.z)
...
WHERE a.j = 'somevalue';
You might want to consider changing your table design for tableA, so as to have rows holding the values rather than columns. That would make updates (and queries in general for that matter) much easier.

one to one distinct restriction on selection

I encountered a problem like that. There are two tables (x value is ordered so that
in a incremental trend !)
Table A
id x
1 1
1 3
1 4
1 7
Table B
id x
1 2
1 5
I want to join these two tables:
1) on the condition of the equality of id and
2) each row of A should be matched only to one row of B, vice verse (one to one relationship) based on the absolute difference of x value (small difference row has
more priority to match).
Only based on the description above it is not a clear description because if two pairs of row which share a common row in one of the table have the same difference, there is no way to decide which one goes first. So define A as "Main" table, the row in table A with smaller line number always go first
Expected result of demo:
id A.x B.x abs_diff
1 1 2 1
1 4 5 1
End of table(two extra rows in A shouldn't be considered, because one to one rule)
I am using PostgreSQL so the thing I have tried is DISTINCT ON, but it can not solve.
select distinct on (A.x) id,A.x,B.x,abs_diff
from
(A join B
on A.id=B.id)
order by A.x,greatest(A.x,B.x)-least(A.x,B.x)
Do you have any ideas, it seems to be tricky in plain SQL.
Try:
select a.id, a.x as ax, b.x as bx, x.min_abs_diff
from table_a a
join table_b b
on a.id = b.id
join (select a.id, min(abs(a.x - b.x)) as min_abs_diff
from table_a a
join table_b b
on a.id = b.id
group by a.id) x
on x.id = a.id
and abs(a.x - b.x) = x.min_abs_diff
fiddle: http://sqlfiddle.com/#!15/ab5ae/5/0
Although it doesn't match your expected output, I think the output is correct based on what you described, as you can see each pair has a difference with an absolute value of 1.
Edit - Try the following, based on order of a to b:
select *
from (select a.id,
a.x as ax,
b.x as bx,
x.min_abs_diff,
row_number() over(partition by a.id, b.x order by a.id, a.x) as rn
from table_a a
join table_b b
on a.id = b.id
join (select a.id, min(abs(a.x - b.x)) as min_abs_diff
from table_a a
join table_b b
on a.id = b.id
group by a.id) x
on x.id = a.id
and abs(a.x - b.x) = x.min_abs_diff) x
where x.rn = 1
Fiddle: http://sqlfiddle.com/#!15/ab5ae/19/0
One possible solution for your currently ambiguous question:
SELECT *
FROM (
SELECT id, x AS a, lead(x) OVER (PARTITION BY grp ORDER BY x) AS b
FROM (
SELECT *, count(tbl) OVER (PARTITION BY id ORDER BY x) AS grp
FROM (
SELECT TRUE AS tbl, * FROM table_a
UNION ALL
SELECT NULL, * FROM table_b
) x
) y
) z
WHERE b IS NOT NULL
ORDER BY 1,2,3;
This way, every a.x is assigned the next bigger (or same) b.x, unless there is another a.x that is still smaller than the next b.x (or the same).
Produces the requested result for the demo case. Not sure about various ambiguous cases.
SQL Fiddle.

SQL Case With Many Columns

Hi I have looked through the "Case with multiple columns" questions and don't see something the same as this so I think I should ask.
Basically I have two tables (both are the result of a subquery) which I want to join. They have the same column names. If I join them on their ids and SELECT * I get each row being something like this:
A.id, A.x, A.y, A.z, A.num, B.id, B.x, B.y, B.z, B.num
What I want is a way to only select the columns of the table with the lower value of num. So in this case the result table would always have 5 columns, id, x, y, z, num, and I don't care which table id, x, y, z, num came from after the fact. Also either table result is fine if they are equal.
SELECT CASE WHEN A.num < B.num THEN A.* ELSE B.* END FROM A JOIN B ON A.id=B.id
would be perfect but you can only return one column in a CASE statement, and I could use a CASE for every column but that seems so wasteful (there are 8 in each table in my actual database so I would have 8 CASE statements).
This is SQLite btw. Any help would be appreciated!
Edit for more info on A and B:
A and B come from Queries like this
SELECT "thought case statement might go here" FROM
(SELECT id, x, y, z, num FROM Table1 a JOIN Table2 b ON a.id=b.id AND (y BETWEEN (53348574-3593) AND (53348574+3593)) AND (z BETWEEN (-6259973-6027) AND (-6259973+6027)) JOIN Table3 c ON c.id= b.id GROUP BY a.id, c.r) A
JOIN
(SELECT id, x, y, z, num FROM Table1 a JOIN Table2 b ON a.id=b.id AND (y BETWEEN (53401007-3593) AND (53401007+3593)) AND (z BETWEEN (-6397286-6027) AND (-6397286+6027)) JOIN Table3 c ON c.id= b.id GROUP BY a.id, c.r ) B ON A.id=B.id
So it joins two tables, made based on geolocation if you're wondering why the big numbers, and needs to decide which of the tables to take its data from based on attributes of what it finds in either of the locations.
try
select A.id, A.x, A.y, A.z, A.num from A JOIN B ON A.id=B.id where a.num<b.num
union
select b.id, b.x, b.y, b.z, b.num from A JOIN B ON A.id=B.id where b.num<a.numhere
I don't know of any RDBMS supporting what you want. You will have to write 8 CASE statements. But why is it so wasteful? Or are you just lazy? :)
Edit:
See, when you write SELECT * ... what your RDBMS is doing is to query system tables (information_schema and so on) and get the list of columns in the table.
So when you write
SELECT CASE WHEN A.num < B.num THEN A.* ELSE B.* END ...
you basically write
SELECT CASE WHEN A.num < B.num THEN A.num, A.whatever, A.more, ... ELSE B.num, B.whatever, B.more... END FROM A JOIN B ON A.id=B.id
and this is unfortunately wrong syntax.

Is it possible to get multiple values from a subquery?

Is there any way to have a subquery return multiple columns in oracle db? (I know this specific sql will result in an error, but it sums up what I want pretty well)
select
a.x,
( select b.y, b.z from b where b.v = a.v),
from a
I want a result like this:
a.x | b.y | b.z
---------------
1 | 2 | 3
I know it is possible to solve this problem through joins, but that is not what I am asking for.
My Question is simply if there is any way, to get two or more values out of a subquery? Maybe some workaround using dual? So that there is NO actual join, but a new subquery for each row?
EDIT: This is a principle question. You can solve all these problems using join, I know. You do not need subqueries like this at all (not even for one column). But they are there. So can I use them in that way or is it simply impossible?
A Subquery in the Select clause, as in your case, is also known as a Scalar Subquery, which means that it's a form of expression. Meaning that it can only return one value.
I'm afraid you can't return multiple columns from a single Scalar Subquery, no.
Here's more about Oracle Scalar Subqueries:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/expressions010.htm#i1033549
It's incorrect, but you can try this instead:
select
a.x,
( select b.y from b where b.v = a.v) as by,
( select b.z from b where b.v = a.v) as bz
from a
you can also use subquery in join
select
a.x,
b.y,
b.z
from a
left join (select y,z from b where ... ) b on b.v = a.v
or
select
a.x,
b.y,
b.z
from a
left join b on b.v = a.v
Here are two methods to get more than 1 column in a scalar subquery (or inline subquery) and querying the lookup table only once. This is a bit convoluted but can be the very efficient in some special cases.
You can use concatenation to get several columns at once:
SELECT x,
regexp_substr(yz, '[^^]+', 1, 1) y,
regexp_substr(yz, '[^^]+', 1, 2) z
FROM (SELECT a.x,
(SELECT b.y || '^' || b.z yz
FROM b
WHERE b.v = a.v)
yz
FROM a)
You would need to make sure that no column in the list contain the separator character.
You could also use SQL objects:
CREATE OR REPLACE TYPE b_obj AS OBJECT (y number, z number);
SELECT x,
v.yz.y y,
v.yz.z z
FROM (SELECT a.x,
(SELECT b_obj(y, z) yz
FROM b
WHERE b.v = a.v)
yz
FROM a) v
Can't you use JOIN like this one?
SELECT
a.x , b.y, b.z
FROM a
LEFT OUTER JOIN b ON b.v = a.v
(I don't know Oracle Syntax. So I wrote SQL syntax)
you can use cross apply:
select
a.x,
bb.y,
bb.z
from
a
cross apply
( select b.y, b.z
from b
where b.v = a.v
) bb
If there will be no row from b to mach row from a then cross apply wont return row. If you need such a rows then use outer apply
If you need to find only one specific row for each of row from a, try:
cross apply
( select top 1 b.y, b.z
from b
where b.v = a.v
order by b.order
) bb
In Oracle query
select a.x
,(select b.y || ',' || b.z
from b
where b.v = a.v
and rownum = 1) as multple_columns
from a
can be transformed to:
select a.x, b1.y, b1.z
from a, b b1
where b1.rowid = (
select b.rowid
from b
where b.v = a.v
and rownum = 1
)
Is useful when we want to prevent duplication for table A.
Similarly, we can increase the number of tables:
.... where (b1.rowid,c1.rowid) = (select b.rowid,c.rowid ....
View this web:
http://www.w3resource.com/sql/subqueries/multiplee-row-column-subqueries.php
Use example
select ord_num, agent_code, ord_date, ord_amount
from orders
where(agent_code, ord_amount) IN
(SELECT agent_code, MIN(ord_amount)
FROM orders
GROUP BY agent_code);

Locally symmetric difference in sql

I have a problem similar to this StackOverflow question, except that I need to exclude certain fields from the comparison but still include it in the result set.
I'm penning the problem as locally symmetric difference.
For example Table A and B have columns X,Y,Z and I want to compare only Y,Z for differences but I still want the result set to include X.
Sounds like this is basically what you want. Match rows between two tables on columns Y and Z, find the unmatched rows, and output the values of columns X, Y, and Z.
SELECT a.x, a.y, a.z, b.x, b.y, b.z
FROM a FULL OUTER JOIN b ON a.y = b.y AND a.z = b.z
WHERE a.y IS NULL OR b.y IS NULL
Old style SQL for a full join - A concatenated with B, excluding rows in B also in A (the middle):
-- all rows in A with or without matching B
select a.x, a.y, a.z
from a
left join b
on a.x = b.x
and a.y = b.y
union all
-- all rows in B with no match in A to "exclude the middle"
select b.x, b.y, null as z
from b
where not exists (select null
from a
where b.x = a.x
and b.y = a.y)
ANSI Style:
select coalesce(a.x, b.x) as x,
coalesce(a.y, b.y) as y,
a.z
from a
full outer join b
on a.x = b.x
and a.y = b.y
The coalesce's are there for safety; I've never actually had cause to write a full outer join in the real world.
If what you really want to find out if two table are identical, here's how:
SELECT COUNT(*)
FROM (SELECT list_of_columns
FROM one_of_the_tables
MINUS
SELECT list_of_columns
FROM the_other_table
UNION ALL
SELECT list_of_columns
FROM the_other_table
MINUS
SELECT list_of_columns
FROM one_of_the_tables)
If that returns a non-zero result, then there is a difference. It doesn't tell you which table it's in, but it's a start.