SQL conditional join with condition priorities like in CASE statement - sql

How to join conditionally on (1) full key OR (2) part of the key, if first condition is not met. Suppose id consists of 7 characters.
select
a.id,
coalesce (t.somevalue, t2.somevalue)
from a
left join t
on a.id=t.id
left join t as t2
on left(a.id,6)=left(t2.id,6)
Is the above join possible in one shot? Something like this but maintaining priority of full key join over part key join?
left join t
on
a.id=t.id OR left(a.id,6)=left(t2.id,6)
I wonder if there is something like
left join t
on
case when condition 1
case when condition 2
Here is sample data and expected results.
+---------+
| a.id |
+---------+
| aaaaa0A |
| aaaaa0B |
| aaaaa1A |
| aaaaa2A |
| aaaaa3B |
+---------+
+---------+
| t.id |
+---------+
| aaaaa0C |
| aaaaa1B |
| aaaaa1A |
| aaaaa2B |
| aaaaa3A |
+---------+
+---------+---------+--------------+
| a.id | t.id | condition no |
+---------+---------+--------------+
| aaaaa0A | aaaaa0C | 2 |
| aaaaa0B | aaaaa0C | 2 |
| aaaaa1A | aaaaa1A | 1 |
| aaaaa2A | aaaaa2B | 2 |
| aaaaa3B | aaaaa3A | 2 |
+---------+---------+--------------+

I think you want outer apply:
select a.id, t.somevalue
from a outer apply
(select top (1) t.*
from t
where left(a.id, 6) = left(t.id, 6)
order by (case when a.id = t.id then 1 else 2 end) -- put full matches first
) t;

You can do a 2nd LEFT join of t under the condition that the 1st join does not match:
select
a.id,
coalesce(t1.id, t2.id) id,
case
when t1.id is not null then 1
when t2.id is not null then 2
else 0
end [condition no]
from a
left join t as t1 on a.id = t1.id
left join t as t2 on t1.id is null and left(a.id, 6) = left(t2.id, 6)
See the demo.
Results:
> id | id | condition no
> :------ | :------ | -----------:
> aaaaa0A | aaaaa0C | 2
> aaaaa0B | aaaaa0C | 2
> aaaaa1A | aaaaa1A | 1
> aaaaa2A | aaaaa2B | 2
> aaaaa3B | aaaaa3A | 2

Related

Take the row after the specific row

I have the table, where I need to take the next row after the row which has course 'TA' and flag = 1. For this I created the column rnum (OVER DATE) which may help for finding it
| student | date | course | flag | rnum |
| ------- | ----- | ----------- | ---- | ---- |
| 1 | 17:00 | Math | null | 1 |
| 1 | 17:10 | Python | null | 2 |
| 1 | 17:15 | TA | 1 | 3 |
| 1 | 17:20 | English | null | 4 |
| 1 | 17:35 | Geography | null | 5 |
| 2 | 16:10 | English | null | 1 |
| 2 | 16:20 | TA | 1 | 2 |
| 2 | 16:30 | SQL | null | 3 |
| 2 | 16:40 | Python | null | 4 |
| 3 | 19:05 | English | null | 1 |
| 3 | 19:20 | Literachure | null | 2 |
| 3 | 19:30 | TA | null | 3 |
| 3 | 19:40 | Python | null | 4 |
| 3 | 19:50 | Python | null | 5 |
As a result I should have:
| student | date | course | flag | rnum |
| ------- | ----- | ------- | ---- | ---- |
| 1 | 17:20 | English | null | 4 |
| 2 | 16:30 | SQL | null | 3 |
There are many ways to get your desired result, let's see some of them.
1) EXISTS
You can use the EXISTS clause, specifying a subquery to match for the condition.
SELECT T2.*
FROM #MyTable T2
WHERE EXISTS (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
)
2) LAG
You ca use window function LAG to access previous row for a given order and then filter your resultset with your conditions.
SELECT w.student, w.date, w.course, w.flag, w.rnum
FROM (
SELECT T1.*
, LAG(course, 1) OVER (PARTITION BY student ORDER BY rnum) prevCourse
, LAG(flag, 1) OVER (PARTITION BY student ORDER BY rnum) prevFlag
FROM #MyTable T1
) w
WHERE prevCourse = 'TA' AND prevFlag = 1
3) JOIN
You can self-JOIN your table on the next rnum and keep only the rows who match the right condition.
SELECT T2.*
FROM MyTable T1
JOIN MyTable T2 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
WHERE T1.course = 'TA' AND T1.flag = 1
4) CROSS APPLY
You can use CROSS APPLY to specify a subquery with the matching condition. It is pretty similar to EXISTS clause, but you will also get in your resultset the columns from the subquery.
SELECT T2.*
FROM #MyTable T2
CROSS APPLY (
SELECT 'x' x
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
AND T1.student = T2.student AND T2.rnum = T1.rnum + 1
) x
5) CTE
You can use common table expression (CTE) to extract matching rows and then use it to filter your table with a JOIN.
;WITH
T1 AS (
SELECT student, rnum
FROM #MyTable T1
WHERE T1.course = 'TA' AND T1.flag = 1
)
SELECT T2.*
FROM #MyTable T2
JOIN T1 ON T1.student = T2.student AND T2.rnum = T1.rnum + 1
Adding the rownumber was a good start, you can use it to join the table with itself:
WITH matches AS (
SELECT
student,
rnum
FROM table
WHERE flag = 1
AND course = 'TA'
)
SELECT t.*
FROM table t
JOIN matches m
on t.student = m.student
and t.rnum = m.rnum + 1

Can't show all records with the same id while join in oracle xe 11g

I'm getting this message while using this query, is there anything wrong?
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t, transaksi_detail td, operator o
WHERE td.transaksi_id = t.transaksi_id AND o.operator_id = t.operator_id
GROUP BY t.transaksi_id
Updated :
After using the answer from #Barbaros Özhan using this query :
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
INNER JOIN operator o ON ( o.operator_id = t.operator_id )
GROUP BY t.tanggal_transaksi, o.nama_lengkap;
the data is successfully displayed. but, there are few problems that occur, the value of the same operator_id cannot appear more than 1 time. Here is the sample data :
+--------------+-------------+-------------------+
| TRANSAKSI_ID | OPERATOR_ID | TANGGAL_TRANSAKSI |
+--------------+-------------+-------------------+
| 1 | 5 | 09/29/2018 |
| 2 | 3 | 09/29/2018 |
| 3 | 3 | 09/29/2018 |
| 4 | 1 | 09/29/2018 |
| 5 | 1 | 09/29/2018 |
+--------------+-------------+-------------------+
After use the query command, the output is :
+-------------------+------------------+--------+
| TANGGAL_TRANSAKSI | NAMA_LENGKAP | TOTAL |
+-------------------+------------------+--------+
| 09/29/2018 | Lina Harun | 419800 |
| 09/29/2018 | Titro Kusumo | 484000 |
| 09/29/2018 | Muhammad Kusnadi | 402000 |
+-------------------+------------------+--------+
When viewed from the operator table, there are 2 data with the same operator_id that is unreadable
+-------------+------------------+
| OPERATOR_ID | NAMA_LENGKAP |
+-------------+------------------+
| 1 | Muhammad Kusnadi |
| 3 | Lina Harun |
| 5 | Tirto Kusumo |
+-------------+------------------+
You need to include the columns in the SELECT-list t.tanggal_transaksi, o.nama_lengkap, also in the GROUP BY-list but not the others like t.transaksi_id. So, you might use the following without any issue :
SELECT t.tanggal_transaksi, o.nama_lengkap, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
INNER JOIN operator o ON ( o.operator_id = t.operator_id )
GROUP BY t.tanggal_transaksi, o.nama_lengkap;
Or this one :
SELECT t.transaksi_id, SUM(td.harga * td.qty) total
FROM transaksi t
INNER JOIN transaksi_detail td ON ( td.transaksi_id = t.transaksi_id )
GROUP BY t.transaksi_id;
P.S. Prefer using ANSI-92 JOIN standard rather than old-style comma-type JOIN.

SQL JOIN two table & show all rows for table A

I have a question about JOIN.
TABLE A | TABLE B |
-----------------------------------------|
PK | div | PK | div | val |
-----------------------------------------|
A | a | 1 | a | 10 |
B | b | 2 | a | 100 |
C | c | 3 | c | 9 |
------------------| 4 | c | 99 |
-----------------------
There are two tables something like above, and I have been trying to join two tables but I want to see all rows from TABLE A.
Something like
SELECT T1.PK, T1.div, T2.val
FROM A T1
LEFT OUTER JOIN B T2
ON T1.div = T2.div
and I want the result would look like this below.
PK | div | val |
-------------------------
A | a | 10 |
A | a | 100 |
B | null | null |
C | c | 9 |
C | c | 99 |
I have tried all JOINs I know but B doesn't appear because it doesn't exist. Is it possible to show all rows on TABLE A and just show null if it doesn't exists on TABLE B?
Thanks in advance!
If you change your query to
SELECT T1.PK, T2.div, T2.val
FROM A T1
LEFT OUTER JOIN B T2
ON T1.div = T2.div
(Note, that div comes from T2 here.), you'll get exactly the result posted (but maybe in a different order, add an ORDER BY clause if you want a specific order).
Your query as it stands will get you:
PK | div | val |
-------------------------
A | a | 10 |
A | a | 100 |
B | b | null |
C | c | 9 |
C | c | 99 |
(Note, that div is b for the row with the PK of B, not null.)
To get to your resultset, all you need to do is use T2.Div as that is the value that does not exist in the second table:
SELECT T1.PK, T2.div, T2.val
FROM A T1
LEFT OUTER JOIN B T2
ON T1.div = T2.div

Getting the last updated name

I am having a table having records like this:
+------+------+
| ID | name |
+------+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | A |
| 7 | A |
| 8 | A |
+------+------+
I need to get value of A after it was last updated from a different value, for example here it would be the row at ID 6.
Try this query (MySQL syntax):
select min(ID)
from records
where name = 'A'
and ID >=
(
select max(ID)
from records
where name <> 'A'
);
Illustration:
select * from records;
+------+------+
| ID | name |
+------+------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | A |
| 5 | B |
| 6 | A |
| 7 | A |
| 8 | A |
+------+------+
-- run query:
+---------+
| min(ID) |
+---------+
| 6 |
+---------+
Using the Lag function...
SELECT Max([ID])
FROM (SELECT [name], [ID],
Lag([name]) OVER (ORDER BY [ID]) AS PrvVal
FROM tablename) tbl
WHERE [name] = 'A'
AND prvval <> 'A'
Online Demo: http://www.sqlfiddle.com/#!18/a55eb/2/0
If you want to get the whole row, you can do this...
SELECT Top 1 *
FROM (SELECT [name], [ID],
Lag([name]) OVER (ORDER BY [ID]) AS PrvVal
FROM tablename) tbl
WHERE [name] = 'A' AND prvval <> 'A'
ORDER BY [ID] DESC
Online Demo: http://www.sqlfiddle.com/#!18/a55eb/22/0
The ANSI SQL below uses a self-join on the previous id.
And the where-clause gets those with a name that's different from the previous.
select max(t1.ID) as ID
from YourTable as t1
left join YourTable as t2 on t1.ID = t2.ID+1
where (t1.name <> t2.name or t2.name is null)
and t1.name = 'A';
It should work on most RDBMS, including MS Sql Server.
Note that with the ID+1 that there's an assumption that are no gaps between the ID's.

How to mass-check for duplicates in MS-Access and log changes?

I want to mass-compare a few hundred fields in MS Access between 2 tables with identical column structures. If there are any differences between the column values, replaces the row in table1 with the new row in table2. If table2 does no longer holds a row that exists in table1, that row should be dropped from table1. All changes to table1 should be logged in tableLOGS.
Take for example:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 0 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 0 | 0 |
After running the desired SQL query, the result should be:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 1 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 2 | 0 | 1 | edit |
| 4 | 3 | 0 | 0 | delete |
I expect this would have to be broken down into 2 separate queries?
Mass-compare rows and update changes Log the changes to tableLOGS This seems like a fairly common task so perhaps MS Access has an easy way of accomplishing this? Thanks for all the help! :)
P.S. I am also open to simply deleting rows from table1 that do not match table2, and INSERT INTO table1 from table2.
This is from memory I didn't actually run this so it may need some fixing. My apologies
Start with the logging for updates
INSERT INTO TableLogs
SELECT A, B
FROM (
SELECT A, B
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL)
Then update table1 with the updated values
UPDATE table1
INNER JOIN(
SELECT *
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL) t2
ON table1.pid = t2.pid
Log missing records in table 1
INSERT INTO tablelogs
SELECT A, B
FROM table2
INNER JOIN table1 t1 ON t1.pid = t2.pid AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL
Delete the missing rows from table1
DELETE table1
WHERE pid NOT IN
(SELECT pid FROM Table2)
You could also put a trigger on table1 to update table logs but its not really best practice.
Hope that helps, like I said I didn't run it yet.