I have a table that looks like this
Table - mylist
-----
id | max_p
----------
1 | 4
2 | 2
3 | 2
4 | 6
5 | 2
6 | 2
I'd like to run a query which will find the minimum number of rows where the sum of max_p=10. so in this instance it would select records 1 and 4
if I wanted to run the same query to find 12 then it would select records 1,4 and 5
if I wanted to find records which equalled 2 it would just select record number 5 as that is the correct number and therefore no more than one record needs to be selected?
ideally this would select just one record if the desired amount was the same as any one row, then if this wasn't possible it would select two records, then three etc. If the desired number wasn't possible then it would return an empty result
fiddle here : http://ideone.com/3ECaT2
CREATE TABLE `my_list` (
`id` int(2) ,
`max_p` int(2),
PRIMARY KEY (`id`)
) ;
INSERT INTO `my_list` (`id`, `max_p`) VALUES
(1, 4),
(2, 2),
(3, 2),
(4, 6),
(5, 2),
(6, 2);
Any help greatly appreciated
To really solve this problem in SQL, you would need recursive subqueries. MySQL does not offer this functionality. What you can do is look for such a combination with up to a given number of elements. The following query implements this for four combinations:
select ml1.max_p as p1, ml2.max_p as p2, ml3.max_p as p3, ml4.max_p as p4
from my_list ml1 left outer join
my_list ml2
on ml1.id < ml2.id left outer join
my_list ml3
on ml2.id < ml3.id left outer join
my_list ml4
on ml3.id < ml4.id
where coalesce(ml1.max_p, 0) + coalesce(ml2.max_p, 0) + coalesce(ml3.max_p, 0) + coalesce(ml4.max_p, 0)
To get the shortest, count the number of elements and use limit:
select ml1.max_p as p1, ml2.max_p as p2, ml3.max_p as p3, ml4.max_p as p4
from my_list ml1 left outer join
my_list ml2
on ml1.id < ml2.id left outer join
my_list ml3
on ml2.id < ml3.id left outer join
my_list ml4
on ml3.id < ml4.id
where coalesce(ml1.max_p, 0) + coalesce(ml2.max_p, 0) + coalesce(ml3.max_p, 0) + coalesce(ml4.max_p, 0)
order by ((ml1.map_p is null) +
(ml2.map_p is null) +
(ml3.map_p is null) +
(ml4.map_p is null)
) desc
limit 1;
Related
I have a MySQL DB that defines series of numbers within sets as such:
set item1 item2
1 1 2
1 2 3
1 3 4
1 4 5
1 5 6
I want to write a query (or queries) that returns to me the fact that set 1 is a series of numbers that spans from 1 to 6. Is this possible?
Please note that the real DB I'm dealing with contains hundreds of sets and that each set can contain a series of items that can be somewhat long as well (up to 50 items per set, I'm guessing). Also, I'm not totally sure, but the DB might also have cases where the series of numbers split. Using the example above, there may be instances like the following:
set item1 item2
1 1 2
1 2 3
1 3 4
1 4 5
1 5 6
1 3 7
1 7 8
1 8 9
In which case, I'd want to know that set 1 has two series of numbers: [1, 2, 3, 4, 5, 6] and [1, 2, 3, 7, 8, 9]. Is this possible with hopefully one query (or if necessary, multiple queries)?
Edit: Please note that I used the numbers 1-9 in sequential order to make the question easier to understand. The real data is much more mixed up and not that orderly.
As you are aware, MySQL cannot handle recursion 'out-of-the-box', so options include:
writing a stored procedure
switching from an adjacency list to an alternative model (e.g. nested set)
joining the table to itself as often as could be required
handling the recursion in application level code (e.g. a bit of PHP)
Here is an example using option 3, but it could be easily adapted to suit option 4...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(
family_id INT NOT NULL,
item_id INT NOT NULL,
parent_id INT NULL,
PRIMARY KEY(family_id, item_id)
);
INSERT INTO my_table
VALUES (101, 1, null), (101, 2, 1), (101, 3, 2), (101, 4, 3),
(101, 5, 4), (101, 6, 5), (101, 7, 3), (101, 8, 7), (101, 9, 8);
SELECT CONCAT_WS(','
, a.item_id
, b.item_id
, c.item_id
, d.item_id
, e.item_id
, f.item_id
, g.item_id
, h.item_id
, i.item_id
) series
FROM
my_table a
LEFT JOIN
my_table b ON b.parent_id = a.item_id AND b.family_id = a.family_id
LEFT JOIN
my_table c ON c.parent_id = b.item_id AND c.family_id = b.family_id
LEFT JOIN
my_table d ON d.parent_id = c.item_id AND d.family_id = c.family_id
LEFT JOIN
my_table e ON e.parent_id = d.item_id AND e.family_id = d.family_id
LEFT JOIN
my_table f ON f.parent_id = e.item_id AND f.family_id = e.family_id
LEFT JOIN
my_table g ON g.parent_id = f.item_id AND g.family_id = f.family_id
LEFT JOIN
my_table h ON h.parent_id = g.item_id AND h.family_id = g.family_id
LEFT JOIN
my_table i ON i.parent_id = h.item_id AND i.family_id = h.family_id
WHERE
a.parent_id IS NULL;
+-------------+
| series |
+-------------+
| 1,2,3,4,5,6 |
| 1,2,3,7,8,9 |
+-------------+
I can solve the first problem.
Note that "set" is a keyword, so I renamed the first column to "sset"
You can see the result in http://sqlfiddle.com/#!9/ef6360/5
Create table and insert data:
create table test
(
sset int not null
, item1 int not null
, item2 int not null
) engine=InnoDB;
insert into test
values
(1, 1, 2)
, (1, 2, 3)
, (1, 3, 4)
, (1, 4, 5)
, (1, 5, 6)
Run the query:
select
sset
, group_concat(distinct item1or2 order by item1or2 asc)
from
(
select
sset
, item1 as item1or2
from test
union all
select
sset
, item2 as item1or2
from test
) u;
The output is:
1,2,3,4,5,6
I have a list of ids pre-generated that I need to check if exist in a table. My table has two columns, id, name, where id is an auto increment integer and name is a varchar(255).
I basically want to get a count of how many ids do not exist in table foo from my pre-generated list. So say my list has the numbers 5 and 10 in it, what's the best way to write something to the extent of:
select count(*) from foo where id does not exist in ( 5, 10 )
The idea here is that if 5 and 10 do not exist, I need the response 2, and not the number of rows in foo that do not have the id 5 or 10.
TL; DR sample data and queries at rextester
The idea here is that if 5 and 10 do not exist, I need the response 2, and not the number of rows in foo that do not have the id 5 or 10.
You should have provided a little more information to avoid confusion.
Example
id | name
1 | tom
2 | joe
3 | mae
4 | goku
5 | vegeta
If your list contains (1, 2, 3) then your answer should be 0 (since all three are in the table )
If your list contains (1, 2, 6) then your answer should be 1. ( since 1 and 2 are in the table but 6 is in't )
If your list contains (1, 6, 7) then your answer should be 2.
If your list contains (6, 7, 8) then your answer should be 3.
assuming this was your question
If you know the length of your list
select 2 - count(*) as my_count from foo where id in (5, 10)
The following query tells you how many are present in foo.
select count(*) from foo where id in (5,10)
So if you want to find those that do not exist, subtract this result from the length of your list.
select n - count(*) as my_count from foo where id in (5, 10,....)
You could use on fly table using union and the a left join
select count(*)
from my_table as m
left join (
select 5 as id from dual
union
select 10 from dual ) t on t.id = m.id
where t.id is null
otherwise you can populate a tempo table with the value you need and use left join
where the value is null
Lets say I got this table
photo_id user_id tag
0 0 Car
0 0 Bridge
0 0 Sky
20 1 Car
20 1 Bridge
2 2 Bridge
2 2 Cat
1 3 Cat
I need to return the k tags that appear the most, WITHOUT USING LIMIT.
tie breaker for tags that appear the same number of times will be the lexicographically order (smallest will have the highest score).
I will need for each tag the number of tags he appeared as well.
for example, for the table above with k=2 the output should be:
Tag #
Bridge 3
Car 2
and for k=4:
Tag #
Bridge 3
Car 2
Cat 2
Sky 1
Try this :
SELECT t1.tag, COUNT(*) as mycount FROM table t1
GROUP BY t1.tag
ORDER BY mycount DESC
LIMIT 2;
Replace the limit ammount for your k var.
Inserting data into table:
INSERT INTO new_table VALUES
(0,0,'Car'),
(0,0,'Bridge'),
(0,0,'Sky'),
(20,1,'Car'),
(20,1,'Bridge'),
(0,0,'bottle');
To query:
SELECT tag, COUNT(1) FROM new_table
GROUP BY tag HAVING COUNT(1) = (
SELECT MIN(c) FROM
(
SELECT COUNT(1) AS c FROM new_table GROUP BY tag
) AS temp
)
Output:
+--------+----------+
| tag | count(1) |
+--------+----------+
| bottle | 1 |
| Sky | 1 |
+--------+----------+
Note : Get smallest count tag
Although this is homework and we are not supposed to answer such questions (not till you've proved that attempted to solve it and not getting desired result), I got a little curious about not using LIMIT in this question, so I am posting here.
The idea is to rank the result and then select only rows whose rank are less than or equal to value k (as in your case). The rank column is like adding a S.No. (serial number) column to your result and selecting till desired number.
DDL statements:
CREATE TABLE new_table(
photo_id INTEGER,
user_id INTEGER,
tag VARCHAR(10)
);
INSERT INTO new_table VALUES
(0, 0, 'Car'),
(0, 0, 'Bridge'),
(0, 0, 'Sky'),
(20, 1, 'Car'),
(20, 1, 'Bridge'),
(2, 2, 'Bridge'),
(2, 2, 'Cat'),
(1, 3, 'Cat');
Query:
SELECT
tag, tag_count,
#k := #k + 1 AS k
FROM (
SELECT
tag,
COUNT(*) AS tag_count
FROM new_table
GROUP BY tag
ORDER BY tag_count DESC
) AS temp, (SELECT #k := 0) AS k
WHERE #k < 2;
Check this SQLFiddle.
I have the following code
select count(*)
from (select Annotations.user_id
from Annotations, Users
where Users.gender = 'Female'
and Users.user_id = Annotations.user_id
and image_id = 1
group by Annotations.user_id
having sum(case when stem = 'taxi' then 1 else 0 end) > 0 and
sum(case when stem = 'zebra crossing' then 1 else 0 end) > 0
) Annotations
It produces a count of how many females who have given the stem 'taxi' and 'zebra crossing' for image 1.
Sample data
user id, image id, stem
1 1 image
1 1 taxi
1 1 zebra crossing
2 1 person
2 1 zebra crossing
2 1 taxi
3 1 person
3 1 zebra crossing
Expected result (or similar)
stem1, stem2, count
taxi , zebra crossing 2
person, zebra crossing 2
However, as there are over 2000 stems, I cannot specify them all.
How would I go around looping through the stem rows with the image_id = 1 and gender = female as opposed to specifying the stem string?
Thank you
As per my understanding, you need to fetch female users that have 2 or more stems
Update: It seems you need to display the user's that have a stem that is used by another user too, I have updated the query for the same
SELECT
distinct a.user_id,
group_concat(DISTINCT a.stem ORDER BY a.stem)
FROM
Annotations a
JOIN Users u ON ( a.user_id = u.user_id AND u.gender = 'Female' )
JOIN
(
SELECT
b.user_id,
b.stem
FROM
Annotations b
) AS b ON ( a.user_id <> b.user_id AND b.stem = a.stem )
WHERE
a.image_id = 1
GROUP BY
a.user_id
UPDATE: As I understand it, you want to select all combinations of 2 stems, and get a count of how many users have that combination of stems. Here is my solution:
SELECT stem1, stem2, count(*) as count FROM
(
SELECT a.user_id,a.image_id,a.stem as stem1,b.stem as stem2
FROM Annotations a JOIN Annotations b
ON a.user_id=b.user_id && b.image_id=a.image_id && a.stem!=b.stem
JOIN Users ON Users.user_id = a.user_id
WHERE Users.gender = "Female"
) as stems GROUP BY stem1, stem2 having count > 1 WHERE image_id=1;
The caveat here is that it will return 2 rows for each combinations of stems. (The second occurrence will have the stems in reverse order).
Here's my attempt to solve your problem:
SELECT COUNT(*) AS Count, a1.stem AS Stem1, a2.Stem AS Stem2
FROM Annotations AS a1
INNER JOIN Annotations AS a2 ON a1.user_id = a2.user_id AND a1.image_id = a2.image_id
AND a1.stem < a2.stem
WHERE a1.image_id = 1
GROUP BY a1.stem, a2.Stem
HAVING COUNT(*) > 1;
I did not include image_id logic.
Please see my SQL Fiddle here: http://sqlfiddle.com/#!2/4ee69/33
Based on the following data (copied from yours) I get the result posted underneath it.
CREATE TABLE Annotations
(`user_id` int, `image_id` int, `stem` varchar(14))
;
INSERT INTO Annotations
(`user_id`, `image_id`, `stem`)
VALUES
(1, 1, 'image'),
(1, 1, 'taxi'),
(1, 1, 'zebra crossing'),
(2, 1, 'person'),
(2, 1, 'zebra crossing'),
(2, 1, 'taxi'),
(3, 1, 'person'),
(3, 1, 'zebra crossing')
;
COUNT STEM1 STEM2
2 person zebra crossing
2 taxi zebra crossing
I basically have a table that holds counts for every date. I want to create a query that gives me the total # of counts over the entire table, as well as the total for yesterday. But when I try to join the table twice, the aggregates are off. Below is how you can replicate the results.
CREATE TABLE a (id int primary key);
CREATE TABLE b (a_id int, b_id int, date date, count int, primary key (a_id,b_id,date));
INSERT INTO a VALUES (1);
INSERT INTO b VALUES (1, 1, UTC_DATE(), 5);
INSERT INTO b VALUES (1, 2, UTC_DATE(), 10);
INSERT INTO b VALUES (1, 1, UTC_DATE()-1, 7);
INSERT INTO b VALUES (1, 2, UTC_DATE()-1, 12);
SELECT A.id,SUM(B.count) AS total_count,SUM(Y.count) AS y FROM a AS A
LEFT JOIN b AS B ON (B.a_id=A.id)
LEFT JOIN b AS Y ON (Y.a_id=A.id AND Y.date=UTC_DATE()-1)
GROUP BY A.id;
Results in:
+----+-------------+------+
| id | total_count | y |
+----+-------------+------+
| 1 | 68 | 76 |
+----+-------------+------+
The correct result should be:
+----+-------------+------+
| id | total_count | y |
+----+-------------+------+
| 1 | 34 | 22 |
+----+-------------+------+
What's going on here? Is this a bug in mysql or am I not understanding how the joins are working.
No, it's not a bug in MySQL.
Your JOIN conditions are generating "duplicate" rows. (Remove the aggregate functions and the GROUP BY, and you'll see what's happening.
That row from table "a" is matching four rows from table "b". That's all fine and good. But when you add the join to the third table ("y"), each row returned from that third "y" table (two rows) is being "matched" to every row from the "b" table... so you wind up with a total of eight rows in your result set. (That's why the "total_count" is getting doubled.)
To get the result set you specify, you don't need to join that table "b" second time. Instead, just use a conditional test to determine whether that "count" should be included in the "y" total or not.
e.g.
SELECT a.id
, SUM(b.count) AS total_count
, SUM(IF(b.date=UTC_DATE()-1 ,b.count,0)) AS y
FROM a a
LEFT
JOIN b b ON (b.a_id=a.id)
GROUP BY a.id;
Note that the MySQL IF expression can be replaced with an equivalent ANSI CASE expression for improved portability:
, SUM(CASE WHEN b.date=UTC_DATE()-1 THEN b.count ELSE 0 END) AS y
If you did want to do JOIN to that "b" table a second time, you would want the JOIN condition to be such that a row from "y" would match, at most, ONE row from "b", so as not to introduce any duplicates. So you'd basically need the join condition to include all of the columns in the primary key.
(Note that the predicates in the join condition for table "y" guarantee that each from from "y" will match no more than ONE row from "b"):
SELECT a.id
, SUM(b.count) AS total_count
, SUM(y.count) AS y
FROM a a
LEFT
JOIN b b
ON b.a_id=a.id
LEFT
JOIN b y
ON y.a_id = b.a_id
AND y.b_id = b.b_id
AND y.date = b.date
AND y.date = UTC_DATE()-1
GROUP BY a.id;
(To get the first statement to return an identical resultset, with a potential NULL in place of a zero, you'd need to replace the '0' constant in the IF expression with 'NULL'.
, SUM(IF(b.date=UTC_DATE()-1 ,b.count,NULL)) AS y
SELECT A.id,b_count AS total_count,y_count as y
FROM a AS A
LEFT JOIN (select a_id,SUM(B.Count) b_count from b
group by B.A_id) AS B1 ON (B1.a_id=A.id)
LEFT JOIN (select a_id,SUM(Count) y_count from b
where date=UTC_DATE()-1
group by B.A_id) AS Y ON (Y.a_id=A.id)
SQLFiddle Demo