Join query issue in MySQL - mysql

I'm storing the records in hierarchy.
Ex.
Account -> Hospital -> Department
Account -> Hospital -> Department -> Section
I'm storing the association of all the records in following manner.
+------+---------------+----------+---------------+-----------+
| Id | ParentType | ParentId | Child Type | ChildId |
+------+---------------+----------+---------------+-----------+
| 1| account| 1| hospital| 10|
| 2| account| 1| hospital| 20|
| 3| hospital| 10| department| 100|
| 4| hospital| 10| department| 101|
| 5| department| 100| device| 1000|
| 6| department| 101| device| 1001|
| 6| department| 101| device| 1002|
| 1| account| 2| hospital| 30|
| 2| account| 2| hospital| 40|
| 3| hospital| 30| department| 200|
| 4| hospital| 40| department| 201|
| 5| department| 200| section| 5000|
| 5| department| 200| section| 5001|
| 6| section| 5000| device| 2001|
| 6| section| 5001| device| 2002|
+------+---------------+----------+---------------+-----------+
So, account with id 1, follows first hierarchy; whereas account with id 2 follows second hierarchy.
I need to fetch the records for the given level.
Ex.
Get all the devices belonging to account with id = 1
Get all the devices belonging to department with id = 200 and account with id = 2
and so on.
I can retrieve these with queries like:
First query:
SELECT a3.ChildType, a3.ChildId FROM association_lookup a1 -- [got hosp level]
JOIN association_lookup a2 ON a2.parentId = a1.ChildId -- [got dept level]
JOIN association_lookup a3 ON a3.parentId = a2.ChildId AND a3.ParentType = a2.ChildType -- [got device level]
WHERE a1.ParentId = 1 AND a1.ParentType = 'account'
AND a3.ChildType = 'device'
I can make this as dynamic query with self joins equal to level difference - 1. i.e. account level = 0, device level = 3; hence 2 joins.
But now, if I want to associate device against hospital level instead of department level; like:
| xx| hospital| 10| device| 1003|
then for the same query this device will be skipped and only the devices associated with department level will be returned. How can I get all the devices (i.e. under both hospital level and department level).

That is a horrible way to store data.
I suggest restructuring and creating separate tables each entity.
I.e. create table account, create table hospital ...
Then you can jion properly. Everything else would require dynamic iterative selection which is not built in to mysql and needs to be done with an external program or by hand.
You can write a script to dynamicall generate a table for each parenttype and childtype though.

Related

From the following tables write a SQL query to display those managers who have average experience for each scheme

Solution:
SELECT s.scheme_code ,
ROUND(SUM(m.running_years) * 1.0/NULLIF(COUNT(DISTINCT m.manager_id),0),2) AS 'Average year of experience'
FROM scheme s JOIN managing_body m
ON m.manager_id = s.scheme_manager_id
GROUP BY s.scheme_code;
Table: managing_body
manager_id|manager_name|running_years|
----------|------------|-------------|
51|James | 5|
52|Cork | 3|
53|Paul | 4|
54|Adam | 3|
55|Hense | 4|
56|Peter | 2|
Table : scheme
scheme_code|scheme_manager_id|
-----------|-----------------|
1001| 51|
1001| 53|
1001| 54|
1001| 56|
1002| 51|
1002| 55|
1003| 51|
1004| 52|
Output:
scheme_code|Average year of experience|
-----------|--------------------------|
1001| 3.50|
1002| 4.50|
1003| 5.00|
1004| 3.00|
https://www.w3resource.com/sql-exercises/challenges-1/sql-challenges-1-exercise-32.php
this is the link if anyone wants to see the table and query.
ROUND(SUM(m.running_years) * 1.0/NULLIF(COUNT(DISTINCT m.manager_id),0),2) - I do not clearly understand this part

MySQL : Having a table with minimum and maximum value columns, how could I find the "highest" values if my search term exceeds maximum value?

Sorry if my question is a bit confusing, but database design (nor queries) are not my strong points.
Let's say I sell a product, which are cables. And those products have three "variations", which
prices would be applied in layers like 'post-processings':
Type 1: Ordinary cable.
Type 2: Ordinary cable plus custom color.
Type 3: Ordinary cable plus custom color plus terminals.
Also, the final price of the cable will depend on the length, and the more meters of cable you buy, the less price per meter I will apply.
So, I've designed a cable_pricings table like this:
id|product_id|product_type_id|min_length|max_length|price|
--|----------|---------------|----------|----------|-----|
1| 1| 1| 0| 10| 0.50|
2| 1| 1| 10| 20| 0.45|
3| 1| 1| 20| 40| 0.40|
4| 1| 1| 40| 50| 0.30|
5| 1| 1| 50| 60| 0.25|
6| 1| 1| 60| 0| 0.15|
7| 1| 2| 0| 10| 0.35|
8| 1| 2| 10| 20| 0.30|
9| 1| 2| 20| 40| 0.30|
10| 1| 2| 40| 50| 0.20|
11| 1| 2| 50| 60| 0.20|
12| 1| 2| 60| 0| 0.20|
13| 1| 3| 0| 10| 0.40|
14| 1| 3| 10| 20| 0.40|
15| 1| 3| 20| 40| 0.30|
16| 1| 3| 40| 50| 0.30|
17| 1| 3| 50| 60| 0.25|
18| 1| 3| 60| 0| 0.25|
Now with this structure, let's say I want to buy 47 meters of cable, with custom color. With a single query like this:
SELECT * FROM cable_pricings
WHERE product_id = 2
AND product_type_id IN (1,2)
AND min_length <= 47
AND max_length > 47;
I got two rows which will hold those type of cable and be in the length intervals, then on my server code, I iterate over results and get final price. Up to here, everything good.
But my problem is on the "edge" cases:
If I want to buy 60 meters of cable, my query won't work, as max_length is 0.
If I want to buy more than 60 meters of cable, my approach won't work as well, because in that case none of the conditions will apply.
I've already tried with MAXs, MINs, but I'm not getting the expected results (and I think aggregate functions check the whole table, so I'd like to -if that's possible- not to use aggregates).
I also thought to put on the 'edge' max_length the value of 9999999 but I think that's just... a dirty fix. Also, this will be managed from a backend, and I don't expect the final user writing lots of 999999s on edge case.
Then my questions are:
Can I solve "edge" cases with a single query? Or I have to split my cases into two separate queries?
Is my table design correct at all?
you can change
AND max_length > 47
To:
AND (max_length > 47 OR max_length = 0)
I wozuld use this query
SELECT *
FROM cable_pricings
WHERE product_id = 1
AND product_type_id IN (1,2)
AND min_length >= 60
AND (max_length > 60 OR max_length = 0);
dbfille exampole
You have to include the null else the max restriction will not trigger

MySQL getting data + recursive query

I have got 3 tables:
+-----+----------+ +-----+----------+-------+ +-----+----------+-------+
| id | A_id | | A_id| B_id | value | | B_id| B_id_ | value |
+-----+----------+ +-----+----------+-------+ +-----+----------+-------+
| 1| 5| | 5| 1| aa| | 1| 2| zzxx|
+-----+----------+ +-----+----------+-------+ +-----+----------+-------+
| 2| 3| | 3| 3| bb| | 2| | vvyy|
+-----+----------+ +-----+----------+-------+ +-----+----------+-------+
| 3| 4| bbll|
+-----+----------+-------+
| 5| | oopp|
+-----+----------+-------+
| 4| 5| mmnn|
+-----+----------+-------+
What SELECT statement i need to use, so that output would look like this(table3 can be up to 4 levels deep into it self):
+----+------------------------------+
| id | value |
+----+------------------------------+
| 1| aa\zzxx\vvyy|
+----+------------------------------+
| 2| bb\bbll\mmnn\oopp|
+----+------------------------------+
As i don't have much experience with DB and SQL, this is hard for me. And I have no vision about how to do this.
This has to be done in MySQL. Hardest thing as i have read is the recursive query in MySQL since it doesn't exist, so people have to simulate it. I have read some SO topics about the recursive Query, but i understood that's not for me.
Any help is appreciated.
By hard and fast learning I managed to solve my problem. Code below.
SELECT DISTINCT
OTHER.DATA,
concat(
'/',ifnull(t4.value,''), CASE WHEN (t4.value is NULL) then '' else '/' END,
ifnull(t3.value,''), CASE WHEN (t3.value is NULL) then '' else '/' END,
ifnull(t2.value,''), CASE WHEN (t2.value is NULL) then '' else '/' END,
ifnull(t1.value,''), CASE WHEN (t1.value is NULL) then '' else '/' END,
table2.value
) as 'My Column name'
FROM
table1
LEFT JOIN table2 ON
(table1.A_id = table2.A_id)
LEFT JOIN table3 as t1 ON
(t1.B_id = table2.B_id)
LEFT JOIN table3 AS t2 ON
(t2.B_id = t1.B_id_)
LEFT JOIN table3 AS t3 ON
(t3.B_id = t2.B_id_)
LEFT JOIN table3 AS t4 ON
(t4.B_id = t3.B_id_)
Big Thanks to #Damodaran and his solution for recursive query.
How to create a MySQL hierarchical recursive query
Be careful with using this code, as I have used it for DB, which is only queried for data. So this approach might be slow on other different usage. If you use this, I suggest you to think about indexing some fields.

CakePHP (sub)query to get youngest record of each group

In a project I manage invoices that have a status which is changed throughout their lifetime. The status changes are saved in another database table which is similar to this:
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 1| 1| 1| 1| 3|2013-11-11 12:00:00|
| 2| 1| 2| 3| 5|2013-11-11 12:30:00|
| 3| 2| 3| 1| 2|2013-11-10 08:00:00|
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
For each invoice, I would like to retrieve the last status change. Thus the result should contain the records with the ids 4 and 5.
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
If I group by the invoice_id and use max(change_date), I will retrieve the youngest date, but the field values of the other fields are not taken from those records containing the youngest date in the group.
That's challenge #1 for me.
Challenge #2 would be to realize the query with CakePHP's methods, if possible.
Challenge #3 would be to filter the result to those records belonging to the current user. SO if the current user has the id 1, the result is
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 4| 1| 1| 5| 6|2013-11-11 13:10:00|
If he or she has user id 2, the result is
|id|invoice_id|user_id|old_status_id|new_status_id|change_date |
-----------------------------------------------------------------------
| 5| 2| 2| 2| 5|2013-11-10 09:00:00|
For the user with id 3 the result would be empty.
In other words, I do not want to find all latest changes that a user has made, regardless whether he was the last one that made a change. Instead, I want to find all invoice changes where that user was the ast one so far who made a change. The motivation is that I want to enable a user to undo his change, which is only possible if no other user after him performed another change.
In case anyone needs an answer
Strictly focusing on:
I want to find all invoice changes where that user was the last one so far who made a change
Write the SQL as
SELECT foo.*
FROM foo
LEFT JOIN foo AS after_foo
ON foo.invoice_id = after_foo.invoice_id
AND foo.change_date < after_foo.change_date
WHERE after_foo.id IS NULL
AND foo.user_id = 1;
Implement using the JOIN clause within Cakephp's find.
The SQL for the suggested algorithm is something like:
SELECT foo.*
FROM foo
JOIN (SELECT invoice_id, MAX(change_date) AS most_recent
FROM foo
GROUP BY invoice_id) AS recently
ON recently.invoice_id = foo.invoice_id
AND recently.most_recent = foo.change_date
WHERE foo.user_id = 1;

Select last 3 entries for each client and insert it into columns

Need your help on the following: need to select last three comments for each client and insert it into columns. So, the input looks like this:
ID| Client_ID| Comment_Date| Comments|
1| 1| 29-Apr-13| d|
2| 1| 30-Apr-13| dd|
3| 1| 01-May-13| ddd|
4| 1| 03-May-13| dddd|
5| 2| 02-May-13| a|
6| 2| 04-May-13| aa|
7| 2| 06-May-13| aaa|
8| 3| 03-May-13| b|
9| 3| 06-May-13| bb|
10| 4| 01-May-13| c|
The output I need to get is as follows:
Client_ID| Last comment| (Last-1) comment| (Last-2) comment|
1| dddd| ddd| dd|
2| aaa| aa| a|
3| bb| b|
4| c|
Please, help!!
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.client_id = x.client_id
AND y.id >= x.id
GROUP
BY x.client_id
, x.id
HAVING COUNT(*) <=3;
If don't think you can get this with an SQL request. Maybe you can, but i think it's easier with PHP. For example, you can get your comments with this request :
SELECT * FROM Comment
WHERE Client_ID = ?
LIMIT 0,3
ORDER BY Date DESC
It will return to you the three last comments of an user. Then, you can do whatever you want with that !
Hope it'll help.