MYSQL Query Fill Gaps with Data with LEFT JOIN - mysql

I am querying a MYSQL database which has a table named PRICE with the following fields: user, price_date, morning, afternoon. I am grabbing all the morning prices for the current week for the current user to be used as an array in a JS.Charts line chart. The issue I am having is that there may be missing days so the array that I am creating from the query is creating date gaps in the chart.
I created another table named calendar with three fields: datefield, morning, afternoon. I filled it with this years dates (YYYY-MM-DD) along with 0's for the morning and afternoon values.
I now have been trying to write a LEFT JOIN query to select all morning values for the current week and join it with the second table to fill in the date gaps with zeros but I can not get it to work. Any help would be greatly appreciated.
Query:
$sql = "SELECT p.morning, c.morning
FROM price p
LEFT JOIN calendar c ON p.price_date = c.datefield
WHERE p.user = '$user' AND YEARWEEK(p.price_date) = YEARWEEK(NOW())
ORDER BY p.price_date";
The data being used and what is being outputted:
Table Price:
user
price_date
morning
afternoon
lpepper
2021-03-15
23
35
lpepper
2021-03-17
43
52
lpepper
2021-03-18
24
35
lpepper
2021-03-19
78
85
Table Calendar (Partial - goes for whole year)
datefield
morning
afternoon
2021-03-15
0
0
2021-03-16
0
0
2021-03-17
0
0
2021-03-18
0
0
2021-03-19
0
0
2021-03-20
0
0
2021-03-21
0
0
I need the query to return the morning prices for this week (M to SUN) with zeros filled in for the missing dates:
Array should be: 23,0,43,24,78,0,0
My query above returns: ,0,0,0
To make the array I am doing:
$result = mysqli_query($conn, $sql);
$morning = array();
while ($row = mysqli_fetch_assoc($result))
{
$morning[] = $row["morning"];
}
Then when used in the graph:
<?php echo implode(", ", $morning); ?>

In a LEFT JOIN, the table with all the rows that should be in the output should be first.
If you have conditions on the other table, you need to put them in the ON clause. If there's no match to c.datefield, you'll get NULL for the p columns, and testing them in WHERE will filter those rows out.
The date should be filtered from the calendar table, not price. And you should return the date so you know what the rows are for.
Give aliases to p.morning and c.morning so you can distinguish them when getting the columns from the results.
You should order by the column in c, since p.price_date will be NULL for the missing dates.
$sql = "SELECT c.datefield, IFNULL(p.morning, 0) AS price_morning, c.morning AS cal_morning
FROM calendar c
LEFT JOIN price p ON p.price_date = c.datefield
AND p.user = '$user'
WHERE YEARWEEK(c.datefield) = YEARWEEK(NOW())
ORDER BY c.datefield";
DEMO
On an unrelated note, you should use a prepared statement rather than substituting a variable into the SQL.

Related

MYSQL Query - joining tables and grouping results

I'm after some help with a report I'm designing please.
My report includes results from a booking database where I'd like to show each booking on a single line. However as the booking database has a number of tables my MYSQL query involves JOINS which is resulting in multiple rows per booking. It is the multiple results for "dcea4_eb_field_values.field_value" per booking causing the repeating rows.
This is my query
SELECT
dcea4_eb_events.event_date,
dcea4_eb_events.title,
dcea4_eb_registrants.id,
dcea4_eb_registrants.first_name,
dcea4_eb_registrants.last_name,
dcea4_eb_registrants.email,
dcea4_eb_registrants.register_date,
dcea4_eb_registrants.amount,
dcea4_eb_registrants.comment,
dcea4_eb_field_values.field_id,
dcea4_eb_field_values.field_value
FROM dcea4_eb_events
INNER JOIN dcea4_eb_registrants ON dcea4_eb_registrants.event_id = dcea4_eb_events.id
INNER JOIN dcea4_eb_field_values ON dcea4_eb_field_values.registrant_id = dcea4_eb_registrants.id
WHERE 1=1
AND (dcea4_eb_field_values.field_id = 14 OR dcea4_eb_field_values.field_id = 26 OR dcea4_eb_field_values.field_id = 27 OR dcea4_eb_field_values.field_id = 15)
AND dcea4_eb_registrants.published <> 2
AND dcea4_eb_registrants.published IS NOT NULL
AND (dcea4_eb_registrants.published = 1 OR dcea4_eb_registrants.payment_method = "os_offline")
[ AND (dcea4_eb_registrants.register_date {RegistrationDate} ) ]
[ AND REPLACE(dcea4_eb_events.title,'\'','') in ({Club}) ]
ORDER BY dcea4_eb_registrants.register_date,
dcea4_eb_events.title
This is what the output currently looks like
current result
and this is what I'd like it to look like
desired result
Any help appreciated

Selecting rows until a column value isn't the same

SELECT product.productID
, product.Name
, product.date
, product.status
FROM product
INNER JOIN shelf ON product.sheldID=shelf.shelfID
WHERE product.weekID = $ID
AND product.date < '$day'
OR (product.date = '$day' AND shelf.expire <= '$time' )
ORDER BY concat(product.date,shelf.expire)
I am trying to stop the SQL statement at a specific value e.g. bad.
I have tried using max-date, but am finding it hard as am making the time stamp in the query. (Combining date/time)
This example table shows that 3 results should be returned and if the status "bad" was the first result than no results should be returned. (They are ordered by date and time).
ProductID Date status
1 2017-03-27 Good
2 2017-03-27 Good
3 2017-03-26 Good
4 2017-03-25 Bad
5 2017-03-25 Good
Think I may have fixed it, I added this to my while loop.
The query gives the results in order by present to past using date and time, this while loop checks if the column of that row is equal to 'bad' if it is does something (might be able to use an array to fill it up with data). If not than the loop is broken.
I know it doesn't seem ideal but it works lol
while ($row = mysqli_fetch_assoc($result)) {
if ($row['status'] == "bad") {
$counter += 1;
}
else{
break;}
I will provide an answer just with your output as if it was just one table. It will give you the main ideia in how to solve your problem.
Basically I created a column called ord that will work as a row_number (MySql doesn't support it yet AFAIK). Then I got the minimum ord value for a bad status then I get everything from the data where ord is less than that.
select y.*
from (select ProductID, dt, status, #rw:=#rw+1 ord
from product, (select #rw:=0) a
order by dt desc) y
where y.ord < (select min(ord) ord
from (select ProductID, status, #rin:=#rin+1 ord
from product, (select #rin:=0) a
order by dt desc) x
where status = 'Bad');
Result will be:
ProductID dt status ord
-------------------------------------
1 2017-03-27 Good 1
2 2017-03-27 Good 2
3 2017-03-26 Good 3
Also tested with the use case where the Bad status is the first result, no results will be returned.
See it working here: http://sqlfiddle.com/#!9/28dda/1

mysql perl select join and update tables

I have 3 tables as shown below, what I would like help with is understanding the joins (if needed) to execute the following query:
My product is in Product table (id,1) It's also available in Deal Table (product_id,1) it has a tariff_id of 10. Because the length in the Tariff table is 0, I don't update available.
Product table (id,2). It's also available in Deal Table (product_id,2) it has a tariff_id of 15. Because the length in the Tariff table is 24 (>0), I need to update available to 1.
Product table (id,3) is in Product Table and Deal Table, but in the Deal Table it has 2 tariffs associated with it (25 and 10). Again this time because the length of one of the tariffs is 12 (>0), I need to update available to 1.
Exactly the same as above for Product table (id,4).
Product Table (id,5). Is not in the Deal Table so no update required.
Product table
-------------------------
id | pm_available
-------------------------
1 0
2 0
3 0
4 0
5 0
Deal Table
------------------------------------------
id | product_id | tariff_id
------------------------------------------
1 1 10
2 2 15
3 3 25
4 3 10
5 4 20
6 4 25
7 4 10
Tariff table
----------------------------
id | length
----------------------------
10 0
15 24
20 0
25 12
The updated Product Table should look like this
Product table
-------------------------
id | pm_available
-------------------------
1 0
2 1
3 1
4 1
5 0
There are over 3000 products and 1 million deals. A product_id can appear in the Deals Table hundreds of times. So I thought if I select one product from Product Table pass that into a second query, the second query would check to see if 1) The product existed in the Deals Table. 2) If it exists what tariff is it linked to. 3) Check if the length of that tariff was greater than 0, if so, do the update.
I have this current script but it doesn't work correctly, I get updates to 'available' when I shouldn't.
my $dbh = DBI->connect($dbiconnect,$dbiuser,$dbipass,{AutoCommit=>1,RaiseError=>1,PrintError=>0});
my $shh = $dbh->prepare(q{SELECT id FROM products }) or die $dbh->errstr;
my $ih = $dbh->prepare(q{UPDATE products SET available = ? WHERE id = ?}) or die $dbh->errstr;
my $sh = $dbh->prepare(q{
SELECT d.id
FROM deals d
JOIN tariffs t ON d.tariff_id = t.id
JOIN products p ON d.product_id = p.id
WHERE d.product_id = ?
AND t.length > 1
LIMIT 0,1
}) or die $dbh->errstr;
$shh->execute or die $dbh->errstr;
my #data;
while (#data = $shh->fetchrow_array()) {
my $id = $data[0];
$sh->execute("$id");
if($sh->rows > 0){
$ih->execute("1", "$id");
$ih->finish;
} else {
$ih->execute("0", "");
$ih->finish;
}
$sh->finish;
usleep($delay);
}
$shh->finish;
Any help would be greatly appreciated.
MySQL will do all of this for you — there is no need to involve Perl or multiple SQL statements.
Like this (untested, because I don't have a MySQL server installed at present).
my $update = $dbh->prepare(<<__SQL__);
UPDATE Product
JOIN Deal ON Product.id = Deal.product_id
JOIN Tariff ON Tariff.id = Deal.tariff_id
SET Product.pm_available = 1
WHERE Tariff.length > 0
__SQL__
$update->execute;
Slight mod to what you were doing, 1st query joins all three tables summing the length from the tariffs table (it will be null or zero if there are none). Then iterating through the results updating the products table.
The MYSQL only answer offered earlier assumes you are running this script on the same machine as the mysql server (won't work if you're not). I think it would also have potential problems one of which being that it will not set your available to 0 in any case so once available has been flagged it will never unflag no matter what.
my $dbh = DBI->connect($dbiconnect,$dbiuser,$dbipass,{AutoCommit=>1,RaiseError=>1,PrintError=>0});
my $ih = $dbh->prepare(q{UPDATE products SET available = ? WHERE id = ?}) or die $dbh->errstr;
my $sh = $dbh->prepare(q{
SELECT p.id,sum(t.length)
FROM ((products p
LEFT JOIN deals d) ON p.id = d.product_id)
LEFT JOIN tariffs t ON d.tariff_id = t.id)
GROUP BY p.id;
}) or die $dbh->errstr;
while (my #data = $sh->fetchrow_array()) {
my $id = $data[0];
my $length = $data[1];
if($length > 0){
$ih->execute("1", "$id");
}else{
$ih->execute("0", "$id");
}
$ih->finish;
usleep($delay);
}
$sh->finish;

MySQL compare two values on same table

I'm trying to compare two values in the same table, and check if there is a difference.
Right now, I have 1485 records in the cms_statistics_pages table, but when the query below:
SELECT
cp.identifier,
COUNT(csp1.statID) AS hits,
COUNT(csp2.statID) AS hits_yesterday,
IF(COUNT(csp1.statID)>COUNT(csp2.statID),1,0) AS growth
FROM cms_pages cp
LEFT JOIN cms_statistics_pages csp1
ON csp1.pageID = cp.pageID
AND DATE(csp1.datetime) = '2012-07-20'
LEFT JOIN cms_statistics_pages csp2
ON csp2.pageID = cp.pageID
AND DATE(csp2.datetime) = '2012-07-19'
GROUP BY cp.identifier
..is fired, I get these results:
identifier hits hits_yesterday growth
index 13395 13395 0
siden-er-under-opdatering 638 638 0
vores-historie 0 3 0
Which is not correct for my purpose. Then if I change:
AND DATE(csp1.datetime) = '2012-07-20'
to a date that will match no records
AND DATE(csp1.datetime) = '2012-07-21'
My result now looks like this:
identifier hits hits_yesterday growth
index 0 141 0
siden-er-under-opdatering 0 29 0
vores-historie 0 3 0
Now the hits are correct, so I'm wondering if the query counts the records multiple times when both the joins contains some data.
Example data from cms_pages:
pageID sectionID templateID identifier default title exclude_title
1 1 1 index 1 Welcome to SiteTech Framework 2012
Example data from cms_statistics_pages:
statID frontend backend pageID sectionID panel datetime
1 0 1 34 6 admin 2012-07-17 12:34:14
I came across this post which provides a more advanced way to count across multiple tables, this may prevent the query from counting the same record multiple times. Haven't tried it myself - https://discussion.dreamhost.com/thread-9112.html
So I've been messing around a bit with the query, and found a solution which includes left joins and sub queries. My query now looks like:
SELECT
cp.identifier,
now.hits AS hits,
yd.hits AS hits_yesterday,
IF(now.hits>yd.hits,1,0) AS growth
FROM cms_pages AS cp
LEFT JOIN
(
SELECT
pageID,
COUNT(pageID) AS hits
FROM cms_statistics_pages
WHERE DATE(datetime) = '2012-07-20'
GROUP BY pageID
) now
ON now.pageID = cp.pageID
LEFT JOIN
(
SELECT pageID,
COUNT(pageID) AS hits
FROM cms_statistics_pages
WHERE DATE(datetime) = '2012-07-19'
GROUP BY pageID
) yd
ON yd.pageID = cp.pageID
Which gave me this correct result!:
identifier hits hits_yesterday growth
index 95 141 0
siden-er-under-opdatering 22 29 0
vores-historie NULL 3 0

MySQL - What's wrong with the query?

I am trying to query a database to find the following.
If a customer searches for a hotel in a city between dates A and B, find and return the hotels in which rooms are free between the two dates.
There will be more than one room in each room type (i.e. 5 Rooms in type A, 10 rooms in Type B, etc.) and we have to query the database to find only those hotels in which there is at least one room free in at least one type.
This is my table structure:
**Structure for table 'reservations'**
reservation_id
hotel_id
room_id
customer_id
payment_id
no_of_rooms
check_in_date
check_out_date
reservation_date
**Structure for table 'hotels'**
hotel_id
hotel_name
hotel_description
hotel_address
hotel_location
hotel_country
hotel_city
hotel_type
hotel_stars
hotel_image
hotel_deleted
**Structure for table 'rooms'**
room_id
hotel_id
room_name
max_persons
total_rooms
room_price
room_image
agent_commision
room_facilities
service_tax
vat
city_tax
room_description
room_deleted
And this is my query:
$city_search = '15';
$check_in_date = '29-03-2010';
$check_out_date = '31-03-2010';
$dateFormat_check_in = "DATE_FORMAT('$reservations.check_in_date','%d-%m-%Y')";
$dateFormat_check_out = "DATE_FORMAT('$reservations.check_out_date','%d-%m-%Y')";
$dateCheck = "$dateFormat_check_in >= '$check_in_date' AND $dateFormat_check_out <= '$check_out_date'";
$query = "SELECT $rooms.room_id,
$rooms.room_name,
$rooms.max_persons,
$rooms.room_price,
$hotels.hotel_id,
$hotels.hotel_name,
$hotels.hotel_stars,
$hotels.hotel_type
FROM $hotels,$rooms,$reservations
WHERE $hotels.hotel_city = '$city_search'
AND $hotels.hotel_id = $rooms.hotel_id
AND $hotels.hotel_deleted = '0'
AND $rooms.room_deleted = '0'
AND $rooms.total_rooms - (SELECT SUM($reservations.no_of_rooms) as tot
FROM $reservations
WHERE $dateCheck
GROUP BY $reservations.room_id) > '0'";
The number of rooms already reserved in each room type in each hotel will be stored in the reservations table.
The thing is the query doesn't return any result at all. Even though it should if I calculate it myself manually.
I tried running the sub-query alone and I don't get any result. And I have lost quite some amount of hair trying to de-bug this query from yesterday. What's wrong with this? Or is there a better way to do what I mentioned above?
Edit: Code edited to remove a bug. Thanks to Mark Byers.
Sample Data in reservation table
1 1 1 2 1 3 2010-03-29 2010-03-31 2010-03-17
2 1 2 3 3 8 2010-03-29 2010-03-31 2010-03-18
5 1 1 5 5 4 2010-03-29 2010-03-31 2010-03-12
The sub-query should return
Room ID : 1 Rooms Booked : 7
Room ID : 2 Rooms Booked : 8
But it does not return any value at all.... If i remove the dateCheck condition it returns
Room ID : 2 Rooms Booked : 8
Your problem is here:
$rooms.total_rooms - (SELECT SUM($reservations.no_of_rooms) as tot,
$rooms.room_id as id
FROM $reservations,$rooms
WHERE $dateCheck
GROUP BY $reservations.room_id) > '0'"
You are doing a subtraction total_rooms - (tot, id) where the first operand is a scalar value and the second is a table with two columns. Remove one of the columns in the result set and make sure you only return only one row.
You also should use the JOIN keyword to make joins instead of separating the tables with commas. That way you won't forget to add the join condition.
You probably want something along these lines:
SELECT column1, column2, etc...
FROM $hotels
JOIN $rooms
ON $hotels.hotel_id = $rooms.hotel_id
JOIN (
SELECT SUM($reservations.no_of_rooms) as tot,
$rooms.room_id as id
FROM $reservations
JOIN $rooms
ON ??? /* Aren't you missing something here? */
WHERE $dateCheck
GROUP BY $reservations.room_id
) AS T1
ON T1.id = room_id
WHERE $hotels.hotel_city = '$city_search'
AND $hotels.hotel_deleted = '0'
AND $rooms.room_deleted = '0'
AND $rooms.total_rooms - T1.tot > '0'