I have a website that lists apartments that are available for rent. I have a few different tables in my MySQL database. One table is for basic information about the Apartment building as a whole, including address, ammenities, photos, an ID (NID), etc.
Another table lists the units available for rent within each building. So each apartment building has multiple floorplans, with a few studios, a few 1 bedroom units, sometimes a few 2 bedroom units, etc.
Currently I query my 'unit' table and ask for all units in a given apartment building. My query looks something like SELECT * FROM node_unit WHERE nid='555'
This might return something like the following:
NID Name Rent SqFT Bedrooms Bathrooms
555 Unit 1 $500 620 0 1
555 Unit 2 $550 680 0 1
555 Unit 3 $600 820 1 1
555 Unit 4 $650 920 1 1
555 Unit 5 $700 1220 2 1
555 Unit 6 $800 1420 2 2
555 Unit 7 $900 1500 3 2
555 Unit 8 $1100 1620 3 3
etc, etc
What I am then doing in my PHP is using an accordian to group the 1 bedrooms together, the 2 bedrooms together, etc.
Not all apartments have 2 bedrooms units, some only have 1 bedroom units, so I need to know within my PHP code if I should print another accordian.
Currently I am using multiple hits to the database to determine if a given building has any 2 bedroom units, if so print another row. Does this building have any 3 bedroom units? If so print another row, but I would like to stop hitting my database so much.
Finally, here is my question:
How can I store the results from my first DB call and somehow parse thru the data and and determine if a given NID has studios, 1 beds, 2 beds, etc? (I just started learning PHP/MySQL recently)
I would suggest a query like this:
SELECT * FROM node_unit WHERE nid='555'
ORDER BY Bedrooms ASC, Bathrooms ASC, Rent ASC
This would return your records ordered by # of bedrooms, # bathrooms, and rent amount (in that order).
You could easily store this in multidimensional array when reading from database like this (assuming mysqli use with result set stored in $result, but concept is same for other DB connection libraries)
$room_array = array();
while ($row = mysqli_fetch_assoc($result)) {
$room_array[(int)$row['Bedrooms']][] = $row;
}
You now have a multidimensional array with number of bedrooms as first index. The array might look like this if you var_dump it:
Array (
[0] => Array (
[0] => Array (
'NID' => '555',
'Name' => 'Unit 1',
'Rent' => '$500',
'SqFt' => '620',
'Bedrooms' => '0',
'Bathrooms' => '1',
)
[1] => Array (
'NID' => '555',
'Name' => 'Unit 2',
'Rent' => '$550',
'SqFt' => '680',
'Bedrooms' => '0',
'Bathrooms' => '1',
)
)
[1] => Array (
[0] => Array (
'NID' => '555',
'Name' => 'Unit 3',
'Rent' => '$600',
'SqFt' => '820',
'Bedrooms' => '1',
'Bathrooms' => '1',
)
[1] => Array (
'NID' => '555',
'Name' => 'Unit 4',
'Rent' => '$650',
'SqFt' => '920',
'Bedrooms' => '1',
'Bathrooms' => '1',
)
[2] => Array (
...
)
[3] => Array (
...
)
)
This makes it really easy to iterate over the number of bedrooms values in an outer loop, which creates your accordions, and then iterate the individual rooms in an inner loop.
foreach ($room_array as $num_bedrooms => $rooms) {
// start accordion
foreach ($rooms as $room) {
// output room details
}
// end accordion
}
All this would only require the single query.
Also make sure you have indexes on bedrooms, bathrooms, rent (or whichever you use in the sort) as well as on nid since it is used as the filter.
Try this as your database query, it will generate a row for each building for each unit type it has. You could also use it as a subquery and join to the parent table to get any other details you need about the building in one shot.
select 'studio' as unit_type, nid from node_unit group by nid having count(case when bedrooms = 0 then 1 end) > 0
union all
select 'one bedroom' as unit_type, nid from node_unit group by nid having count(case when bedrooms = 1 then 1 end) > 0
union all
select 'two bedroom' as unit_type, nid from node_unit group by nid having count(case when bedrooms = 2 then 1 end) > 0
union all
select 'three bedroom' as unit_type, nid from node_unit group by nid having count(case when bedrooms = 3 then 1 end) > 0
Related
I'm trying to restructure my arrays so that I can get the desired output in one final array that will be used to fill a csv, eventually spreadsheet.
My first msyql query and the resulting array give me this structure:
array(
[0] => Array
(
[sku_id] => 1
[dealer_id] => 1976
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2018-03-
)
[1] => Array
(
[sku_id] => 1
[dealer_id] => 5400
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2017-04-
)
[2] => Array
(
[sku_id] => 1
[dealer_id] => 11316
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2017-02-
)
)
So I get 3 records, each for the same product (sku_id) but a different customer(dealer_id). Then I match this data up in a similar table in DB2 to get quantity for each order of the sku_id. That 2nd query/array shows this:
Array
(
[0] => Array
(
[CSTNOC] => 1976
[TOTALQTY] => 2
)
[1] => Array
(
[CSTNOC] => 5400
[TOTALQTY] => 5
)
[2] => Array
(
[CSTNOC] => 11316
[TOTALQTY] => 14
)
)
However, this is what I'm trying to achieve.
In the below example, sku_id, groupID and location would come directly from the first query. Days is the number of days between start_date (from query 1) and curDate(). Qty is orqtyc from query 2 and Average is a calculation based on locations, days and qty. Each row is based on a different customer, so even though the below example is for one sku, the rows signify a different customer with different locations, qty, etc.
SKU_id | groupID | locations (n) | Days (x) | Qty(q) | Average (x/(q/n))
--------------------------------------------------------------------------------
123 1 3 120 15 24
123 1 2 12 6 4
The Report or CSV would then only show one line item for the above:
SKU | Group | Average Days | Total units sold
123 | 1 | 28 | 21
Basically, for every sku I'm getting those records and then I need to do those calculations by row, and get a total of those values for each sku. The report will only have one line item per sku.
I have everything I need to get there, but how can I structure a final array to get my desired outputs like above?
FULL SCRIPT:
$skuQuery = "
SELECT
sku_id,
dealer_id,
locations,
s.sku_group_id as groupID,
s.frame as frame,
s.cover1 as cover,
s.color1 as color,
start_date - interval 7 day as start_date
from products p
inner join skus s on p.sku_id = s.id
where curdate() between p.start_date and p.expire_date
group by sku_id, dealer_id
limit 3";
$skuRslt = mysqli_query($conn,$skuQuery);
while($skuRow = mysqli_fetch_assoc($skuRslt)){
$skuResult[] = $skuRow;
$dealerQuery = "
SELECT
cstnoc,
sum(orqtyc) as TotalQTY
from table
where cstnoc = {$skuRow['dealer_id']}
AND framec = {$skuRow['frame']}
AND colr1c = {$skuRow['color']}
AND covr1c = {$skuRow['cover']}
AND extd2d >= " . str_replace('-', '', $skuRow['start_date']) . "
group by cstnoc, framec
";
$dealerRslt = odbc_exec($DB2Conn, $dealerQuery);
foreach($skuResult as $skuRow){
while($dealerRow = odbc_fetch_array($dealerRslt)){
$dealerResult[] = $dealerRow;
}
}
print_r($dealerResult);
}
I'm trying to build arrays based off of two queries, one in mysql one on db2. I've built the main arrays, the first dealing with product info and the 2nd dealing with customer info but they have some values in common.
This is working as far as structure but I've been trying to build a final array to contain data from the initial arrays that will build a report.
For now, I'm just going to try and output a report or csv file for each sku_id from the first query, and the total number/ quantity from the 2nd query.
The 2nd query/array contain multiple attributes on a per product/customer basis.
(
[1] => 2
)
Array
(
[1] => 5
)
Array
(
[1] => 14
)
Basically, I pulled three records from the first table for sku_id '1' and matched those three records in db2 for the same sku. Each element corresponds to a customer, so this structure says "customer A had 2 of sku_id 1, customer B had 5 of sku_id 1 and customer C had 14 of sku_id 1'
For the purpose of the report, each line will be for each sku so I want to instead output this as:
array
(
[1] => 21
)
Showing that overall, sku_id 1 had a total of 21. So I still want to keep my initial arrays structured like they are, but I want to modify my final array to just give me line items for the report.
How can I do that?
$skuQuery = "
SELECT
sku_id,
dealer_id,
locations,
s.sku_group_id as groupID,
s.frame as frame,
s.cover1 as cover,
s.color1 as color,
start_date - interval 7 day as start_date
from products p
inner join skus s on p.sku_id = s.id
where curdate() between p.start_date and p.expire_date
group by sku_id, dealer_id
limit 3";
$skuRslt = mysqli_query($conn,$skuQuery);
while($skuRow = mysqli_fetch_assoc($skuRslt)){
$skuResult['sku_id'] = $skuRow;
$dealerQuery = "
SELECT
cstnoc,
sum(orqtyc) as TotalQTY
from table
where cstnoc = {$skuRow['dealer_id']}
AND framec = {$skuRow['frame']}
AND colr1c = {$skuRow['color']}
AND covr1c = {$skuRow['cover']}
AND extd2d >= " . str_replace('-', '', $skuRow['start_date']) . "
group by cstnoc, framec
";
$dealerRslt = odbc_exec($DB2Conn, $dealerQuery);
$dealerResult = [];
foreach($skuResult as $skuRow){
while($dealerRow = odbc_fetch_array($dealerRslt)){
if ( !isset($dealerResult[$dealerRow['CSTNOC']]) ) {
$dealerResult[$dealerRow['CSTNOC']] = 0;
}
$dealerResult[$dealerRow['CSTNOC']] += $dealerRow['TOTALQTY'];
}
}
$skuTots = array_fill_keys(array_unique(array_column($skuResult, 'sku_id')), 0);
foreach ($skuResult as $rec) {
$skuTots[$rec['sku_id']] += $dealerResult[$rec['dealer_id']];
}
print_r($skuTots);
}
UPDATE:
This is the print out for my first two arrays:
Array
(
[0] => Array
(
[sku_id] => 1
[dealer_id] => 1976
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2018-03-
)
[1] => Array
(
[sku_id] => 1
[dealer_id] => 5400
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2017-04-
)
[2] => Array
(
[sku_id] => 1
[dealer_id] => 11316
[locations] => 1
[groupID] => 1
[frame] => 1051
[cover] => 1150
[color] => 99
[start_date] => 2017-02-
)
)
Array
(
[0] => Array
(
[CSTNOC] => 1976
[TOTALQTY] => 2
)
[1] => Array
(
[CSTNOC] => 5400
[TOTALQTY] => 5
)
[2] => Array
(
[CSTNOC] => 11316
[TOTALQTY] => 14
)
)
And here is an example of my end result I'm trying to achieve:
In the below example, sku_id, groupID and location would come directly from the first query. Days is the number of days between start_date (from query 1) and curDate(). Qty is orqtyc from query 2 and Average is a calculation based on locations, days and qty. Each row is based on a different customer, so even though the below example is for one sku, the rows signify a different customer with different locations, qty, etc.
SKU_id | groupID | locations (n) | Days (x) | Qty(q) | Average (x/(q/n))
--------------------------------------------------------------------------------
123 1 3 120 15 24
123 1 2 12 6 4
The Report or CSV would then only show one line item for the above:
SKU | Group | Average Days | Total units sold
123 | 1 | 28 | 21
Basically, for every sku I'm getting those records and then I need to do those calculations by row, and get a total of those values for each sku. The report will only have one line item per sku.
on my ratings table for my software i have 4 fields.
id autoincrement
rvid vendor id
ratedate date of rating
rating the actual numeric rating
I have done alot with it over the last few months but this time im stumped and i cant get a clear picture in my head of the best way to do this. What i am trying to do is find out if the vendor has had 3 low 'consecutive' ratings. If their last three ratings have been < 3 then i want to flag them.
I have been playing with this for a few hours now so i thought i would ask (not for the answer) but for some path direction just to push me forward, im stuck in thought going in circles here.
I have tried GROUP BY and several ORDER BY but those attempts did not go well and so i am wondering if this is not a mysql answer but a php answer. In other words maybe i just need to take what i have so far and just move to the php side of things via usort and the like and do it that way.
Here is what i have so far i did select id as well at first thinking that was the best way to get the last consective but then i had a small breakthrough that if they have had 3 in a row the id does not matter, so i took it out of the query.
$sql = "SELECT `rvid`, `rating` FROM `vendor_ratings_archive` WHERE `rating` <= '3' ORDER BY `rvid` DESC";
which give me this
Array
(
[0] => Array
(
[rvid] => 7
[rating] => 2
)
[1] => Array
(
[rvid] => 5
[rating] => 1
)
[2] => Array
(
[rvid] => 5
[rating] => 0
)
[3] => Array
(
[rvid] => 5
[rating] => 3
)
)
this is just just samples i tossed in the fields, and there are only 4 rows here where as in live it will be tons of rows. But basically this tells me that these are the vendors that have low ratings in the table. And that is where i get stumpted. I can only do one sort in the query so that is why i am thinking that i need to take this and move to the php side to finish it off.
I think i need to sort the elements by rvid with php first i think, and then see if three elements in a row are the same vender (rvid).
Hope that makes sense. My brain hurts lol...
update - here is all of the table data using *
Array
(
[0] => Array
(
[id] => 7
[rvid] => 7
[ratedate] => 2016-05-01
[rating] => 2
)
[1] => Array
(
[id] => 8
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 1
)
[2] => Array
(
[id] => 6
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 0
)
[3] => Array
(
[id] => 5
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 3
)
)
Here's one way you can begin approaching this - completely in SQL:
Get the last rating for the vendor. ORDER BY date DESC, limit 1.
Get the second to last rating for the vendor. ORDER BY date DESC, limit 1, OFFSET 1.
Then write a query that does a LEFT join of the first two tables. You will have a dataset that has three columns:
vendor id
last rating
second to last rating
Then you can write an expression that says "if column1 is <3 and column2 < 3, then this new column is true"
You should be able to extend this to three columns relatively easily.
Here is what a came up with to solve this riddle. I think explaining it on here helped as well as Alex also helped as he keyed my brain on using the date. I first started looking at using if statment inside of the query and actually that got my brain out of the box and then it hit me what to do.
It is not perfect and certainly could use some trimming to reduce the code, but i understand it and it seems to work, so that is par for me on this course.
the query...
$sql = "SELECT `rvid`, `ratedate`,`rating` FROM `vendor_ratings_archive` WHERE `rating` <= '3' ORDER BY `ratedate`, `rvid` DESC";
which gives me this
Array
(
[0] => Array
(
[rvid] => 7
[ratedate] => 2016-05-01
[rating] => 2
)
[1] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 1
)
[2] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 0
)
[3] => Array
(
[rvid] => 5
[ratedate] => 2016-05-01
[rating] => 3
)
)
notice how vendor (rvid) 5 is grouped together which is an added plus.
next a simple foreach to load a new array
foreach($results as $yield)
{
$rvidarray[] = $yield['rvid'];
}//close foreach
which gives me this
Array
(
[0] => 7
[1] => 5
[2] => 5
[3] => 5
)
then we count the array values to group dups
$rvidcounter = array_count_values($rvidarray);
which results in this
Array(
[7] => 1
[5] => 3
)
so now vender 7 as 1 low score and vendor 5 has 3 low scores and since they were already sorted by date i know that its consecutive. Well it sounds good anyway lol ")
then we create our final array with another foreach
foreach($rvidcounter as $key => $value)
{
//anything 3 or over is your watchlist
if($value > 2)
{
$watchlist[] = $key; //rvid number stored
}
}//close foreach
which gives me this
Array
(
[0] => 5
)
this was all done in a service function. So the final deal is everyone in this array has over 3 consecutive low ratings and then i just use a returned array back in my normal php process file and grab the name of each vender by id and pass that to the html and print out the list.
done...
please feel free to improve on this if you like. I may or may not use it because the above code makes sense to me. Something more complicated may not make sense to me 6 mos from now lol But it would be interesting to see what someone comes up with to shorten the process a bit.
Thanks so much and Happy Coding !!!!!
Dave :)
You could do it in SQL like that:
SET #rvid = -1;
SELECT DISTINCT rvid FROM
(
SELECT
rvid,
#neg := rating<3, /* 0 or 1 for addition in next line */
#count := IF(#rvid <> rvid , #neg, #count+#neg) AS `count`, /* set or add */
#rvid := rvid /* remember last row */
FROM
testdb.venrate
ORDER BY
rvid, datetime desc
) subq
WHERE count>=3
;
You set a variable to a non existing id. In each chronologically sorted row you check if rating is too low, that results in 1 (too low) or 0 (ok). If rvid is not equal to the last rvid, it means a new vender section is beginning. On begin of section set the value 0 or 1, else add this value. Finally store the current row's rvid for comparison in next row process.
The code above is looking for 3 consecutive low ratings (low means a value less than 3) over all the time.
A small modification checks if all the latest 3 ratings has been equal to or less than 3:
SET #rvid = -1;
SELECT DISTINCT
rvid
FROM
(
SELECT
rvid,
#high_found := rating>3 OR (#rvid = rvid AND #high_found) unflag,
#count := IF(#rvid <> rvid , 1, #count+1) AS `count`,
#rvid := rvid /* remember last row */
FROM
testdb.venrate
ORDER BY
rvid, datetime desc
) subq
WHERE count=3 AND NOT unflag
;
I am trying to execute this query in codeigniter 2.2. I read the documentation http://www.codeigniter.com/user_guide/database/results.html.
My controller code is this
$query = $this->db->query("SELECT a.id, a.child, a.immune, a.immun_date, b.id, b.fname, b.lname, c.id, c.name
FROM immun a, children b, immun_master c
WHERE a.child = b.id
AND c.id = a.immune
");
$immun = array();
foreach ($query->result()as $row) {
$immun[] = array(
$row->id,
$row->child,
$row->immune,
$row->immun_date,
);
}
The results that are turned is this:
array (
0 =>
array (
0 => '2',
1 => '1001',
2 => '2',
3 => '2011-04-23',
),
1 =>
array (
0 => '3',
1 => '1001',
2 => '3',
3 => '2011-04-30',
),
2 =>
array (
0 => '6',
1 => '1002',
2 => '6',
3 => '2011-04-30',
),
3 =>
array (
0 => '5',
1 => '1002',
2 => '5',
3 => '2011-04-29',
),
4 =>
array (
0 => '1',
1 => '1003',
2 => '1',
3 => '2011-01-06',
),
5 =>
array (
0 => '3',
1 => '1005',
2 => '3',
3 => '2010-10-04',
),
6 =>
array (
0 => '3',
1 => '1231',
2 => '3',
3 => '2014-08-01',
),
)
These are wrong results. I was expecting is the merged results of the query. Below is what I get when I run the query in phpmyadmin
id child immune immun_date id fname lname id name
1 1001 2 2011-04-23 1001 Johny Jame 2 Swine Flu Vaccine
2 1001 3 2011-04-30 1001 Johny Jame 3 Bird Flu Vaccine
3 1002 6 2011-04-30 1002 Chelsea James 6 Hepatitis B
4 1002 5 2011-04-29 1002 Chelsea James 5 Measles Vaccine
5 1003 1 2011-01-06 1003 Charles Jacob 1 H1N1 Vaccine
6 1005 3 2010-10-04 1005 Hansome Little 3 Bird Flu Vaccine
7 1231 3 2014-08-01 1231 Jennifer Ylanan 3 Bird Flu Vaccine
Now, it would be nice if I could get CI to return the same set of merged data. I can see that is it only returning the table query for immun and CI is not join data from the other table.s I read somewhere that CI was not build to handle complex queries? Is that true?
Any ideas how to get the data I need?
Thanks!
You could see the query CI ran in your database.
Put the following code on the controller that render the page where this query is used:
$this->output->enable_profiler(TRUE);
This way CI will output a profiler in the end of the page with lots of information, including the executed queries needed for rendering the page.
This should help.
Another hint, you must use alias in case you need to select columns with equal names from different tables. CI don't handle it well.
function immChild() {
$this->db->select('c.id, c.name, b.id, b.fname, b.lname, a.id, a.child, a.immune, a.immun_date');
$this->db->join('immun_master as c', 'c.id = a.immune','true');
$this->db->join('children as b', 'a.child = b.id', 'true');
$query = $this->db->get('immun as a')->result();
return $query;
}
This is the correct query for the cross join for codeigniter. In my original post, I did not have conditionals. I found it here
https://ellislab.com/codeIgniter/user-guide/database/active_record.html
in the section about join. I seen that there was a place for conditions of the join. Once I added the conditions. I got the correct result set returned.
Let's say I have one table: "cars" with 3 fields: id, brand, cost.
There's a second table: "models" with 3 fields: id, brand, model_name.
Each "cars" row can have multiple related "models" rows.
Is it possible to do an sql-select whose output looks like this?
edit: I use PHP for the database querys
array(
[0] => array(
[id] => 1
[brand] => mercedes
[cost] => 1000
[models] => array(
[0] => array(
[id] => 1
[brand] => mercedes
[model_name] => slk
)
[1] => array(
[id] => 2
[brand] => mercedes
[model_name] => clk
)
[2] => array(
[id] => 3
[brand] => mercedes
[model_name] => whatever
)
)
)
)
You need to add a foreign key relation to the models table, say car_id. Then:
SELECT * FROM cars JOIN models ON car_id = models.id;
This will output something similar to what you are looking for.
Assuming you are using PHP, using the output:
$query= "SELECT * FROM cars JOIN models ON car_id = models.id";
$r= #mysqli_query($dbc, $query);
while ($row= mysqli_fetch_array($r, MYSQLI_ASSOC)) {
$carstuff['id']=$row[id];
$carstuff['brand']=$row[brand];
$carstuff['cost']=$row[cost];
$carstuff[$row['models']][]=$row['model_name'];
}
var_dump($carstuff);
Note, that the id, brand and cost are repeatedly overwritten, but that is okay because they are overwritten with the same information. I'm not too sure about the cleanliness of the code, but that is the basic idea.
Try this:
Query:
SELECT c.ID, c.brand,c.cost, GROUP_CONCAT(model_name SEPARATOR '","') as models
, GROUP_CONCAT(m.ID SEPARATOR ',') as MID
, GROUP_CONCAT(m.brand SEPARATOR '","') as mbrand
FROM cars c
LEFT OUTER JOIN model m
ON m.brand = c.brand
GROUP BY brand;
Output:
ID BRAND COST MODELS MID MBRAND
1 audi 1000 m11","m22 4,5 audi","audi
1 mercedes 1200 m1","m2","m3 1,2,3 mercedes","mercedes","mercedes
Now in your php code you can process the MODEL,MID and MBrand
(By using explode)
$modelArray = explode(" ,", $row["MODELS");
SQLFIDDLE