SQL select from 1 x N where all bigger than - mysql

I have tables books and bookType which pose a 1 X n relationship.
books
+-----+------------------+----------+-------+
| id | title | bookType | price |
+-----+------------------+----------+-------+
| 1 | Wizard of Oz | 3 | 14 |
| 2 | Huckleberry Finn | 1 | 16 |
| 3 | Harry Potter | 2 | 25 |
| 4 | Moby Dick | 2 | 11 |
+-----+------------------+----------+-------+
bookTypes
+-----+----------+
| id | name |
+-----+----------+
| 1 | Fiction |
| 2 | Drama |
| 3 | Children |
+-----+----------+
How would I retrieve bookTypes where all books are more expensive than e.g. 12($)?
In this case, the expected output would be:
+-----+----------+
| id | name |
+-----+----------+
| 1 | Fiction |
| 3 | Children |
+-----+----------+

You can use not exists:
select t.*
from bookTypes t
where not exists (
select 1
from books b
where b.bookType = t.id and b.price < 12
)
If you want to select book types that also have at least one associated book:
select t.*
from bookTypes t
where
exists (select 1 from books b where b.bookType = t.id)
and not exists (select 1 from books b where b.bookType = t.id and b.price < 12)

Do a GROUP BY, use HAVING to return only booktypes having the lowest price > 12.
SELECT bt.name
FROM bookTypes bt
INNER JOIN books b ON b.bookType = bt.id
group by bt.name
HAVING SUM(b.price <= 12) = 0;

You can directly consider using having min(price) >= 12 with grouping by bookType
select t.id, t.name
from bookTypes t
join books b
on t.id = b.bookType
group by b.bookType
having min(price) >= 12
Moreover, if your DB's version is at least 10.2, then you can also use some window functions for analytical queries such as min(..) over (partition by .. order by ..) :
with t as
(
select t.id, t.name, min(price) over (partition by bookType) as price
from bookTypes t
join books b
on t.id = b.bookType
)
select id, name
from t
where price >= 12
in which min() over (..) window function determines minimum price for each booktype by use of partition by bookType
Demo

I think GMB's solution is likely the best so far. But for sake of completeness: You can also use the ALL operator with a correlated subquery. That's probably the most straight forward solution.
SELECT *
FROM booktypes bt
WHERE 12 < ALL (SELECT b.price
FROM books b
WHERE b.booktype = bt.id);

Can you not just select from books inner join bookTypes on id WHERE price > 12?
SELECT bt.*
FROM bookTypes bt
INNER JOIN books b ON b.bookType = bt.id
WHERE b.price > 12

Related

MySQL JOIN, WHERE and IF issue

i have table of members
table members :
pid| id | name
1 | id01 | jenny
2 | id02 | kain
3 | id03 | alex
and have another table members_opt
table members_opt
pid | members_id | category
1 | id01 | cat
2 | id01 | dog
3 | id02 | dog
4 | id03 | NULL
now i use below SQL query
SELECT * FROM members a JOIN
(SELECT members_id, max(category) as category FROM members_opt GROUP BY members_id) b
ON a.id = b.members_id
But this SQL Query not catch "id03"'s data because "id03"'s members_opt.category is NULL
I want this result
result :
id | name | category
id01 | jenny | cat
id02 | kain | dog
id03 | alex | NULL
(the result now showed double name, double id value.)
How can i use SQL query?
You can try this -
SELECT * FROM members a JOIN
(SELECT members_id, max(CASE WHEN category IS NULL THEN 0) as category FROM members_opt GROUP BY members_id) b
ON a.id = b.members_id
Use the below query to get your desired output -
SELECT a.id, a.name, b.category FROM members a INNER JOIN
(SELECT members_id, category FROM members_opt GROUP BY members_id) b
ON a.id = b.members_id
Just change JOIN to LEFT JOIN :
SELECT a.id, a.name, b.category
FROM members a
LEFT JOIN (
SELECT members_id, max(category) as category
FROM members_opt GROUP BY members_id) b
ON a.id = b.members_id
and if you want 'cat' for 'jenny', you should use aggregation function min.

Sum columns from multiple tables in MySQL

I am trying to sum some columns from multiple tables in just one SQL query, but I seem to be getting the wrong results. I think there is a problem with the code that I have provided below. Please any help on this is appreciated.
item Names
| id | name |
| 1 | AB |
| 2 | CA |
table1
| id | interest | year |
| 1 | 20.00 | 2014 |
| 2 | 30.00 | 2013 |
| 1 | 10.00 | 2013 |
table2
| id | deposit | year |
| 1 | 10.00 | 2014 |
| 2 | 10.00 | 2014 |
This is the query that I tried:
SELECT
a.name,
b.year,
sum(b.interest) as 'total'
FROM
`table1` b
INNER JOIN
`item names` a
ON
b.id=a.id
GROUP BY
b.id
UNION ALL
SELECT
c.name,
d.year,
sum(d.deposit) as 'total'
FROM
`table2` d
INNER JOIN
`item names` c
ON
d.id=c.id
GROUP BY
d.id
EXPECTED RESULTS
UPDATE
I am trying to find the total sum of interest and deposit for a particular year and for a particular item
|name | year | total |
| AB | 2014 | 30.00 |
| AB | 2013 | 10.00 |
| CA | 2013 | 30.00 |
| CA | 2014 | 10.00 |
Perhaps... assuming table1 and table2 have same structure.
First I generate a set with the union values from one and two then we use a simple aggregate and a group by to sum the values by name and year.
SELECT I.Name, B.year, Sum(B.Total)
FROM item I
INNER JOIN
(SELECT * FROM table1 UNION select * FROM table2) B
on B.ID = I.ID
GROUP BY I.Name, B.Year
In the query you have posted, you need to group by year also to get the results. Then, you can use UNION to get all of the rows from the first set, along with all of the rows from the second set:
SELECT a.name, b.year, SUM(b.interest) AS total
FROM names a
JOIN table1 b ON b.id = a.id
GROUP BY a.name, b.year
UNION
SELECT a.name, c.year, SUM(c.deposit) AS total
FROM names a
JOIN table2 c ON c.id = a.id
GROUP BY a.name, c.year;
However, this doesn't give you your final results, as names that appear in each table ('AB' for example) will appear twice. One row for the year in deposits, one row for the year in interests. To combine those, just use the above as a subquery, sum the totals and again group by name and date:
SELECT name, year, SUM(total) AS total
FROM(
SELECT a.name, b.year, SUM(b.interest) AS total
FROM names a
JOIN table1 b ON b.id = a.id
GROUP BY a.name, b.year
UNION
SELECT a.name, c.year, SUM(c.deposit) AS total
FROM names a
JOIN table2 c ON c.id = a.id
GROUP BY a.name, c.year) temp
GROUP BY name, year;
Here is an SQL Fiddle example.

Get min price id without inner select

I have a table called a with this data:
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 1 | 1 | 100 |
| 2 | 1 | 200 |
| 3 | 1 | 99 |
| 4 | 2 | 1000 |
| 5 | 2 | 999 |
+-----+-----------+-------+
I want to get the id of min pirce for each parent_id.
There is any way to get this result without subquery?
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 3 | 1 | 99 |
| 5 | 2 | 999 |
+-----+-----------+-------+
SELECT D1.id, D1.parent_id, D1.price
FROM Data D1
LEFT JOIN Data D2 on D2.price < D1.price AND D1.parent_id = D2.parent_id
WHERE D2.id IS NULL
Here is a shot at how to do it without subqueries. I haven't tested, let me know if it works!
SELECT t.id, t.parent_id, t.price
FROM table t
LEFT JOIN table t2
ON (t.parent_id = t2.parent_id AND t.price > t2.price)
GROUP BY t.id, t.parent_id, t.price
HAVING COUNT(*) = 1 AND max(t2.price) is null
ORDER BY t.parent_id, t.price desc;
Try this:
SELECT T1.id,T2.parent_id,T2.price FROM
(SELECT id,price
FROM TableName) T1
INNER JOIN
(
SELECT parent_id,MIN(price) as price
FROM TableName
GROUP BY parent_id) T2 ON T1.price=T2.price
See result in SQL Fiddle.
Try group by,
SELECT parent_id,min(price)
FROM TableName
GROUP BY parent_id
You can do this with a LEFT JOIN
SELECT a.id, a.parent_id, a.price
FROM a
LEFT JOIN a AS b ON b.price < a.price AND b.parent_id = a.parent_id
WHERE b.id IS NULL
Find the results at this fiddle:
http://sqlfiddle.com/#!2/09c888/10
You can try this without using any join or subquery you will surely get the desired result.
SELECT TOP 2 FROM a ORDER BY price

MySQL combine 3 queries in to 1

I'm trying to combine 3 MySQL queries in to one.
This is what I am using to grab my news entries (with pagination) :
SELECT A.sid,
A.title,
A.time,
A.bodytext,
A.author,
A.url
FROM news A
INNER JOIN
(SELECT sid
FROM news
WHERE approved=1
ORDER BY sid DESC LIMIT $start, $limit) B USING (sid)
Now, I've recently added a comments feature and would like to get the total amount of comments for each "sid"
The part that gets confusing for me is that I need to match "sid" (from news.news) with "page_id" (from comments.pages) to grab it's unique "id".
+----+---------+
| id | page_id |
+----+---------+
| 1 | 87 |
| 2 | 86 |
| 41 | 85 |
| 3 | 84 |
| 13 | 83 |
+----+---------+
Now with that unique "id", I need to query "comments.comments" and match it with "page_id" column and count(*) how many comments it has - WHERE is_approved = 1
SELECT page_id,is_approved,count(*) FROM comments WHERE page_id = $id and is_approved = 1;
+---------+-------------+----------+
| page_id | is_approved | count(*) |
+---------+-------------+----------+
| 1 | 1 | 2 |
+---------+-------------+----------+
Is this possible?
Edited my response to conform to the updated question:
SELECT A.sid, A.title, A.time, A.bodytext, A.author, A.url,
D.page_id, D.num_comments
FROM news.news A
INNER JOIN (SELECT sid
FROM news
WHERE approved=1
ORDER BY sid desc
LIMIT $start, $limit) B USING (sid)
LEFT JOIN comments.pages C ON A.sid = C.id
LEFT JOIN (SELECT page_id,is_approved,count(*) as num_comments
FROM comments.comments
WHERE is_approved = 1) D ON C.page_id = D.page_id

Finding the MIN value that appears for each unique value in either of two other columns

Given the following (simplified) tables:
People p
id name registered
-----------------------------------
1 Geoff 2011-03-29 12:09:08
2 Phil 2011-04-29 09:03:54
3 Tony 2011-05-29 21:22:23
4 Gary 2011-06-21 22:56:08
...
Items i
date p1id p2id
----------------------------------------
2011-06-29 20:09:44 1 2
2011-06-26 10:45:00 1 3
2011-06-23 12:22:43 2 3
2011-06-22 13:07:12 2 4
...
I'd like:
The earliest single i.date that each p.id appears in either column p1id or p2id; or p.registered if they feature in neither.
So far, I've tried:
CREATE TEMPORARY TABLE temp (id INT);
INSERT INTO temp (id)
SELECT DISTINCT u FROM (
SELECT p1id AS u FROM Items UNION ALL
SELECT p2id AS u FROM Items
)tt;
SELECT registered,id FROM People
WHERE id NOT IN (SELECT id FROM temp);
Which gets me as far as the second part, albeit in a fairly clumsy way; and I'm stuck on the first part beyond some sort of external, scripted iteration through all the values of p.id (ugh).
Can anyone help?
I'm on MySQL 5.1 and there's ~20k people and ~100k items.
One more solution:
SELECT id, name, IF(min_date1 IS NULL AND min_date2 IS NULL, registered, LEAST(COALESCE(min_date1, min_date2), COALESCE(min_date2, min_date1))) date FROM (
SELECT p.id, p.name, p.registered, MIN(i1.date) min_date1, MIN(i2.date) min_date2 FROM people p
LEFT JOIN items i1
ON p.id = i1.p1id
LEFT JOIN items i2
ON p.id = i2.p2id
GROUP BY id
) t;
OR this:
SELECT p.id, p.name, COALESCE(MIN(i.date), p.registered) FROM people p
LEFT JOIN (
SELECT p1id id, date FROM items
UNION ALL
SELECT p2id id, date FROM items
) i
ON p.id = i.id
GROUP BY id;
Result:
+------+-------+---------------------+
| id | name | date |
+------+-------+---------------------+
| 1 | Geoff | 2011-06-26 10:45:00 |
| 2 | Phil | 2011-06-22 13:07:12 |
| 3 | Tony | 2011-06-23 12:22:43 |
| 4 | Gary | 2011-06-22 13:07:12 |
+------+-------+---------------------+
This is tested in Postgres, but I think it ought to work in MySQL with few or no changes:
SELECT p.id,COALESCE(MIN(x.date),p.registered) AS date
FROM p
JOIN (
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p1id)
GROUP BY p.id
UNION
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p2id)
GROUP BY p.id
) AS x ON x.id = p.id
GROUP BY p.id,p.registered;
Output (given your sample data):
id | date
----+---------------------
3 | 2011-06-23 12:22:43
1 | 2011-06-26 10:45:00
2 | 2011-06-22 13:07:12
4 | 2011-06-22 13:07:12
(4 rows)