Related
I got a little problem with my recursive query.
I got a database of menu of a bar.
We got: Category, each category got sub-categories and each-subcategories got multiple items.
The database is this one and the query is linked inside:
CREATE TABLE category (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
parent_id int(10) unsigned DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES category (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `items` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`cat_id` int unsigned DEFAULT NULL,
`parent_id` int unsigned DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `cat_id` (`cat_id`),
KEY `sub_id` (`parent_id`),
CONSTRAINT `cat_id` FOREIGN KEY (`cat_id`) REFERENCES `category` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sub_id` FOREIGN KEY (`parent_id`) REFERENCES `category` (`parent_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
BEGIN;
INSERT INTO `category` VALUES (1, 'Colazione', NULL);
INSERT INTO `category` VALUES (2, 'Pranzo', NULL);
INSERT INTO `category` VALUES (3, 'Primi piatti', 2);
INSERT INTO `category` VALUES (4, 'Second dish', 2);
INSERT INTO `category` VALUES (5, 'Other things for lunch', 2);
COMMIT;
-- ----------------------------
-- Records of items
-- ----------------------------
BEGIN;
INSERT INTO `items` VALUES (1, 1, NULL, 'Cornetto');
INSERT INTO `items` VALUES (2, 3, 2, 'Pasta al sugo 1');
INSERT INTO `items` VALUES (3, 3, 2, 'Pasta al sugo 2');
INSERT INTO `items` VALUES (4, 3, 2, 'Pasta al sugo 3');
INSERT INTO `items` VALUES (5, 3, 2, 'Pasta al sugo 1 X');
INSERT INTO `items` VALUES (6, 3, 2, 'Pasta al sugo 2 X');
INSERT INTO `items` VALUES (7, 4, 2, 'Pasta al sugo 3 X');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
Query:
with combine_trees as (
with make_tree as (
WITH RECURSIVE category_path AS
(
SELECT id, title, parent_id
FROM category
WHERE parent_id IS NULL
UNION ALL
SELECT c.id, c.title, c.parent_id
FROM category_path AS cp JOIN category AS c
ON cp.id = c.parent_id
)
SELECT cp.title, cp.id,
if(cp.id = category.id,
json_arrayagg(json_object('item_name', it.name)),
json_object(cp.title, json_object('items',json_arrayagg(json_array(json_object('item_name', it.name))))))
as tree
FROM category_path cp
INNER JOIN items it ON it.cat_id = cp.id
join category on category.id = ifnull(cp.parent_id, cp.id)
group by cp.title, cp.id, category.id
)
select json_arrayagg(json_object(title, json_array('items', tree))) output_json from make_tree group by id
)
select json_object('menu',group_concat(output_json)) as output from combine_trees;
https://sqlize.online/
The problem is that its not printing the result as JSON but its printing it formatted in one-string. How can we transform it in a JSON without that all the output is an unique string?
In your last line,
select json_object('menu',group_concat(output_json)) as output from combine_trees
you cannot use group_concat to combine the json arrays you get from the 2nd to last line (e.g. select json_arrayagg(...) output_json from make_tree group by id).
The arrays you get there each look like [...], and group_concat will give you [...], [...]. This is not a valid json array (which would need additional brackets around it, e.g. [[...],[...]]), but a string, and creating a json object from it will give you, well, that string as the value.
To combine your json arrays, you can use (as you did before) json_arrayagg instead of group_concat, e.g.
select json_object('menu',json_arrayagg(output_json)) as output from combine_trees
I have this query:
SELECT options.id, options.text,
COUNT(options2.id) AS num_children
FROM options
JOIN history_uids AS uids
ON uids.uid = options.id
LEFT JOIN options AS options2
ON (options.id = options2.id_parent)
LEFT JOIN history_uids AS uids1
ON uids1.uid = options2.id
WHERE (
options.id = 25
AND (uids1.active = 1 OR
options2.id IS NULL) # Problem
)
GROUP BY `options`.`id`
There is an options table, and another, history_uids, which has a column named uid with the id of every options, and an active column set to 1 or 0.
I am expecting to get a result with:
The ID and the text of the option which has the id 25, and which has active set to 1 in history_uids.
The number of options which have an id_parent equal to the id (25) and for which active in history_uids is set to 1
So whatever is this number I should get my row if it has active set to 1. I cannot understand how to achieve this: I wanna set as last condition uids1.active = 1 OR "options2 doesn't exist", but to get this I should have my active condition in the ON of options2, which is not possible because at that moment the table history_uids is not yet referenced...
In my case the row has active set to 1 and has 5 children with active set to 0, so I should get my row with num_children set at 0. Instead whatever combination I do in the JOIN or the WHERE I get either num_children set to 5 or no result at all.
Thanks for your help (and for reading!)
Here is the full structure and data for testing:
CREATE TABLE `history_uids` (
`uid` int(10) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `history_uids` (`uid`, `active`) VALUES
(1, 1),
(2, 0),
(3, 0),
(4, 0),
(5, 0),
(6, 0);
CREATE TABLE `options` (
`id` int(10) NOT NULL,
`id_parent` int(10) DEFAULT NULL,
`text` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `options` (`id`, `id_parent`, `text`) VALUES
(1, NULL, 'parent active'),
(2, 1, 'child 1 inactive'),
(3, 1, 'child 2 inactive'),
(4, 1, 'child 3 inactive'),
(5, 1, 'child 4 inactive'),
(6, 1, 'child 5 inactive');
ALTER TABLE `history_uids`
ADD PRIMARY KEY (`uid`);
ALTER TABLE `options`
ADD PRIMARY KEY (`id`),
ADD KEY `id_parent` (`id_parent`);
ALTER TABLE `options`
ADD CONSTRAINT `ibfk_3` FOREIGN KEY (`id_parent`) REFERENCES `options` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
ADD CONSTRAINT `ibfk_4` FOREIGN KEY (`id`) REFERENCES `history_uids` (`uid`) ON DELETE CASCADE ON UPDATE CASCADE;
You need to put the active test for the child row into the ON clause, so that inactive rows will be filtered out of the LEFT JOIN. And you should be counting uids1.uid, not options2.id, since the inactive rows aren't filtered out until you join with uids1.
SELECT options.id, options.text,
COUNT(uids1.uid) AS num_children
FROM options
JOIN history_uids AS uids
ON uids.uid = options.id
LEFT JOIN options AS options2
ON (options.id = options2.id_parent)
LEFT JOIN history_uids AS uids1
ON uids1.uid = options2.id and uids1.active = 1
WHERE
uids.active = 1
GROUP BY `options`.`id`
DEMO
I have a problem with my database and I hope you can help me with this.
I have tables like this:
categories
--------------------
|id | name | parent|
____________________
categories_x_products (m:n)
---------------------
|id | ctg_id | p_id |
---------------------
products
------------
| id, name |
------------
And my question is: "how to get all my & subcategories product count?"
For example:
categories:
id = 1, name = computers, parent = 0 (1 product)
id = 2, name = notebooks, parent = 1 (2 products)
and I want to get
computers : 3
notebooks : 2
I try this but does not working
select a.name, count(b.id)
FROM categories a
LEFT JOIN categories x ON x.id=a.parent
LEFT JOIN category_x_product b ON b.ctg_id=a.id
group by a.id
Thank you for answers.
Here is some example data:
-- Adminer 4.2.3 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `categories`;
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`parent` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `categories` (`id`, `name`, `parent`) VALUES
(1, 'computers', 0),
(2, 'notebooks', 1),
(3, 'lenovo', 2);
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `products` (`id`, `name`) VALUES
(1, 'lenovo thinkpad 001'),
(2, 'lenovo thinkpad 002'),
(3, 'lenovo thinkpad 003'),
(4, 'lenovo thinkpad 004'),
(5, 'lenovo thinkpad 005'),
(6, 'random comp.');
DROP TABLE IF EXISTS `product_x_category`;
CREATE TABLE `product_x_category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`category_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `product_x_category` (`id`, `product_id`, `category_id`) VALUES
(1, 1, 3),
(2, 2, 3),
(3, 3, 3),
(4, 4, 3),
(5, 5, 3),
(6, 6, 1);
-- 2016-02-23 08:16:30
I've tried to run your SQL Query (Create table and insert value).
And I run this query
SELECT a.name, COUNT( b.id )
FROM categories a
LEFT JOIN product_x_category b ON b.category_id = a.id
GROUP BY a.id
And it returns this
This is your product_x_Category table (where category_id --> 3 = lenovo and category_id --> 1 = computer)
I think the result is what you want, isn't it?
I'm having three tables: articles, tags and articles_tags. As you can imagine, each article can have multiple tags and each tag can be assigned to multiple articles. I have so-called main article (represented by unique URL) and would like to get related articles of it, based on shared tags between them like: if main article and article 2 has one tag in common, show both articles (and ideally, it would not show/include in the results the main article). Unique URL of main article is passed in SQL query.
The expected result is beyond my reach, so any help would be appreciated.
SQLFiddle
Copied code, if site above goes offline:
Databases and content:
CREATE TABLE `articles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`status` tinyint(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `articles_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`article_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `articles` (`url`, `title`, `status`) VALUES
('test-article-1', 'Test Article #1', 1),
('test-article-2', 'Test Article #2', 1),
('test-article-3', 'Test Article #3', 0),
('test-article-4', 'Test Article #4', 0),
('test-article-5', 'Test Article #5', 1);
INSERT INTO `tags` (`tag`, `url`) VALUES
('Test', 'test'),
('City', 'city'),
('Nature', 'nature');
INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(3, 1),
(3, 2),
(4, 2),
(5, 1);
Latest (not working properly) SQL query:
SELECT
tags.tag,
articles.url,
articles.title
FROM articles
LEFT JOIN articles_tags ON articles_tags.article_id=articles.id
LEFT JOIN tags ON articles_tags.tag_id=tags.id
WHERE (articles.url='test-article-1'
OR tags.id IN (articles_tags.tag_id))
AND articles.status=1
GROUP BY articles.id
Result:
As you can see on SQLFiddle, it shows articles 1, 2 and 5, but in my mind it should show only 1 and 5
Expected Result: Articles 1 and 5, ideally only 5 (excluding article 1 because it's the main one).
I am not quite sure I understand why you didn't expect article 2 in your results, as it and article 1 both have tag 2. This below should still return article 2, so it may not be what you want, but it is the most straight forward "similarly tagged ranking" query I can think of:
SELECT b.*, COUNT(1) AS tagMatches
FROM articles AS a
INNER JOIN articles_tags AS aTags ON a.id=aTags.article_id
INNER JOIN articles_tags AS bTags
ON aTags.article_id<>bTags.article_id
AND aTags.tag_id = bTags.tag_id
INNER JOIN articles AS b ON bTags.article_id
WHERE a.url = ?
GROUP BY b.url
ORDER BY tagMatches DESC, b.title
;
Edit: This assumes articles cannot have the same tag more than once. If this is not the case, it will skew the rankings (but that might be favorable if the duplicated tags should have more weight).
Edit2: It is also worth noting, that * probably should not be used for final results; I just used it here for simplicity.
Your OR condition OR tags.id IN (articles_tags.tag_id)) fires on these rows:
INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES
(1, 1),
...
(3, 1),
...,
(5, 1);
so, for me the result looks fine
I have two tables: categories and items. i have stored categories using nested set structure. Categories have items. Items can be only added to leaf nodes of a root category.
For eg:
Categories
Vehicles
Bikes
Bajaj
Automobiles
Art & Antiques
Amateur Art
Items can be added to category Bajaj, Automobiles and Amateur Art in this case.
Lets say there are 2 items inside Bajaj, 5 items inside Automobiles, 2 inside Amateur Art
For root level categories I want to display as follow:
- Vehicles (7 items)
- Art & Antiques (2 items)
How can I do this ?
Here is the sql dump to work with some sample data
--
-- Table structure for table `categories`
--
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL,
`title` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
`lft` int(11) NOT NULL,
`lvl` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
`root` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_3AF34668727ACA70` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=28 ;
--
-- Dumping data for table `categories`
--
INSERT INTO `categories` (`id`, `parent_id`, `title`, `lft`, `lvl`, `rgt`, `root`) VALUES
(22, NULL, 'Vehicles', 1, 0, 8, 22),
(23, 22, 'Bikes', 2, 1, 5, 22),
(24, 23, 'Bajaj', 3, 2, 4, 22),
(25, 22, 'Automobiles', 6, 1, 7, 22),
(26, NULL, 'Art & Antiques', 1, 0, 4, 26),
(27, 26, 'Amateur Art', 2, 1, 3, 26);
-- --------------------------------------------------------
--
-- Table structure for table `items`
--
CREATE TABLE IF NOT EXISTS `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`title` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_403EA91BA33E2D84` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
--
-- Dumping data for table `items`
--
INSERT INTO `items` (`id`, `category_id`, `title`) VALUES
(1, 24, 'Pulsor 150 cc'),
(2, 24, 'Discover 125 cc'),
(3, 27, 'Art of dream'),
(4, 25, 'Toyota Car');
--
-- Constraints for dumped tables
--
--
-- Constraints for table `categories`
--
ALTER TABLE `categories`
ADD CONSTRAINT `FK_3AF34668727ACA70` FOREIGN KEY (`parent_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL;
--
-- Constraints for table `items`
--
ALTER TABLE `items`
ADD CONSTRAINT `FK_403EA91BA33E2D84` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE;
root nodes have NULL in the field of parent_id
Update:
I was able to fetch for roots using this query:
SELECT c.id,c.title,cte.co FROM categories c
JOIN
(SELECT
c0_.id,c0_.root,COUNT(i.id) co
FROM
categories c0_
JOIN items i ON c0_.id=i.category_id
WHERE c0_.rgt = 1 + c0_.lft
GROUP BY c0_.id
) cte
ON cte.root=c.id
WHERE c.parent_id is null
The above query works for root level category. Now when the user clicks on root level category, I want to do the same.
for eg when somebody clicks on vehicles I should get:
Bikes (2)
Automobiles (5)
For that I tried :
SELECT c.id,c.title,cte.co FROM categories c
JOIN
(SELECT
c0_.id,c0_.root,COUNT(i.id) co
FROM
categories c0_
JOIN items i ON c0_.id=i.category_id
WHERE
c0_.rgt = 1 + c0_.lft
GROUP BY c0_.id
) cte
ON cte.root=c.id
WHERE c.parent_id=1
This returned empty result set. what is wrong in this query ?
SELECT parent.title,
( SELECT count(i.id) count FROM items i
WHERE category_id IN
(
SELECT child.id FROM categories child WHERE child.lft>=parent.lft AND
child.rgt<=parent.rgt AND child.root=parent.root
)
)
FROM categories parent
WHERE parent.parent_id=#parent_id;
Please inform me if this does not work
How about something like this:
SELECT COUNT(items.id),
(SELECT lookup.title
FROM categories lookup
WHERE lookup.id = categories.root)
FROM items, categories
WHERE categories.id = items.category_id
GROUP BY categories.root;
based on the input from the script above gives me:
3 | Vehicles
1 | Art & Antiques
to select for a particular root add
AND categories.root = #id
where #id is your root id you're looking for.
Alternatively if you want to select by the root name do something (scary) like this:
SELECT title, total
FROM
(SELECT COUNT(items.id) total,
(SELECT lookup.title
FROM categories lookup
WHERE lookup.id = categories.root) title
FROM items, categories
WHERE categories.id = items.category_id
GROUP BY categories.root;
) AS some_table
WHERE some_table.title = #root_name
where #root_name is the name of your root node (in quotes of course)
Something like this should work.
select c1.title, count(*) itemcount
from categories c1 join categories c2
on c2.parent_id = c1.id
join items on items.category_id = c2.id
group by c1.title