mysql - recursive query for similar records - mysql

I have two tables with below constructions:
create table article (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
title varchar(50) NOT NULL,
text text,
date timestamp NOT NULL default CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;
create table article_relate (
article_id_1 int NOT NULL,
article_id_2 int NOT NULL
);
Example records in table "article"
id title
--------------------------------------
1 About Ferrari
2 About Lamborghini
3 About my friends
4 About my kitchen
In the second table - "article_relates", I described the relations of similar themed records from the first table.
article_id_1 article_id_2
-----------------------------------
1 2
2 1
when I add the next article, I will link it to the first article I know about identical subject matter.
Insert into article (id, title) values (5, 'About Maserati');
and a link to a similar article with id = 2
Insert into article_relate (article_id_1, article_id_2) values (5, 2);
Insert into article_relate (article_id_1, article_id_2) values (2, 5);
Now I want to send a query to the database to get all entries related to the article about id = 5. Also, those entries with which the article about id = 2 is associated. The result for this query should be records with id = 5, 2 and 1
What query should be built to get this effect?
Can you help me?

You can simply use a select with an inner select query.
SELECT article.id,article.name FROM article WHERE article.id IN (SELECT article_id_2 from article_relate where article_id_1 = 5);

sounds like a JOIN clause:
SELECT article.id,
article.name,
article_relate.id AS article_relate_id
FROM article
LEFT JOIN article_relates ON article.id = article_relate.article_id_1
OR article.id = article_related
WHERE article.id = 5
will list all related articles for article id 5. does this help you?
about join in mysql - https://dev.mysql.com/doc/refman/8.0/en/join.html

Related

How to autofill a column with sequential number, but based on a parameter?

I'm trying to add a new column to few MySQL (MariaDB) tables. I want to make the column auto-filled with sequential number. What I figured out so far is:
SELECT #count:=0;
UPDATE users SET ordering = #count:=#count+1;
It works perfectly. However, I don't know how to make it so that the order in which the numbers are assigned is based on another value, namely ascending order of another integer field called regdate. Here's an example.
Current result:
login
regdate
ordering
user
1633205589
1
guy
16332060000
3
account
16332090000
2
data
16332095000
4
What I want:
login
regdate
ordering
user
1633205589
1
guy
16332060000
2
account
16332090000
3
data
16332095000
4
I hope it's pretty clear and concise :)
You can use a joined table with ROW_NUMBER
CREATE TABLE users (
`login` VARCHAR(7),
`regdate` VARCHAR(20) ,
`ordering` INTEGER
);
INSERT INTO users
(`login`, `regdate`, `ordering`)
VALUES
('user', '1633205589', '1'),
('guy', '16332060000', '3'),
('account', '16332090000', '2'),
('data', '16332095000', '4');
UPDATE users u1
JOIN (SELECT `login`, `regdate`, row_number() over (ORDER BY regdate ASC) rn FROM users) u2
ON u1.`login` = u2.`login` AND u1.`regdate` = u2.`regdate`
SET u1.ordering = u2.rn ;
SELECT * FROM users
login | regdate | ordering
:------ | :---------- | -------:
user | 1633205589 | 1
guy | 16332060000 | 2
account | 16332090000 | 3
data | 16332095000 | 4
db<>fiddle here
UPDATE users usr1
JOIN (SELECT #a:=#a+1 rn, id FROM users, (SELECT #a:= 0) AS a) usr2
ON usr1.id = usr2.id
SET usr.serial = usr2.rn
Does users have a PRIMARY KEY? If so, the following won't work. (Please provide SHOW CREATE TABLE users;)
CREATE TABLE users2 LIKE users;
ALTER TABLE users2
ADD COLUMN ordering INT UNSIGNED NOT NULL AUTO_INCREMENT
PRIMARY KEY(ordering);
INSERT INTO users2
SELECT *, -- the existing columns
NULL -- 1,2,etc automatically filled in
FROM users;
RENAME TABLE users TO trash,
users2 TO users;
DROP TABLE trash;
What happens when a new user is added? AUTO_INCREMENT automatically gives it the next higher number. Is that OK? If you need to renumber the rows, no method is good.
As long as your update just involves a single table, you can specify an order:
UPDATE users SET ordering = #count:=#count+1 ORDER BY regdate;

How to display 'InValid' records depending on, if the latest record is validated?

Goal
Retrieve InValid records but with some complexity. Let me explain.
Taking a look at this output: There is an InValid record for ItemId of 1.
Using the query below, I am able to see InValid records - to display to the user this Item needs to be Re-Checked.
SELECT * FROM `Records` WHERE IsValid = 0;
Problem
This is where I am stuck.
In the Red is the InValid record, recorded on the 28-06-2021. The next day, 29-09-2021 the ItemId is now a Valid record.
But using this query below is not relevant anymore, as it will show me the records that are still Invalid. Even though the record has been validated the next day.
SELECT * FROM `Records` WHERE IsValid = 0;
My idea to solve this problem (See Edit 1 below, for further details to this solution)
My idea would be to create a Trigger that will check if today's record is valid, if it is valid, then Update all the items to true where date is before today. From here, I can use the simple query above to see InValid records.
Also, I thought of creating a History table and a trigger to see what actions have been performed on the Records table.
Question
I am not sure if my idea is appropriate to solve my problem, creating a trigger to update all previous records does not seem the records are valid at all. But, my history table will show me valid values.
Is there a query I can use and avoid creating any triggers or it's best to go with my solution?
Schema (MySQL v5.7)
-- Base
CREATE TABLE `Items` (
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
ItemName VARCHAR(30));
CREATE TABLE `Records` (
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Date TIMESTAMP NOT NULL,
ItemId INT NOT NULL,
IsValid BOOLEAN NOT NULL,
FOREIGN KEY (ItemId) REFERENCES `Items`(Id));
-- History
CREATE TABLE `Records_History` LIKE `Records`;
ALTER TABLE `Records_History`
MODIFY COLUMN Id INT NOT NULL,
DROP PRIMARY KEY,
ADD Action VARCHAR(8) DEFAULT 'insert' FIRST;
CREATE TRIGGER Records_AfterInsert
AFTER INSERT ON `Records` FOR EACH ROW
INSERT INTO `Records_History` SELECT 'insert', d.*
FROM `Records` AS d WHERE d.Id = NEW.Id;
-- Insert Records
INSERT INTO `Items`
(ItemName)
VALUES
('Item 1'),
('Item 2');
INSERT INTO `Records`
(Date, ItemId, IsValid)
VALUES
('2021-09-28', 1, 0),
('2021-09-28', 2, 1),
('2021-09-29', 1, 1),
('2021-09-29', 2, 1);
Query #1
select * from `Records`;
Id
Date
ItemId
IsValid
1
2021-09-28 00:00:00
1
0
2
2021-09-28 00:00:00
2
1
3
2021-09-29 00:00:00
1
1
4
2021-09-29 00:00:00
2
1
Query #2
select * from `Records_History`;
Action
Id
Date
ItemId
IsValid
insert
1
2021-09-28 00:00:00
1
0
insert
2
2021-09-28 00:00:00
2
1
insert
3
2021-09-29 00:00:00
1
1
insert
4
2021-09-29 00:00:00
2
1
View on DB Fiddle
Edit 1
Unfortunately my solution is not an option. As I will hit this error:
Tried my solution: Error Code: 1442. Can't update table 'Records' in stored function/trigger because it is already used by statement which invoked this stored function
This basically means: I have a chance of causing an infinite loop.
This is what I have done to achieve my goal:
Schema (MySQL v5.7)
SET GLOBAL sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY',''));
CREATE TABLE `Items` (
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
ItemName VARCHAR(30));
INSERT INTO `Items`
(ItemName)
VALUES
('Item 1'),
('Item 2');
CREATE TABLE `Records` (
Id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
Date TIMESTAMP NOT NULL,
ItemId INT NOT NULL,
IsValid BOOLEAN NOT NULL,
FOREIGN KEY (ItemId) REFERENCES `Items`(Id));
INSERT INTO `Records`
(Date, ItemId, IsValid)
VALUES
('2021-09-28', 1, 1),
('2021-09-28', 2, 1),
('2021-09-29', 1, 0),
('2021-09-29', 2, 1),
('2021-09-30', 1, 1),
('2021-09-30', 2, 1),
('2021-10-01', 1, 0),
('2021-10-01', 2, 1);
Query #1
SELECT * FROM Records a WHERE IsValid = 0 AND ItemId NOT IN (
SELECT ItemId FROM Records b WHERE IsValid = 1 AND b.Date >= a.Date
) GROUP BY ItemId;
Id
Date
ItemId
IsValid
7
2021-10-01 00:00:00
1
0
View on DB Fiddle

How to get this table in MySQL?

I have the following schema:
And I want to generate a table with 3 columns - tag's name, thread's name, answers count.
Example:
Foo bar 5
Foo something 6
Foo2 somethingElse 3
What's more, there's a table "priority_threads" (1:1 with threads). I want to display only those priority threads in this table.
How can I do this? I have absolutely no idea how to even start. The only thing I did is:
SELECT tag.name, thread.title, COUNT(answer.id_answer)
FROM tag, thread, answer
WHERE thread.id_tag = tag.id_tag
AND answer.id_thread = thread.id_thread
AND thread.id_thread = priority_threads.id_thread
GROUP BY tag.name, thread.title
ORDER BY tag.name;
However using this query all values in the answer's count column are the same - count(*) from Answer table...
Try this:
SELECT tag.name, thread.title, COUNT(answer.id_answer)
FROM tag
JOIN thread ON
tag.id_tag=thread.id_tag
JOIN answer ON
thread.id_thread = answer.id_thread
JOIN priority_threads
ON thread.id_thread = priority_threads.id_thread
GROUP BY tag.name, thread.title
ORDER BY tag.name;
Here is a SQLfiddle link to the query to test it out;
Sample code:
CREATE TABLE Tag
(
id_tag int auto_increment primary key,
name varchar(20)
);
INSERT INTO Tag
(name)
VALUES
('Foo'),
('Foo2');
CREATE TABLE Thread
(
id_thread int auto_increment primary key,
id_tag int,
title varchar(20)
);
INSERT INTO Thread
(id_tag, title)
VALUES
(1,'Bar'),
(1,'Something'),
(2,'SomethingElse');
CREATE TABLE Answer
(
id_answer int auto_increment primary key,
id_thread int,
text varchar(200)
);
INSERT INTO Answer
(id_thread, text)
VALUES
(1,'jlkjalkjl'),
(1,'ioioixhakjjkj'),
(1, 'jjalkjijkajk'),
(1, 'jjalkjijkajk'),
(1, 'jjalkjijkajk'),
(2, 'jjalkjijkajk'),
(2, 'jjalkjijkajk'),
(2, 'jjalkjijkajk'),
(2, 'qqweeweraata'),
(2, 'jjalkjijkajk'),
(2, 'jjalkjijkajk'),
(3, 'popoapopop'),
(3, 'zkkasjkljz'),
(3, 'jjalkjijkajk')
;
CREATE TABLE priority_threads
(
id_priority_threads int auto_increment primary key,
id_thread int,
priority int
);
INSERT INTO priority_threads
(id_thread, priority)
VALUES
(1,1),
(3,2);
so:
SELECT tag.name, thread.title, COUNT(answer.id_answer)
FROM tag, thread, answer
WHERE thread.id_tag = tag.id_tag
AND answer.id_thread = thread.id_thread
GROUP BY tag.name, thread.title
ORDER BY tag.name;
can look like this:
SELECT tag.name, thread.title, COUNT(answer.id_answer)
FROM tag
JOIN thread ON
tag.id_tag=thread.id_tag
JOIN answer ON
thread.id_thread = answer.id_thread
GROUP BY tag.name, thread.title
ORDER BY tag.name;
The results are the same, the former is preferred by most. Okay, but I think the real issue here is this line here in your posted code:
AND thread.id_thread = priority_threads.id_thread
you are referencing another table, priority_threads, which is not in your query. Either add the join or remove it and you should be good to go.
-james

How to update database records based on values in another table?

i have USERS TABLE that contains the following fields _id name number_of_posts
and i have POSTS TABLE that contains the following fields _id user_id post_text
no the relationship between the users and posts is one- to -many i.e one user have many post
the question is how to update the number_of_posts in users table such that it will hold the number of post in the posts table
UPDATE
I have these tables filled with about hundreds of records there is no way to use the trigger to update the number_of_posts .
Below given sample script shows how to get the number of posts associated with a user by joining both users and posts table using LEFT OUTER JOIN and then uses GROUP BY on the *_id* field in users table to fetch the posts count.
Click here to view the sample in SQL Fiddle.
Query:
CREATE TABLE users
(
_id INT NOT NULL
, name VARCHAR(30) NOT NULL
);
CREATE TABLE posts
(
_id INT NOT NULL
, _userid INT NOT NULL
, name VARCHAR(30) NOT NULL
);
INSERT INTO users (_id, name) VALUES
(1, 'user 1'),
(2, 'user 2'),
(3, 'user 3');
INSERT INTO posts (_id, _userid, name) VALUES
(1, 1, 'post 1'),
(2, 1, 'post 2'),
(3, 2, 'post 3');
SELECT u._id
, u.name
, COUNT(p._id) number_of_posts
FROM users u
LEFT OUTER JOIN posts p
ON u._id = p._userid
GROUP BY u._id;
Output:
_ID NAME NUMBER_OF_POSTS
--- ------ ---------------
1 user 1 2
2 user 2 1
3 user 3 0
You can do this with a trigger on the posts table. Every time a post is inserted, update the number_of_posts on the users table.
See http://dev.mysql.com/doc/refman/5.6/en/triggers.html
Update: full worked solution
drop table if exists users;
create table users (
_id bigint unsigned auto_increment primary key,
name varchar(50) not null,
number_of_posts integer not null default 0
);
drop table if exists posts;
create table posts (
_id bigint unsigned auto_increment primary key,
user_id bigint unsigned not null,
post_text text
);
-- Populate with dummy data
insert into users (name) values ('Bob'), ('Sally');
insert into posts (user_id, post_text)
values (1, 'First!!'), (1, 'Second...'),
(2, 'Post 1'), (2, 'another post'), (2, 'more posts');
-- One-time update of users table
update users u
set u.number_of_posts = (
select count(0) from posts p
where u._id = p.user_id
);
-- trigger to keep post count up to date when future posts are made
drop trigger if exists trg_post_count;
delimiter $$
create trigger trg_post_count
after insert on posts
for each row
begin
select count(0) into #post_count
from posts
where user_id = NEW.user_id;
update users
set number_of_posts = #post_count
where _id = NEW.user_id;
end;
$$
delimiter ;
-- test trigger
insert into posts (user_id) values (2);
select * from users;
As a one time update, try this, but the trigger solution is the right one to keep it current:
UPDATE users AS u SET u.number_of posts = (SELECT COUNT(*) FROM posts AS p WHERE p.user_id=u.user_id)
As dwurf suggested, I would definitely recommend putting this in a trigger to keep the field up-to-date. However, I completely disagree with him that it should simply be a number that you increment on each update. That's fragile and error prone. You should use the following query to figure out exactly how many posts each user has:
update
users
set
number_of_posts = (
select
count(0)
from
posts p
where
p.user_id = users.id
)
;

Aggregate joined table results in an SQL SELECT on MySQL 5

I have the following schema, and would like to do a query that returns one row for each entry in the articles table, with it's corresponding content column from the content table, and a column with each of that articles tags, such as you might get by using concat.
The query should SELECT only rows that match a certain tag. So if the tag atdi was provided, the result set would look something like:
id content tags
1 on my way nails broke and fell song,atdi,invalid
3 im all alone so far up here and my oxygen is all gone song,atdi,hourglass
4 you know your insides true better than i do song,atdi,starslight
I've tried a few different ways with subqueries, but keep getting errors - it's quite frustrating.
Here's the schema:
CREATE TABLE articles (
id int not null default 0,
published datetime,
author int not null default 0,
primary key (id)
);
INSERT INTO articles
(id, published, author)
VALUES
(1, CURRENT_TIMESTAMP, 1),
(2, CURRENT_TIMESTAMP, 1),
(3, CURRENT_TIMESTAMP, 1),
(4, CURRENT_TIMESTAMP, 1);
CREATE TABLE content (
id int not null default 0,
content varchar(250) not null default '',
primary key (id)
);
INSERT INTO content
(id,content)
VALUES
(1,'on my way nails broke and fell'),
(2,'exo skeleton junction at the railroad delayed'),
(3,'im all alone so far up here and my oxygen is all gone'),
(4,'you know your insides true better than i do');
CREATE TABLE tags (
id int not null default 0,
tag varchar(100) not null default '',
primary key (id,tag)
);
INSERT INTO tags
(id,tag)
VALUES
(1,"song"),
(2,"song"),
(3,"song"),
(4,"song"),
(1,"atdi"),
(2,"mars"),
(3,"atdi"),
(4,"atdi"),
(1,"invalid"),
(2,"roulette"),
(3,"hourglass"),
(4,"starslight");
Try something like this one
select a.id, a.content, b.tags_1
from content as a inner join (
select id, GROUP_CONCAT(tag SEPARATOR ',') as tags_1 FROM tags group by id
) as b on a.id = b.id
INNER JOIN tags AS c ON a.id = c.id
WHERE c.tag = 'atdi'
Using the GROUP_CONCAT() method