MySQL Merging my selects together - php

and thanks for taking the time to try and help me.
Informations
I'm currently using CodeIgniter if it might have anything to do with your answer ;).
Problem
I'm in a hotel site, trying to figure out how to do my reservation rooms.
I want users to select a list of available services and return to them, a list of rooms that contains these services ( all of them ) AND after that, a list that contains at least one. This way I'll show to them a list of rooms that comply with all their need, and one that might do the trick, but doesnt have everything.
Here's how I store my services for my rooms ( Here might lie my problem in fact ... )
Table "services_rooms"
id_services_rooms | id_room | id_service
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
5 | 1 | 5
11 | 2 | 2
12 | 2 | 3
... | ... | ...
How can I manage to do my SQL to ask my server give me all of the rooms that contains the services 1, 2 AND 3, therefore, only my "id_room" 1 should come back ?
I've tried doing some joins / group_bys but the most I got was for exemple, 3 row coming back saying :
Return rows :
ID_ROOM 1 | ID_SERVICE 1
ID_ROOM 1 | ID_SERVICE 2
ID_ROOM 1 | ID_SERVICE 3
Another way to see it, would be like that : I want to ask my server which rooms contains ALL of these services : 1,2,3
It would answer : ID_ROOM 1.
I've seen a couple of other questions talking about merges and such but couldn't quite apply their answers to my problem.
Thanks again.

This is called Relational Division.
SELECT id_room
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(*) = 3
SQLFiddle Demo
if unique constraint was not enforced on id_service for ech id_room, DISTINCT is required.
SELECT id_room
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(DISTINCT id_service) = 3

The answer to your question returns all rooms that have at least one of the services. The query is:
SELECT id_room, count(*) as NumServices
FROM services_rooms
WHERE id_service IN (1,2,3)
GROUP BY id_room
HAVING COUNT(*) > 0
order by count(*) desc

Related

how to query for at least X results with a specific attribute

I am trying to get a certain amount of rows of which another amount of rows satisfy a specific condition.
I'll explain.
table 1:
ID | NAME
1 | Thomas
2 | Jason
3 | Oleg
4 | Matt
5 | Sheldon
6 | Jenny
table 2:
ID | ACTIVE
1 | 1
2 | 0
3 | 1
4 | 1
5 | 0
6 | 1
Query:
SELECT tbl_1.ID, tbl_1.NAME, tbl_2.ACTIVE
FROM tbl_1 JOIN tbl_2 ON
tbl_1.ID = tbl_2.ID
WHERE tbl_2.ACTIVE=1
LIMIT 5
in this example I would like to get a minimum number of 5 users, of which 3 are active.
of course the query above will not do the job right, as it limits the total rows to 5. But 3 of the rows in the result (or less if no more exist) MUST be active.
the other way I can think of getting this done, is a union, but my query is so cumbersome, long and complex.
Any ideas?
Use ORDER BY instead:
SELECT tbl_1.ID, tbl_1.NAME, tbl_2.ACTIVE
FROM tbl_1 JOIN
tbl_2
ON tbl_1.ID = tbl_2.ID
ORDER BY (tbl_2.ACTIVE = 1) DESC
LIMIT 5;
This puts the active users at the top of the list and then fills in the rest with other users.
Note: The ORDER BY clause could simply be ORDER BY tbl_2.ACTIVE DESC. I left the boolean logic so you could see the similarity to the WHERE clause.
The way to at least x results is to use the count aggregate and the keyword having
select f1, count(*) records
from yourTable
where whatever
group by f1
having count(*) > x

MySQL: GROUP BY within ranges

I have a table with scores like this:
score | user
-------------------
2 | Mark
4 | Alex
3 | John
2 | Elliot
10 | Joe
5 | Dude
The table is gigantic in reality and the real scores goes from 1 to 25.
I need this:
range | counts
-------------------
1-2 | 2
3-4 | 2
5-6 | 1
7-8 | 0
9-10 | 1
I've found some MySQL solutions but they seemed to be pretty complex some of them even suggested UNION but performance is very important. As mentioned, the table is huge.
So I thought why don't you simply have a query like this:
SELECT COUNT(*) as counts FROM score_table GROUP BY score
I get this:
score | counts
-------------------
1 | 0
2 | 2
3 | 1
4 | 1
5 | 1
6 | 0
7 | 0
8 | 0
9 | 0
10 | 1
And then with PHP, sum the count of scores of the specific ranges?
Is this even worse for performance or is there a simple solution that I am missing?
Or you could probaly even make a JavaScript solution...
Your solution:
SELECT score, COUNT(*) as counts
FROM score_table
GROUP BY score
ORDER BY score;
However, this will not returns values of 0 for count. Assuming you have examples for all scores, then the full list of scores is not an issue. You just won't get counts of zero.
You can do what you want with something like:
select (case when score between 1 and 2 then '1-2'
when score between 3 and 4 then '3-4'
. . .
end) as scorerange, count(*) as count
from score_table
group by scorerange
order by min(score);
There is no reason to do additional processing in php. This type of query is quite typical for SQL.
EDIT:
According to the MySQL documentation, you can use a column alias in the group by. Here is the exact quote:
An alias can be used in a query select list to give a column a
different name. You can use the alias in GROUP BY, ORDER BY, or HAVING
clauses to refer to the column:
SELECT
SUM(
CASE
WHEN score between 1 and 2
THEN ...
Honestly, I can't tell you if this is faster than passing "SELECT COUNT(*) as counts FROM score_table GROUP BY score" into PHP and letting PHP handle it...but it add a level of flexibility to your setup. Create a three column table as 'group_ID', 'score','range'. insert values into it to get your groupings right
1,1,1-2
1,2,1-2
1,3,3-4
1,4,3-4
etc...
Join to it on score, group by range. THe addition of the 'group_ID' allows you to set groups...maybe have group 1 break it into groups of two, and let a group_ID = 2 be a 5 set range (or whatever you might want).
I find the table use like this is decently fast, requires little code changing, and can readily be added to if you require additional groupings or if the groupings change (if you do the groupings in code, the entire case section needs to be redone to change the groupings slightly).
How about this:
select concat((score + (1 * (score mod 2)))-1,'-',(score + (1 * (score mod 2)))) as score, count(*) from TBL1 group by (score + (1 * (score mod 2)))
You can see it working in this fiddle: http://sqlfiddle.com/#!2/215839/6
For the input
score | user
-------------------
2 | Mark
4 | Alex
3 | John
2 | Elliot
10 | Joe
5 | Dude
It generates this:
range | counts
-------------------
1-2 | 2
3-4 | 2
5-6 | 1
9-10 | 1
If you want a simple solution which is very powerful, add an extra field within your table and put a value in it for the score so 1 and 2 have the value 1, 3 and 4 has 2. With that you can group by that value. Only by inserting the score you've to add an extra field. So your table looks like this:
score | user | range
--------------------------
2 | Mark | 1
4 | Alex | 2
3 | John | 2
2 | Elliot | 1
10 | Joe | 5
5 | Dude | 3
Now you can do:
select count(score),range from table group by range;
This is always faster if you've an application where selecting has prior.
By inserting do this:
$scoreRange = 2;
$range = ceil($score/$scoreRange);

mysql rank by column value grouped by column value

I am trying to figure out how to rank based on 2 different column numbers in laravel but raw mysql will do. I have a list of videos, these videos are inside of competitions and are given votes if someone likes the video. Each video will have a vote number and a competition number. I am trying to rank based on votes within competition. So for example below I have competition 8, I need the rank of all the videos in that competition based on votes. I then need the same for competition 5 etc.
|rank|votes|competition|
------------------
| 1 | 100 | 8 |
------------------
| 2 | 50 | 8 |
------------------
| 3 | 30 | 5 |
------------------
| 1 | 900 | 5 |
------------------
| 2 | 35 | 5 |
------------------
I have tried various group and selectby methods but nothing seems to work, any ideas?
In Mysql you can use user-defined variables to calculate rank,case statement checks if competition is same as the previous row then increment rank by one if different then assign 1 an order by is needed to have correct rank for the video
SELECT t.*,
#current_rank:= CASE WHEN #current_rank = competition
THEN #video_rank:=#video_rank +1
ELSE #video_rank:=1 END video_rank,
#current_rank:=competition
FROM t ,
(SELECT #video_rank:=0,#current_rank:=0) r
ORDER BY competition desc, votes desc
See Demo
If you are confused with the last extra column you can use a subselect
See Demo
You can use windowing functions:
select
competition, votes, rank() over (partition by competition order by votes desc) as rank
from
table1

Summarize db table player stats on output (multiple entries)

I'm trying to output a "scoreboard"/"standings" list of hockey players. I currently have a MySQL table like this:
statsID | matchID | playerID | goals | assists
120 | 2 | 3 | 4 | 1
121 | 2 | 4 | 2 | 2
122 | 3 | 3 | 0 | 3
So for each match the players gets added along with goals and assists. This means that there's multiple entries (several matches) for each player.
So basically, is there a good way to fetch the player data, summarize goals and assists (+ overall points) and output it in a list?
Right now, I have a basic query just outputting each row (as far as my current knowledge reaches). So how can I just output one entry of each player with his stats summarized?
Here's one approach:
SELECT t.playerID
, SUM(t.goals) AS total_goals
, SUM(t.assists) AS total_assists
FROM sometable t
GROUP BY t.playerID
ORDER BY t.playerID
Not clear what calculation you use for "overall points", if you are just adding goals and assists, then you can use this in the SELECT list:
, SUM(t.goals)+SUM(t.assists) AS overall_points
You need to use a group by clause.
SELECT playerID, SUM(goals + assist) overall points
FROM table_name
GROUP BY playerID

mysql select query problem

i have a form that has a multiple select drop down. a user can select more than one options in the select. the name of the select is array[]; using php I call implode(",",$array)
in mysql db, it stores the field as a text in this format "places"= "new york, toronto, london" when i want to display these fields i explode the commas.
I am trying to run a report to display the places. here is my select:
"select * from mytable where db.places .. userSelectedPlaces"
how can i check toronto in lists of "places" that user selected? note "places" in the db might be either just "toronto" or it might be comma separated lists of places like "ny, toronto, london, paris, etc".
If it is possible, you would be much better off using another table to hold the places that the user has selected. Call it SelectedPlaces with columns:
mytable_id - To join back to the table in your query
place - EG: "Toronto"
Then you can run a simple query to figure out if Toronto has been selected:
SELECT *
FROM mytable m
INNER JOIN SelectedPlaces sp ON sp.mytable_id = m.id
WHERE sp.place = 'Toronto'
If I understand you correctly, your database design is just wrong. Try reading about it more. Generally, in good design you should not have lists of values as one field in database and you should introduce new table for it.
But if you want to do it this way, you can use strcmp function.
If i understood correctly, this should work:
WHERE DB.PLACES LIKE '%TORONTO%'
but as other users said, its not a nice thing to have denormalized tables.
To directly answer your question, your query needs to look something like this
SELECT *
FROM mytable
WHERE places LIKE( '%toronto%' )
But, be aware, that LIKE() is slow.
To indirectly answer your question, your database schema is all wrong. That is not the right way to do a M:N (many-to-many) relationship.
Imagine instead you had this
mytable place mytable_place
+------------+ +----------+----------+ +------------+----------+
| mytable_id | | place_id | name | | mytable_id | place_id |
+------------+ +----------+----------+ +------------+----------+
| 1 | | 1 | new york | | 1 | 1 |
| 2 | | 2 | toronto | | 1 | 2 |
| 3 | | 3 | london | | 1 | 3 |
+------------+ +----------+----------+ | 2 | 2 |
| 3 | 1 |
| 3 | 3 |
+------------+----------+
The table mytable_places is what's called a lookup table (or, xref/cross-reference table, or correlation table). Its only job is to keep track of which mytable records have which place records, and vice versa.
From this example we can see that The 1st mytable record has all 3 places, the 2nd has only toronto, and the 3rd has new york and london.
This opens you up too all sorts of queries that would be difficult, expensive, or impossible with your current design.
Want to know how many mytable records have toronto? No problem
SELECT COUNT(*)
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
WHERE p.name = 'toronto';
How about the number of mytable records per place, sorted?
SELECT p.name
, COUNT(*) as `count`
FROM mytable_place x
LEFT JOIN place p
ON p.place_id = x.place_id
GROUP BY p.place_id
ORDER BY `count` DESC, p.name ASC
And these are going to be much faster than any query using LIKE since they can use indexes on columns such as place.name.

Categories