mysql perl select join and update tables - mysql

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;

Related

SQL to SELECT all groups belonging to a specific member ID AND all the users that belong to each group

I have three tables, the relevant parts of which are:
users
users.userID, users.userNickname
groups
groups.groupID, groups.groupOwner, groups.groupName
mapFriendsGroups
mapFriendsGroups.userID, mapFriendsGroups.groupID
To be clear, the functionality is that a primary user can create new groups which appear in the groups table. A primary user has a list of friends (secondary users, each with their own entry in the users table) that they can add to one of their groups; this is represented by that friend's users.userID value being entered into the mapFriendsGroups.userID field of the mapFriendsGroups table. I then wrote this SQL to get the data that I want to display:
SELECT `groups`.*, `users`.* FROM `groups` JOIN `mapFriendsGroups` ON `groups`.`groupID` = `mapFriendsGroups`.`groupID` JOIN `users` ON `users`.`userID` = `mapFriendsGroups`.`userID` WHERE `groups`.`groupOwner` = ? ORDER BY `groups`.`groupID`, `users`.`userLastName`
I handle this as a prepared statement in PHP and bind the primary user's users.userID value (which is held in a session variable) to the ?
This very nearly works but I discovered one problem - if a primary user creates a group but has not assigned any members to that group, the group will not be part of the results. I am convinced I'm doing a JOIN wrong but can't seem to get it to work.
I checked several other questions on stackoverflow (e.g. Select all users that belong to a certain group, complicated query and SQL: query get all users for a specific group and each user with all groups ) but all of those answers seem to assume that the groups are not restricted to a subset and filtered by the current user's ID.
Anyone have any suggestions?
In case it matters, here's what I'm using to parse the data after I get it:
if ($r->num_rows > 0) { // If it ran OK.
$data1 = $r->fetch_assoc();
$myGroups = '';
$currentGroup = '';
do {
// check the value of the current group variable against the actual current group name
if ($currentGroup == $data1['groupName']){
// this is part of a prior group...
$myGroups .= ', '.$data1['userFirstName'].' '.$data1['userLastName'];
} else {
// this is not part of a prior group. check if the current group variable is empty - if it is then just start a new row
if ($currentGroup == ''){
$myGroups .= '<tr>';
$myGroups .= '<td><strong>'.$data1['groupName'].'</strong></td>';
$myGroups .= '<td>'.$data1['userFirstName'].' '.$data1['userLastName'];
} else {
// close the prior group then start a new row
$myGroups .= '</td>';
$myGroups .= '</tr>';
$myGroups .= '<tr>';
$myGroups .= '<td><strong>'.$data1['groupName'].'</strong></td>';
$myGroups .= '<td>'.$data1['userFirstName'].' '.$data1['userLastName'];
}
}
// assign the current group name to the current group variable
$currentGroup = $data1['groupName'];
} while($data1 = mysqli_fetch_assoc($r));
// close the final row
$myGroups .= '</td></tr>';
It ain't pretty, but it outputs a table of data where the users sees all of his groups (that have members in them) and who are the members (note that the creator of a group is NOT part of the group - this is important for later functionality). Again, I just need a tweak that will let this show empty groups. It's not an error in the PHP code - I've tested the SQL directly in phpMyAdmin's SQL tool and even there I'm not seeing the groups with zero members.
I wish I could just add the creator of the group to the group - that would enable my current code to do the job - but again that's not an option for this project.
EDIT 01
Here's a sample of the data:
from the users table
userID = 5 is the primary user (the value of ? in the test query)
other userID values are 1,2,3,4,8,10,11,12
from the groups table, only those groups whose groups.groupOwner value = 5
groupID = 11 | groupName = BFFs
groupID = 12 | groupName = Car
groupID = 13 | groupName = Shopping
groupID = 14 | groupName = Travel
groupID = 15 | groupName = Test
groupID = 18 | groupName = Gamers
from the mapFriendsGroups table, the relevant rows
userID = 1 | groupID 11
userID = 2 | groupID 11
userID = 3 | groupID 11
userID = 1 | groupID 12
userID = 2 | groupID 13
userID = 3 | groupID 13
userID = 1 | groupID 14
userID = 3 | groupID 14
userID = 1 | groupID 15
userID = 2 | groupID 15
EDIT 02
I took the csv export option from phpMySQL and dumped it into a text editor, removed the irrelevant columns, and am pasting it here. Note there's 10 rows.
11,5,BFFs,3,Alex,Smith
11,5,BFFs,2,Ben,Smith
11,5,BFFs,1,Allison,Smith
12,5,Car,1,Allison,Smith
13,5,Shopping,3,Alex,Smith
13,5,Shopping,2,Ben,Smith
14,5,Travel,3,Alex,Smith
14,5,Travel,1,Allison,Smith
15,5,Test,2,Ben,Smith
15,5,Test,1,Allison,Smith
I'm trying to get this same type of output, but with an 11th row that would be:
18,5,Gamers,,,
That is, the group that has no members in it.
EDIT 03 - FINAL EDIT
I've got it working. The trick seems to be to make both of the joins into LEFT JOIN - that gives me the needed output of 11 rows, i.e. 10 rows with associated members of a group and 1 row for a group that has no members (and which has null values for the fields of userID and userName so I'll need to handle that in the PHP code that parses it... but that's trivial).
Thank you very much to all of you that contributed. I will mark as the correct answer the contribution which helped me the most to find the correct answer.
You have not a relation between groups and mapFriendsGroups you should use LEFT JOIN ( and not JOIN - INNER JOIN ) for manage this case
SELECT `groups`.*, `users`.*
FROM `groups`
LEFT JOIN `mapFriendsGroups` ON `groups`.`groupID` = `mapFriendsGroups`.`groupID`
LEFT JOIN `users` ON `users`.`userID` = `mapFriendsGroups`.`userID`
WHERE `groups`.`groupOwner` = ?
ORDER BY `groups`.`groupID`, `users`.`userLastName`
You should use left join from the "groups" table to "mapFriendsGroups" table. That way if there is no record in the "mapFriendsGroups" you still can retrieve record from "groups" table. Here is the query:
SELECT groups.*, users.*
FROM groups
LEFT JOIN mapFriendsGroups ON groups.groupID =
mapFriendsGroups.groupID
INNER JOIN users ON users.userID =
mapFriendsGroups.userID
WHERE groups.groupOwner = ?
ORDER BY
groups.groupID, users.userLastName

MYSQL Query Fill Gaps with Data with LEFT JOIN

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.

Mysql inq - querying multiple tables for results

I'm working on a issue tracking script and looking at the mysql statement I was able to get the counts of all the priority issues but got stuck with getting the count on the status.
How do I go about retrieving the status count using the mysql statement below?
tbl_status
index status
1 open
2 closed
3 pending
tbl_priority
index priority
1 low
2 medium
3 high
tbl_incident
incident priority status
adfadf 1 2
adfsdf 2 2
adfadf 1 1
adfadf 3 2
adfasdf 1 3
I was able to group the priority as such (works):
Low 3
Hedium 1
high 1
Like the same results with status but its not working out. maybe asking too much from a single statement.
open 1
closed 3
high 1
try
{ $stmt = $dbcon1->query("SELECT COUNT(tbl_incident.status),
tbl_priority.priority, count(tbl_incident.priority), tbl_status.status
FROM tbl_incident
LEFT JOIN tbl_priority
ON tbl_priority.index = tbl_incident.priority
LEFT JOIN tbl_status
ON tbl_status.index = tbl_incident.status
GROUP BY tbl_priority.pry_priority ");
$priorityCount = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
maybe asking too much from a single statement.
Yes, try to separate statements for each question. It will be more efficient on my opinion.
For statuses:
select tbl_status.status, count(*)
from tbl_incident
inner join tbl_status on tbl_status.index = tbl_incident.status
group by tbl_status.status;
And for priority:
select tbl_priority.priority, count(*)
from tbl_incident
inner join tbl_priority on tbl_priority.index = tbl_incident.priority
group by tbl_priority.priority;

mysql select while skipping some number of rows

Is there a mysql syntax that can hop some rows?
For example
id value
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
SELECT * FROM table HOP BY 2
so the result will be
id value
3 c
6 f
9 i
or
id value
1 a
4 d
7 g
Take note: We don't know the actual ID of a row so we can't use a WHERE clause like this
WHERE ID is a multiple of 3 or etc.
I didn't realize you could do math in sql queries. Learned something new. Cool. Here's code that would select 1, 4, and 7.
$stmt = mysqli_prepare($connection, "SELECT * FROM users WHERE (ID+2)%3 = 0 AND ID>1");
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while($row = mysqli_fetch_assoc($result)){
echo $row['username'];
}
I don't see why it wouldn't work if id has gaps, as the man with ?mandarin? symbols for a name said.
Modulus(%), if you don't know, gives the number of decimals given by a division problem. So 3/3=1, with no decimals, so 3%3=0, whereas 4/3=1.333333..., so 4/3 equals infinity(not really in programming, but close enough).
SELECT * FROM hoptable WHERE ID%3 =0 AND ID>1
SET #row_idx := 0;
SELECT *, (#row_idx := #row_idx + 1) AS idx
FROM table
HAVING idx % 3 = 0;
You probably want to include an ORDER BY clause so the row index can actually be meaningful though. You can't rely on the result being ordered by id without specifying it.

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'