MySQL One-to-Many: Select where only one relations exists - mysql

My Tables:
CREATE TABLE `binary` (
`binaryid` int(15) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`binaryid`)
);
CREATE TABLE `binarycollection` (
`binaryid` int(10) unsigned NOT NULL,
`collectionid` int(10) unsigned NOT NULL,
UNIQUE KEY `collectionid` (`collectionid`,`binaryid`),
KEY `binaryid` (`binaryid`)
);
In the binary table there can only exist one record to a binaryid.
The binarycollection table ties the binary to multiple collections.
What I need to do is make a query that will select all rows in binary that have exactly 1 relations in binarycollection.
So given the example:
binary:
1
2
3
4
5
6
7
binarycollection:
(binaryid collectionid)
1 1
2 1
3 1
3 2
4 1
4 2
5 2
6 2
It should return binaryids 1, 2, 5, and 6.
Any help is appreciated. :)
ps. This needs to be efficient, the tables contain millions of rows.

Use GROUP BY :
Select binaryid from binarycollection group by binaryid having count(*)=1

It should work out to a simple query since your referential integrity doesn't allow repeat pairs in the binarycollection table:
SELECT binaryid
FROM binarycollection
GROUP BY binaryid
HAVING ( COUNT(binaryid) = 1 )

join it with the original binary table to check for valid reference then group the binaryID
SELECT a.binaryid
FROM `binary` a
INNER JOIN `binarycollection` b
on a.binaryid = b.binaryid
GROUP BY a.binaryid
HAVING COUNT(a.binaryid) = 1

Related

Recursive query hangs then get "Error Code: 1030. Got error 1 - 'Operation not permitted' from storage engine" error

I'm trying to build a recursive query to enable me to find all future sports match records for the two players of a given match. In addition to this I need the query to return any match for any player that plays in any descendant match. To illustrate using some example data:
match_id
match_date
p1_id
p2_id
1
01/01/2022
1
2
2
02/01/2022
1
3
3
03/01/2022
3
4
4
04/01/2022
5
6
I only really need match_id so if the start match is match_id = 1 then I'm looking for the query to return 1. The query should also return 2 because p1_id = 1 played in the start match. The query should also return 3 because p2_id = 3 played in match_id = 2.
I've written the following query:
WITH RECURSIVE match_ids AS (
SELECT
rt1.match_id,
rt1.p1_id,
rt1.p2_id,
rt1.match_date
FROM recursive_test_so AS rt1
WHERE rt1.match_id = 1
UNION ALL
SELECT
rt2.match_id,
rt2.p1_id,
rt2.p2_id,
rt2.match_date
FROM recursive_test_so AS rt2
JOIN match_ids ON
rt2.match_date > match_ids.match_date
WHERE (
rt2.p1_id IN (match_ids.p1_id, match_ids.p2_id)
OR rt2.p2_id IN (match_ids.p1_id, match_ids.p2_id)
)
)
SELECT DISTINCT match_id
FROM match_ids;
This works fine on the sample data.
However, when I scale the data up to 10k rows then the query runs for about 5 mins with no output and then I get the following error:
Error Code: 1030. Got error 1 - 'Operation not permitted' from storage engine
What might I be doing wrong?
SQL to replicate the sample data table:
CREATE TABLE `recursive_test_so` (
`match_id` int NOT NULL,
`match_date` date NOT NULL,
`p1_id` int NOT NULL,
`p2_id` int NOT NULL,
PRIMARY KEY (`match_id`),
KEY `match_date` (`match_date`),
KEY `p1_id` (`p1_id`),
KEY `p2_id` (`p2_id`),
KEY `comp_all` (`match_date`,`p1_id`,`p2_id`),
KEY `comp_player_ids` (`p1_id`,`p2_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci;
INSERT INTO `recursive_test_so`
VALUES
(1,'2022-01-01',1,2),
(2,'2022-01-02',1,3),
(3,'2022-01-03',3,4),
(4,'2022-01-04',5,6);
Not sure how I could post the 10k rows data?

get next question if exists, if not go back to the start - mysql

I have questions about different categories, questions about math, about english...
my table questions:
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`cat_id` int(11) UNSIGNED NOT NULL,
`question` text NOT NULL,
KEY `ids` (`id`, `cat_id`),
FOREIGN KEY (`cat_id`) REFERENCES categories (`id`),
PRIMARY KEY (`id`)
So, for example, the user is answering question about math (cat_id = 1), he is in question id 3 (the first one). When he click NEXT I pass the question id to mysql to get next one:
SELECT id, question FROM questions where cat_id = 1 and id > 3 limit 1
With this select I get the next math question (id 5). But if it is the last math question I'd like to go back to the first question (for example no id > 6, go to the id = 3):
1 - 3(english) - can or may?
2 - 2(history) - Who Discovered America?
3 - 1(math) - 1+1 = ?
4 - 3(english) - can or could?
5- 1(math) - 2+2 = ?
6 - 1(math) - 3+3 = ?
7 - 2(history) - When Was the War of 1812?
So how can I go back to the first question of some category if there is not a new one after to select? I'd like to create an infinity loop in questions.
This query should work:
select id, question from (
SELECT id, question FROM questions where cat_id = 1 and id > 3 order by id limit 1
UNION
SELECT id, question FROM questions where cat_id = 1 order by id limit 1
) limit 1
Select one record with id greater than your id and the first of all. So if you get no with id greater your id you shoult get the first one

MySQL - Join row with the next N smaller rows

I have a table:
id timestamp
1 1
23 2
12 4
45 6
3 7
4 8
I need this result:
major minor
1 2
1 4
1 6
2 4
2 6
2 7
I need to join each number, with the next 3 smallest numbers. Since these numbers are inserted out of order, I can't use the ids.
Because the numbers are also not in regular intervals I cannot set a specific limit to find the max number to join with.
Solutions I have:
I could create a temp table and use an auto increment id to do this.
I can do this for a single number, and write a script to iterate through the table. This is the query for it (Going with this for now, till something better comes up):
SELECT * FROM
(SELECT id major_id, timestamp major_timestamp FROM timestamps WHERE interval_id=7 ORDER BY timestamp DESC limit 1) timestamps_major
LEFT JOIN
(SELECT id minor_id, timestamp minor_timestamp FROM timestamps WHERE timestamp < (SELECT timestamp FROM timestamps WHERE interval_id=7 ORDER BY timestamp DESC limit 1) ORDER BY timestamp DESC LIMIT 2) timestamps_minor
ON major_timestamp>minor_timestamp
This just needs to be done for all numbers once, and then once per day to calculate and store a moving average. So speed is not an issue.
Wondering what is the best way to approach this. Thanks.
EDIT:
This is the actual table with timestamps and ids. The example I posted is just simplified for the sake of the question.
CREATE TABLE `timestamps` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`interval_id` tinyint(3) unsigned NOT NULL,
`timestamp` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `interval_timestamp` (`interval_id`,`timestamp`),
KEY `interval_id` (`interval_id`),
KEY `timestamp` (`timestamp`),
CONSTRAINT `timestamps_ibfk_1` FOREIGN KEY (`interval_id`) REFERENCES `intervals` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=75157 DEFAULT CHARSET=latin1
Here's a possible solution (see this sqlfiddle to play around with it)
SELECT *
FROM mytable major inner join mytable minor
ON minor.timestamp > major.timestamp
WHERE (SELECT COUNT(*) FROM mytable m WHERE m.timestamp < minor.timestamp and m.timestamp > major.timestamp) < 3
ORDER BY major.timestamp, minor.timestamp
I'm definitely not confident this is the cleanest solution (and I didn't do anything to handle "ties" for equal timestamps), but it does do what you want so it might be something to build off of at a minimum.
All I am doing is joining the tables then counting the number of rows "between" the major and minor so that I don't get too many.

whether to use pivot or non-pivot without any aggregate function

I got two tables table 1 and 2 as shown below. I need to insert the Std_rid for the row which has A and its corresponding Act_rid present in table 2 in resultant table 3. I'm only using the sample here. I'm very confused whether to use pivot or unpivot here. Any help appreciated. Thanks
Table 1
----------
G_Standard N_Standard Skill One_for_all Am_Water B2Future Std_rid
ELW.6 Lit.W.K.6 Writing A 1
ELW.8 Lit.W.K.8 Writing A 2
ELW.7 Lit.W.K.7 Writing A 3
Table 2
----------
Act_rid Act_Desc date createdby
3 Am_Water 2/4/15 sys
6 B2Future 2/4/15 sys
1 One_for_all 2/14/15 sys
Output Result Table 3
----------
ID Std_rid Act_rid
1 1 3
2 2 6
3 3 1
This is an UNPIVOT. UNPIVOT turns columns into rows.
;WITH Unpivot_Table_1 AS (
SELECT Std_rid,
Act_Desc
FROM [Table 1]
UNPIVOT (Value FOR Act_Desc IN ([One_for_all],[Am_Water],[B2Future])) u
WHERE u.Value = 'X'
)
SELECT ROW_NUMBER() OVER(ORDER BY t1.Std_rid) AS ID,
t1.Std_rid,
t2.Act_rid
FROM Unpivot_Table_1 t1
JOIN [Table 2] t2
ON t2.Act_Desc = t1.Act_Desc;
I hope this is something for data integration. Otherwise, y'all need to shoot whomever designed this.
Test data:
CREATE TABLE dbo.[Table 1]
(
Std_rid INT NOT NULL PRIMARY KEY,
One_for_all VARCHAR(1),
Am_Water VARCHAR(1),
B2Future VARCHAR(1)
);
INSERT INTO dbo.[Table 1] (Std_rid,One_for_all,Am_Water,B2Future)
VALUES
(1,NULL,'X',NULL),
(2,NULL,NULL,'X'),
(3,'X',NULL,NULL);
CREATE TABLE dbo.[Table 2]
(
Act_rid INT NOT NULL PRIMARY KEY,
Act_Desc VARCHAR(30)
);
INSERT INTO dbo.[Table 2] (Act_rid,Act_Desc)
VALUES
(3,'Am_Water'),
(6,'B2Future'),
(1,'One_for_all');

MySQL: Combine the values of two different columns of two associated rows respectively

An example to demonstrate the problem:
ID: The primary key.
argID: A foreign key pointing to another table.
dependentID: A foreign key pointing to the field ID of the table itself.
dependentArgID: A foreign key pointing to the same table as argID.
I want to combine two associated rows (having the same dependentID) into one result row respectively, always selecting the date of the first and the number of the next row:
ID argID dependentID dependentArgID date number
1 1 2 2 2016-06-06 null
2 2 2 null null 1
3 1 4 2 2016-06-07 null
4 2 4 null null 2
...
Desired result:
argID date dependentArgID number
1 2016-06-06 2 1
1 2016-06-07 2 2
...
Problem in short form: To rows with the same dependentID should be "merged" into one row with the date and the number (and optionally the argID and the dependentArgID) of these rows.
What I tried, is a self-join, but I do not get the right rows grouped:
NOT working correctly (and without the additional result fields):
SELECT `b`.`date`, `a`.`number`
FROM `table` `a` LEFT JOIN `table` `b` ON `a`.`argID` = `b`.`dependentArgID`
WHERE `a`.`argID` = 2
GROUP BY `a`.`dependentID`;
The first try (see my post) pointed to the right direction.
The correct solution is:
SELECT `b`.`argID`, `b`.`date`, `b`.`dependentArgID`, `a`.`number`
FROM `table` `a`
CROSS JOIN `table` `b`
ON `a`.`ID` = `b`.`dependentID`
WHERE `a`.`argID` = 2 AND `b`.`date` IS NOT NULL
GROUP BY `a`.`dependentID`;
Thanks to all helpers for the brain stimulation.