Include a complex logic in a single MySQL Query - mysql

I wanted to reduce the programming logic in a code that search hotels by using a single query.
Let say this is the database table. There are several contracts from a single hotel. I wanted to get all the raws that satisfy input number of rooms for max_adults 1(capacity 1) rooms, number of rooms for rooms for max_adults 2(capacity 2) rooms and number of rooms for rooms for max_adults 3(capacity 3) rooms.
eg: Input : 2 rooms of max_adults=1(capacity 1) 1 rooms of max_adults=2(capacity 2) 3 rooms of max_adults=3(capacity 3)
The rows that satisfy all the 3 conditions with the same hotel id must be output.
As in here the output must contain all the raws of hotel 1 and hotel 2
Since they are two separate results, an extra column is needed to show a generated result_id (kind of serial number as in here which must has duplicates acording to this problem).
I'm thinking lots of ways to do this, but nothing works well. Will this be possible to do in a single query?

Use a self join:
SELECT r1.HOTEL_ID, r1.MAX_ADULTS, r1.NO_OF_ROOMS,
r2.MAX_ADULTS, r2.NO_OF_ROOMS,
r3.MAX_ADULTS, r3.NO_OF_ROOMS,
FROM rooms AS r1
INNER JOIN rooms AS r2 ON r1.HOTEL_ID=r2.HOTEL_ID
INNER JOIN rooms AS r3 ON r1.HOTEL_ID=r3.HOTEL_ID
WHERE r1.MAX_ADULTS=1
AND r2.MAX_ADULTS=2
AND r3.MAX_ADULTS=3
You will have to add clauses to check the input number of rooms conditions.
The generated result_id would be r1.HOTEL_ID.
You would get a single row per hotel.

Related

Averaging a one-to-one field will summing a one to many in MySQL

I put together this example to help
http://sqlfiddle.com/#!9/51db24
The idea is I have 3 tables. One is the "root" table, in this case person which has a score attached to it. They then have some category I need to group by person_cat.cat and a one to many field called CS.
I would like to query for the average of the score, the sum of the one to many field person_co.co and group by the category.
SELECT
person_cat.cat,
person.id,
SUM(person_co.co),
AVG(person.cs)
FROM
person
LEFT JOIN person_co USING (id)
LEFT JOIN person_cat USING (id)
GROUP BY cat;
The issue I'm currently having is the average gets thrown off due to the join for the one to many. I can accomplish this with multiple queries, which is ok if that is the answer. However it would be nice to get this as one query.

Query to obtain the lowest price per room per hotel in a relational database

I have a relational database model based on a hotel booking site set up for a uni project, however I am stumped by one query.
This is as far as I have gotten:
SELECT DISTINCT
property.property_id,
property_name,
property_description,
star_rating,
room_base_price
FROM
property
INNER JOIN rooms ON property.property_id = rooms.property_id;
The problem is that I have several rooms per property listed in my database. In this query, I want to select the room with the minimum price per hotel. The way I am currently doing it, it is showing every single property with every single room and its individual price.
Can anyone advise me on how I can write a query where I will be able to return the lowest price of a room in a property(i.e. a hotel), where it will only be one row per hotel instead of returning multiple rows for each property?
Try following query:
SELECT DISTINCT
property.property_id,
property_name,
property_description,
star_rating,
room_base_price
FROM
property
INNER JOIN rooms ON property.property_id = rooms.property_id;
where (rooms.property_id,rooms.room_base_price) in
(select property_id,min(room_base_price)
from rooms
group by property_id
)
;
Hope it helps!

mysql calculation based on the conditions into a column

I have a t_personne table.
I encodes a general form for each person.
I also encodes a table "interview" (t_entretien) a presence per project (proj_id).
For the project, it can have multiple actions.
The project action "RAE" are encoded: 2-01.01, 2-02.01, 2-02.02 ...... 2-03.01, 2-03.02, etc.
I have a following calculation rule: A unit is equal to 2 actions from two different categories into the actions encoded.
For example, a person with one action 2-02.01 and 2-03.01 with 2-03.04 actions will only be counted as one unit.
A person who has 4 shares of 2-03 types will not count.
I have to calculate the total number of units.
I started thinking:
SELECT COUNT(*) AS Expr1
FROM (SELECT t_entretien.id
FROM t_entretien
INNER JOIN
t_action on t_action.cact_caction = t_entretien.ent_id
GROUP BY t_entretien.id
HAVING(COUNT(DISTINCT t_action.cact_caction) > 1)) AS derivedtbl_1
Here is a little FIDDLE http://sqlfiddle.com/#!2/6c831/1
Thanks for all !
EDITION
The context is the following.
I get a person (t_personne) and using a form I introduce general data.
This person, I enrolled into a project (t_projet) at the time of the interview (t_entretien).
During the interview, I realize actions.
Every actions are specific projects (t_action).
For the project, including the "cact_code" is 2, I chose different actions encoded (see table t_codeaction).
My problem is the following. I must calculate the number of actions based on a formula that is imposed on me: one unit = 2 actions belonging to different categories. Categories are identified as follows (t_codeaction): 2-01 this category has only one action (2-01.01), the following category includes 4 actions 2-02 and 2-03 in the third category includes 12 actions, etc.
Imagine that the person "ent_id" = 8105 received four actions, one in the category 2-01.01 and 2-03 in the other 3. It is one and only one unit under the rule as it has received at least two actions two different categories. 3 actions in the 2-03 category account for an action. Thus, the person whose "ent_id" = 8114 received only 3 actions of the same class 2-03, this unit is 0.
I am probably wildguessing because the question is bad described, but anyway, something like this?
SELECT id as entretiedId, count(*)/2 as units from
(SELECT t_entretien.id, SUBSTRING(t_action.cact_caction,1,4) as CATEGORY, COUNT(*)
FROM t_entretien
INNER JOIN
t_action on t_action.ent_id = t_entretien.ent_id
GROUP BY t_entretien.id, CATEGORY) as t_derived
GROUP BY id having count(distinct category) > 1
The derived table counts actions, but with one row per ent per category. then the outer group by counts the subactions per category, and divides by two for calculating the units.
fiddle: http://sqlfiddle.com/#!2/6c831/22/0

How do I model product ratings in the database?

What is the best approach to storing product ratings in a database? I have in mind the following two (simplified, and assuming a MySQL db) scenarios:
Create two columns in the products table to store the number and the sum of all votes respectively. Use the columns to get an average at run time or using a query.
This approach means I only need to access one table, simplifying things.
Normalize the data by creating an additional table to store the ratings.
This isolates the ratings data into a separate table, leaving the products table to furnish data on available products. Although it would require a join or a separate query for ratings.
Which approach is best, normalised or denormalised?
A different table for ratings is highly recommended to keep things dynamic. Don't worry about hundreds (or thousands or tens of thousands) of entries, that's all peanuts for databases.
Suggestion:
table products
id
name
etc
table products_ratings
id
productId
rating
date (if needed)
ip (if needed, e.g. to prevent double rating)
etc
Retrieve all ratings for product 1234:
SELECT pr.rating
FROM products_ratings pr
INNER JOIN products p
ON pr.productId = p.id
AND p.id = 1234
Average rating for product 1234:
SELECT AVG(pr.rating) AS rating_average -- or ROUND(AVG(pr.rating))
FROM products_ratings pr
INNER JOIN products p
ON pr.productId = p.id
AND p.id = 1234;
And it's just as easy to get a list of products along with their average rating:
SELECT
p.id, p.name, p.etc,
AVG(pr.rating) AS rating_average
FROM products p
INNER JOIN products_ratings pr
ON pr.productId = p.id
WHERE p.id > 10 AND p.id < 20 -- or whatever
GROUP BY p.id, p.name, p.etc;
I know that my answer is not what you actually ask for, but you might want to have a chance of facilitating that new products with your system can almost never beat the old products. Say that you would get a product with 99% rating. It would be very difficult for new products to get high if you sort by products with the highest rating.
Do not store a record of each rating unless you absolutely need them specifically. An example of such a case could be a psychological experiment that tends to analyze specific properties of the raters themselves. So, yeah! You'd have to be just as crazy to store each rate in a separate record.
Now, coming to the solution, add two more columns to your product table: AverageRating and RateCount.
What would you store in them? Well, suppose you have an already-calculated average of the two numbers: 2 and 3, which is 2.5; having a new rate of 10, you'll multiply the average (2.5) by the rate count (2 in this case). Now, you have 5. Add this result to the new rate value (10) and divide the result by 3.
Let's cover all the above in a simple formula,
(AverageRating * RateCount + NewRateValue) / (RateCount + 1)
So (2.5 * 2 + 10) / (2 + 1) = 5.
Calculate the average on the server-side (not in your database) and store the average in the AverageRating column and the rate count in the RateCount column.
Simple, right?!
Edit
This solution doesn't require storing each rating separately as long as no review, edit or delete operations are involved. Yet, for such cases; let's assume that you've got a review with a rating of 3 that the owning user would like to modify to 4. Then, the formula to recalculate the average rating would be like this,
(AverageRating * RateCount - OldRateValue + NewRateValue) / RateCount
References
https://math.stackexchange.com/a/106314

Mysql joining tables question

I have a tables rooms, apartments and hotel. Now its easy for me to get all free rooms, name of hotel address or same thing for apartments by using separate queries ex :
Get all free rooms :
SELECT hotel.name, count(*) AS num_of_free_rooms
FROM hotel, rooms
WHERE rooms.occupied !=0
AND hotel.hotel_id = rooms.room_hotel
GROUP BY hotel.hotel_id
Get all free apartments :
SELECT hotel.name, count(*) AS num_of_free_ap
FROM hotel, apartments
WHERE apartments.occupied_ap !=0
AND hotel.hotel_id = apartments.apartment_hotel
GROUP BY hotel.hotel_id
How could I get results like this :
Name of a hotel | Number of free rooms other than 0 | Number of apartments 0 or any other
Should I organize my data differently, like adding in the table rooms field type 1 for rooms, 2 for apartments or do it with separate tables. I'm open to any suggestions in order to get results that I need.
If you have rooms and apartments in separate tables, and you try to count them in a single query, you'll produce what's called a Cartesian product between rooms and apartments. The effect is that your counts of each will be much higher than reality (they'll be multiplied together).
Keep in mind you don't have to solve every problem in SQL with a single query! Running separate queries is simpler and often runs faster than a single overly-complex query. So don't overlook the option of simply running two queries.
That said, if I were designing this database, I would make rooms and apartments part of the same table, and use an attribute to distinguish between these two types, just as you describe. Unless you need a lot of columns specific to one type or the other.
For more options, see my answer in "Product table, many kinds of product, each product has many parameters."
You can use this until someone comes up with a (more) efficient solution :)
SELECT tmp.name, tmp.num_of_free_rooms, num_of_free_ap
FROM
(SELECT hotel.name, count(*) AS num_of_free_rooms
FROM hotel, rooms
WHERE rooms.occupied !=0
AND hotel.hotel_id = rooms.room_hotel
GROUP BY hotel.hotel_id) AS tmp
LEFT JOIN
(SELECT hotel.name, count(*) AS num_of_free_ap
FROM hotel, apartments
WHERE apartments.occupied_ap !=0
AND hotel.hotel_id = apartments.apartment_hotel
GROUP BY hotel.hotel_id) AS tmp2
ON tmp1.hotel.name = tmp2.hotel_name
WHERE tmp.num_of_free_rooms > 0
If rooms and apartments share many attributes, it's worth putting them both in the same table (with a flag) just to simplify queries like the one you mentioned. You can put the special attributes in room_details and apt_details tables and only refer to them when needed.