I have got 2 MySQL tables that have (InnoDB) foreign keys going into each other. For example,
-- 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 `a`;
CREATE TABLE `a` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`null_or_b_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `null_or_b_id` (`null_or_b_id`),
CONSTRAINT `a_ibfk_1` FOREIGN KEY (`null_or_b_id`) REFERENCES `b` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `a` (`id`, `null_or_b_id`) VALUES
(1, NULL),
(2, 2),
(4, 3),
(3, 4),
(5, 5),
(6, 6),
(7, 7),
(8, 8);
DROP TABLE IF EXISTS `b`;
CREATE TABLE `b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`null_or_a_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `null_or_a_id` (`null_or_a_id`),
CONSTRAINT `b_ibfk_1` FOREIGN KEY (`null_or_a_id`) REFERENCES `a` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `b` (`id`, `null_or_a_id`) VALUES
(1, NULL),
(8, NULL),
(2, 2),
(4, 3),
(3, 4),
(5, 6),
(6, 7),
(7, 8);
-- 2016-02-03 06:45:07
What I want to do is delete the records with the ids 1, 2, 3 and 5 from a and delete any records that need to deleted in both a and b due to the foreign key constraints. I have tried:
delete from a where a.id in (1,2,3,5);
delete a,b from a left join b on b.null_or_a_id = a.id where a.id in (1,2,3,5);
Both the above give the same error:
Error in query (1451): Cannot delete or update a parent row: a foreign key constraint fails (`test/multi_delete_with_references`.`b`, CONSTRAINT `b_ibfk_1` FOREIGN KEY (`null_or_a_id`) REFERENCES `a` (`id`))
I get the same error even if I remove the foreign key constraint on b defined in a.
Things I can't do:
Disable foreign key checks: because both the tables are also referenced by other tables and I don't want those tables to have orphaned rows, if this delete is going to cause such orphaned rows I need this delete to fail.
Delete from the child table first: because as you can see in the case of rows with id 2 in both tables, they reference each other so one can't be deleted without the other, also in the case of rows with ids 3 and 4 in both tables, they form a self-referencing chain.
I have looked at the answer here and it won't work for me because both the tables reference each other.
Is there a way out of this?
BTW, I also tried to generate complex, nested queries dynamically but it ended up being endless:
delete from a where id in (1,2,3,5);
delete from b where null_or_a_id is not null and null_or_a_id in (select * from (select id from a where id in (1,2,3,5)) x);
delete from a where null_or_b_id is not null and null_or_b_id in (select * from (select id from b where null_or_a_id is not null and null_or_a_id in (select * from (select id from a where id in (1,2,3,5)) x)) x);
delete from b where null_or_a_id is not null and null_or_a_id in (select * from (select id from a where null_or_b_id is not null and null_or_b_id in (select * from (select id from b where null_or_a_id is not null and null_or_a_id in (select * from (select id from a where id in (1,2,3,5)) x)) x)) x)
...
Your problem is quite similar to this one and the same solution applies: first remove the references by setting to NULL the referencing columns in those rows that you intend to delete. Then delete.
Related
I am working on a personal project and I don't know how to create a dynamic query with MySQL. I would like to get all version of a file stored in a database. For this purpose I replaced file by simplest name "first" in a test database created with this script:
/*
DROP DATABASE IF EXISTS request_test;
CREATE DATABASE IF NOT EXISTS request_test
DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE request_test;
*/
DROP TABLE IF EXISTS first_next;
CREATE TABLE first_next(
id INT(3) UNSIGNED ,
first VARCHAR(40) NOT NULL,
ik_next INT(3) NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE first_next ADD PRIMARY KEY (`id`);
ALTER TABLE first_next MODIFY `id` INT(3) NOT NULL AUTO_INCREMENT;
ALTER TABLE first_next ADD CONSTRAINT fn1 FOREIGN KEY (ik_next) REFERENCES first_next(id);
Sample data:
INSERT INTO first_next (id, first, ik_next)
VALUES
(1, 'toto1', NULL),
(2, 'titi1', NULL),
(3, 'riri1', NULL),
(4, 'titi2', 2),
(5, 'fifi1', NULL),
(6, 'toto2', 1),
(7, 'titi3', 4),
(8, 'fifi2', 5),
(9, 'fifi3', 8),
(10, 'toto3', 6),
(11, 'loulou1', NULL),
(12, 'toto4', 10);
As you can see toto1 is replaced by successively 3 other name toto2, toto3 and toto4. With the following query I get the full history of toto1:
SELECT first.id,first.first AS initiale,
second.id,second.first AS suivante_1,
third.id,third.first AS suivante_2,
fourth.id,fourth.first AS suivante_3
FROM first_next AS first
INNER JOIN first_next AS second
ON second.ik_next = first.id
INNER JOIN first_next AS third
ON third.ik_next = second.id
INNER JOIN first_next AS fourth
ON fourth.ik_next = third.id
WHERE fourth.id = 12
ORDER BY first.id ASC;
In this case I added manually 3 "INNER JOIN" and I would like to dynamically adapt query with history of name. Imagine if toto5, toto6 exists. This is where I am stuck and I don't know how to solve my problem.
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
Hello,
I have 3 tables:
CREATE TABLE `invoice` (
`id` int(11) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `invoice` (`id`) VALUES
(1),
(2),
(3);
CREATE TABLE `invoice_deduction` (
`id` int(11) NOT NULL,
`invoiceId` int(11) NOT NULL,
`deductionId` int(11) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (`invoiceId`) REFERENCES `invoice` (`id`),
FOREIGN KEY (`deductionId`) REFERENCES `invoice` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `invoice_deduction` (`id`, `invoiceId`, `deductionId`) VALUES
(1, 2, 1),
(2, 3, 1),
(3, 3, 2);
CREATE TABLE `invoice_item` (
`id` int(11) NOT NULL,
`invoiceId` int(11) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (`invoiceId`) REFERENCES `invoice` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `invoice_item` (`id`, `invoiceId`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 2),
(5, 2),
(6, 3),
(7, 3),
(8, 3);
For example:
The first invoice contains 3 items (total: 3 items)
The second invoice contains 3 items from first invoice (deduction) and 2 new items (total: 5 items)
The third invoice contains 3 items from first invoice (deduction), 2 items from second invoice (deduction) and 3 new items (total: 8 items)
So I want to have a query with this result:
id | count of items (with deductions)
3 | 8
2 | 5
1 | 3
I start with this query:
SELECT
i.id, COUNT(*) as countItems
FROM
invoice i JOIN invoice_item it ON i.id = it.invoiceId
GROUP BY
it.invoiceId
ORDER BY
countItems
DESC
I thank you in advance for your help.
I think you want a cumulative sum:
SELECT i.id, COUNT(*) as countItems,
SUM(COUNT(*)) OVER (ORDER BY it.id)
FROM invoice i JOIN
invoice_item it
ON i.id = it.invoiceId
GROUP BY i.id
ORDER BY countItems;
I have no idea why you are using a LEFT JOIN. I don't know why you would have invoiceId values that do not match a valid invoice. So I switched it to an inner join.
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
So I've been having a problem and either I am forgetting something fundamental or I found a bug. I've narrowed it down to this:
My table:
CREATE TABLE `tbl` (
`id` int(11) NOT NULL,
`timestmp` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Simple. My query:
insert into tbl (id, timestmp) values (1, 1)
on duplicate key update timestmp = if(values(timestmp) > timestmp,
values(timestmp), timestmp)
Table will contain
(1, 1)
Repeat the query with
values (1, 2)
Table will contain
(1, 2)
Repeat the query with
values (1, 1)
Table will still contain
(1, 2)
Great. Just what we want. Instead with this table:
CREATE TABLE `tbl` (
`id` int(11) NOT NULL,
`timestmp` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Let's do the same queries:
insert into tbl (id, timestmp) values (1, 1)
on duplicate key update timestmp = if(values(timestmp) > timestmp,
values(timestmp), timestmp)
Table contains:
(1, 1)
Query with:
values (1, 2)
Table contains:
(1, 1)
What? Let's try
values (1, -1)
Table contains:
(1, -1)
JKLFSD!!!!! What's going on? Is this supposed to happen? Am I completely misinterpreting something? Do I need a special function for bigint comparison?