SQL query for joining four tables - mysql

I am a newbie to writing SQL queries, Can anyone please help me write SQL query for below conditions and joins?
I have 4 tables let's say T1, T2, T3 and T4
T1 have columns a1(PK), a2, a3, a4
T2 have columns b1(PK), a1(FK), b2
T3 have columns c1(PK), a1(FK)
T4 have columns d1(PK), c1(FK), d2, d3
Conditions :
I want to have all columns from T1 when a4 == "xx"
If T1.a1 == T2.a1 and T1.a2 == "x", then I want b2 to be included in the final result as new column.
If T3.c1 == T4.c1 and T4.d2 == "y", then take value from d3 which needs to be joined to step4
If T1.a1 == T3.a1 and T1.a2 == "z", then value from d3 (step 3) should be included in the final result's new column
I am trying to solve as below, but I dont know how to add value d3 from step 3 to b2 in the final result.
select T1.a1,T1.a2,T1.a3,T1.a4,T2.b2 from
T1
left join T2 on T1.a1 == T2.a1 AND T1.a2 == "x"
left join (
select T3.a1,T4.d3 from T3,T4
where T3.c1 == T4.c1 AND T4.d2 == "y")
) joined on joined.a1 == T1.a1 and T1.a2 == "z"
where a4 == "xx";
Sample data
T1 table :
a1 | a2 | a3 | a4 |
1 | x | cat| xx |
2 | aa | hat| la |
3 | z | mat| xx |
T2 table :
b1 | b2 | a1 |
11 | 984 | 1 |
22 | 234 | 2 |
T3 table :
c1 | a1 |
111 | 3 |
222 | 7 |
T4 table :
d1 | d2 | d3 | c1 |
1111 | y | 100 | 111 |
2222 | yy | 200 | 333 |
Expected Result :
a1 | a2 | a3 | a4 | new column
1 | x | cat | xx | 984 (from T2.b2)
3 | z | mat | xx | 100 (from T4.d3)
Please help me to correct my query. Appreciate your help.

Assuming you want no results when your conditions are not met, this query will give you the results you want. It uses a CASE expression to select the correct value for new column, using the value of a2 to decide whether to use b2 or d3 for that value. Rows which don't meet your conditions are excluded by the WHERE clause.
SELECT T1.*,
CASE WHEN T1.a2 = 'x' THEN T2.b2
WHEN T1.a2 = 'z' THEN T4.d3
END AS `new column`
FROM T1
LEFT JOIN T2 ON T2.a1 = T1.a1
LEFT JOIN T3 ON T3.a1 = T1.a1
LEFT JOIN T4 ON T4.c1 = T3.c1
WHERE a4 = 'xx' AND (a2 = 'x' OR a2 = 'z' AND d2 = 'y')
ORDER BY a1
Output:
a1 a2 a3 a4 new column
1 x cat xx 984
3 z mat xx 100
Demo on dbfiddle

You can write this as:
SELECT T1.*,
(CASE WHEN T1.a2 = 'x'
THEN (SELECT T2.b2
FROM T2
WHERE T2.a1 = T1.a1
)
ELSE (SELECT T4.d3
FROM T3 JOIN
T4
ON T4.c1 = T3.c1
WHERE T3.a1 = T1.a1 AND T4.d2 = 'y'
)
END) as new_column
FROM T1
WHERE T1.a4 = 'xx' AND T1.a2 IN ('x', 'z')
ORDER BY T1.a1;
This assumes that at most one row matches from each of the two possibilities.
If so, I find that the logic here better captures your intent.

Related

How to select all rows from group, until occurrence of a value

I'm trying to extract all rows from same Group until I hit breakpoint value B. The example data below is ordered virtual table:
+----+--------+------------+
| ID | Group | Breakpoint |
+----+--------+------------+
| 1 | 1 | A |
| 2 | 1 | A |
| 3 | 1 | B |
| 4 | 1 | A |
| 5 | 2 | A |
| 6 | 2 | A |
| 7 | 2 | A |
| 8 | 3 | A |
| 9 | 3 | B |
+----+--------+------------+
This would be my result.
+----+--------+------------+
| ID | Group | Breakpoint |
+----+--------+------------+
| 1 | 1 | A |
| 2 | 1 | A |
| 5 | 2 | A |
| 6 | 2 | A |
| 7 | 2 | A |
| 8 | 3 | A |
+----+--------+------------+
Notice that when there are both A and B breakpoint values within a group, I want to have the rows until the first A value in this order. If there are only A values for a group like in group 2, I want to have all of the items in the group.
Here's a simple solution that uses no subqueries or GROUP BY logic.
SELECT t1.ID, t1.Group, t1.Breakpoint
FROM MyTable AS t1
LEFT OUTER JOIN MyTable AS t2
ON t1.ID >= t2.ID AND t1.`Group` = t2.`Group` AND t2.Breakpoint = 'B'
WHERE t2.ID IS NULL
For each row t1, try to find another row t2 with 'B', in the same Group, with an earlier ID. If none is found, the OUTER JOIN guarantees that t2.ID is NULL. That will be true only up until the desired breakpoint.
From you example above, you are not really grouping the results. you just need to display the records where Breakpoint is A:
Select * From Table
Where Breakpint ='A'
You may use NOT EXISTS
select *
from your_table t1
where not exists (
select 1
from your_table t2
where t1.group = t2.group and t2.id <= t1.id and t2.breakpoint = 'B'
)
or ALL can work as well if you never have NULL in id
select *
from your_table t1
where t1.id < ALL(
select t2.id
from your_table t2
where t1.group = t2.group and t2.breakpoint = 'B'
)
Assuming that we are ordering by ID column, we could do something like this:
SELECT d.*
FROM mytable d
LEFT
JOIN ( SELECT bp.group
, MIN(bp.id) AS bp_id
FROM mytable bp
WHERE bp.breakpoint = 'B'
GROUP BY bp.group
) b
ON b.group = d.group
WHERE b.bp_id > d.id OR b.bp_id IS NULL
ORDER BY d.group, d.id
This takes into account cases where there is no breakpoint='B' row for a given group, and returns all of the rows for that group.
Note that the inline view b gets us the lowest id value from rows with breakpoint='B' for each group. We can outer join that to original table (matching on group), and then conditional tests in the WHERE clause to exclude rows that follow the first breakpoint='B' for each group.
SQL tables represent unordered sets. Hence, there is no "before" or "after" a particular row.
Let me assume that you have some column that specifies the ordering. I'll call it id. You can then do what you want with:
select t.*
from t
where t.id < (select min(t2.id) from t t2 where t2.group = t.group and t2.breakpoint = 'B');
To get all rows when if there are no 'B':
select t.*
from t
where t.id < (select coalesce(min(t2.id), t.id + 1) from t t2 where t2.group = t.group and t2.breakpoint = 'B');

select one table from two tables with same primary key but different value?

Table1 Table2
id | name id | name
----------- ------------
2 | A 2 | E
4 | B 3 | F
6 | C 4 | G
8 | D 5 | H
Output:
Id | name
------------
2 | A *** E
4 | B *** G
6 | C *** EMPTY
8 | D *** EMPTY
3 | EMPTY *** F
5 | EMPTY *** H
You didn't specify a DBMS, so this is ANSI SQL:
select coalesce(t1.id, t2.id) as id,
t1.name,
t2.name
from table1 t1
full outer join table2 t2 on t1.id = t2.id
where t1.name is distinct from t2.name;
select isnull(a.id, b.id), case when a.value is not null and b.value is not null then a.value + '***' + b.value
else coalesce(a.value,b.value) end from table_1 a full outer join table_2 b on a.id = b.id

Why the sum value from mysql_fetch was double up?

I have an 2 database.
1 is game_jnship_user, 2 is game_jnship_equip and the data as below:
Here is game_jnship_user
+-----+-----------------+
| ID | uid |
+-----+-----------------+
| 1 | 50 |
+-----------------------+
Here is game_jnship_equip
+-----+------------+---------------+--------------+
| ID | uid | ebonus | etype |
+-----+------------+---------------+--------------+
| 1 | 50 | 100 | 1 |
| 2 | 50 | 10 | 1 |
| 3 | 50 | 120 | 2 |
+-------------------------------------------------+
And I query as below:
$_G['uid'] = '50';
$ushuxing = DB::query("SELECT sum(t2.ebonus) AS equipatk, sum(t3.ebonus) AS equipdef FROM ".DB::table('game_jnship_user')." t1 LEFT JOIN ".DB::table('game_jnship_equip')." t2 ON (t1.uid = t2.uid AND t2.etype = '1') LEFT JOIN ".DB::table('game_jnship_equip')." t3 ON (t1.uid = t3.uid AND t3.etype = '2') WHERE t1.uid = $_G[uid]");
$rushuxing = DB::fetch($ushuxing);
After that, my output data as below:
$rushuxing[equipatk] = 110;
$rushuxing[equipdef] = 240;
Why the $rushuxing[equipdef] output is 240 not 120?
Thank you.
Here is the raw MySQL query from the example above:
SELECT sum(t2.ebonus) AS equipatk, sum(t3.ebonus) AS equipdef
FROM game_jnship_user t1
LEFT JOIN game_jnship_equip t2
ON (t1.uid = t2.uid AND t2.etype = '1')
LEFT JOIN game_jnship_equip t3
ON (t1.uid = t3.uid AND t3.etype = '2')
WHERE t1.uid = '50'
The record having etype = 2 is matched with 2 records having etype = 1 in the LEFT JOIN operation, so you get 240 instead of 120.
You can use instead the following query that makes use of conditional aggregation:
SELECT SUM(CASE WHEN etype = 1 THEN ebonus ELSE 0 END) AS equipatk,
SUM(CASE WHEN etype = 2 THEN ebonus ELSE 0 END) AS equipdef
FROM game_jnship_equip
WHERE uid = 50
GROUP BY uid clause is not required since you are performing aggregation for a specific uid value.

Find the minimum discerning subset in MySQL

I have a t table, with let's say field1 and field2. field1 is an identifier of sets and field2 contains the member of the sets. Now, the question is, how to find out the smallest amount of field2 values that will uniquely find a given field1?
Sample data
+------+------+
|field1|field2|
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | A |
| 2 | C |
| 2 | D |
| 3 | B |
| 3 | D |
+------+------+
I am looking for a query which given 1 as an argument returns (A,B), for 2 returns (A,D) or (C,D) both are good and for 3 returns (B,D). If the generic case is too hard then let's ask: is there such a pair that does this.
Once I have such a pair I can plug it into this query:
SELECT DISTINCT field1
FROM t t1
INNER JOIN t t2 USING(field1)
WHERE t1.field2 = 'A' AND t2.field2 = 'B'
and get only a single row.
I have tried something like SELECT field2 FROM t WHERE field2 NOT IN (SELECT field2 FROM t WHERE field1 != 1) and field1 = 1 but this obviously doesn't work.
You write a subquery that returns all pairs of field2 values for each field1. Then use a left join of this with itself, finding pairs that have the same field2 values, but different field1. These then get excluded with the NULL check, and you're left with the unique pairs for each field1.
SELECT subq1.field1, subq1.a, subq1.b
FROM (
SELECT t1.field1, t1.field2 AS a, t2.field2 AS b
FROM t AS t1
JOIN t AS t2 ON t1.field1 = t2.field1 AND t1.field2 < t2.field2
) as subq1
LEFT JOIN (
SELECT t1.field1, t1.field2 AS a, t2.field2 AS b
FROM t AS t1
JOIN t AS t2 ON t1.field1 = t2.field1 AND t1.field2 < t2.field2
) AS subq2 ON subq1.field1 != subq2.field1 AND subq1.a = subq2.a AND subq1.b = subq2.b
WHERE subq2.field1 IS NULL
DEMO
I have written below query
SELECT temp. field1,temp.firstValue,temp.secondValue
FROM (SELECT table1.field1 , table1.field2 firstValue ,table2.field2 secondValue FROM T table2 ,T table1
WHERE table1.field1 = table2.field1 AND table2.field2<> table1.field2 AND table1.field2 < table2.field2
) temp
GROUP BY temp. field1,temp.firstValue
It will give you below pairs
field1 firstValue secondValue
------ ---------- -----------
1 A B
1 B C
2 A C
2 C D
3 B D
Please let me know if for input 2 pair (A,C) and (A,D) would be sufficient

mysql selecting a union where values in one don't appear in the other

sorry for the poorly titled post.
Say I have the following table:
C1 | C2 | c3
1 | foo | x
2 | bar | y
2 | blaz | z
3 | something| y
3 | hello | z
3 | doctor | x
4 | name | y
5 | continue | x
5 | yesterday| z
6 | tomorrow | y
I'm trying to come up w/ a sql statement which performs the following union:
1st retrieval retrieves all records w/ c3 = 'y'
2nd retrieval retrieves the first instance of a record where c3 <> 'y' and the result is not in the previous union
So, for the result, I should see:
C1 | C2
1 | foo
2 | bar
3 | something
4 | name
5 | continue
6 | tomorrow
So two questions: 1: Am I totally smoking crack where I think I can do this, and 2: (assuming I can), how do I do this?
Try this one:
SELECT a.C1, a.C2
FROM MyTable a
WHERE a.C3 = 'y'
UNION
SELECT b.C1, b.C2
FROM MyTable b
WHERE b.C3 <> 'y' AND
b.C1 not in
(
SELECT c.C1
FROM MyTable c
WHERE c.C3 = 'y'
)
UPDATE 1
by the way, why is that there is only one record of 5 in your desired result? where, in fact, there could be two.
SEE FOR DEMO 1
OR
SELECT g.C1, MIN(g.C2) C2
FROM
(SELECT a.C1, a.C2
FROM MyTable a
WHERE a.C3 = 'y'
UNION
SELECT b.C1, b.C2
FROM MyTable b
WHERE b.C3 <> 'y' AND
b.C1 not in
(
SELECT c.C1
FROM MyTable c
WHERE c.C3 = 'y'
)
) g
GROUP BY g.C1
SEE FOR DEMO 2 (yields same result with your desired result)
DEMO # Sql Fiddle.
select *
from table1
where c3 = 'y'
union all
(select table1.*
from table1
left join table1 t1
on table1.c1 = t1.c1
and t1.c3 = 'y'
where table1.c3 <> 'y'
and t1.c1 is null
-- The meaning of first becomes clear here
order by table1.c3, table1.c2
limit 1)
Note: foo is not in a list because it is marked as x.
Try this:
SELECT C1, C2
FROM Table1
Where C3 = 'y'
UNION
(
SELECT C1, C2
FROM Table1
Where C3 <> 'y' ORDER BY C1 LIMIT 1
)
ORDER BY C1