MySQL retrieve full descendants from given parent ID - php

I'm struggling to create a single query to retrieve all descendants for a given parent id.
I have the following table structure:
menu_id | parent_id | title
1 0 Text 1
2 0 Text 2
3 2 Text 2.1
4 2 Text 2.2
5 2 Text 2.3
6 0 Text 3
7 6 Text 3.1
8 6 Text 3.2
9 6 Text 3.3
10 6 Text 3.4
11 10 Text 3.4.1
12 10 Text 3.4.2
13 10 Text 3.4.3
14 10 Text 3.4.4
I want to achieve the following result, for given parent id 6:
menu_id
6
7
8
9
10
11
12
13
14
Yet, I'm not able to go trough all descendants, but to the first node..
This is the query that I managed to create until now:
SELECT T2.menu_id
FROM (
SELECT
#r AS _id,
(SELECT #r := menu_id FROM bo_admin_menu WHERE parent_id = _id) AS menu_id,
#l := #l + 1 AS lvl
FROM
(SELECT #r := 6, #l := 0) vars,
bo_admin_menu h
WHERE #r <> 0) T1
JOIN bo_admin_menu T2
ON T1._id = T2.parent_id
ORDER BY T1.lvl DESC
Could someone point me to the correct path?

In your case, why not just use like on the title column (ignoring parent column), like so:
select menu_id
from bo_admin_menu t2 join bo_admin_menu t1 on
t2.`title` like CONCAT(t1.title, '%')
where t1.menu_id = 6
order by t2.menu_id

Related

I want to sort my CatId in this order 1 2 3 4 1 2 3 4 but there is a catch that it first check allocationId last 14 digits if catid is same

My query is this:-
SELECT m.allocationID,mt.CatId,mt.CatSName
FROM msttransaction m,msttemp mt WHERE m.isPending='Y'
AND m.allocationID IN (
SELECT mt.AllocationId FROM msttemp WHERE mt.quarterId='010100001'
) ORDER BY SUBSTRING(m.AllocationId, -14)
output:-
12980013120170919125006 1 A
12980013320170919125404 3 C
12980013420170919125603 4 D
12980013820170919130113 2 B
12980013920170919130315 3 C
12980014020170919130519 4 D
12980013220170919130613 2 B
12980013720170919130722 1 A
In 129800 series last 14 digits is date and time. First I have to sort my output according to 1 2 3 4 1 2 3 4 but first it check last 14 digits of 129800 of same catId that comes first which comes first.
Expected output
12980013120170919125006 1 A
12980013820170919130113 2 B
12980013320170919125404 3 C
12980013420170919125603 4 D
12980013720170919130722 1 A
12980013220170919130613 2 B
12980013920170919130315 3 C
12980014020170919130519 4 D
Does this do it?
ORDER BY m.CatId, SUBSTRING(m.AllocationId, -14)
The point is to order first by that CatId, then order by the embedded datastamp in the AllocationId.
If it were me I'd be more formal about the datestamp ordering, by formally extracting it from your strings using STR_TO_DATE() like this. This isn't strictly necessary, but good practice anyhow.
ORDER BY m.CatId, STR_TO_DATE(SUBSTRING(m.AllocationId, -14), '%Y%m%d%H%i%s'))
Plus, then you could use date manipulation like
ORDER BY m.CatId, LAST_DAY(STR_TO_DATE(SUBSTRING(m.AllocationId, -14), '%Y%m%d%H%i%s')))
to gather all the datestamps in a month together for ordering, or some such thing.
Here's an example: http://sqlfiddle.com/#!9/1c87ee/6/0
You can introduce a variable that will give you the order of the CatId when the result set would have been ordered by CatId and then the 14 last digits of the AllocationId. So in your example this variable would be either 1 or 2.
Once you have that variable value for each record, you can sort by that value first, and then by CatId:
select t.allocationId, CatId
from (
select t.*,
#rn := if(#CatId = CatId, #rn+1, if(#CatId := CatId, 1, 1)) rn
from (
SELECT m.allocationID, mt.CatId,mt.CatSName
FROM msttransaction m,
INNER JOIN msttemp mt
ON m.allocationID = mt.AllocationId
WHERE m.isPending='Y'
AND mt.quarterId='010100001'
ORDER BY CatId, SUBSTRING(m.AllocationId, -14)
) t,
(select #CatId := -1, #rn := -1) init
) t
order by rn, CatId;

MySQL Select top 10 items for each category [duplicate]

This question already has answers here:
Get top n records for each group of grouped results
(12 answers)
Closed 6 years ago.
Hello I have a mysql database in which has multiple categories. I would like to be able to pull only the first 10 items per category for example:
I have the following table, I would like to pull the first 2 rows for name = a, same for name = b and name = c
-----------------------------------
name | value | description | logo
-----------------------------------
a | 2.00 | its a letter| image
-----------------------------------
a | 5.00 | its a letter| image
-----------------------------------
b | 6.00 | its a letter| image
-----------------------------------
c | 3.00 | its a letter| image
-----------------------------------
c | 1.00 | its a letter| image
------------------------------------
This is what I have so farm post filter is a string of objects that comes in when the call is made. unfortunately it only gives me the first 10 of everything together, if you can point me in the right direction that would be great thank you!
code:
SELECT *
FROM object_list
Where object IN (".$_POST['filter'].")
ORDER BY date DESC, object ASC,id DESC
You can get groups along with element count by the below query:
SELECT name, value,
#count := IF(#value = name, #count + 1, 1) AS count,
#value := name AS some_value
FROM test, (SELECT #count := 1, #value := NULL) a
WHERE test.name in ('a', 'b')
Now, if you need to restrict the rows to 2 per group then you just need to wrap this query into another select and add a criteria, e.g.:
SELECT *
FROM (
SELECT name, value,
#count := IF(#value = name, #count + 1, 1) AS count,
#value := name AS some_value
FROM test, (SELECT #count := 1, #value := NULL) a
WHERE test.name in ('a', 'b')) a
WHERE a.count <= 2;
Here's the SQL Fiddle.
Does this work?
SELECT
yourtable.*
FROM
yourtable
JOIN (
SELECT
t1.name,
t1.value,
COUNT(t2.name) AS theCount
FROM yourtable t1
LEFT JOIN yourtable t2 ON t1.name = t2.name AND t1.value > t2.value
WHERE t1.name in ('a', 'b')
GROUP BY t1.name, t1.value
HAVING theCount < 2
) AS dt USING (name, value);
Source: http://thenoyes.com/littlenoise/?p=36

Mysql tree traverse and count

I`ve got the following table in mysql
id|top_id|amount
1 NULL 2
2 NULL 8
3 NULL 4
4 3 7
5 2 8
6 2 4
7 5 5
8 7 1
9 6 6
10 8 6
For the first 3 ids i need to sum up all amounts of its successors to get the following:
id | amount
1 8
2 32
3 11
I guess there should be joins but unfortunately I can`t get the working mysql request. Can somebody help me?
UPD: In php I have the following query:
$tops = $mysqli->query('SELECT * FROM table WHERE top_id IS NULL')
which obviously returns me just 3 top ids and their plain amounts (2, 8 and 4 respectively). Instead of 2, 8 and 4 I need to get 12, 28 and 11 and that`s the problem((
This is the crudest solution... No doubt someone will provide some hints for a more scalable solution shortly (perhaps employing PHP)...
SELECT a.id
, COALESCE(a.amount,0)
+ COALESCE(b.amount,0)
+ COALESCE(c.amount,0)
+ COALESCE(d.amount,0)
+ COALESCE(e.amount,0)
+ COALESCE(f.amount,0) total
FROM my_table a
LEFT
JOIN my_table b
ON b.top_id = a.id
LEFT
JOIN my_table c
ON c.top_id = b.id
LEFT
JOIN my_table d
ON d.top_id = c.id
LEFT
JOIN my_table e
ON e.top_id = d.id
LEFT
JOIN my_table f
ON f.top_id = e.id
WHERE a.top_id IS NULL;

Mysql Where Column A = X and Column B = Y and or Column B = Z

I'm trying to get a set of values from a pivot table where column A is equal to an array of values, so for example ID 12 has attribute_value_id equal to 3 and 9. Can this be done? I've got this far...
ID | post_id | attribute_id | attribute_value_id
8 12 1 3
9 12 2 13
10 13 1 3
11 13 2 9
12 16 1 3
13 16 2 9
88 11 1 1
89 11 2 8
90 11 3 18
91 11 4 22
The query...
select *
from `searching_for_posts`
where (
select count(*)
from `attributes`
inner join `searching_for_attributes`
on `attributes`.`id` = `searching_for_attributes`.`attribute_id`
where `searching_for_attributes`.`searching_for_post_id` = `searching_for_posts`.`id`
and (`attribute_value_id` = 3 and `attribute_value_id` = 9)
) >= 1
If I use the and then I get no values. If I use the or then I get 3 values but it should return 2. I have limited SQL experience.
You can do this using group by and having. Your logic is hard to follow, but it is something like this:
select post_id
from table t
where attribute_value_id in (3, 9)
group by post_id
having count(distinct attribute_id) = 2;
I would think you would want to check on attribute_id as well, but that doesn't seem to be part of the question.
EDIT:
If these are stored in another table:
select a.post_id
from attributes a join
searching_for_attributes sfa
on a.attribute_id = sfa.attribute_id and
a.attribute_value_id = sfa.attribute_value_id
group by a.post_id
having count(*) = (select count(*) from searching_for_attributes);
In response to #GordonLinoff answer, I've managed to use GROUP BY and HAVING COUNT to get the desired data. Here's what I came up with and hope this helps someone else...
select *
from `searching_for_posts`
where (
select count(*)
from `attributes`
inner join `searching_for_attributes` on `attributes`.`id` = `searching_for_attributes`.`attribute_id`
where `searching_for_attributes`.`searching_for_post_id` = `searching_for_posts`.`id`
and `attribute_value_id` in (3, 9)
having count(distinct `attributes`.`id`) = 2
) >= 1
group by `id`

Shortest string within same path (branch)

I have a tree representation in mysql table based on id, depth, parent_id and path. Every root record within this table has a depth of 0, parent_id != null and path representation based on hex value of ID padded left with 0.
Every element of the tree is constructed by specifying depth = parent.depth + 1, path = parent.path + hex(id), parent_id = parent.id (pseudo code) for example:
id path depth parent_id assigned_user_id
------------------------------------------------------------
1 001 0 NULL NULL
2 002 0 NULL 1
3 001003 1 1 2
4 002004 1 2 1
5 001003005 2 3 2
6 001003005006 3 5 2
7 002004007 2 4 1
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
and so on...
The problem is how to get the records for specific user id limited to the shortest path in the same branch. For example for assigned_user_id = 2 retrive:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
8 002004008 2 4 2
9 002004009 2 4 2
Instead of:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
5 001003005 2 3 2
6 001003005006 3 5 2
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
SELECT t1.*
FROM atable t1
LEFT JOIN atable t2
ON t2.assigned_user_id = t1.assigned_user_id AND
t2.path = LEFT(t1.path, CHAR_LENGTH(t2.path)) AND
t2.id <> t1.id
WHERE t1.assigned_user_id = 2
AND t2.id IS NULL
If I get you right, it might be enough to exclude rows whose parent_id is among the ids selected. This is because if the parent and child is selected, they must be in the same branch. The parent's path will be shorter, therefore it's OK to exclude the child.
Something like:
SELECT *
FROM x
WHERE assigned_user_id = 2
AND parent_id NOT IN (SELECT id FROM x WHERE assigned_user_id = 2)
If you would have a tree like this (numbers are your assigned user ids):
A1 G2
/ \ / \
B2 C2 H2 I2
| \ | | \
D2 E2 L1 J2 K2
|
M2
B2, C2, G2 and M2 would be selected. I'm still not sure if this was your intention, though.
I would try something like this:
SELECT * FROM PATHS WHERE ASSIGNED_USER_ID = 2
AND NOT PARENT_ID IN (SELECT ID FROM PATHS WHERE ASSIGNED_USER_ID = 2)
Basically the idea is to select top parent nodes for the given user.
Idea behind this: B is shorter than A if A starts with B. Maybe there's something better than LIKE to do this "begins with".
SELECT a.* FROM node AS a
WHERE a.assigned_user_id = ?
AND NOT EXIST
(SELECT * FROM node AS b
WHERE b.assigned_user_id = ?
AND LENGTH(a.path) > LENGTH(b.path)
AND a.path LIKE CONCAT(b.path, '%') )
Both ? are mapped to the desired user id.
EDIT
Forgot to include the assigned_user_id. Changed the code.
2nd EDIT
Changed code to avoid the case of b=a.
Have you tried something like this?
select child.assigned_user_id, child.id
from node as child
left join node as parent
on child.path like CONCAT(parent.path, '%')
and child.assigned_user_id = parent.assigned_user_id
and child.id <> parent.id
group by child.assigned_user_id, child.id
having max(parent.id is null) = true
(Not sure it'll work exactly as above, but basically: to left join on the path in order to extract the full list of parents, and then to aggregate in such a way that you only keep the nodes without any parents when grouped by assigned_user_id.)

Categories