I have one student table links to two scores tables which are exactly the same structures. The only different is that one table will stores high scores, the other table stores low scores. Now I need to query both high and low score tables, it will list all subjects scores with student name if it is from high score table and scores with name is student if its from low score table, and I need to order the result by time.
SELECT u.student_name,
a.subject1_score,
a.subject2_score,
a.subject3_score,
a.subject4_score,
a.subject5_score,
a.exam_date
FROM Student u
INNER JOIN High_Score_Table a
On u.student_id = a.student_id
ORDER BY a.exame_date = time
Then for low_score_Table I will have almost same query except that the student name will equal to Student by default.
Then I will need to put this together in a list and order by time. How could I do that shorter and better ?
Btw, I can merge two tables low and high_score into one, and add a column called "flag" into that, whenever the flag value equal to "show" then I show student name with all score records, else "hidden" I will just show "Student" and all score records. How could I do that in one query ?
It sounds like you need a UNION, because you are concatenating two distinct result sets - one from High_Score_Table and one from (presumably) Low_Score_Table:
select s.student_name,
h.subject1_score,
h.subject2_score,
h.subject3_score,
h.subject4_score,
h.subject5_score,
h.exam_date
from High_Score_Table h
join Student s on h.student_id = u.student_id
union all
select 'student' as student_name,
l.subject1_score,
l.subject2_score,
l.subject3_score,
l.subject4_score,
l.subject5_score,
l.exam_date
from Low_Score_Table l
order by exam_date
The takeaway here is an ORDER BY clause in a union sorts over the entire result set - which is in this case exactly what you want.
Related
I need to report the number of records that match each of several criteria. For example, how many customers live in each state. I know I can accomplish this through a series of MySQL SELECT statements, but that seems cumbersome and produces a ton of (unnecessary?) MySQL calls.
Can you tell me a better method to use? Can I query the database with one SELECT statement and then use PHP to filter the results to variables?
I'd suggest creating a view for this task just to hide the complexity of the query. Also, in the event that your table schema changes, it is likely that you are still going to want to retrieve this same information from the database. You'd be able to change the view in one place, instead of having to change the queries in, possibly, multiple places to satisfy your schema changes.
I'll just show you the queries, though, since you'd need to know how to do that to create a view anyways.
Sticking with your example of customers living in each state, let's pretend you also want statistics on how many customers share the same last name.
I've setup a mock structure of what your database might be like at this SqlFiddle.
Customers with Same LastName
The following query might be used to get the number of customers with the same last name:
SELECT
LastName AS "Value",
COUNT(*) AS "Count"
FROM Customers
GROUP BY
LastName;
Customers in Same State
Similarly, the customers in the same state might be retrieved with a query as follows:
SELECT
S.Name AS "Value",
COUNT(*) AS "Count"
FROM Customers AS C
INNER JOIN CustomerAddresses AS CA ON C.Id = CA.CustomerId
INNER JOIN Addresses AS A ON CA.AddressId = A.Id
INNER JOIN States AS S ON A.State = S.Id
GROUP BY
A.State;
Getting Your Desired Format
The format that you want is an aggregation of these two queries. You want both returned as a single result set. So, let's workout a schema for the returned table:
ResultType - This will hold a value that corresponds to the type of result. i.e. "State"
Value - This will hold the value of the aggregated column. i.e. "Florida"
Count - This will hold the total number of records that match the aggregated column.
So, now that we have a format, let's create a query that uses our two queries from above, and puts them into this format.
First, I add a new field to each of the above queries: ResultType
For example:
"LastName" AS "ResultType"
Now, I combine my queries into a single query using the UNION ALL statement:
SELECT * FROM (
/* LastName query */
SELECT
"LastName" AS "ResultType",
LastName AS "Value",
COUNT(*) AS "Count"
FROM Customers
GROUP BY
LastName
UNION ALL
/* States query */
SELECT
"State" AS "ResultType",
S.Name AS "Value",
COUNT(*) AS "Count"
FROM Customers AS C
INNER JOIN CustomerAddresses AS CA ON C.Id = CA.CustomerId
INNER JOIN Addresses AS A ON CA.AddressId = A.Id
INNER JOIN States AS S ON A.State = S.Id
GROUP BY
A.State
) AS A
In my SqlFiddle above, this produces an output like:
RESULTTYPE VALUE COUNT
=================================
LastName Jetson 1
LastName Johnson 2
LastName Milton 1
State Florida 2
State Georgia 1
State Utah 1
As you can see, this could get quite complex, so you might consider looking into placing this into a view. Then, you'd be able to query your view, as if it was the table above (ResultType, Value, and Count). That would also allow you to filter on it.
create select query make number of aliens of table and make your related columns aliens which is you want to use.
lest see sample example
SELECT a.id AS id
FROM Table1 AS a
WHERE ...
UNION ALL
SELECT b.name AS name
FROM Table2 AS b
WHERE ...
ORDER BY (a or b) ...
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'm trying to set up a high scores board for a game I'm making. On the board, I'm using foreign keys to two other boards, players and weapons. Each score stores the four weapons the player used on that run. The tables are set up like this:
Scores
id|playerid|score|weapon0id|weapon1id|weapon2id|weapon3id
Players
id|name
Weapons
id|name
I want to select multiple rows from the scores table with ids replaced by the appropriate names. I'm able to get the correct player name and one weapon using this statement:
SELECT scoreID, Players.playerName, scoreVal,
Weapons.weaponLabel, scoreW1, scoreW2, scoreW3
FROM Scores, Players, Weapons
WHERE Players.playerID = scorePlayer AND Weapons.weaponID = scoreW0
Everywhere I've looked shows that to be the best way to get a value from a row referred to by a foreign key. It works fine for the player name, but there seems to be no way to expand this to fill in multiple weapon names at once. Using an OR with the remaining weapons or using weaponID IN (w0,w1,w2,w3) seems to get one row for each weapon, not one row with each weapon in the appropriate spot.
Is there any way to get the correct weapon names just using the select statement? Or will I need to have extra code loop through and replace each weapon id with the correct name?
This design is questionable: weapon0..n will likely lead to nothing but difficult queries like this. The queries will also have to be de-normalized - e.g. one join per weapon0..n.
Anyway, the query is wrong and will return many more rows than desired because it uses the form FROM a,b which implies a CROSS JOIN between a and b and there is not appropriate selectors in the WHERE to make it an equi-join. Try to use a normal (INNER) JOIN and ON to make each join more apparent:
SELECT s.scoreID, p.playerName, s.scoreVal,
w0.weaponLabel as w0Label,
w1.weaponLabel as w1Label
-- etc
FROM Scores s
JOIN Players p ON p.id = s.playerID
JOIN Weapons w0 ON w0.weaponID = s.scoreW0
JOIN Weapons w1 ON w1.weaponID = s.scoreW1
-- etc, ick!!!
By now it should become apparent why the de-normalized data is icky!
Each column must be joined with a different relation (w0, w1, etc).
I usually have to create a looping procedure to get all the denormalized columns in one row per unique set, in your case player, weaponlabel.
I am trying to pull all of the records in each of these tables that have a value in Games.date_added that is greater than an inputed date. Trying to pull all the new records.
Can I do that all in one query? Or is it easier to just add a date_added field to each table and query each table separately?
GAMES
id
date_added
game_name
release_date
game_category
game_type
game_console
TROPHIES
trophies_id
game_name
tr_name
tr_description
tr_color
tr_ach_value
TROPHY_TOTALS
trophy_totals_id
game_name
bronze_ttl
silver_ttl
gold_ttl
plat_ttl
hidden_ttl
It will be much better and clearer if you do 3 separate queries. Each one will give you a different amount of rows. The query given by glglgl that uses 2 joins will result in multiplied rows from the games table. This is because tropies and tropy totals can contain multiple records with same game. Of course if game_name is a unique column than the join will be OK.
First of all, it would surely better to have your foreign key fields in TROPHIES and TROPHY_TOTALS be id resp. game_id rather than game_name.
This said, I will concentrate on your current table structure.
SELECT <wanted fields>
FROM GAMES
LEFT JOIN TROPHIES USING (game_name)
LEFT JOIN TROPHY_TOTALS USING (game_name)
WHERE date_added > <given>
For each item in the first table, there is a 'numberOf' field. The value of this field must have an identical number of rows in a related table. These are like reservation rows so multiple users can book the item at the same time. This syncronisation sometimes goes out and there are more rows than the 'numberOf' field variable, and vice versa.
So I want to display a table that outputs the 'numberOf' from the first table, and the amount of rows that correspond to it from the other table. They are linked by the Item ID. Hope this isn't too confusing. The query is output with a do while loop. Here is the query I have so far anyway:
$querySync = sprintf("SELECT
COUNT(reserve_id), item_id, details, numberOf
FROM
reservations
JOIN
items ON item_id = itemID_reserved
WHERE
itemID_reserved = 1 ");
So at the moment it counts the number of rows in the reservations table. It then joins the items table so I can display the description and numberOf etc. Of course at the moment it only outputs the item with ID 1. But I can't seem to get it to go though each item, check its numberOf, and compare it to the number of rows in reservations table.
The idea is to have it all on one column and at the end of the row print if it is out of sync etc. I then need to rebuild the rows in the reservations table to match the numberOf.
Sorry thats a long one!
SELECT COUNT(reserve_id), item_id, details, numberOf,
COUNT(reserve_id) > numberOf AS overbook
FROM items
LEFT JOIN
reservations
ON itemID_reserved = item_id
GROUP BY
item_id
It might be easier to just directly calculate which items are "out of sync":
select i.item_id
from reservations r JOIN items i on (i.item_id = r.itemID_reserved)
group by i.item_id
having count(r.itemID_reserved) > i.numberOf
I'm making some assumptions there about which tables have which fields, but it should be sufficiently illustrative.