I'm making a list with certain songs. Each song has its own unique ID. OK let's say I have this table called list. A new:
The ID is self-explanatory. It's used to ID rows. song_one through song_ten is filled with a song's unique ID.
Every time a user makes a new list, they add songs and each row gets filled. Now, how would I get the average rank of the songs in the tables list using the song's ID that is filled between song_one through song_ten?
Redesign your database. Make a new table with songid, listid and rank. This will make your job easy.
listsongs
-----------------
songid [PK] -- Foreign key referencing the songs table
listid [PK] -- Foreign key referencing the lists table
rank
Selecting average song ranks:
SELECT
a.song_id
AVG(b.rank) AS avgrank
FROM
songs a
LEFT JOIN
listsongs b PM a.song_id = b.song_id
GROUP BY
a.song_id
Please do as others have said about normalizing your DB structure. If you wish to continue with this design however, you can use this clunky-looking solution:
SELECT
a.song_id,
AVG(
CASE
WHEN a.song_id = b.song_one THEN 1
WHEN a.song_id = b.song_two THEN 2
WHEN a.song_id = b.song_three THEN 3
WHEN a.song_id = b.song_four THEN 4
WHEN a.song_id = b.song_five THEN 5
WHEN a.song_id = b.song_six THEN 6
WHEN a.song_id = b.song_seven THEN 7
WHEN a.song_id = b.song_eight THEN 8
WHEN a.song_id = b.song_nine THEN 9
WHEN a.song_id = b.song_ten THEN 10
END
) AS AvgRank
FROM
songs a
INNER JOIN
list b ON
a.song_id IN
(
b.song_one,
b.song_two,
b.song_three,
b.song_four,
b.song_five,
b.song_six,
b.song_seven,
b.song_eight,
b.song_nine,
b.song_ten
)
GROUP BY
a.song_id
I would listen to #Mark Byers and #Shehzad Bilal, who said that you need to redesign your database structure.
When you think in the terms of tables and their attributes, think logical - think in the terms of code.
For example: If you are writing to a file, would it be easier to create a universal loop to output all the things needed, or to open the file with different pieces of code every time you needed to write something.
In your database, it would be easier to have one table that represents the song itself (that is the general idea behind a database design) than having a table that represents all the songs.
(table) (attribute)
song
id
albumid (fk from table album)
name
title
(...etc)
list
id
songid (fk from table song)
ip
date
(...etc)
If you wanted to create a ranking system, you would do it through code. In some cases, purely depending on your design, you would have a table for it, but it would also be universal.
Related
This question already has answers here:
Many-to-many relationships examples
(5 answers)
Is storing a delimited list in a database column really that bad?
(10 answers)
Closed 2 years ago.
I have two tables. 1.Products 2. Combo
Product Table has column like (product_id, product_name).
Combo Table has column like (combo_id, combo_name, combo_included_products /* combo might have 2 or more products from product table*/)
Rule: 1 Combo can have many products.
Product Table
product_id product_name
1 Pen
2 Pencil
3 Pen Holders
4 Sharpeners
-
Combo Table
combo_id combo_name this_combo_includes_below_products
1 Big_combo (1,2,3,4) => big combo includes all products
2 Small_combo (2,4) => this combo only includes product 2,4
3 test_combo (1,2)
4 Other_combo (1,4)
So How can I insert multiple products ids in third column of combo table?
I am thinking to store data like 1,2,3,4 and 2,4 and so on. Then problem will be to write join query. i.e
select combo.combo_id, combo.combo_name, product.product.id from combo join product ON combo.this_combo_included_products_id = product.product_id <= As there will be multiple product ids, It will not possible.
I am also thinking make a script where I will first fetch combo table as it is, then I will split third cloumn by "," and will run iterate (select * from combo where product id=this_combo_included_item_id[i]) <= I am not sure this is good idea however this can be a alternate solution which require some coding afterwards.
(I use phpmysql to fetch data anyways - so I can process that after fetching.)
$sql = "SELECT * FROM combo";
$result = $conn->query($sql);
while($row = $result->fetch_assoc()) {
// I can run other query here
$child_query = "select combo.combo_id, combo.combo_name, product.product.id from combo join product
ON combo.this_combo_included_products_id = product.product_id";
}
However Is there anything else I can do while designing Database table. Thanks.
Don't store multiple values in a single row. Don't store numbers as strings. The accepted answer to famous SO question gives great insights on how bad this is.
You have a many to many relationship between products and combos: each product may appear in may combos, and each combo may contain many products. From normalization perspective, the proper way to represent it is to create another table, called a bridge table, to store the relations.
create table product_combo (
product_id int references product(product_id),
combo_id int references combo(combo_id),
primary key (product_id, combo_id)
);
For your sample data, the bridge table would contain:
product_id combo_id
1 1
1 2
1 3
1 4
2 2
2 4
3 1
3 2
4 1
4 4
With this set-up in place, say that you want to select a given combo along with all its associated products, then you would go:
select c.*, p.*
from combos c
inner join product_combos pc on pc.combo_id = c.combo_id
inner join products p on p.product_id = pc.product_id
where c.combo_id = ?
And if you really want to, you can even rebuild the csv lists of products for each combo:
select c.combo_id, c.combo_name, group_concat(p.product_name) product_names
from products p
inner join product_combos pc on pc.product_id = p.product_id
inner jon combos c on c.combo_id = pc.combo_id
group by c.combo_id, c.combo_name
I am building a news feed from multiple tables status, events and tracks. The data retrieved from these tables should correspond to the user-id of all the users that I follow. On the face of it I thought this seemed simple enough and I could do this with a few joins.
Every row in each of the status, events and tracks table has unique ID and they are also unique from each other, this should make matters easier later. I have done this using a unique_id table with a primary key to retrieve ID's before inserting.
My trouble is upon joining everything together the values duplicate.
Example
If I have this data.
----------
**Status**
user-id = 1
id = 1
status = Hello Universe!
----------
**Events**
user-id = 1
id = 2
event-name = The Big Bang
----------
**Tracks**
user-id = 1
id = 3
track-name = Boom
----------
Assuming I follow user 1 I would want to retrieve this.
user-id ---- id ---- status ---- event-name ---- track-name
1 1 Hello NULL NULL
Universe
1 2 NULL The Big Bang NULL
1 3 NULL NULL Boom
But in reality what I would get is something like this.
user-id ---- status.id ---- events.id ---- tracks.id ---- status ---- event-name ---- track-name
1 1 2 3 Hello The Big Bang Boom
Universe
And that row would be repeated 3 times.
Most of the queries I have tried will get something along those lines.
SELECT * FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN status ON followers.`follows-id` = status.`user-id`
LEFT JOIN events ON followers.`follows-id` = events.`user-id`
LEFT JOIN tracks ON followers.`follows-id` = tracks.`user-id`
WHERE users.`id` = 2
I am using laravel, so eventually this query will be put into Eloquent format. If there is a simpler and a not performance degrading way of doing this in Eloquent please let me know.
Edit
I cannot use a UNION as there is a different number of values in each table. The example is simplified for ease of reading.
Thanks to Frazz for pointing out I could use UNIONS. I have researched into them and come up with this query.
SELECT stream.*, users.id AS me FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN (
SELECT `id`,`user-id`,`created_at`, `name`, NULL as status
FROM events
UNION ALL
SELECT `id`,`user-id`, `created_at`,NULL AS name, `status`
FROM status
) AS stream ON stream.`user-id` = `followers`.`follows-id`
WHERE users.id = 2
Now comes the process of converting it to an eloquent model...
Hey I have the following MYSQL DB structure for 3 tables with many to many relation. Many users can have many cars and cars can be for many users as showing below:
Users
ID | Name
---------
100|John
101|Smith
Cars
ID | Name
---------
50|BMW
60|Audi
Users_cars
ID | UID | CID
---------
1| 100 |50
2| 100 |60
3| 101 |60
I have a page users_cars.php this page have two drop down lists
list of all users
list of all cars
In this page you can select a user from user's list and select a car from car's list then click add to insert into users_cars table.
What am trying to do is to exclude from user's drop down list all the users that have been linked with all the available cars from cars table.
In the example above user's drop down list will just have "Smith" because "John" linked with all cars available (BMW,AUDI), if "Smith" also has the BMW he will be excluded from the list. I need a select query for this condition and i don't want to use any nest select query to count user records inside users_cars table
If I understand what you are after you need to use GROUP BY in your query. So to select all users:
SELECT ID, UID FROM Users_cars GROUP BY UID
and for all cars:
SELECT ID, CID FROM Users_cars GROUP BY CID
That will group results that are the same, so you only get one instance of each user, or one instance of each car.
I hope I understood your question right.
I think you can so this using some programming -
With PHP/mysql -
Get count of all distinct car ID's
Get count of cars for each user. (making sure this lists only unique car ID's)
Loop through all users and in each loop compare the above two and exclude the user where this condition matches.
SELECT *
FROM users
WEHRE id NOT IN (SELECT uid
FROM (SELECT uid, COUNT(cid), COUNT(*)
FORM cars
LEFT OUTER JOIN users_cars ON cars.id = users_cars.cid
GROUP BY uid
HAVING COUNT(cid) = COUNT(*)
Basically, what you want to do is that (if I understood your problem) :
SELECT UID FROM Users_cars WHERE CID NOT IN (SELECT ID FROM Cars);
But carefull, this is a greedy request (depends on the size of the tables of course) and you should better put a flag on the user table and update then when your user uses the last available car (or with a batch) so you don't run the request too often !
in our project we've got an user table where userdata with name and different kind of scores (overall score, quest score etc. is stored). How the values are calculated doesn't matter, but take them as seperated.
Lets look table 'users' like below
id name score_overall score_trade score_quest
1 one 40000 10000 20000
2 two 20000 15000 0
3 three 30000 1000 50000
4 four 80000 60000 3000
For showing the scores there are then a dummy table and one table for each kind of score where the username is stored together with the point score and a rank. All the tables look the same but have different names.
id name score rank
They are seperated to allow the users to search and filter the tables. Lets say there is one row with the player "playerX" who has rank 60. So if I filter the score for "playerX" I only see this row, but with rank 60. That means the rank are "hard stored" and not only displayed dynamically via a rownumber or something like that.
The different score tables are filled via a cronjob (and under the use of a addional dummy table) which does the following:
copies the userdata to a dummy table
alters the dummy table by order by score
copies the dummy table to the specific score table so the AI primary key (rank) is automatically filled with the right values, representing the rank for each user.
That means: Wheren there are five specific scores there are also five score tables and the dummy table, making a total of 6.
How to optimize?
What I would like to do is to optimize the whole thing and to drop duplicate tables (and to avoid the dummy table if possible) to store all the score data in one table which has the following cols:
userid, overall_score, overall_rank, trade_score, trade_rank, quest_score, quest_rank
My question is now how I could do this the best way and is there another way as the one shown above (with all the different tables)? MYSQL-Statements and/or php-code is welcome.
Some time ago I tried using row numbers but this doesn't work a) because they can't be used in insert statements and b) because when filtering every player (like 'playerX' in the example above) would be on rank 1 as it's the only row returning.
Well, you can try creating a table with the following configuration:
id | name | score_overall | score_trade | score_quest | overall_rank | trade_rank | quest_rank
If you do that, you can use the following query to populate the table:
SET #overall_rank:=-(SELECT COUNT(id) FROM users);
SET #trade_rank:=#overall_rank;
SET #quest_rank:=#overall_rank;
SELECT *
FROM users u
INNER JOIN (SELECT id,
#overall_rank:=#overall_rank+1 AS overall_rank
FROM users
ORDER BY score_overall DESC) ovr
ON u.id = ovr.id
INNER JOIN (SELECT id,
#trade_rank:=#trade_rank+1 AS trade_rank
FROM users
ORDER BY score_trade DESC) tr
ON u.id = tr.id
INNER JOIN (SELECT id,
#quest_rank:=#quest_rank+1 AS quest_rank
FROM users
ORDER BY score_quest DESC) qr
ON u.id = qr.id
ORDER BY u.id ASC
I've prepared an SQL-fiddle for you.
Although I think performance will weigh in if you start getting a lot of records.
A bit of explanation: the #*_rank things are SQL variables. They get increased with 1 on every new row.
I know I can do joins but its not exactly what I want.
I'm making a live chat system that has 2 tables mainly: the main chat table (call it table a), and then a mod table (call this one table b). If a user gets suspended, messages reach over 100 for that channel, or they are over 1 week, the messages get moved from the main chat table to the mod table.
I store the ID of the chat messages as ID(primary) on the main chat table and as chatID on the mod table.
What I'm doing is making a separate page for my Mods and I want to be able to combine the two tables into 1 area but I want them to be ordered by their respective tables.
So lets say we had the following:
Main table ID's: 1,2,4
Mod table ID: 3
I want my results to show up 1,2,3,4 no matter which table the ID is in.
Any help is greatly appreciated!
Edit: I got the answer and this is what I used to do so:
SELECT ab.* FROM
((SELECT ID as table_id FROM a
WHERE roomID = 'newUsers' ORDER BY ID ASC)
UNION ALL
(SELECT chatID as table_id FROM b
WHERE roomID = 'newUsers' ORDER BY chatID ASC)) ab
ORDER BY ab.table_id
Use a UNION in a subselect.
SELECT ab.* FROM (
SELECT 1 as table_id, * FROM a
UNION ALL
SELECT 2 as table_id, * FROM b
) ab
ORDER BY ab.id
If you want the result of table A to appear before table B, change the query to:
SELECT ab.* FROM (
SELECT 1 as table_id, * FROM a
UNION ALL
SELECT 2 as table_id, * FROM b
) ab
ORDER BY ab.table_id, ab.id
Some background
UNION ALL will merge two tables resultsets into one resultset.
UNION will do the same but will eliminate duplicate rows.
This takes time and slows things down, so if you know there will be no duplicate records (or you don't care about dups) use UNION ALL.
See also: http://dev.mysql.com/doc/refman/5.5/en/union.html