User variable not updating in subquery - mysql

I am using a RECURSIVE statement on MariaDB, to get a product category path when I know the product category unique ID, from a self-referencing category table.
This works:
WITH RECURSIVE categories AS (
SELECT * FROM tbl_eav_categories tec2 WHERE tec2.category_id = 1023
UNION
SELECT tec3.* FROM tbl_eav_categories tec3, categories AS c WHERE tec3.category_id = c.parent_category_id
)
SELECT GROUP_CONCAT(CONCAT(category_default_label,' [',category_id,']') ORDER BY category_id ASC SEPARATOR ' >> ') FROM categories
And returns:
Consumables [7] >> Catering Equipment and Supplies [95] >> Tea Bags [1023]
Great.
But now I need to list all category ID's and in the second column, their paths.
I thought this would simply be a matter of doing a SELECT on the primary category table ('tbl_eav_categories') table, and dropping the above query in as a subquery column. Like this:
SELECT
#CatID := category_id AS 'cat_id',
(
WITH RECURSIVE categories AS (
SELECT * FROM tbl_eav_categories tec2 WHERE tec2.category_id = #CatID
UNION
SELECT tec3.* FROM tbl_eav_categories tec3, categories AS c WHERE tec3.category_id = c.parent_category_id
)
SELECT GROUP_CONCAT(CONCAT(category_default_label,' [',category_id,']') ORDER BY category_id ASC SEPARATOR ' >> ') FROM categories
) 'categorypath'
FROM tbl_eav_categories;
However, all I get is:
cat_id categorypath
1 Bearings [1]
2 Bearings [1]
3 Bearings [1]
4 Bearings [1]
5 Bearings [1]
6 Bearings [1]
...
(like this until the bottom of the entire result set).
After some research, I do believe it has something to do with the #CatID variable being evaluated before it gets assigned, but I can't work out how to work around it.
I tried to follow Ben English's guidance here: User variable in MySQL subquery but it baffles me :(
Please help! :)

I believe I have found the answer, by including the entire table scan in the actual CTE:
WITH RECURSIVE category_path (id, title, path) AS
(
SELECT category_id, category_default_label, category_default_label path FROM tbl_eav_categories WHERE parent_category_id IS NULL
UNION ALL
SELECT c.category_id, c.category_default_label, CONCAT(cp.path, ' [',c.parent_category_id, '] >> ', c.category_default_label) FROM category_path cp JOIN tbl_eav_categories c ON cp.id = c.parent_category_id
)
SELECT id,path FROM category_path
ORDER BY path;

Related

Hierarchy based on three tables

I would like to have rows like a single table like the following
id name parentId
My current tables are (keys between them are foreign keys)
category
id name
which is the parent of all
and
subcategory
id name catId
and the last table which is activity
activity
id name subcatId
For the parentId of category table, it would be nothing as category is the parent of all
My attempt so far have been unsuccessful
Sample Data
category
C-1 HR null
C-2 Development null
subcategory
SC-1 Hiring C-1
SC-2 Admin C-1
SC-3 Developer C-2
activity
A-1 College Hiring SC-1
A-2 Job Fair SC-1
A-3 Java Development SC-3
Result Needed
1 HR null
2 Development null
3 Hiring C-1
4 Admin C-1
5 Developer C-2
6 College SC-1
7 Job Fair SC-1
8 Java Development SC-3
I hope it is clearer. If you need any further information, please let me know
Thanks
my attempt on 2 tables
select name
from (
select c.name
from category c
union all
select b.name
from subcategory b
inner join category ca
on ca.id = b.parentId
)
I get an error saying
Every derived table must have its own alias
Do I need to add the following lines to my query
start with parent_id is null
connect by prior id = parent_id
Try something like this:
SELECT #id := #id + 1 AS id, name, parent_id
FROM ( SELECT name, NULL AS parent_id FROM category
UNION ALL
SELECT id, name, catId FROM subcategory
UNION ALL
SELECT id, name, parentId FROM activity
) q,
( SELECT #id := 0 ) w;
Used UNION ALL to concatenate the results from multiple queries.
In UNION the column names of the first query is used as column names of the result set.
Used variable #id and the assignment operator := to generate the calculated values of the column ID.
Be advised: If you use LIMIT or OFFSET, the values of column id would not be coherent.
Actually, like this should solve your problem:
select name
from (
select c.name
from category c
union all
select b.name
from subcategory b
inner join category ca
on ca.id = b.parentId
) A -- alias is required for sub-query.
You can set anything as alias provided that it won't conflict with some of MySQL reserved word. You can set like this as well ) AS A.
For you situation, I don't think it's necessary to do sub-query or inner join. If you really want the result to be like that you can just use union all:
SELECT name,'' as ID from category
UNION ALL
SELECT name,catId FROM subcategory
UNION ALL
SELECT name,subcatId FROM activity;

How to get All categories comma separated from this query

I have two tables Posts, categories. Here in the posts table I stored the category values as comma separated string like this 5,8,23,7. While displaying the posts, I just want to show the post categories as comma separated like this Flower, Birds, Animals. So I tried some queries nothing helped me to get it. The Posts Table Example.
ID Post title categories
3 Example Post 5,7,23,8
And the Categories Table will be like this
ID name
5 Flowers
7 Animals
8 Birds
23 Naturals
And I want result like this
ID Post Tile Category
3 Example Post Flowers, Animals, Birds
For that I tried this query but didn't help me to get it .
SELECT post.ID, post.Post_title, (SELECT cat.name FROM Categories as cat WHERE cat.ID IN (post.category)) AS Categories FROM Posts as post
And it returns only one category, it retrieves the first category name only.
If you simply must use that schema, you could try something like this:
select P.ID, P.Title, (
select group_concat(C.name SEPARATOR ', ')
from Categories C
where LOCATE(CONCAT(C.ID, ','), P.categories) > 0
or LOCATE(CONCAT(', ', C.ID), P.categories) > 0
) as categories
from Post P;
It's hacky because in a comma separated list either a value occurs before a comma or after a comma, taking into account values at the beginning or end of the list. You can't just do a straight substring, because otherwise you'll get a category ID of 5 matched to a 'categories' value of '1, 2, 555'.
EDIT: Updated to consider the fact that Posts.categories is a CSV value.
You need to use the GROUP_CONCAT() function, and also the trick posted in SQL split comma separated row in order to split the JOIN CSV and then create the output CSV:
SELECT
Posts.ID,
Posts.Post_title,
GROUP_CONCAT(Categories.name SEPARATOR ',') AS `Category`
FROM Posts
INNER JOIN Categories
ON Categories.ID IN (
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(Posts.categories, ',', n.n), ',', -1) value
FROM (
SELECT a.N + b.N * 10 + 1 n
FROM
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a
,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b
ORDER BY n
) n
WHERE n.n <= 1 + (LENGTH(Posts.categories) - LENGTH(REPLACE(Posts.categories, ',', '')))
ORDER BY value
)
GROUP BY
Posts.ID,
Posts.Post_title
Fiddle: http://sqlfiddle.com/#!9/b1ddc9/4
I believe you can use group_concat? Just join the Categories table and group_concat the name group_concat(name)
as for the JOIN try to use find_in_set(Categories.ID, Post.ID) > 0
Thou this approach may not work if the comma delimited Category ID has space i.e. 1, 2 etc...But if you are saving it accurately this may work.

Trying to return all columns of child records for a parent record

I was searching for a solution by which I could get all the child records for a parent record. I found a solution that meet my needs as shown here
Only problem is that the above solution is concatenating the IDs.
Current Resultset
It is comma separated of ID column with values = 2,3,4
Expected Output
ID Name ParentID
1 1st null
2 2nd 1
3 3rd 1
4 4th 2
I tried below code.
SELECT #pv:=
(SELECT * FROM tblreport WHERE ParentID IN (#pv)) AS lv FROM tblreport
JOIN
(SELECT #pv:=2)tmp
WHERE ParentID IN (#pv)
and got an error message : Operand should contain 1 column(s)
My sample SQL Fiddle
Now, they are coming comma separated. But, I am expecting to return all columns of a particular row.
You could use CSV ids result like:
SELECT *
FROM tblreport
WHERE FIND_IN_SET(ID,(SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
SELECT #pv:=(SELECT GROUP_CONCAT(ID SEPARATOR ',')
FROM tblreport WHERE ParentID IN (#pv)) AS lv FROM tblreport
JOIN (SELECT #pv:=1)tmp
WHERE ParentID IN (#pv)) a));
DBFiddle Demo
Output:
ID Name ParentID
2 2nd 1
3 3rd 1
4 4th 2
EDIT:
If you need also original row you could use UNION ALL:
SET #var = 1;
SELECT *
FROM tblreport
WHERE FIND_IN_SET(ID,(SELECT GROUP_CONCAT(lv SEPARATOR ',') FROM (
SELECT #pv:=(SELECT GROUP_CONCAT(ID SEPARATOR ',')
FROM tblreport WHERE ParentID IN (#pv)) AS lv FROM tblreport
JOIN (SELECT #pv:=#var)tmp
WHERE ParentID IN (#pv)) a))
UNION ALL
SELECT *
FROM tblReport
WHERE ID = #var
ORDER BY ID;
DBFiddle Demo2
That output is simply provided by this short query:
SELECT * from tblreport WHERE FIND_IN_SET(id, '2,3,4');
Better yet...
Use MySQL 8.0 or MariaDB 10.2 -- each has recursive CTE queries which conveniently drill down a hierarchical database tree.
(Do not use CSV, only use separate rows.)

MySQL. Subqueries

I have troubles understanding subqueries. Can someone properly explain it to me please?
I have this query:
SELECT substring_index(marca, ' ', 1) AS companie, count(*) AS numar_masini
FROM tip_masina
GROUP BY companie;
That displays:
companie numar_masini
Chevrolet 5
Dacia 1
Dodge 5
Ford 6
And this query:
SELECT substring_index(id_masina, ' ', 1) AS marca, SUM(nr_vehicule) AS nr_masini
FROM proprietate
GROUP BY marca;
That displays:
marca nr_masini
Chevrolet 18
Dodge 9
Ford 11
I want to 'combine' this queries properly and get a result of:
companie numar_marci numar_masini
Chevrolet 5 18
Dacia 1 0 (or null)
Dodge 5 9
Ford 6 11
How can I do that properly? I don't want code, I want a proper explanation of subqueries as I did not understand them properly and I didn't find a proper documentation to explain it to me.
A sub query (in this way) is just returning a set of rows. You can treat the result of a sub query as if it were a table.
For example your queries could be used as follows.
SELECT b.marca, b.nr_masini, c.numar_masini
(
SELECT substring_index(id_masina, ' ', 1) AS marca, SUM(nr_vehicule) AS nr_masini
FROM proprietate
GROUP BY marca
) b
LEFT OUTER JOIN
(
SELECT substring_index(marca, ' ', 1) AS companie, count(*) AS numar_masini
FROM tip_masina
GROUP BY companie
) c
ON b.marca = c.companie
Here it is taking your first query and getting the 4 makes (with the counts to go with them), then joining that result against the result of your 2nd query. You 2nd query only brings back 3 rows.
Possibly that both queries could return rows that the other query doesn't return. In which case you might use a 3rd sub query to get the main rows. Then you can join the other 2 against that:-
SELECT a.companie, b.nr_masini, c.numar_masini
FROM
(
SELECT substring_index(marca, ' ', 1) AS companie
FROM tip_masina
UNION
SELECT substring_index(id_masina, ' ', 1) AS companie
FROM proprietate
) a
LEFT OUTER JOIN
(
SELECT substring_index(id_masina, ' ', 1) AS marca, SUM(nr_vehicule) AS nr_masini
FROM proprietate
GROUP BY marca
) b
ON a.companie = b.marca
LEFT OUTER JOIN
(
SELECT substring_index(marca, ' ', 1) AS companie, count(*) AS numar_masini
FROM tip_masina
GROUP BY companie
) b
ON a.companie = c.companie
In this way you are using one sub query to get a list of all the possible companie values, and treating that as a table to join against the other 2 sub queries.
You could set up each of these sub queries as a view which would make them look even more like tables when you join them.
That is not a subquery. substring_index is a function.
This is how a subquery works.
SELECT *
FROM
(
-- This is the subquery
SELECT id, test1, test2
FROM table
) TableAlias
Basically, mysql collects the result from the inner select:
SELECT id, test1, test2 FROM table
is now the TableAlias
So the syntax can be used like this now
SELECT TableAlias.Id, TableAlias.test1, TableAlias.test2
FROM
(
-- This is the subquery
SELECT id, test1, test2
FROM table
) TableAlias
Subqueries are useful for many things. Like when using aggergated functions like SUM, AVG. Then it can really help to ORDER reuslts.
Try this link: http://www.toadworld.com/platforms/mysql/w/wiki/6355.table-subqueries.aspx
I hope that explains Subqueries a little bit better for you.

MySQL fetch X columns for each category_id

I don't know if this is possible in mysql via ONE query .
assuming I have a table "products" that has "id","category_id","product_name","price"
Case 1 : I want to fetch 5 products from each category where price is more than 100$
Case 2: I want to fetch
1-"3 products from category 1"
2- "5 products from category 2"
3- "3 products from category 2 where the price is more than 100 and is not fetched in point 2 above"
each case in ONE query , is that possible ?
PS : The table has about 100K rows ...
I found this method : it is VERY fast and gave me exactly what i wanted :
SELECT l.*
FROM (
SELECT category,
COALESCE(
(
SELECT id
FROM writings li
WHERE li.category= dlo.category
ORDER BY
li.category, li.id
LIMIT 15, 1
), CAST(0xFFFFFFFF AS DECIMAL)) AS mid
FROM (
SELECT id as category
FROM cats dl
) dlo
) lo, writings l
WHERE l.category>= lo.category
AND l.category<= lo.category
AND l.id <= lo.mid
I think that you could do this using nested select statements.
http://dev.mysql.com/tech-resources/articles/4.1/subqueries.html
Although, I'm not sure how you would utilize them as seperate select statements after you've gotten your query. I mean, they will all be one record set.
Case 2 I think I've got...
(
select * from products where category_id = 1 limit 3
)
union
(
select * from products where category_id = 2 limit 5
)
union (
select * from products where category_id = 2 and price > 100 limit 3
)
Note that union will eliminate duplicates, union all will allow duplicates (and is therefor faster).
Have you seen?
mySQL Returning the top 5 of each category
That looks like what you're asking...