How to JOIN these 2 tables together (MySQL, Hierarchical query)? - sql

I have a categories table that looks like this:
id | name | parent
-----------------------
1 | Toys | 1
2 | Clothing | 1
3 | Kid's Toys | 0
I have another table called category_relationships which looks like this:
id | category_id | parent_id
----------------------------
1 | 3 | 1
I want to have the following output:
Categories:
Toys
- Kid's Toys
Clothing
How to achieve this with one query?

A better/proper/robust answer will probably be create a MySQL PROCEDURE for this, but if your data can fit in these limitations, you can use the below:
no more than 5 levels (or expand the pattern as required)
IDs are no more than 6 digits (or change the concat expressions)
This query uses Concat to build a sortable reference so that children of A come after A etc. The names are indented manually using concat and leading spaces.
select concat(1000000 + a.id, '|') SORT
,a.name
from categories a
where a.parent = 1 # top level parents only
union all
select concat(1000000 + a.id, '|',
1000000 + IFNULL(b.id,0), '|')
,concat(' - ', b.name)
from categories a
inner join category_relationships a1 on a1.parent_id = a.id
inner join categories b on b.id = a1.category_id
where a.parent = 1
union all
select concat(1000000 + a.id, '|',
1000000 + IFNULL(b.id,0), '|',
1000000 + IFNULL(c.id,0), '|')
,concat(' - ', c.name)
from categories a
inner join category_relationships a1 on a1.parent_id = a.id
inner join categories b on b.id = a1.category_id
inner join category_relationships b1 on b1.parent_id = b.id
inner join categories c on c.id = b1.category_id
where a.parent = 1
union all
select concat(1000000 + a.id, '|',
1000000 + IFNULL(b.id,0), '|',
1000000 + IFNULL(c.id,0), '|',
1000000 + IFNULL(d.id,0), '|')
,concat(' - ', d.name)
from categories a
inner join category_relationships a1 on a1.parent_id = a.id
inner join categories b on b.id = a1.category_id
inner join category_relationships b1 on b1.parent_id = b.id
inner join categories c on c.id = b1.category_id
inner join category_relationships c1 on c1.parent_id = c.id
inner join categories d on d.id = c1.category_id
where a.parent = 1
union all
select concat(1000000 + a.id, '|',
1000000 + IFNULL(b.id,0), '|',
1000000 + IFNULL(c.id,0), '|',
1000000 + IFNULL(d.id,0), '|',
1000000 + IFNULL(e.id,0))
,concat(' - ', e.name)
from categories a
inner join category_relationships a1 on a1.parent_id = a.id
inner join categories b on b.id = a1.category_id
inner join category_relationships b1 on b1.parent_id = b.id
inner join categories c on c.id = b1.category_id
inner join category_relationships c1 on c1.parent_id = c.id
inner join categories d on d.id = c1.category_id
inner join category_relationships d1 on d1.parent_id = d.id
inner join categories e on e.id = d1.category_id
order by SORT

Related

How to manage COUNT, GROUP BY and HAVING?

I'm not experimented in SQL and I try maybe to do something which is impossible. SQL give this sensation it is possible to do it on only 1 request, but maybe not...
I have 4 joined tables A -> S -> T -> F.
A simple request give me these data :
select a.id, s.id, t.type, f.name
from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C';
a.id | s.id | t.type | f.name
-----------------------------
1 | 1 | E | C
1 | 2 | R | C
2 | 3 | E | C
3 | 4 | R | C
I would like to find ALL A ids which have multiple S rows associated.
And I would like to find ALL a ids which have only one S row of T type = R.
For the first one, I made this SQL query :
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) > 1;
But now, for the second query I don't understand how to filter on t.type and count.
I try this request but the response is not good (a.id = 1 is returned)
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) = 1 and t.type = 'R';
Any idea ?
Thank you
I would like to find ALL a ids which have only one S row of T type = R.
Does this do what you want?
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(*) = 1 and min(t.type) = 'R'
This gives you groups that have only one row, whose type is "R".

Conditional Left Join SQL

table A
----------------------------
NAME | CODE | BRANCH
----------------------------
bob | PL | B
david | AA | B
susan | PL | C
joe | AB | C
alfred | PL | B
table B
----------------------------
CODE | DESCRIPTION
----------------------------
PL | code 1
PB | code 2
PC | code 3
table C
----------------------------
CODE | DESCRIPTION
----------------------------
AA | code 4
AB | code 5
AC | code 6
Is there any way to join table A, B and C. without join all the table?
select A.*, COALESCE(B.DESCRIPTION, C.DESCRIPTION) AS DESCRIPTION from A
left join B on A.CODE = B.CODE
left join C on A.CODE = C.CODE
In my real case there will be more than 10 to join with the same column.
So I need conditional left join, something like this
SELECT A* , DESCRIPTION
FROM A LEFT JOIN (
CASE
WHEN A.CODE = 'B' THEN SELECT * FROM B
WHEN A.CODE = 'C' THEN SELECT * FROM C
END
) BC ON A.CODE = BC.CODE
You cannot use CASE to implement flow control. In SQL CASE is an expression that returns a single value.
You can instead use the following query:
select A.*,
CASE A.BRANCH
WHEN 'B' THEN B.DESCRIPTION
WHEN 'C' THEN C.DESCRIPTION
END AS DESCRIPTION
from A
left join B on A.CODE = B.CODE AND A.BRANCH = 'B'
left join C on A.CODE = C.CODE AND A.BRANCH = 'C'
You could use this to generate queries. Then you write a PL/SQL block to loop through all these queries and execute dynamically to give you separate results.
SELECT 'SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN '
|| CASE WHEN A.BRANCH = 'B' THEN 'TABLEB B' END
|| CASE WHEN A.BRANCH = 'C' THEN 'TABLEC C' END
|| ' ON '
|| 'A.CODE = '
|| CASE WHEN A.BRANCH = 'B' THEN 'B.CODE' END
|| CASE WHEN A.BRANCH = 'C' THEN 'C.CODE' END
v_query
FROM TableA A;
Output
V_QUERY
--------------------------------------------------------------------------------
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE

Sum query with reference to different coloumn for each row?

Is it possible to carry out a sum query where the row for each part of the sum is determine from a join?
For example if I have tables
table A
id | value
1 | 10
2 | 15
3 | 10
And
table b
id | b | c
1 | 2 | 3
2 | 1 | 2
Is it possible to do a SUM(tableA.value * tableB.<specific_column>) Where either the SUM is carried out directly as a join or the join table is prequired from a specification, for sake of argument, a string "bcb"?
Edit:
The end result I'm hoping to achieve would be equivalent to this:
SUM(SELECT value * b FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1,
SELECT value * c FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 2,
SELECT value * b FROM tableA a JOIN tableB b ON b.id = 2 WHERE a.id = 3);
I guess there's two parts to this: A simple join of A and selected values from B such that B is reduced to a single selectValue column.
Thanks.
As asked in comment it should be better to show us what output you really wants, but as I understand you wants to do something like :
SELECT id, SUM(a.value * b.b)
FROM a JOIN b USING(id)
GROUP BY id;
It's what you want ? I do not really understand you "bcb" point ...
Not because in your comment you said SUM(value, value, value) and I think you want to add those values so, I'll do something like this :
WITH
sum1 AS (SELECT value * b AS res
FROM tableA a
JOIN tableB b ON b.id = 1
WHERE a.id = 1),
sum2 AS (SELECT value * c AS res
FROM tableA a
JOIN tableB b ON b.id = 1
WHERE a.id = 2),
sum3 AS (SELECT value * b AS res
FROM tableA a
JOIN tableB b ON b.id = 2
WHERE a.id = 3)
SELECT SUM(sum1.res + sum2.res + sum3.res)
FROM sum1, sum2, sum3;
I've tested #Hervé Piedvache's code and it returns NULL, because SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1 has two rows. A work around would be:
SELECT SUM(val) FROM
(SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 1
UNION
SELECT value * c AS val FROM tableA a JOIN tableB b ON b.id = 1 WHERE a.id = 2
UNION
SELECT value * b AS val FROM tableA a JOIN tableB b ON b.id = 2 WHERE a.id = 3) data;

SQL Server, selecting from 2 columns from different tables

I have these columns from 2 tables
Table1 Table2
Code ID Code ID
A 1 A 1
B 1 B 1
C 1 C 1
D 1
E 1
My query:
Select
a.id, a.code, b.code
from
Table1 a, Table2 b
where
a.id = '1' and a.id = b.id
What I expected
ID code code
1 A A
1 B B
1 C C
1 D NULL
1 E NULL
What I got
ID code code
1 A A
1 B A
1 C A
1 D A
1 E A
1 A B
1 B B
1 C B
....
Any ideas? distinct didn't help
Thanks
Well, all the ID's in both tables are 1, so by joining on ID you'll get the cartesian product of both tables.
Instead, you'll need to do a left outer join based on Table1.Code:
Select a.id, a.code, b.code
from Table1 a LEFT OUTER JOIN Table2 b
on a.code = b.code
where a.id = '1';
You need to do a LEFT OUTER JOIN instead of a Cartesian Product
SELECT a.Id, a.Code, b.Code FROM Table1 a
LEFT OUTER JOIN Table2 b ON a.Code = b.Code
WHERE a.Id = '1'
A LEFT OUTER JOIN returns all rows from the left-hand side of the join (in this case Table 1) regardless of whether there is a matching record in the table on the right-hand side of the join (in this case Table 2). Where there is no match a NULL is returned for b.Code as per your requirements.
Reference OUTER JOINS

SQL aggregation query, grouping by entries in junction table

I have TableA in a many-to-many relationship with TableC via TableB. That is,
TableA TableB TableC
id | val fkeyA | fkeyC id | data
I wish the do select sum(val) on TableA, grouping by the relationship(s) to TableC. Every entry in TableA has at least one relationship with TableC. For example,
TableA
1 | 25
2 | 30
3 | 50
TableB
1 | 1
1 | 2
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
should output
75
30
since rows 1 and 3 in Table have the same relationships to TableC, but row 2 in TableA has a different relationship to TableC.
How can I write a SQL query for this?
SELECT
sum(tableA.val) as sumVal,
tableC.data
FROM
tableA
inner join tableB ON tableA.id = tableB.fkeyA
INNER JOIN tableC ON tableB.fkeyC = tableC.id
GROUP by tableC.data
edit
Ah ha - I now see what you're getting at. Let me try again:
SELECT
sum(val) as sumVal,
tableCGroup
FROM
(
SELECT
tableA.val,
(
SELECT cast(tableB.fkeyC as varchar) + ','
FROM tableB WHERE tableB.fKeyA = tableA.id
ORDER BY tableB.fkeyC
FOR XML PATH('')
) as tableCGroup
FROM
tableA
) tmp
GROUP BY
tableCGroup
Hm, in MySQL it could be written like this:
SELECT
SUM(val) AS sumVal
FROM
( SELECT
fkeyA
, GROUP_CONCAT(fkeyC ORDER BY fkeyC) AS grpC
FROM
TableB
GROUP BY
fkeyA
) AS g
JOIN
TableA a
ON a.id = g.fkeyA
GROUP BY
grpC
SELECT sum(a.val)
FROM tablea a
INNER JOIN tableb b ON (b.fKeyA = a.id)
GROUP BY b.fKeyC
It seems that is it needed to create a key_list in orther to allow group by:
75 -> key list = "1 2"
30 -> key list = "1 2 3"
Because GROUP_CONCAT don't exists in T-SQL:
WITH CTE ( Id, key_list )
AS ( SELECT TableA.id, CAST( '' AS VARCHAR(8000) )
FROM TableA
GROUP BY TableA.id
UNION ALL
SELECT TableA.id, CAST( key_list + ' ' + str(TableB.id) AS VARCHAR(8000) )
FROM CTE c
INNER JOIN TableA A
ON c.Id = A.id
INNER join TableB B
ON B.Id = A.id
WHERE A.id > c.id --avoid infinite loop
)
Select
sum( val )
from
TableA inner join
CTE on (tableA.id = CTE.id)
group by
CTE.key_list