PHP: SQL from two queries - ignore duplicates - php

So I have this piece of code. It retreives a list of rooms from the table "rom" and is then supposed to check this against the same rooms in "booket". How can I just print out the rooms that are NOT in booket in the set time.
"Rom"-database looks like this:
romnavn | romtype (not relevant here)
ex: 81-77 | 2
"Booket" looks like:
romnavn | bruker | dato | fra | til
ex: 81-77 | foo#bar.net | 03.04.2013 | 16 | 18
(this means that the room will be booked from 16:00:00 to 18:00:00)
If the room shows up in both queries it should be ignored.
My guess was two while-loops, "$notFreeA" inside the first one, but I'm not getting the result I want.
I'm fairly sure that both the DB and queries are, well..,bad, but any help would be very much appreciated :)
require "sql/sqlConnect.php";
require "functions/functions.php";
date_default_timezone_set('Europe/Oslo');
$time = date('H:i:s');
$date = date('d.m.Y');
$nearestHour = roundToNearestHour($time);
$allRooms = "SELECT * FROM rom";
$notFree = "SELECT * FROM booket WHERE dato='$date' AND fra<='$nearestHour';";
$allRoomA = mysql_query($allRooms);
$notFreeA = mysql_query($notFree);
The function to round up/down to nearest room looks like this:
function roundToNearestHour($time) {
$part = explode(":", $time);
if(count($part) != 3)
return 0;
if($part[2] > 30)
$parts[1]++;
if($part[1] > 30)
$part[0]++;
return $part[0];
}

Try doing this with one query:
select *
from room r
where r.romnavn not in (select roomnavn booket WHERE dato='$date' AND fra<='$nearestHour')

You only need one query to do this. Any one of these three will produce the results you desire:
SELECT * FROM rom WHERE NOT EXISTS
(SELECT * FROM booket WHERE dato='$date' AND fra<='$nearestHour'
AND rom.romnavn = booklet.romnavn)
SELECT rom.* FROM rom LEFT OUTER JOIN booket USING romnavn
WHERE dato='$date' AND fra<='$nearestHour' AND booket.romnavn IS NULL
SELECT * FROM rom WHERE romnavn NOT IN
(SELECT romnavn FROM booket WHERE dato='$date' AND fra<='$nearestHour')
That said, this table design is very poor. You don't appear to be using native datetime types, and your primary keys are poorly named and should be something easier to index (like integer ids).
If there's only one change you can make, you should at least ensure that the dato column is actually a DATE type and not a varchar. As it is, any date-based sorting or filtering will be extremely difficult to perform with SQL.

Related

How to have Mysql query (with multiple join) faster and more efficient

I have a big problem with the execution of a MySql query that is very slow.. too slow... unusable!
To read 1000 products with their prices it takes more than 20 seconds!!!
$dati = mysqli_query($mysqli_connect, "
SELECT *
FROM $tb_products
LEFT JOIN $tb_categories ON $tb_products.product_category = $tb_categories.category_id_master
LEFT JOIN $tb_subcategories ON $tb_products.product_subcategory = $tb_subcategories.subcategory_id_master
LEFT JOIN $tb_logos ON $tb_products.product_logo = $tb_logos.logo_id_master
LEFT JOIN $tb_prices ON (
$tb_products.product_brand = $tb_prices.price_brand
AND $tb_products.product_code = $tb_prices.price_code
AND $tb_prices.price_validity = (
SELECT MAX($tb_prices.price_validity)
FROM $tb_prices
WHERE $tb_prices.price_validity<=DATE_ADD(CURDATE(), INTERVAL +0 DAY)
AND $tb_products.product_code = $tb_prices.price_code
)
)
WHERE $tb_products.product_language='$activeLanguage' AND $tb_products.product_category!=0
GROUP BY $tb_products.product_code
ORDER BY $tb_products.product_brand, $tb_categories.category_rank, $tb_subcategories.subcategory_rank, $tb_products.product_subcategory, $tb_products.product_rank
");
EDIT:
I've changed, as suggested from Mr.Alvaro, the SELECT * with a more efficient SELECT [list of values] and the execution time dropped from 20 seconds to 14 seconds. Still too slow...
END EDIT
Each product can have different prices, so I use the (select max...) to take the most recent (but not future) price.
Maybe is this function that slow down everything? Are there better solutions in your opinion?
Consider that the same query without the join with the prices it takes only 0.2 seconds.
So I'm convinced that the problem is all in that part of the code.
$dati = mysqli_query($mysqli_connect, "
SELECT *
FROM $tb_products
LEFT JOIN $tb_categories ON $tb_products.product_category = $tb_categories.category_id_master
LEFT JOIN $tb_subcategories ON $tb_products.product_subcategory = $tb_subcategories.subcategory_id_master
LEFT JOIN $tb_logos ON $tb_products.product_logo = $tb_logos.logo_id_master
WHERE $tb_products.product_language='$activeLanguage' AND $tb_products.product_category!=0
GROUP BY $tb_products.product_code
ORDER BY $tb_products.product_brand, $tb_categories.category_rank, $tb_subcategories.subcategory_rank, $tb_products.product_subcategory, $tb_products.product_rank
");
I also considered the fact that it could depend on the power of the server but I would tend to exclude it because the second query (without prices) is quite acceptable as speed.
The prices table is as following
+----------------+-------------+
| price_id | int(3) |
| price_brand | varchar(5) |
| price_code | varchar(50) |
| price_value | float(10,2) |
| price_validity | date |
| price_language | varchar(2) |
+----------------+-------------+
Maybe is because you are using SELECT *, this is known as bad practice. Check this question in stack overflow.
Is there a difference between Select * and Select [list each col]
There, Mitch Wheat wrote:
You should specify an explicit column list. SELECT * will bring back more columns than you need creating more IO and network traffic, but more importantly it might require extra lookups even though a non-clustered covering index exists (On SQL Server).
Blockquote
SOLVED
The problem was in the last JOIN with the prices table.
Following the suggestions I managed to execute the SELECT MAX (...) separately and it tooks 0.1 seconds to execute.
So I decide to run the main query without the prices and then, in the WHILE cicle to fetch the array, I run a second query to take the price for every single product!
This work perfectly and my page has dropped down from 20 seconds to a few tenths of a second.
So, the code become something like this:
$dati = mysqli_query($mysqli_connect, "
SELECT *
FROM $tb_products
LEFT JOIN $tb_categories ON $tb_products.product_category = $tb_categories.category_id_master
LEFT JOIN $tb_subcategories ON $tb_products.product_subcategory = $tb_subcategories.subcategory_id_master
LEFT JOIN $tb_logos ON $tb_products.product_logo = $tb_logos.logo_id_master
WHERE $tb_products.product_language='$activeLanguage' AND $tb_products.product_category!=0
GROUP BY $tb_products.product_code
ORDER BY $tb_products.product_brand, $tb_categories.category_rank, $tb_subcategories.subcategory_rank, $tb_products.product_subcategory, $tb_products.product_rank
");
and then..
while ($array = mysqli_fetch_array($dati)) {
$code = $array['product_code'];
$dati_prices = mysqli_query($mysqli_connect, "
SELECT *
FROM $tb_prices
WHERE $tb_prices.price_brand = '$brand' AND $tb_prices.price_code = '$code' AND $tb_prices.price_validity = (
SELECT MAX($tb_prices.price_validity)
FROM $tb_prices
WHERE $tb_prices.price_validity<=DATE_ADD(CURDATE(), INTERVAL +0 DAY) AND $tb_prices.price_code = '$code'
)
GROUP BY $tb_prices.price_code
") ;
}
Probably is not the best and elegant solution but for me works pretty well!

Query column for multiple values

I have a table that looks somewhat like the one below, and I am doing a search query with multiple fields to refine a search for a job. At the moment I am able to enter into multiple fields, however, my search results query the entire database, not just for one specific ID.
Table:
contentId meta_key meta_value
1 1 vacancyType Hospitality
2 1 vacancyRole Chef
3 1 vacancyDate 2014-01-01
4 2 vacancyType Adin
5 2 vacancyArea St Albans
6 2 vacancyDate 2014-01-01
Code:
$getJobs1 = "SELECT distinct *
FROM cms_contentExtra, cms_content
WHERE cms_contentExtra.meta_value IN ('$type','$key')
AND cms_content.contentId = cms_contentExtra.contentId
GROUP BY cms_content.contentId";
$getJobs2 = mysql_query($getJobs1) or die("didn't query");
while ($getJobs3 = mysql_fetch_array($getJobs2)) {
echo ' - ' . $getJobs3[meta_value] . ' - ' . ' - ' . $getJobs3[contentId];
}
This will return:
- St Albans - - 8435 - St Albans - - 8436 - Hospitality & Catering - - 8437 - St Albans - - 8440 - Hospitality & Catering - - 8444 - Hospitality & Catering - - 8450 - Hospitality & Catering - - 8451 - St Albans - - 8453
However, I only want to show results that have BOTH the area and type, and disregard the others. At the moment it seems to be displaying the type and then displaying the area separately, so I am unable to show a type in a specific area.
I am using two tables to compare the contentId to more data stored in a different table.
The IN operand that you use is an OR operation.
Try to use LIKE such as:
WHERE cms_contentExtra.meta_value LIKE "%$type%$key%"
OR cms_contentExtra.meta_value LIKE "%$key%$type%"
Let's have a closer look at your query. You group by contentId. So you get one record per contentId. But you select *. (This must be MySQL, for I don't know any other dbms allowing this.) So you get the content record belonging to the content id, and one (random) cms_contentExtra record belonging to the content id. Then you use DISTINCT, so you only get distinct rows. But the rows must be distinct, because they differ at least in the content id. Having said this, your query doesn't make sense, and I recommend you look up GROUP BY and DISTINCT in a book or tutorial. (Moreover you shouldn't use comma-separated join syntax. That was replaced more than twenty years ago with explicit joins. Use INNER JOIN instead.) So far to this :-)
To clean things up for you, I lead you step by step. Maybe this is not exactly what you are looking for, but I think it will help you build the final query.
To get content IDs with the vacancy type $type:
select contentid
from cms_content
where meta_key = 'vacancyType' and meta_value = #type;
To get content IDs with the location $key:
select contentid
from cms_content
where meta_key = 'vacancyArea' and meta_value = #key;
To get content IDs that match $type or $key:
select distinct contentid
from cms_content
where (meta_key = 'vacancyType' and meta_value = #type)
or (meta_key = 'vacancyArea' and meta_value = #key);
To get content IDs that match both $type and $key:
select contentid
from cms_content
where (meta_key = 'vacancyType' and meta_value = #type)
or (meta_key = 'vacancyArea' and meta_value = #key)
group by contentid
having count(*) = 2;

Join mysql query results to an array

I am trying to generate a table list from data held in 2 tables. One table is called PrimaryEvents and some sample data looks like:
|id|event |validity|eventsrequired
|1 |event1 |7 |10
|1 |event2 |25 |1
|1 |event3 |12 |50
id here is just the user id of whoever created the data.
The second table is called portfolio, some sample data looks like:
|id|name |number|datacompleted
|21|event1 |3 |2014-07-07
|15|event1 |5 |2014-07-05
|21|event2 |5 |2014-05-08
|15|event1 |1 |2013-05-05
|15|event1 |1 |2014-05-05
|21|event1 |13 |2014-08-07
|21|event1 |6 |2014-07-08
id here is the id of the user that has completed the event.
I have a query that populates an array to allow a table to be shown to the user, the table shows a list of ALL the events from PrimaryEvents with a left join of data from portfolio if they have completed an event by the same name.
I would like to change the functionality slightly to allow for the following:
Only events that are within the validity period date range are merged, AND the merged data automatically SUMs the number (from portfolio.number).
I am not sure how to progress this really. Currently, I have managed to extract the data range using the following code:
$currentDate = DATE("Y-m-d");
$eventList = query("SELECT event,validity FROM PrimaryEvents ORDER BY event");
foreach ($eventList as $row)
{
//
$event["validity"] = $row["validity"];
$validityDate = Date("Y-m-d", strtotime("-". $row["validity"] . "days"));
$eventsDateValidCompleted = query("SELECT * FROM portfolio WHERE id = ? AND datecompleted BETWEEN ? AND ? ORDER BY name", $_SESSION["id"], $validityDate , $currentDate);
}
What I am missing is how to do something useful with the daterange sorted array data.
Questions:
1) How can I SUM the event data within the valid date range (IE returned in $eventsDateValidCompleted array)?
2) How can I LEFT JOIN this array of data to each line of my current query?
$allEvents = query("SELECT * FROM PrimaryEvents LEFT JOIN portfolio ON (PrimaryEvents.event = portfolio.name) WHERE PrimaryEvents.role = ? AND (portfolio.id = ? Or portfolio.id is null) ORDER BY PrimaryEvents.event", $currentRole, $_SESSION["id"]);
3) Is there a better way to make this happen?
Thanks for any help that can be offered on this.
You can sum values from a query in MySQL using the SUM() function along with a GROUP BY clause. For example if you wanted to know the sum of all relevant portfolio.number values for a given user id and date range you could change this line:
$eventsDateValidCompleted = query("SELECT * FROM portfolio WHERE id = ? AND datecompleted BETWEEN ? AND ? ORDER BY name", $_SESSION["id"], $validityDate , $currentDate);
to this:
$eventsDateValidCompleted = query("SELECT SUM(number) AS total_number FROM portfolio WHERE id = ? AND datecompleted BETWEEN ? AND ? GROUP BY id", $_SESSION["id"], $validityDate , $currentDate);
And if you wanted to get this sum value by event, and as part of the original query you could do something like this:
SELECT e.event,e.validity, SUM(p.number) AS total_number
FROM PrimaryEvents e
LEFT JOIN portfolio p ON p.name = e.event
GROUP BY e.id
ORDER BY e.event
I'm not sure I understand exactly what you want, but I suggest using SQL SUM() and GROUP BY along these lines:
SELECT pe.id, pe.event, sum(p.number) FROM PrimaryEvents pe
LEFT JOIN portfolio p ON (pe.event = p.name) AND p.id = pe.role AND p.datecompleted BETWEEN ? AND ?
WHERE pe.role = ?
GROUP BY pe.id, pe.event
But, as I said, your (reduced) data model and the queries are not quite consistent, which is why I cannot tell if the above will do anything for you.

MySQL Select from 3 tables and get attendance In and Out Time for all Members in specific date

I have three table Like this:
members_tbl
id | Fullname | Email | MobileNo
attendance_in_tbl
id | member_id | DateTimeIN
attendance_out_tbl
id | member_id | DateTime_OUT
I want to select all members for date: 2014-03-10 by this query:
SELECT
attendance_in.EDatetime,
members_info.mfullname,
attendance_out.ODatetime
FROM
attendance_in
LEFT JOIN members_info ON members_info.id = attendance_in.MemID
LEFT JOIN attendance_out ON attendance_out.MemID = attendance_in.MemID
WHERE date(attendance_in.EDatetime) OR date(attendance_out.ODatetime) = "2014-03-10"
But it give me different results in Attendace_out Results
You have a mistake in your query.
You wrote:
WHERE date(attendance_in.EDatetime) /* wrong! */
OR date(attendance_out.ODatetime) = "2014-03-10"
This is wrong, as the first expression date(attendance_in.EDatetime) always evaluates to true.
You may want
WHERE date(attendance_in.EDatetime) = "2014-03-10"
OR date(attendance_out.ODatetime) = "2014-03-10"
But, this is guaranteed to perform poorly when your attendance_in and attendance_out tables get large, because it will have to scan them; it can't use an index.
You may find that it performs better to write this:
WHERE (attendance_in.EDatetime >='2014-03-10' AND
attendance_in.EDatetime < '2014-03-10' + INTERVAL 1 DAY)
OR (attendance_out.EDatetime >='2014-03-10' AND
attendance_out.EDatetime < '2014-03-10' + INTERVAL 1 DAY)
That will check whether either the checkin our checkout time occurs on the day in question.

PHP/MySQL - Ranking based on last db column

I’ve two tables (MySQL): player and scores. In scores table are stored annual scores in this way:
player_id | 2002 | 2003 | 2004
1 | 5 | 6 | 4
2 | 3 | 2 | 5
Etc.
I write the follow code to make a ranking based on last year scores.
$extract = mysql_query("SELECT * FROM players AS p, scores AS s WHERE p.player_id =s.player_id ORDER BY s.2004 desc");
while ($row = mysql_fetch_assoc($extract)) {
$name = $row['name'];
$score = $row['2004'];
if ($row['2004'] < $row['2003']) {
$value = "-";
}
else if ($row['2004'] > $row['2003']) {
$value = "+";
}
else if ($row['2004'] == $row['2003']) {
$value = "=";
}
echo "<b>".$name."</b> | ".$score." ".$value."<br>\n";
}
But this code has two big problems:
1) In the query I have to specify the last year (ORDER BY s.2004), so if I add another column (eg. 2005) into scores table, I have to manually change the code.
2) Same thing for the “$value” var. If I add 2005, the comparison between 2004 and 2003 becomes wrong (it should be between 2005 and 2004).
I know I have to use loops and array but... how?
You should redesign your scores table to store player_id, score and year in one row. So, each score gets its own row. When you find yourself duplicating columns, you are usually heading in the wrong direction.
Then, your query would look like:
Select Year(CURDATE());
select *
from players p
inner join scores s on p.player_id = s.player_id
where s.`Year` = Year(CURDATE()) --or (select max(Year) from Score)

Categories