Select From a table limiting by column of another table - mysql

I have a table named question_dispositions composed like this
Type | chapter | number
-------------------------
A | 1 | 3
B | 1 | 2
B | 4 | 1
The second table named question has columns type, text and chapter.
Type | chapter | text
-----------------------
A | 1 | T1
A | 1 | T2
B | 1 | T3
B | 1 | T4
B | 1 | T5
B | 2 | T6
B | 2 | T7
B | 3 | T8
B | 4 | T9
What I need is to fetch X random questions from table Question where the X is the column number from question_dispositions
So if I need questions of type B the result that I expect is this:
chapter | text
--------------
1 | T3
1 | T4
4 | T9
How can I Select random question limiting by the column number in another table? I tried with this query but returns every row in the table Question as expected because I can not limit with qd.count
SELECT * FROM
question_dispositions AS qd, question AS q
WHERE qd.chapter = q.chapter AND qd.type = q.type AND qd.type = 'B'

ORDER BY RAND() is a typical approach to randomness in MySQL but it is usually criticized because it does not scale well. Here however you appear to be selecting a quite small number of rows as questions but to achieve some randomness of those. So, here is an approach using ORDER BY RAND() which should be acceptable in performance for small result sets.
There is an added requirement to LIMIT by a stored Number, however in the small example supplied that number isn't consistent for type B so I chose to limit by the maximum of that number for the wanted type B. MySQL doesn't allow use of variables or subqueries with the LIMIT clause so instead a generated row number is used in a where clause to achieve that outcome.
Refer to this SQL Fiddle
Data:
CREATE TABLE question_dispositions
(`Type` varchar(1), `chapter` int, `number` int)
;
INSERT INTO question_dispositions
(`Type`, `chapter`, `number`)
VALUES
('A', 1, 3),
('B', 1, 2),
('B', 4, 1)
;
CREATE TABLE question
(`Type` varchar(1), `chapter` int, `text` varchar(2))
;
INSERT INTO question
(`Type`, `chapter`, `text`)
VALUES
('A', 1, 'T1'),
('A', 1, 'T2'),
('B', 1, 'T3'),
('B', 1, 'T4'),
('B', 1, 'T5'),
('B', 2, 'T6'),
('B', 2, 'T7'),
('B', 3, 'T8'),
('B', 4, 'T9')
;
Query:
SELECT *
FROM (
SELECT #rownum := #rownum + 1 AS rn , q.*
FROM question_dispositions AS qd
INNER JOIN question AS q ON qd.chapter = q.chapter AND qd.type = q.type
CROSS JOIN (SELECT #rownum := 0) r
WHERE qd.type = 'B'
ORDER BY RAND()
) d
WHERE rn <= (SELECT MAX(NUMBER) FROM question_dispositions WHERE type = 'B')
Example Results:
| rn | Type | chapter | text |
|----|------|---------|------|
| 1 | B | 1 | T3 |
| 2 | B | 1 | T4 |

Related

Select records that match at least all items in a list

Assuming that i have the following database table:
ID | Name | Type | Value|
--- --------- ---------- ------
1 | First | A | 10 |
2 | First | B | 20 |
3 | First | C | 30 |
4 | First | D | 40 |
5 | Second | A | 10 |
6 | Second | B | 20 |
and a previous query returned:
ID | Name | Type | Value|
--- --------- ---------- ------
1 | Third | A | 10 |
2 | Third | B | 20 |
3 | Third | C | 30 |
My question is:
What is the best way to query the first table and get all records that have at least all the type returned in the previous query?
In the above example the name "Third" has types A B C. Using these as a list, I would like to retrieve only the "First" records (as "First" has A B C D) but not "Second" (as "Second" has only A B - missing C).
The IN statement matches eveything, and I want the query to match at least all items in my "type" list. The list is does not necessarily come from an sql statement but can be provided
EDIT: I'm working with MySQL
Query
Included are two variations of the same query for either database.
MySQL
DBFiddle
SELECT main.*
FROM main
LEFT JOIN (
SELECT name, json_arrayagg(type) as type
FROM main
GROUP BY name
) AS main_agg USING(name)
WHERE EXISTS (
SELECT 1
FROM (
select json_arrayagg(type) as type
from query
group by name
) AS query_agg
WHERE JSON_CONTAINS(main_agg.type, query_agg.type)
)
groups types by name
uses the JSON_CONTAINS function to compare the table to the query
Postgres
SQLFiddle
WITH main_agg AS
(
SELECT name, array_agg(type) "type"
FROM main
GROUP BY name
)
SELECT main.*
FROM main
JOIN main_agg USING(name)
WHERE EXISTS (
SELECT 1
FROM (select array_agg(type) "type" from query group by name) query_agg
WHERE main_agg."type" #> query_agg."type"
)
groups types by name
utilizes the Array #> (contains operator) to compare to the query
Setup
(Works for MySQL or PostgreSQL)
CREATE TABLE main
(ID int, Name varchar(6), Type varchar(1), Value int)
;
INSERT INTO main
(ID, Name, Type, Value)
VALUES
(1, 'First', 'A', 10),
(2, 'First', 'B', 20),
(3, 'First', 'C', 30),
(4, 'First', 'D', 40),
(5, 'Second', 'A', 10),
(6, 'Second', 'B', 20)
;
CREATE TABLE query
(ID int, Name varchar(5), Type varchar(1), Value int)
;
INSERT INTO query
(ID, Name, Type, Value)
VALUES
(1, 'Third', 'A', 10),
(2, 'Third', 'B', 20),
(3, 'Third', 'C', 30)
;
In standard SQL, you can do this as a join and group by with some filtering. The following assumes that the types are unique for each name in each table:
with prevq as (
. . .
)
select t.name
from t join
prevq
on t.type = prevq.type
group by t.name
having count(*) = (select count(*) from prevq);
EDIT:
MySQL does not support CTEs (before 8.0). This is easy enough to do without:
select t.name
from t join
(<your query here>) prevq
on t.type = prevq.type
group by t.name
having count(*) = (select count(*) from (<your query here>) prevq);

Minimum / maximum of "sum" with relative values

I have the following table test:
+----+-------+
| id | value |
+----+-------+
| 1 | -3 |
| 2 | -5 |
| 3 | 10 |
| 4 | -1 |
+----+-------+
For MIN(value) I get -5, for MAX(value) I get 10, and for SUM(value) I get 1. However, I would like to get the minimum and maximum value when progressing through the table step by step.
Example 1: SELECT AWESOME_FUNCTION_SUM_MIN(value) FROM test ORDER BY id ASC
This should return -8 (first row is -3, plus the second row -5 results in the lowest value over the course of all values).
Example 2: SELECT AWESOME_FUNCTION_SUM_MAX(value) FROM test ORDER BY id ASC
This should return 2 (first row -3, second -5, and third row +10 leads to the highest value over the course of all values).
Obviously, ORDER BY does not really make sense, since it is used for ordering the results of a query, but I used it here anyways for demonstration purposes. To me, this is such a basic functionality, so I was surprised to find nothing about it. I potentially am using the wrong keywords. Can somebody help me out? Or do I have to extract all values and do the analysis externally (=not with MySQL)?
Create table/insert data.
CREATE TABLE test
(`id` INT, `value` INT)
;
INSERT INTO test
(`id`, `value`)
VALUES
(1, -3),
(2, -5),
(3, 10),
(4, -1)
;
MySQL doesnt have those functions but you can simulate them using a self join.
Query SUM_MIN
SELECT
SUM(test.value)
FROM
test
INNER JOIN (
SELECT
id
FROM
test
WHERE
test.value > 0
ORDER BY
id ASC
LIMIT 1
)
AS
positive_number
ON
test.id < positive_number.id
ORDER BY
test.id
Result
sum(test.value)
-----------------
-8
Query SUM_MAX
SELECT
SUM(test.value)
FROM
test
INNER JOIN (
SELECT
id
FROM
test
WHERE
test.value > 0
ORDER BY
id ASC
LIMIT 1
)
AS
positive_number
ON
test.id <= positive_number.id
ORDER BY
test.id
Result
sum(test.value)
-----------------
2
Here's one way:
SELECT x.*
, #least:=LEAST(#least,value) least
, #greatest:=GREATEST(#greatest,value) greatest
, #i:=#i+value running
FROM my_table x
, (SELECT #least:=1000,#greatest:=-1000,#i:=0) vars
ORDER
BY id;
+----+-------+-------+----------+---------+
| id | value | least | greatest | running |
+----+-------+-------+----------+---------+
| 1 | -3 | -3 | -3 | -3 |
| 2 | -5 | -5 | -3 | -8 |
| 3 | 10 | -5 | 10 | 2 |
| 4 | -1 | -5 | 10 | 1 |
+----+-------+-------+----------+---------+
To get a cumulative sum, you can join a table to itself.
select min(val)
from (select sum(a.value) as val from test a join test b
on a.id<=b.id group by b.id) t1;
/* answer: -8 */
select max(val)
from (select sum(a.value) as val from test a join test b
on a.id<=b.id group by b.id) t1;
/* answer: 2 */

sql - relation is unknown inside a subquery

I have a relation that is built from 2 integers photo_id , user_id and a string -info, (this is the tag) ,
primary key is (user_id, photo_id, info)
photo_id | user_id | info
---------------------------
5 | 3 | aa
7 | 6 | aa
2 | 2 | bb
1 | 2 | cc
1 | 9 | aa
2 | 8 | cc
1 | 4 | cc
9 | 9 | cc
I'm trying to find the k most common tags in my relation.
(secondary sort is by tags).
in this example i would like to get:
k=2 : aa , cc
k=1 : cc
By using this sql query :
SELECT info,tagCount
FROM (SELECT info, COUNT(photo_id) as tagCount
FROM Tags
GROUP BY info
ORDER BY tagCount DESC, info ASC) T
WHERE (SELECT count(info) FROM T T1
WHERE ((T1.tagCount > T.tagCount) OR
(T1.tagCount = T.tagCount AND T1.info < T.info))) < 'k';
But I get the error:
SQL error:
ERROR: relation "t" does not exist
Where is my mistake?
While I still remain unclear on what you are trying to achieve, and assuming the query is for MySQL (not "sql server") then the following may also help. Please note that the cause of the error message is that alias T refers to a resultset, but you cannot reuse that entire resultset in the where clause (the subquery T1 assume that you can reuse T). Regrettablly MySQL (at the time of writing) does not support common table expressions which would allow referencing T like this:
/* T as a common table expression (CTE) */
with T as (
SELECT info, COUNT(photo_id) as tagCount
FROM Tags
GROUP BY info
)
SELECT info,tagCount
, (SELECT count(info) FROM T T1
WHERE (T1.tagCount > T.tagCount) OR
(T1.tagCount = T.tagCount AND T1.info < T.info)
) as k
FROM T
ORDER BY tagCount DESC, info ASC
;
So, in the absence of a CTE capability, you have to repeat the initial subquery, like this:
SELECT
info
, tagCount
, (
SELECT
COUNT(info)
FROM (
SELECT
info
, COUNT(photo_id) AS tagCount
FROM Tags
GROUP BY
info
) T1
WHERE (T1.tagCount > T.tagCount)
OR (T1.tagCount = T.tagCount
AND T1.info < T.info)
)
AS k
FROM (
SELECT
info
, COUNT(photo_id) AS tagCount
FROM Tags
GROUP BY
info
) T
ORDER BY
tagCount DESC
, info ASC
;
and the result of that query (from the sample data) is as follows:
| info | tagCount | k |
|------|----------|---|
| cc | 4 | 0 |
| aa | 3 | 1 |
| bb | 1 | 2 |
Now, exactly how you derive the "expected result" shown in the question (where tag "bb" is not included) I remain unclear.
By the way. Another issue in your original query is that the where clause predicate is comparing an integer to 'k'
where (select count(info) ....) < 'k'
count(info) is an integer, 'k' is a string, so it will fail.
This may only be a step toward your solution as I don't completely understand the question. I think you need to count(distinct column) then use a much simpler where clause.
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Tags
(`photo_id` int, `user_id` int, `info` varchar(2))
;
INSERT INTO Tags
(`photo_id`, `user_id`, `info`)
VALUES
(5, 3, 'aa'),
(7, 6, 'aa'),
(2, 2, 'bb'),
(1, 2, 'cc'),
(1, 9, 'aa'),
(2, 8, 'cc'),
(1, 4, 'cc'),
(9, 9, 'cc')
;
Query 1:
SELECT
info
, COUNT(distinct photo_id) AS photoCount
, COUNT(distinct user_id) AS userCount
FROM Tags
GROUP BY
info
ORDER BY
photoCount DESC
, userCount DESC
, info ASC
Results:
| info | photoCount | userCount |
|------|------------|-----------|
| cc | 3 | 4 |
| aa | 3 | 3 |
| bb | 1 | 1 |

JOIN each row only once — arrange (distribute) rows 1 to 1

I have two tables to join, and I want each row to be joined only once. Here is sample data:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
When we join on id we obtain:
mysql> SELECT * FROM A JOIN B ON A.id = B.id;
+------+------+------+------+
| id | val | id | val |
+------+------+------+------+
| 2 | b | 2 | x |
| 3 | c | 3 | y |
| 3 | d | 3 | y |
| 3 | c | 3 | k |
| 3 | d | 3 | k |
+------+------+------+------+
What I want is either:
+------+------+------+------+ +------+------+------+------+
| id | val | id | val | | id | val | id | val |
+------+------+------+------+ or +------+------+------+------+
| 2 | b | 2 | x | | 2 | b | 2 | x |
| 3 | c | 3 | y | | 3 | d | 3 | y |
| 3 | d | 3 | k | | 3 | c | 3 | k |
+------+------+------+------+ +------+------+------+------+
The order and arrangement don’t matter.
Is it possible? How?
According to this answer I need to specify how to select the matching row. In that case I guess would need to check in a subquery if the row of the joined table has already been used; or a kind of counter related to the id... but I do not know how to write this.
Edit:
To clarify I want each row with id 3 to be mapped with another one in the joined table, such as each row is mapped only once (I am also interested to know what happens when the number of rows with same id is different in the two tables):
(3, c) -> (3, y) [join only with the first row such as B.id = 3]
(3, d) -> (3, k) [the first row has been used, so map with (and only with) the second row such as B.id = 3]
But as I said mapping may be in any other order (e.g. mapping rows in reverse order).
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE A (id smallint, val varchar(1) );
CREATE TABLE B (id smallint, val varchar(1) );
INSERT INTO A VALUES (1, 'a'), (2, 'b'), (3, 'c'), (3, 'd');
INSERT INTO B VALUES (2, 'x'), (3, 'y'), (4, 'z'), (3, 'k');
Query 1:
select
aa.id as aid
, aa.val as aval
, bb.id as bid
, bb.val as bval
from (
select
#row_num :=IF(#prev_value=a.id,#row_num+1,1)AS RowInGroup
, a.id
, a.val
, #prev_value := a.id
from (
SELECT id, val
FROM A
group by id, val
/* order by ?? */
) a
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) aa
INNER JOIN (
select
#row_num :=IF(#prev_value=b.id,#row_num+1,1)AS RowInGroup
, b.id
, b.val
, #prev_value := b.id
from (
SELECT id, val
FROM B
group by id, val
/* order by ?? */
) b
CROSS JOIN (
SELECT #row_num :=1, #prev_value :=''
) vars
) bb on aa.id = bb.id and aa.RowInGroup = bb.RowInGroup
order by
aa.id
, aa.val
Results:
| id | val | id | val |
|----|-----|----|-----|
| 2 | b | 2 | x |
| 3 | c | 3 | k |
| 3 | d | 3 | y |
nb: you could influence the final result by introducing order by in the subqueries that group by id, val where the sequence RowInGroup is calculated.
Finally I did it!
SELECT T.ID_A,
T.VAL_A,
T.XXXX,
T.ID_B,
T.VAL_B,
T.YYYY
FROM (
SELECT A.id AS ID_A,
A.VAL AS VAL_A,
ROW_NUMBER() OVER (PARTITION BY A.ID, A.VAL ORDER BY A.ID, A.VAL) AS XXXX,
B.ID AS ID_B,
B.VAL AS VAL_B,
ROW_NUMBER() OVER (PARTITION BY B.ID, B.VAL ORDER BY B.ID DESC, B.VAL) AS YYYY
FROM A INNER JOIN B ON A.id = B.id) AS T
WHERE T.YYYY = 1
I could make it thanks to this blog post:
SELECT A2.id, A2.val, B2.val FROM (
SELECT l.id, l.val, COUNT(*) AS n1 FROM A AS l JOIN A AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS A2 JOIN (
SELECT l.id, l.val, COUNT(*) AS n2 FROM B AS l JOIN B AS r ON l.id = r.id AND l.val >= r.val GROUP BY l.id, l.val
) AS B2 ON
A2.id = B2.id AND n1 = n2;
The result is:
+------+------+------+
| id | val | val |
+------+------+------+
| 2 | b | x |
| 3 | c | k |
| 3 | d | y |
+------+------+------+

How to update in MySql columns

There are 5 columns X and A, B, C, D in table t. Columns A, B, C, D are varchar. and column X has to show us how many of the next row repeating characters. I need help to update column X.
Example :
|ID | X | A | B | C | D |
=========================
| 4 | 1 | 7 | J | 7 | Q |
| 3 | 2 | K | Q | 8 | 8 |
| 2 | 3 | 7 | 8 | 9 | J | next row X=3
| 1 | 0 | 7 | J | 8 | K | 0 default
ID-1 is the first and X is zero by default and from there begin calculations ID-2 and X=3 because we have ID-1 "7" "J" and "8" and the next row ID-2 have the combination "7 8 9 J" in ID-1 there "7 J 8 -" and X should be 3. Values ​​of X can be between 0 and 4. ID-3, X=2 "- - 8 8" because in ID-2 have the combination "7 8 9 J" and i have 8 - twice in "K Q 8 8".
Added : 08/06
http://sqlfiddle.com/#!2/4586e/1
CREATE TABLE tmp
(
id int,
alnum CHAR(1),
cnt int,
PRIMARY KEY (id, alnum)
);
INSERT INTO tmp (id, alnum, cnt) SELECT id, A, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, B, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, C, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
INSERT INTO tmp (id, alnum, cnt) SELECT id, D, 1 FROM tab ON DUPLICATE KEY UPDATE cnt = cnt + 1;
UPDATE tab INNER JOIN (
SELECT t1.id AS id, SUM(t2.cnt) AS sum
FROM tmp t1 INNER JOIN tmp t2 ON t1.id + 1 = t2.id
AND t1.alnum = t2.alnum
GROUP BY t1.id
) tmp3 ON tab.id = tmp3.id + 1
SET tab.X = tmp3.sum;
SELECT * FROM tab WHERE X > 4;
Original answer : 08/05
How about using tmp table which stores for each A, B, C, D columns into single rows. This makes us easy to calculate X value.
Below code assumes id is sequencial value. If not, please let me know there is another query for it.
CREATE TABLE tab
(
id INT,
X INT,
A CHAR(1),
B CHAR(1),
C CHAR(1),
D CHAR(1)
);
INSERT INTO tab VALUES (1, 0, '7', 'J', '8', 'K');
INSERT INTO tab VALUES (2, 0, '7', '8', '9', 'J');
INSERT INTO tab VALUES (3, 0, 'K', 'Q', '8', '8');
INSERT INTO tab VALUES (4, 0, '7', 'J', '7', 'Q');
CREATE TABLE tmp
(
id INT,
alnum CHAR(1)
);
INSERT INTO tmp SELECT id, A FROM tab;
INSERT INTO tmp SELECT id, B FROM tab;
INSERT INTO tmp SELECT id, C FROM tab;
INSERT INTO tmp SELECT id, D FROM tab;
UPDATE tab INNER JOIN (
SELECT t1.id AS id, COUNT(*) AS cnt
FROM tmp t1 INNER JOIN tmp t2 ON t1.id + 1 = t2.id
AND t1.alnum = t2.alnum
GROUP BY t1.id
) tmp3 ON tab.id = tmp3.id + 1
SET tab.X = tmp3.cnt;
mysql> SELECT * FROM tab ORDER BY id DESC;
+------+------+------+------+------+------+
| id | X | A | B | C | D |
+------+------+------+------+------+------+
| 4 | 1 | 7 | J | 7 | Q |
| 3 | 2 | K | Q | 8 | 8 |
| 2 | 3 | 7 | 8 | 9 | J |
| 1 | 0 | 7 | J | 8 | K |
+------+------+------+------+------+------+
4 rows in set (0.00 sec)
The X values in your example are from the "next" row, not the previous. Assuming you have an auto-incrementing id, you can calculate this information by looking at the next row. You can generate a query to do the calculation:
select ((case when t1.A in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.B in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.C in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end) +
(case when t1.D in (tnext.A, tnext.B, tnext.C, tnext.D) then 1 else 0 end)
) as X, t1.*
from (select t.*, (select max(id) from table t2 where t2.id > t.id) as nextid
from table t
) t1 left outer join
t tnext
on tnext.id = t1.nextid;
Depending on the database you are using, this code can be simplified and expressed differently. Also, the specific update syntax might depend on the database.