What I did was, I wanted each user to have their own "unique" numbering system. Instead of auto incrementing the item number by 1, I did it so that Bob's first item would start at #1 and Alice's number would also start at #1. The same goes for rooms and categories. I achieved this by creating "mapping" tables for items, rooms and categories.
The query below works, but I know it can definitely be refactored. I have primary keys in each table (on the "ids").
SELECT unique_item_id as item_id, item_name, category_name, item_value, room_name
FROM
users_items, users_map_item, users_room, users_map_room, users_category, users_map_category
WHERE
users_items.id = users_map_item.map_item_id AND
item_location = users_map_room.unique_room_id AND
users_map_room.map_room_id = users_room.room_id AND
users_map_room.map_user_id = 1 AND
item_category = users_map_category.unique_category_id AND
users_map_category.map_category_id = users_category.category_id AND
users_category.user_id = users_map_category.map_user_id AND
users_map_category.map_user_id = 1
ORDER BY item_name
users_items
| id | item_name | item_location |item_category |
--------------------------------------------------------
| 1 | item_a | 1 | 1 |
| 2 | item_b | 2 | 1 |
| 3 | item_c | 1 | 1 |
users_map_item
| map_item_id | map_user_id | unique_item_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_rooms
| id | room_name |
----------------------
| 1 | basement |
| 2 | kitchen |
| 3 | attic |
users_map_room
| map_room_id | map_user_id | unique_room_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
users_category
| id | room_name |
----------------------
| 1 | antiques |
| 2 | appliance |
| 3 | sporting goods |
users_map_category
| map_room_id | map_user_id | unique_category_id |
----------------------------------------------------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
Rewriting your query with explicit JOIN conditions makes it more readable (while doing the same).
SELECT mi.unique_item_id AS item_id
, i.item_name
, c.category_name
, i.item_value
, r.room_name
FROM users_map_item mi
JOIN users_items i ON i.id = mi.map_item_id
JOIN users_map_room mr ON mr.unique_room_id = i.item_location
JOIN users_room r ON r.room_id = mr.map_room_id
JOIN users_map_category mc ON mc.unique_category_id = i.item_category
JOIN users_category c ON (c.user_id, c.category_id)
= (mc.map_user_id, mc.map_category_id)
WHERE mr.map_user_id = 1
AND mc.map_user_id = 1
ORDER BY i.item_name
The result is unchanged. Query plan should be the same. I see no way to improve the query further.
You should use LEFT [OUTER] JOIN instead of [INNER] JOIN if you want to keep rows in the result where no matching rows are found in the right hand table. You may want to move the additional WHERE clauses to the JOIN condition in this case, as it changes the outcome.
Related
so what I am trying to do is having 3 tables (pictures, collections, and bridge) with the following columns:
Collections Table:
| id | name |
------------------
| 1 | coll1 |
| 2 | coll2 |
------------------
Pictures Table: (timestamps are unix timestamps)
| id | name | timestamp |
-------------------------
| 5 | Pic5 | 1 |
| 6 | Pic6 | 19 |
| 7 | Pic7 | 3 |
| 8 | Pic8 | 892 |
| 9 | Pic9 | 4 |
-------------------------
Bridge Table:
| id | collection | picture |
-----------------------------
| 1 | 1 | 5 |
| 2 | 1 | 6 |
| 3 | 1 | 7 |
| 4 | 1 | 8 |
| 5 | 2 | 5 |
| 6 | 2 | 9 |
| 7 | 2 | 7 |
-----------------------------
And the result should look like this:
| collection_name | picture_count | newest_picture |
----------------------------------------------------
| coll1 | 4 | 8 |
| coll2 | 3 | 9 |
----------------------------------------------------
newest_picture should always be the picture with the heighest timestamp in that collection and I also want to sort the result by it. picture_count is obviously the count of picture in that collection.
Can this be done in a single statement with table joins and if yes:
how can I do this the best way?
A simple method uses correlated subqueries:
select c.*,
(select count(*)
from bridge b
where b.collection = c.id
) as pic_count,
(select p.id
from bridge b join
pictures p
on b.picture = b.id
where b.collection = c.id
order by p.timestamp desc
limit 1
) as most_recent_picture
from collections c;
A more common approach would use window functions:
select c.id, c.name, count(bp.collection), bp.most_recent_picture
from collections c left join
(select b.*,
first_value(p.id) over (partition by b.collection order by p.timestamp desc) as most_recent_picture
from bridge b join
pictures p
on b.picture = p.id
) bp
on bp.collection = c.id
group by c.id, c.name, bp.most_recent_picture;
I am having trouble with an SQL query. I have two tables.
My first table:
+------------+-------------+---------------+
| id_mission | Some column | Other column |
+------------+-------------+---------------+
| 1 | ... | ... |
| 2 | ... | ... |
+------------+-------------+---------------+
My second table:
+------------+-------------+---------+
| id_mission | id_category | points |
+------------+-------------+---------+
| 1 | 1 | 3 |
| 1 | 2 | 4 |
| 1 | 3 | 4 |
| 1 | 4 | 8 |
| 2 | 1 | -4 |
| 2 | 2 | 3 |
| 2 | 3 | 1 |
| 2 | 4 | -7 |
+------------+-------------+---------+
And I would like to have this kind of result with my SELECT request
+------------+-------------+--------------+---------------+----------------+
| id_mission | Some column | Other column | id_category 1 | id_category X |
+------------+-------------+--------------+---------------+----------------+
| 1 | ... | ... | ... | ... |
| 2 | ... | ... | ... | ... |
+------------+-------------+--------------+---------------+----------------+
I have tried this with the first two column but it doesn't work, I also tried GROUP_CONCAT, it works but it's not the result I want.
SELECT m.id_mission ,mc.id_category 1,mc1.id_category 2
from mission m
left join mission_category mc on m.id_mission = mc.id_mission
left join mission_category mc1 on m.id_mission = mc1.id_mission
Can someone help me?
You can use conditional aggregation. Assuming that you want to pivot the points value per category:
select
t1.*,
max(case when t2.id_category = 1 then points end) category_1,
max(case when t2.id_category = 2 then points end) category_2,
max(case when t2.id_category = 3 then points end) category_3
from t1
inner join t2 on t2.id_mission = t1.id_mission
group by t1.id_mission
This assumes that id_mission is the primary key of t1 (else, you need to enumerate the columns you want in both the select and group by clauses).
I'm creating e-commerce web site using MySQL. I have successfully created and inserted data to database.
Here is my database schema
table: categories table: product_types
+----+--------------+ +----+-------------+------------+
| id | name | | id | category_id | name |
+----+--------------+ +----+-------------+------------+
| 1 | Electronics | | 1 | 1 | Smartphone |
| 2 | Fashion | | 2 | 1 | Speakers |
+----+--------------+ +----+-------------+------------+
table: products
+----+-----------------+-------------+-------------------+-------+
| id | product_type_id | category_id | name | price |
+----+-----------------+-------------+-------------------+-------+
| 1 | 1 | 1 | Samsung Galaxy A3 | 300 |
| 2 | 1 | 1 | Samsung Galaxy A7 | 400 |
+----+-----------------+-------------+-------------------+-------+
table: options table: option_values
+----+-----------------+-------+ +----+-----------+------------+
| id | product_type_id | name | | id | option_id | name |
+----+-----------------+-------+ +----+-----------+------------+
| 1 | 1 | RAM | | 1 | 1 | 512 MB |
| 2 | 1 | Screen| | 2 | 1 | 1 GB |
| 3 | 1 | OS | | 3 | 3 | Android 5 |
+----+-----------------+-------+ | 4 | 3 | Android 6 |
| 5 | 2 | HD |
| 6 | 2 | FHD |
+----+-----------+------------+
table: product_option_values
+----+------------+-----------+-----------------+
| id | product_id | option_id | option_value_id |
+----+------------+-----------+-----------------+
| 15 | 1 | 1 | 1 |
| 16 | 1 | 2 | 5 |
| 17 | 1 | 3 | 3 |
| 18 | 2 | 1 | 2 |
| 19 | 2 | 2 | 6 |
| 20 | 2 | 3 | 4 |
+----+------------+-----------+-----------------+
Search must trigger through name column of each table and return name and price from products table.
The problem is that I don't know how to perform full text search joining all that tables.
Is there any easy way to do it?
You need a query that LEFT JOINs on each table to search with a condition based on fulltext search function MATCH, with a WHERE clause to filter out non-matching records. The SELECT DISTINCT ensures that you will not see duplicates.
We need to adjust manually the JOIN criteria from each table to products : option_values is the most complicated case as it does not directly references products (an additional join on product_option_values is needed, aliased pov below.
SELECT DISTINCT p.name, p.price
FROM
products p
LEFT JOIN categories c
ON MATCH(c.name) AGAINST('foo' IN NATURAL LANGUAGE MODE)
AND c.id = p.category_id
LEFT JOIN product_types pt
ON MATCH(pt.name) AGAINST('foo' IN NATURAL LANGUAGE MODE)
AND pt.category_id = p.category_id
LEFT JOIN options o
ON MATCH(o.name) AGAINST('foo' IN NATURAL LANGUAGE MODE)
AND o.product_type_id = p.product_type_id
LEFT JOIN product_option_values pov
ON pov.product_id = p.id
LEFT JOIN option_values ov
ON MATCH(ov.name) AGAINST('foo' IN NATURAL LANGUAGE MODE)
AND ov.id = pov.option_value_id
WHERE
COALESCE(c.id, pt.id, o.id, ov.id) IS NOT NULL
I have a question and answer website like stackoverflow. Here is the structure of some tables:
-- {superfluous} means some other columns which are not related to this question
// q&a
+----+-----------------+--------------------------+------+-----------+-----------+
| id | title | body | type | related | author_id |
+----+-----------------+--------------------------+------+-----------+-----------+
| 1 | How can I ... | I'm trying to make ... | q | NULL | 3 |
| 2 | | You can do that by ... | a | 1 | 1 |
| 3 | Why should I .. | I'm wonder, why ... | q | NULL | 1 |
| 4 | | First of all you ... | a | 1 | 2 |
| 5 | | Because that thing ... | a | 3 | 2 |
+----+-----------------+--------------------------+------+-----------+-----------+
// users
+----+--------+-----------------+
| id | name | {superfluous} |
+----+--------+-----------------+
| 1 | Jack | |
| 2 | Peter | |
| 3 | John | |
+----+--------+-----------------+
// votes
+----+----------+-----------+-------+-----------------+
| id | user_id | post_id | value | {superfluous} |
+----+----------+-----------+-------+-----------------+
| 1 | 3 | 4 | 1 | |
| 2 | 1 | 1 | -1 | |
| 3 | 2 | 1 | 1 | |
| 4 | 3 | 2 | -1 | |
| 5 | 1 | 4 | 1 | |
| 6 | 3 | 5 | -1 | |
+----+--------+-------------+-------+-----------------+
// tags
+----+------------+-----------------+
| id | name | {superfluous} |
+----+------------+-----------------+
| 1 | PHP | |
| 2 | SQL | |
| 3 | MySQL | |
| 4 | HTML | |
| 5 | CSS | |
| 6 | C# | |
+----+------------+-----------------+
// q&aTag
+-------+--------+
| q&aid | tag_id |
+-------+--------+
| 1 | 1 |
| 1 | 4 |
| 3 | 5 |
| 3 | 4 |
| 4 | 6 |
+-------+--------+
Now I need to find top users in a specific tag. For example, I need to find Peter as top user in PHP tag. Because his answer for question1 (which has PHP tag) has earned 2 upvotes. Is doing that possible?
Try this:
select q1.title, u.id, u.name, sum(v.value) total from `q&a` q1
left join `q&atag` qt ON q1.id = qt.`q&aid`
inner join tags t ON qt.tag_id = t.id
left join `q&a` q2 ON q2.related = q1.id
left join users u ON q2.author_id = u.id
left join votes v ON v.post_id = q2.id
where t.name = 'PHP'
group by q1.id, u.id
and here is a simple divided solution:
Let us divide it into sub queries:
get the id of the tag you will search for: select id from tags where name = 'PHP'
get the questions with this tag: select 'q&aid' from 'q&aTag' where tag_id = 1.
get the ids of answers for that question: select id, author_id fromq&awhere related in (2.)
get the final query: select user_id, sum(value) from votes where post_id in (3.) group by user_id
Now combining them all give the result:
select user_id, sum(`value`) total from votes
where post_id in (
select id from `q&a` where related in (
select `q&aid` from `q&aTag` where tag_id IN (
select id from tags where name = 'PHP'
)
)
)
group by user_id
you can add this at the end if you want only one record:
order by total desc limit 1
My database looks like this, I have client accounts which are assigned to specific profiles, and I have profiles which are assigned to specific categories, like in this schema:
| categories | | profiles | | categories_map |
--------------- ------------- ----------------------------
| ID | name | | ID | name | | ID | profile_id | cat_id |
--------------- ------------- ----------------------------
| 1 | cat1 | | 1 | p1 | | 1 | 1 | 1 |
| 2 | cat2 | | 2 | p2 | | 2 | 2 | 1 |
| 3 | cat3 | | 3 | p3 | | 3 | 3 | 1 |
| 4 | p4 | | 4 | 1 | 2 |
| 5 | 3 | 2 |
| 6 | 4 | 3 |
| profiles_map |
-----------------------------
| ID | profile_id | acc_id |
-----------------------------
| 1 | 1 | 1 |
| 2 | 3 | 1 |
| 3 | 4 | 1 |
I need to get categories assigned to accounts - which means when I want to get categories for acc_id = 1, I should get categories with ID 2 and 3 ( category with ID 2 doesn't fit because it contains profile with ID 2 which isn't assigned to this account). I tried this query but it doesn't work
select cats.id from profiles_map map
right join categories_map catm on catm.profile_id = map.profile_id
right join categories cats on cats.id = catm.cat_id
where catm.profile_id in (select profile_id from profiles_map where acc_id = 1)
and map.acc_id = 1 group by cats.id;
Could anybody help me with this question?
Can you try this one?
SELECT DISTINCT C.ID
FROM profiles_map PM
INNER JOIN categories_map CM ON CM.profile_id = PM.profile_id
INNER JOIN categories C ON C.ID = CM.cat_id
WHERE PM.acc_id= 1
If you want to get only category id, Please try following query:
SELECT DISTINCT cm.cat_id FROM categories_map cm
WHERE cm.profile_id in
(SELECT profile_id FROM profiles_map WHERE acc_id = 1)
Or if want to get category name and id then , use following query:
SELECT cat.id,cat.name FROM categories cm
WHERE cat.id in (SELECT DISTINCT cm.cat_id FROM categories_map cm
WHERE cm.profile_id in
(SELECT pm.profile_id FROM profiles_map pm WHERE pm.acc_id = 1))