I have three tables, and duplicate column names also :) I want to join albums to products and images to albums. Images are many. Trying such query, it gives me duplicate products. Is there a chance to grab everything in one query?
SELECT
*, p.name as nazwa, a.name as nazwa_al, i.name as obrazek
FROM products p
JOIN
albums a on p.album_id=a.id
JOIN
(SELECT *, images.name AS nazwa_im FROM images ORDER BY images.order ASC) i
ON i.album_id=a.id
ORDER BY p.order ASC
Products
+-------------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| description | text | NO | | NULL | |
| album_id | int(11) | YES | | NULL | |
| order | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+----------------+
Albums
+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
+-------+---------+------+-----+---------+----------------+
Images
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | text | NO | | NULL | |
| alt | text | NO | | NULL | |
| album_id | int(11) | NO | | NULL | |
| order | int(11) | NO | | NULL | |
+----------+---------+------+-----+---------+----------------+
For the sake of simplicity, I don't want to modify structure of db. The easiest solution for me would be: one product=>one album=>many images
Use joins and use aliases to solve duplicate name error.
You can use distint or group by have results aligned as per same product id.
SELECT
*, p.name as nazwa, a.name as nazwa_al, i.name as obrazek
FROM
products p
JOIN
albums a on p.album_id = a.id
JOIN
images i ON i.album_id = a.id
GROUP BY p.id
ORDER BY p.order ASC
You need to use group_concat if multiple rows on right side.
SELECT
*, p.name as nazwa, a.name as nazwa_al, group_concat(i.name) as obrazek
FROM
products p
JOIN
albums a on p.album_id = a.id
JOIN
images i ON i.album_id = a.id
GROUP BY p.id
ORDER BY p.order ASC
Related
I have the following table storing data in the EAV model:
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| user_id | int(11) | NO | MUL | NULL | |
| question_id | int(11) | NO | MUL | NULL | |
| answer | blob | YES | | NULL | |
+-------------+---------+------+-----+---------+-------+
With a table to hold the different types of question:
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| question | blob | YES | | NULL | |
+----------+---------+------+-----+---------+----------------+
As well as a users table:
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_name | varchar(128) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
How would I write a query to insert a row with a null value for answer for each question_id that each user does not currently have a row for?
For example, if I have question_ids 1,2,3,4 and my table storing data looks like:
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 1 | example|
| 1 | 3 | example|
| 1 | 4 | example|
+--------------+---------+-----+--------+
I want to insert a row that looks like :
+--------------+---------------+--------+
| user_id | question_id | answer |
+--------------+---------+-----+--------+
| 1 | 2 | NULL |
+--------------+---------+-----+--------+
I tried something like this:
INSERT INTO profile_answers
(
user_id,
question_id,
answer
)
SELECT
id,
profile_answers.question_id,
null
FROM users
LEFT JOIN profile_answers ON profile_answers.user_id = users.id
WHERE NOT EXISTS (
SELECT answer
FROM profile_answers
WHERE user_id = id
AND question_id IN (
SELECT GROUP_CONCAT(id SEPARATOR ',') FROM profile_questions
)
)
But I ended up inserting rows with a question id of 0.
I've given this some thought, but I couldn't find anything better than using a cartesian product between users and questions, and a filtering subquery:
SELECT u.id, q.id
FROM users u,
questions q
WHERE NOT EXISTS(
SELECT *
FROM profile_answers a
WHERE a.question_id = q.id AND a.user_id = u.id
);
Demo
Categories table:
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| cat_id | int(8) | NO | PRI | NULL | auto_increment |
| cat_name | varchar(255) | NO | UNI | NULL | |
| cat_description | varchar(255) | NO | | NULL | |
| cat_order | int(8) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
Topics table:
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| topic_id | int(8) | NO | PRI | NULL | auto_increment |
| topic_subject | varchar(255) | NO | | NULL | |
| topic_date | datetime | NO | | NULL | |
| topic_cat | int(8) | NO | MUL | NULL | |
| topic_by | int(8) | NO | MUL | NULL | |
+---------------+--------------+------+-----+---------+----------------+
topic_cat corresponds to cat_id in a foreign key relationship.
How can i write an sql statement which returns: The NEWEST topic from every category plus the category name???
Heres what i have so far:
SELECT * FROM categories
LEFT JOIN
(SELECT topic_id, topic_subject, max(topic_date) AS MaxDate, topic_cat, topic_by
FROM topics
GROUP BY topic_subject) AS mt
ON categories.cat_id = mt.topic_cat
ORDER BY cat_order;
It returns multiple topics from the same category, where i only want one topic per category.
When you try to get the greatest-n-per-group, you need to join back to the original table (topics) to pick the row that has the max date. Because just mentioning MAX(topic_date) in your subquery doesn't make the other columns come from the row where that max date is found. What if you also mentioned MIN(topic_date)?
This should be a solution (though I haven't tested it):
SELECT *
FROM topics AS t
JOIN (SELECT topic_cat, MAX(topic_date) AS topic_date
FROM topics
GROUP BY topic_cat) AS maxt USING (topic_cat, topic_date)
RIGHT JOIN categories AS c ON t.topic_cat = c.cat_id
The first, easy, but not so powerfull solution would be to GROUP BY topic_cat and ORDER BY topic_date in topics select:
SELECT * FROM categories
LEFT JOIN
(SELECT topic_id, topic_subject, topic_date, topic_cat, topic_by
FROM topics
ORDER BY topic_date DESC
GROUP BY topic_cat) AS mt
ON categories.cat_id = mt.topic_cat
ORDER BY cat_order;
I think MySQL should be able to handle this, even it's not really universal solution for other database engines, where the rules for grouping are more strict.
I have the following tables
Business
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| b_id | bigint(20) | NO | PRI | NULL | auto_increment |
| b_name | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Locations
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| l_id | bigint(20) | NO | PRI | NULL | auto_increment |
| l_name | varchar(255) | NO | | NULL | |
| b_id | big(20) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Jobs
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| j_id | bigint(20) | NO | PRI | NULL | auto_increment |
| j_name | varchar(255) | NO | | NULL | |
| b_id | bigint(20) | NO | | NULL | |
| l_id | bigint(20) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
People
+-------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------+------+-----+---------+----------------+
| u_id | bigint(20) | NO | PRI | NULL | auto_increment |
| salutation | varchar(10) | NO | | NULL | |
| first_name | varchar(25) | NO | | NULL | |
| last_name | varchar(25) | NO | | NULL | |
+-------------+---------------+------+-----+---------+----------------+
People's Jobs
+-------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+----------------+
| pj_id | bigint(20) | NO | PRI | NULL | auto_increment |
| u_id | bigint(20) | NO | | NULL | |
| j_id | bigint(20) | NO | | NULL | |
| l_id | bigint(20) | NO | MUL | NULL | |
+-------------+------------+------+-----+---------+----------------+
I need to produce a table that shows
+----------+-------------------------+------------+------------+------------+
| b_id | b_name | Locations | Jobs | People |
+----------+-------------------------+------------+------------+------------+
| 21 | Widgets Inc | 0 | x | 0 |
| 24 | Prince Privates | 0 | 0 | 0 |
| 23 | Halon plc | x | 0 | 0 |
| 18 | Stinky Hotels | x | x | x |
| 20 | Pylon Catering Corps | x | x | x |
| 22 | Skytrain Biscuits | 0 | 0 | 0 |
+----------+-------------------------+------------+------------+------------+
I can achieve a correct count of matching locations for each business with:
SELECT b.b_id,
b.b_name,
count(l.l_id) AS locations
FROM business AS b
LEFT JOIN locations AS l ON b.b_id=l.b_id
GROUP BY b.b_id
ORDER BY b_name
If I extend it to include a count of the jobs at each business and then the count of people at each business it all goes pear shaped.
I know that the following is inherently wrong with regards to getting the count of people (as people can hold more than 1 job). I don't know if I need to use sub selects or COALESCE?
SELECT b.b_id,
b.b_name,
count(l.l_id) AS locations,
count(j.j_id) AS jobs,
count(p.u_id) AS people
FROM business AS b
LEFT JOIN locations AS l ON b.b_id=l.b_id
LEFT JOIN job AS j ON b.b_id=j.b_id
LEFT JOIN people_jobs AS p ON l.l_id=p.l_id
GROUP BY b.b_id
ORDER BY b_name
I think you can do a quick-and-dirty fix of your query by using count(distinct):
SELECT b.b_id, b.b_name,
count(distinct l.l_id) AS locations,
count(distinct j.j_id) AS jobs,
count(distinct p.u_id) AS people
FROM business b LEFT JOIN
locations l
ON b.b_id = l.b_id LEFT JOIN
job j
ON b.b_id = j.b_id LEFT JOIN
people_jobs p
ON l.l_id = p.l_id
GROUP BY b.b_id
ORDER BY b_name ;
It is also possible that the problem is simply that the join to people_jobs needs more conditions:
people_jobs p
ON l.l_id = p.l_id and j.j_id = p.j_id
And maybe a condition on u.
Your problem is that you are trying to do aggregation across multiple dimensions and getting a cartesian product for each business. An alternative that is sometimes necessary is to do the counts in subqueries.
This query should do what you need:
SELECT
b.b_id,
b.b_name,
(SELECT COALESCE(COUNT(l_id ),0) FROM locations WHERE b_id=b.b_id) AS locations,
(SELECT COALESCE(COUNT(j_id ),0) FROM jobs WHERE b_id=b.b_id) AS jobs,
(SELECT COALESCE(COUNT(DISTINCT u_id),0)
FROM jobs j
JOIN people_jobs pj ON pj.j_id=j.j_id
WHERE j.b_id=b.b_id
) AS people
FROM business as b
ORDER BY b_name
You don't need the GROUP BY if you use subSELECTs, as the outer query will return 1 row per b_id, no more.
If instead you do JOIN the 4 tables at the main query level, like you were doing, you have two difficulties:
number of rows increases (avoidable with GROUP BY)
a simple COUNT does not work properly (avoidable with COUNT(DISTINCT
...))
(as shown in Gordon's answer)
You can try This Query:-
SELECT b.b_id,b.b_name,count(l.l_id) AS locations,count(j.j_id) AS jobs,count(p.u_id) AS people
FROM business as b LEFT JOIN locations as l ON b.b_id=l.b_id
LEFT JOIN job as j ON b.b_id=j.b_id
LEFT JOIN people_jobs as p ON l.l_id=p.l_id
GROUP BY b.b_id, b.b_name
ORDER BY b_name
I hope this will work for you.
This is very basic, I know, but I tend to happen on this issue often.
Tables
mysql> describe Posts;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| Time | int(11) | YES | MUL | NULL | |
+-----------+---------+------+-----+---------+----------------+
mysql> describe PostCategories;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| pid | int(11) | YES | | NULL | |
| Category | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Query
SELECT P.id AS pid, P.Time, PC.Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
ORDER BY P.Time DESC
Returns
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
| 7 | 1396524835 | value2 |
+-----+------------+----------+
Desired outcome
I would like it to only give me one row for every pid. In other words, no matter how many categories the Post have, I want it to only result in one row in the result dataset.
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
+-----+------------+----------+
Category result does not matter, I will not fetch it once it works as I want it to.
SELECT P.id AS pid, MIN(P.Time) as first_post_time, MAX(PC.Category) as Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.id
ORDER BY P.Time DESC
Just add group by and calculate time and category (use min or max)
Group posts by using Group By and you can also get all categories if you want by using group_concat procedure in mysql.
SELECT P.id AS pid, P.Time, GROUP_CONCAT(PC.Category, ' , ') FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.pid
ORDER BY P.Time DESC
I have three tables that look like this:
People:
+------------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fname | varchar(32) | NO | | NULL | |
| lname | varchar(32) | NO | | NULL | |
| dob | date | NO | | 0000-00-00 | |
| license_no | varchar(24) | NO | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
| status | varchar(8) | NO | | Allow | |
+------------+-------------+------+-----+-------------------+----------------+
Units:
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| number | varchar(3) | NO | | NULL | |
| resident | int(11) | NO | MUL | NULL | |
| type | varchar(16) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Visits:
+----------+-----------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-----------+------+-----+---------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| vis_id | int(11) | NO | MUL | NULL | |
| unit | int(11) | NO | MUL | NULL | |
| time_in | timestamp | NO | | CURRENT_TIMESTAMP | |
| time_out | timestamp | NO | | 0000-00-00 00:00:00 | |
+----------+-----------+------+-----+---------------------+----------------+
There are multiple foreign keys linking these tables:
units.resident -> people.id
visits.unit -> units.id
visits.vis_id -> people.id
I am able to run this query to find all residents ie - everyone from people that are referenced by the units.resident foreign key:
SELECT concat(p.lname, ', ', p.fname) as 'Resident', p.dob as 'Birthday',
u.number as 'Unit #'
from people p, units u
where p.id = u.resident
order by u.number
It returns the results I want... However, it'd be useful to do the opposite of this to find all the people who are not residents ie- everyone from people who aren't referenced by the units.resident foreign key.
I've tried many different queries, most notably some inner and left joins, but I'm getting waaaaay too many duplicate entries (from what I've read here, this is normal). The only thing I've found that works is using a group by license_no, because as of now the "residents" don't have this information, like this:
SELECT p.id, concat(p.lname, ', ', p.fname) as 'Visitor',
p.license_no as 'License', u.number from people p
left join units u on u.number <> p.id
group by p.license_no order by p.id;
This works for all but one resident, who's u.number is displayed on ALL results. The residents will soon have license_no entries, and I can't have that one odd entry in the returned results all the time, so this query won't work as a long-term solution.
How can I structure a query without a group by that will return the results I want?
This should work
SELECT
p.id
, P.fname
, P.lname
FROM
people AS p
LEFT JOIN
units AS u
ON
p.id = u.resident
WHERE
u.resident IS NULL
Extra hint.
Table people should be called person.
By u.resident you mean a person. so it should be a person_id there in the unit table...
Better logic helps to write SQL better, if your name convention is clear to use.
Use a NOT EXISTS clause to exclude those people who are residents.
SELECT P.id
,P.fname
,P.lname
,etc...
FROM People P
WHERE NOT EXISTS (SELECT 1 FROM Units U WHERE U.resident = P.id)