MySQL - need better solution for query, extrem load time - php

I've got 2 tables -> apartments and availabilities.
In table apartments there are all informations about a apartment. In table availabilities there are all informations about the availability for a apartment for the next 365 days.
Table apartments:
id, name
Example:
1, Apartment 1
2, Apartment 2
Table availabilities:
id, apartment_id, date, status
Example:
1, 1, 2017-06-01, free
2, 1, 2017-06-02, booked
3, 1, 2017-06-03, free
4, 1, 2017-06-04, free
5, 1, 2017-06-05, free
...
6, 2, 2017-06-05, free
7, 2, 2017-06-05, free
8, 2, 2017-06-05, free
9, 2, 2017-06-05, booked
10, 2, 2017-06-05, free
...
Now i want search free apartments for a week in a multiple date range like:
SELECT apartments where id in (
SELECT apartment_id
FROM availabilities
WHERE EXISTS (
SELECT apartment_id
FROM availabilities as a
WHERE availabilities.apartment_id = a.apartment_id
AND date >= "2017-06-01"
AND date < "2017-06-08"
AND status = "free"
GROUP BY apartment_id
HAVING COUNT(apartment_id) = DATEDIFF("2017-06-08", "2017-06-01"))
OR EXISTS (
SELECT apartment_id
FROM availabilities as a
WHERE availabilities.apartment_id = a.apartment_id
AND date >= "2017-06-02"
AND date < "2017-06-09"
AND status = "free"
GROUP BY apartment_id HAVING COUNT(apartment_id) = DATEDIFF("2017-06-09", "2017-06-02"))`
This should lookup for all possibilities for a week in a date range (2017-06-01 - 2017-06-30)
My problem here is, we got many apartments and this query has a extreme load time.
Any ideas for a better solution?

Fixed it with (COUNT(*) and filter for status = "booked") = 0
Query is 30% faster.

Related

show list of top scorers in a table of Mysql

I have a table with 3 columns named gameresults: game_id,user_id,status
i put every time two players play a game two rows in the table
game_id = 1, user_id = 12, status = 1
game_id = 1, user_id = 13, status = -1
status will get only 3 value (1 for wins, -1 for lose, 0 for draw)
now I want to show a list of top players who has wins more games with some info of how many games played.
how can i query this out?
and one other thing that i want is to show the current user(example user_id 2) rank in current game(game_id 1)

Count SQL results depending on two rows

Here an example of my SQL structure:
magazines
magazines.id
magazines.templateId
magazines.userId
I would like to count the number of times a user have used a specific template id.
Considering 3 templates id : 100, 101 and 102 and considering 5 users (10, 20, 30, 40, 50). here is an example of the SQL datas:
magazines
1, 100, 10
2, 100, 20
3, 100, 30
Here, 3 differents users have the template #100. But the result of the query should be [100 = 1] because it's different users. On the other hand, if I have :
magazines
1, 100, 10
2, 100, 10
I should have templateId [100 = 2].
I tried multiple queries with GROUP BY and UNIQUE, by results are completely wrong. If someone can help, thanks.
If you want the maximum number of times that a user uses a template:
select templateid, userid, count(*)
from magazines m
where temploateid = X
group by templateid, userid
order by count(*) desc
limit 1;
EDIT:
You can get what you want by aggregating two times:
select templateid, cnt, count(*) as numUsers
from (select templateid, userid, count(*) as cnt
from magazines m
where temploateid = X
group by templateid, userid
) tu
group by templateid, cnt
order by templateid, cnt;
This will get you the amount of times each user has used a particular template:
SELECT
templateId,
userId,
COUNT(id) as cnt
FROM
Magazines GROUP BY templateId, userId
If your table had the following data:
(1, 100, 10),
(2, 100, 10),
(3, 100, 20),
(4, 100, 30)
Then you result set would be:
templateId userId cnt
100 10 2
100 20 1
100 30 1

How to JOIN tables to get all of Table A, and most of Table B

I'm pretty sure I need a LEFT JOIN for this, but I have a snag. I need to pull one column from table B dependant on a column in table A.
TABLE A = list
list_id
user_id
operator_id
operator_name
operator_level
TABLE B = operators
operators_id
type
image
skill1
skill2
skill3
1
2
3
...
10
Here is the SQL Query that I have now:
SELECT * FROM list l
LEFT JOIN operators o ON l.operator_id = o.operators_id
WHERE l.user_id=1
ORDER BY o.10 DESC
It returns all of Table A, which I want, and also returns all of Table B, which I don't need.
The columns 1-10 contain INT values, and those columns correspond to the operator_level in Table A.
So really what I need is to create a temp column, and put whatever INT is in the 1-10 column that corresponds to the operator_level, or only return that column for that row. I have no idea how to do that though.
Here is some sample data, and expected results:
list list_id, user_id, operator_id, operator_name, operator_level
1, 1, 2, Johnson, Bob, 1
2, 1, 3, Mouse, Mickey, 9
3, 1, 2, Duck, Donald, 5
operators operator_id, type, image, skill1, skill2, skill3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
1, pilot, image.jpg, F16, B32, , 50, 60, 70, 80, 90, 100, 110, 120, 130, 140
2, medic, image.jpg, first aid, trauma, general surgery, 100, 105, 110, 115, 120, 125, 130, 135, 140, 145
3, kitchen, image.jpg, knife, soup, , 20, 22, 24, 26, 28, 30, 32, 34, 36, 38
Expected Results list_id, user_id, operator_id, operator_name, operator_level, type, image, skill1, skill2, skill3, op_rate
1, 1, 2, Johnson, Bob, 1, pilot, image.jpg, F16, B32, 50
2, 1, 3, Mouse, Mickey, 9, medic, image.jpg, first aid, trauma, general surgery, 140
3, 1, 2, Duck, Donald, 5, kitchen, image.jpg, knife, soup, 28
Sorry, it lined up well in my editor, but lost the alignment. when I pasted.
Added sample data to sqlfiddle, don't know how to add expected results there.
http://sqlfiddle.com/#!2/390d8/1
You have the basics right withe the outer join, but what you can do is limit the fields you select in your query like this:
select
l.list_id,
l.user_id,
l.operator_id,
l.operator_name,
l.operator_level,
`1`+`2`+`3`+...+`10` as opLevel
FROM
list l
LEFT JOIN operators o
ON l.operator_id = o.operators_id
WHERE
l.user_id=1
ORDER BY
o.10 DESC
This is based on the fact that it would seem you have 0 for and or a value for the right operator.
Having said that, why on earth would you have ten fields to store one bit of information? Your table should only have the ONE field called something like "OperatorLevel" and have the value in it - unless I am missing something.
Alternately, you could also use a greatest() function in your query if you have multiple values and you want the highest one:
select
l.list_id,
l.user_id,
l.operator_id,
l.operator_name,
l.operator_level,
greatest(`1`, `2`, ... `10`) as opLevel
FROM
list l
LEFT JOIN operators o
ON l.operator_id = o.operators_id
WHERE
l.user_id=1
ORDER BY
o.10 DESC
Edit: Okay, based on additional information you can use the following (ick ick ick) statement:
select
l.list_id,
l.user_id,
l.operator_id,
l.operator_name,
l.operator_level,
case
when l.operator_level=1 then o.`1`
when l.operator_level=2 then o.`2`
when l.operator_level=3 then o.`3`
// etc etc yuck!
when l.operator_level=10 then o.`10`
end as yicky
FROM
list l
LEFT JOIN operators o
ON l.operator_id = o.operators_id
WHERE
l.user_id=1
ORDER BY
o.10 DESC
Edit 2:
I would very much suggest a data normalisation.
Given what you have as data, it seems that you are duplicating data to all sorts of users. This kinda sounds like a nightmare to update. From what I understand, it seems that there is a pay scale for each "skill" and it goes up based on the level of the operator?
I would make a table with the following structure (assumptions based on user 1):
skill level value
B32 1 25
B32 2 30
.....
B32 10 75
F16 1 25
F16 2 30
...
F16 10 75
Then you could simply perform a link from the "list" table to the "operators" table, and then link to the "grades" table based on data within then.
This would make for a much simpler query.
Having said ALL THAT, I would actually look at normalizing your "operators" table down to skills only. Move the "type" and "image" into the "list" table (and call it users while you are at it).
Now the "operators" table should be renamed to a skillset table with data like this:
userID skill Level
1 F16 4
1 B32 8
2 F_Aid 2
This would allow you to have users with more than 3 skills as well as allowing you to easily record each skill level of the user. They might be a superhero at B32, but only mediocre at F16.
Assuming only one int column has value maybe something like this?
SELECT l.*, COALESCE(1,2,3,4,5,6,7,8,9,10) opLevel
FROM list l
LEFT JOIN operators o ON l.operator_id = o.operators_id
WHERE l.user_id=1
ORDER BY COALESCE(1,2,3,4,5,6,7,8,9,10) DESC

MySQL/PHP storing multiple data points

I am relatively new to programming and databases.
I have a MySQL database with a "sales" table. This table lists all sales in a state (i.e. each record is a particular sale), with fields for sellername, and buyerzip. I would like to have another table "seller" that would include sellername, and also include fields to define the sellers market area by zip code - say 50+ zip codes define a market, and the seller could define multiple markets.
These market areas would be used for future queries: showing all sales in a particular market area.
Where do I start in terms of thinking how to store that "market area" data, and then use it for future queries?
Thanks
Table structure:
Sales table: ID, sellerID, buyerZipcode, amount
Seller table: ID, sellerName
Market table: ID, marketName
SellerMarketLink table: ID, sellerID, marketID
MarketZipCode table: ID, marketID, zipCode
Example data
Encoding the information in the first comment to your question
Market table
1, "Store A, first market"
2, "Store A, second market"
3, "Store B market"
MarketZipCode table
1, 1, 1
2, 1, 2
...
7, 1, 7
8, 1, 8
9, 2, 4
10, 2, 5
...
14, 2, 9
15, 2, 10
16, 3, 1
17, 3, 2
18, 3, 3
19, 3, 8
20, 3, 9
21, 3, 10
22, 3, 11
23, 3, 12
Query
Total sales in a market for each seller. Note that since one zip code can be part of several markets, the total of all values in "Total Sales" can be larger than the sum of all 'amount' values in the Sales table.
select Market.marketName, Seller.sellerName, sum(Sales.amount) as "Total Sales"
from Sales join Seller join SellerMarketLink join Market join MarketZipCode
on (Sales.sellerID = Seller.ID AND Seller.ID = SellerMarketLink.sellerID
AND SellerMarketLink.marketID = Market.ID
AND Market.ID = MarketZipCode.marketID
AND MarketZipCode.zipCode = Sales.buyerZipCode)
group by Market.ID, Market.marketName, Seller.ID, Seller.sellerName
order by Market.marketName, Seller.sellerName
I'm going to make the following assumptions here:
sellers have many markets
A market can span many zipcodes
Only one seller is responsible for a market
Markets can't overlap
Leading to the following structure...
sales table
seller | buyerzip
markets table
market | market_zipcodes_id
zipcodes table
market_id | zipcode
Hope that helps...

showing a grandtotal of values in a field

good pm. i was thinking is it possible to show the summation or grand total of a selected field in the table and with relation to date:
for example is i want to know the total beer consumption of my hotel every month.
i have here my table on services:
[services_id[pk],
customer_id[fk],
date_in,date_out,room_type,room_number,
extra_ref,
extra_bed,extra_snack,
extra_beer,extra_softdrinks,
extra_pillows,extra_breakfast,
extra_snack_q,
extra_beer_q,
extra_softdrinks_q,
extra_pillows_q,
extra_breakfast_q]
can you give some advice on how can i get it.
thanks in advance:
-renz
SELECT SUM(beer_amount) as monthly_beer_amount
FROM [DATABASE].[TABLE]
WHERE beer_sold_date BETWEEN '20110201' AND '20110228'
[EXTRA INFO]
Also I believe that the best way to organize this table is to separate out this table into a few other tables. Store customer info in the first table such as
[customer_id, customer_name, date_in, date_out, room_type, room_number].
1, Bob, 20110101, 20110110, big, 200
2, Joe, 20110101, 20110110, small, 202
....
And have another table named something like room_items which would have the following,
[id, item_name]
1, BEER
2, BED
3, SNACK
...
And then another table named room_purchases which will have the following,
[customer_id, purchase_id, amount, date....]
1, 1, 10, 20110101
2, 3, 5, 20110101
3, 1, 9, 20110101
....
This would help you to do a join on all three tables and they would be more normalized in this way.
SELECT SUM(t2.amount) as beer_amount
FROM customers as t1
LEFT JOIN room_purchases as t2 ON t1.customer_id = t2.customer_id
WHERE t2.purchase_id = 1 AND t2.date BETWEEN 20110101 AND 20110131
SELECT DATE_FORMAT(date_in,"%m-%Y") AS month,SUM(extra_beer) AS beers
FROM your_table
GROUP BY month
this will return something like this:
02-2011 | 43
03-2011 | 52
if you want to limit the query depending on the date just add a WHERE constraint before the GROUP BY

Categories