selecting specific row number from select - mysql

I'm a beginner at MySQL syntax. So there are a few question I want to ask.
I got a clue DB where users can add in clues. And have a webmethod that select a range of numbers to do random function. (random for the sake of the game, no point doing same clue over and over right?)
But my main problem right now is that what if the author decided to add in more clues?
then my clue db will be looking like this.
+--------+-------------+-----------+--------+
| cID | clueDetails | location | author |
+--------+-------------+-----------+--------+
| 1 | abcde | loc 1 | auth 1 |
| 2 | efghi | loc 1 | auth 1 |
| 3 | jklmno | loc 2 | auth 1 |
| 4 | pqrstu | loc 2 | auth 1 |
| 5 | vwxyz | loc 1 | auth 1 |
+--------+-------------+-----------+--------+
If the player select loc1 auth 1, it will be showing cID 1,2 and 5. so I couldn't use my random function effectively as it select the first and last of loc and auth and 3 and 4 doesnt fit in. I know right now it's very vague as information are scarce. And to actually understand the whole process, goes right down to the game, and the method/function I have. (which will be very long)
Cutting to the chase, my result will be something as shown below, and the way to identify it will be by cID, but in the event that clue were added in different order ( as shown above) then my function will get rather screw up.
EDIT: assuming this random function give me back 2 clues, because I want to play 2 clues. this random function give me back 1 and 3. so from the table result below, 1 and 3 will give me cID1 and cID5 as they are row number 1 and 3. (sorry for the confusion caused)
+--------+-------------+-----------+--------+
| cID | clueDetails | location | author |
+--------+-------------+-----------+--------+
| 1 | abcde | loc 1 | auth 1 |
| 2 | efghi | loc 1 | auth 1 |
| 5 | vwxyz | loc 1 | auth 1 |
+--------+-------------+-----------+--------+
So with that, I want to ask if can we select row by its number? e.g row[3] = cID 5, vwxyz, loc 1, auth 1.
As far as I'm concerned, I've done massive research and there doesn't seem to be any function in MySQL that allow us to select by row number. (though all the article were pretty old dated, 2010 and before. Not sure if MySQL has added in any new function)
I saw a SO thread - MySQL - Get row number on select and from how I see it, it seems to be generating a field called ranking.
What I want to know is, is this field ranking temp or permanent? Because if it's just a temp field, then I could shift the identifier from cID to this numbering.
Or do any of you have any suggestion to go around solving this issue? I thought of clearing the db, and re create the db, but that will be taking too much time. And over time when the DB get large it will be slower as well. And another method is to make a datatable to fill all the current clue where loc=?loc and auth=?auth and add them in again with the new clue(latest), but i figure that will cause the cID to boom and fly at a very fast rate. And I'm afraid this will cause memory management issue / memory leak.
EDIT2: As the create field is just a temp field, and seem to be the only alternative, I tried this MySQL command.
set #rank=0;
select #rank:=#rank+1 AS rank, cId, clueDetails, location, author from tbl_clue where location = "loc" and author = "auth" order by rank ASC
It seem to display what I want, but my command seem different from what other usually give. (more bracket and other stuff). Is my command ok? will there be any indirect implication caused by it?

You can try this one. Please add a comment if this helps :)
SELECT cID, clueDetails, location, author
FROM
(
SELECT #rownum := #rownum + 1 as `RowNo`,
p.cID,
p.clueDetails,
p.location,
p.author
FROM (
SELECT cID, clueDetails, location, author
FROM myTableName
WHERE location = 'loc 1' AND author = 'auth 1'
) p , (SELECT #rownum:=0) r
) y
WHERE y.RowNo = 3
ORDER BY RowNo

I'm not sure if I understand you correctly, but assuming you end up with:
+--------+-------------+-----------+--------+
| cID | clueDetails | location | author |
+--------+-------------+-----------+--------+
| 1 | abcde | loc 1 | auth 1 |
| 2 | efghi | loc 1 | auth 1 |
| 5 | vwxyz | loc 1 | auth 1 |
+--------+-------------+-----------+--------+
and you only want one record at random instead of 3 records you could do the following:
$query = "THE QUERY";
if ($result = $dbc->query($query))
{
$num_rows = mysql_num_rows($result);
$random_number = rand(1, $num_rows);
$count = 1;
while($nt = $result->fetch_assoc())
{
if ($count = $random_number)
{
//SAVE THE CLUE DETAILS
}
$count = $count + 1;
}
}

Related

Using nested SELECT result for IN statement of another nested SELECT

Be gentle. I'm a high school principal coding on the side for our school site.
I have looked at answers, here, here, and here. I might just not know enough to ask the right question.
We have events that have multiple sessions and there are workshops that can be associated with multiple sessions in multiple events.
I'm trying to get a csv result, later to be put into an array, for the associated sessions and events for my Workshops.
The query below works without the second nested Select statement.
In the Alt_Events statement, I need to pull the Event_IDs that are associated with the Session_IDs that are pulled from the first nested Select.
Events
ID | Name | Description
1 | Flex Learning | A day of flexible learning.
2 | Moonshot Expo | A day to join partners to solve problems.
Event_Sessions
ID | Event_ID | Name | Description
1 | 1 | Morning Session | The first session of the day.
2 | 1 | Afternoon Session | The afternoon session.
3 | 1 | Tutoring Session | A chance to get help from teachers.
4 | 2 | Partner Field Trip | The first session of the day.
5 | 2 | Brainstorming Session | The afternoon session.
6 | 2 | Tutoring Session | A chance to get help from teachers.
Event_Workshops
ID | Name | Description
1 | Math Tutorial | Get help from your math teachers.
Event_Workshop_Links
ID | Workshop_ID | Session_ID
1 | 1 | 3
2 | 1 | 6
Output Table:
ID | Name | Description | ... | Alt_Sessions | Alt_Events
1 | Math Tutorial | Get help... | ... | 3,6 | 1,2
Here is my query.
SELECT
ws.ID, ws.Name, ws.Description, ws.Location, ws.Owner_ID, ws.Max_Attendees,
ws.Eng_Major_Allowed, ws.Eng_Minor_Allowed,
ws.HC_Major_Allowed, ws.HC_Minor_Allowed,
ws.IT_Major_Allowed, ws.IT_Minor_Allowed,
u.LastName as Owner_LastName, u.FirstName AS Owner_FirstName, u.Email AS Owner_Email,
(SELECT group_concat(SESSION_ID) FROM Events_Workshops_Links WHERE Workshop_ID = ws.ID) AS Alt_Sessions,
(SELECT group_concat(Event_ID) FROM Event_Sessions WHERE Session_ID IN Alt_Sessions) AS Alt_Events
FROM Event_Workshops as ws
LEFT JOIN users AS u
ON ws.Owner_ID = u.ID
WHERE ws.ID = ?
ORDER BY ws.Name
I need to be able to pull the all event_ids that are in the Alt_Sessions result.
I'm guessing I can't use the result of the first nested query in the second nested query. If that's the problem, how can I pull that list of event ids?
Any and all help is greatly appreciated.
(Updated to show expected output. Also one error in transcribing the query. Session_ID instead of Event_ID in second nested statement.
Use the subquery instead of Alt_Sessions in the IN predicate like below.
(SELECT group_concat(SESSION_ID) FROM Events_Workshops_Links WHERE Workshop_ID = ws.ID) AS Alt_Sessions,
(SELECT group_concat(Event_ID) FROM Event_Sessions WHERE Session_ID IN (SELECT SESSION_ID FROM Events_Workshops_Links WHERE Workshop_ID = ws.ID)) AS Alt_Events
Also, there is a way to make combinations of Alt_Sessions and Alt_Events first and then join to Event_Workshops.
SELECT * FROM Event_Workshops ws
JOIN
(
SELECT
wsl.Workshop_ID,
GROUP_CONCAT(wsl.Session_ID) Alt_Sessions,
GROUP_CONCAT(wsl.ID) Alt_Events
FROM Event_Workshop_Links wsl
GROUP BY wsl.Workshop_ID
) w
ON ws.ID = w.Workshop_ID

Missing an option to use SQL's HAVING clause in F3

Is there a way to use MySQL's HAVING clause with any of Fat Free Framework's SQL Mapper object's methods? Let's assume I have the following DB table:
+----+-------+--------+
| id | score | weight |
+----+-------+--------+
| 2 | 1 | 1 |
| 2 | 2 | 3 |
| 2 | 3 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 4 |
| 3 | 3 | 1 |
| 3 | 4 | 3 |
+----+-------+--------+
Now I would like to run a following query:
SELECT id, SUM(score*weight)/SUM(weight) AS weighted_score GROUP BY id HAVING weighted_score>2
Truth to be told I would actually like to count the number of these records, but a count method doesn't support $options.
I can run the query without a HAVING clause and then loop through them to check weighted_score against the value, but with a growing number of records will make it more and more resource consuming. Is there any built-in solution to solve this problem?
EDIT 1:
The way I know how to do it if there is no support for the HAVING clause (based on manual):
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$usersInfo = $dataMapper->find([],["group"=>"id"]);
$place = 1;
foreach ( $usersInfo as $userInfo ) {
if ( $usersScores->weightedScore > 2) $place++;
}
If I were able to use HAVING clause then the foreach loop would not be needed and the number of items loaded by a query would be reduced:
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$usersInfo = $dataMapper->find([],["group"=>"id", "having"=>"weighted_score<2"]); // rough idea
$place = count($usersInfo);
And if count method supported $options it would be even simpler and it would save memory used by the app as no records would be loaded:
$databaseObject = new DB\SQL(...);
$dataMapper = new \DB\SQL\Mapper($databaseObject, "tableName");
$dataMapper->weightedScore = "SUM(weight*score)/SUM(weight)";
$place = $dataMapper->count([],["group"=>"id", "having"=>"weighted_score<2"]); // rough idea
Use Sub Query.
select count (0) from (SELECT id, SUM(score*weight)/SUM(weight) AS weighted_score GROUP BY id) where weighted_score>2;
Hope it will help.
As far as I know, you can put the HAVING clause into the group option:
$usersInfo = $dataMapper->find([],["group"=>"id HAVING weighted_score<2"]);
Another way could be to create a VIEW in mysql and filter the records on a virtual fields in that view.

In what situations should I use DB Triggers?

I have two tables, one which stores articles and the number of votes it has received:
| article_id | visitor_votes | member_votes | voting_opened |
-------------------------------------------------------------
| 1 | 12 | 394 | Y |
| 3 | 94 | 5821 | Y |
I also have another table which keeps track of which user voted for which article
| vote_id | user_id | article_id | date |
--------------------------------------------
| 1 | 12 | 1 | 7/28/2012
| 2 | 23 | 3 | 7/28/2012
One user can only place one vote per transaction. I currently use a trigger that increments the number of votes in the articles table every time a record is inserted into the votes table. Is this good practice or should I be doing this in my application (PHP web-based website)? I also want to stop voting after a certain number of votes (voting_opened = N), should I use a trigger to check if the total votes (visitor_votes + member_votes >= 6000) and then update the article row to set voting_opened = N? Or is this something I should be doing in my application as well? I need a solution that is scale-able because I will have thousands of votes for possibly hundreds of articles and I don't want to run into a case where the number of votes goes over the threshold because an update didn't update quick enough or whatever. Can someone shed some light on this scenario please?
Thank you!
Both solutions are valid and should work equally well.
You can try something like this in the application
UPDATE articles SET
visitor_votes = visitor_votes + 1
voting_opened = IF(visitor_votes + member_votes >= 6000, 'N', 'Y')
WHERE
article_id = xxxx
AND voting_opened = 'Y'
then check affected rows and if it is > 0 insert the row in the votes table.

MySQL - COUNT before INSERT in one query

Hey all, I am looking for a way to query my database table only once in order to add an item and also to check what last item count was so that i can use the next number.
strSQL = "SELECT * FROM productr"
After that code above, i add a few product values to a record like so:
ID | Product | Price | Description | Qty | DateSold | gcCode
--------------------------------------------------------------------------
5 | The Name 1 | 5.22 | Description 1 | 2 | 09/15/10 | na
6 | The Name 2 | 15.55 | Description 2 | 1 | 09/15/10 | 05648755
7 | The Name 3 | 1.10 | Description 3 | 1 | 09/15/10 | na
8 | The Name 4 | 0.24 | Description 4 | 21 | 09/15/10 | 658140
i need to count how many times it sees gcCode <> 'na' so that i can add a 1 so it will be unique. Currently i do not know how to do this without opening another database inside this one and doing something like this:
strSQL2 = "SELECT COUNT(gcCode) as gcCount FROM productr WHERE gcCode <> 'na'
But like i said above, i do not want to have to open another database query just to get a count.
Any help would be great! Thanks! :o)
There's no need to do everything in one query. If you're using InnoDB as a storage engine, you could wrap your COUNT query and your INSERT command in a single transaction to guarantee atomicity.
In addition, you should probably use NULL instead of na for fields with unknown or missing values.
They're two queries; one is a subset of the other which means getting what you want in a single query will be a hack I don't recommend:
SELECT p.*,
(SELECT COUNT(*)
FROM PRODUCTR
WHERE gccode != 'na') AS gcCount
FROM PRODUCTR p
This will return all the rows, as it did previously. But it will include an additional column, repeating the gcCount value for every row returned. It works, but it's redundant data...

SQL concatenate rows query

Say we have a table
table posts
+---------+-----------+--------------------------------+
| postId | title | status | bodyText |
+---------+-----------+--------------------------------+
| 1 | Hello! | deleted | A deleted post! |
| 2 | Hello 2! | deleted | Another one! |
| 3 | New 1 | new | A new one! |
| 4 | New 2 | new | A new one again! |
Can we, in SQL, retrieve a concatenation of a field across rows, by issuing a single query, not having to do the join up in a loop in our back-end code?
Something like
select title from posts group by status ;
Should give a result like
+---------+--------------------+
| deleted | Hello!, Hello 2! |
| new | New 1, New 2 |
If you use MySQL then you can use GROUP_CONCAT:
SELECT status, GROUP_CONCAT(title)
FROM posts
GROUP BY status
In MySQL:
SELECT status, GROUP_CONCAT(title SEPARATOR ', ')
FROM posts
GROUP BY
status
In PostgreSQL:
SELECT status,
ARRAY_TO_STRING(
ARRAY(
SELECT title
FROM posts pi
WHERE pi.status = po.status
))
FROM posts po
GROUP BY
status
You didn't indicate a particular SQL engine.
In Firebird (from 2.1) you can use the LIST() function. Take a look at: link text
It's an aggregate function to do exactly what you need.
I guess it exists in other engines (LIST in Sybase SQL Anywhere, GROUP_CONCAT in MySQL)