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.
Related
I can't figure out how to get results from 2 tables, in 1 query result (can't simple JOIN)
I have these 2 tables in my MySQL database:
Table 1: sales
id
name
info
Table 2: users
sale_id
user_id
Now, every sale have different number of assigned users. Some sale have 2 users, some sale have 10 users.
In single row, I need to have columns from sale table, and all assigned users to it (connected with same Sale_id)
I need result, something like this:
enter image description here
Try this :
SELECT s.*,
(SELECT GROUP_CONCAT(u.user_id SEPARATOR ', ')
FROM users u
WHERE u.sale_id = s.id) AS users
FROM sales s
Some insight on your programming language would have been nice.
And yes, as suggested by wogsland and icoder, one typically use joins and loop through results to build en array. But the use of GROUP_CONCAT, as Yoleth pointed out, is what you need. I don’t know if it was the goal here, but it can reduce memory used in the result because there is no row repetition.
SELECT info FROM Sales AS s,
(
SELECT sale_id, GROUP_CONCAT(user_id) AS assigned_users
FROM Users
GROUP BY sale_id) AS u
WHERE s.id=u.sale_id;
In a single query, with a fancy JOIN:
SELECT s.info AS info, u.sale_id AS sale_id, GROUP_CONCAT(u.user_id) AS assigned_users
FROM Sales AS s LEFT JOIN Users AS u
ON s.id=u.sale_id
WHERE sale_id IS NOT NULL GROUP BY u.sale_id;
You can simply join two tables and get query result set like this:
saleID | saleName | userID | userName
1 | Oct Sale | 5 | Tim
1 | Oct Sale | 6 | Nik
2 | Nov Sale | 7 | Bill
Then you can walk each row and build associative array from that data:
$sales = array();
while( $row = mysqli_fetch_assoc($result)) {
if (!array_key_exists($row['saleID'], $sales)) {
$sales[$row['saleID']] = array(
'saleID' => $row['saleID'],
'saleName' => $row['saleName'],
'users' => array()
);
}
array_push($sales[$row['saleID']]['users'], array(
'userID' => $row['userID'],
'userName' => $row['userName']
));
}
Well, MySQL isn't going to return you a nice nested array like that. But you can create it by looping through the result. Assuming your MySQL connection is named $mysqli then try something like
$sales = array();
$result = $mysqli->query("SELECT sales.*, users.user_id FROM sales, users WHERE sales.id = users.sales_id");
while ($row = $result->fetch_assoc()) {
$sales[$row->id]['sales_id'] = $row->id;
$sales[$row->id]['name'] = $row->name;
$sales[$row->id]['info'] = $row->info;
$sales[$row->id]['assigned_users'][] = $row->user_id;
}
I have 2 tables - products and productimages.
product has unique id and title.
productimages has productid and imageurl. these are examples of my tables:
products:
|id|title |
_____________
|1 |Laptop |
|2 |Speakers |
productimages:
|productid|imageurl|
___________________
| 1 |lap1.png|
| 1 |lap2.png|
| 1 |lap3.png|
| 2 |spe1.png|
Right now I have a nested loop in PHP.
loop through all rows of -> select * from products
and for every product inside the loop -> select * from productimages where productid = id which is basically another loop inside the first loop.
and then I take all productimages into array and decode to JSON [title,photos].
Now imagine you have 2 million rows in productimages, the query times are too high, is there any way to make it more efficient?
$query = "SELECT * FROM products ORDER BY id LIMIT 10;
$result = mysqli_query($con,$query);
if(mysqli_num_rows($result)>0)
{
$response = array();
while($row = mysqli_fetch_assoc($result)) {
$photos = array();
$id = $row["id"];
$title = $row["title"];
$queryp = "select imageurl from productimages where productid= '".$id."';";
$resultp = mysqli_query($con,$queryp);
if(mysqli_num_rows($resultp)>0)
{
while($row2 = mysqli_fetch_assoc($resultp)) {
$photourl = $row2["imageurl"];
array_push($photos,$photourl);
}
}
}
}
Some betterment for you could be:
1) Don't use select *. Use column names instead. e.g. select products.id, products.title, productimages.imageurl
2) Use JOIN instead of nested loop
So, you can try querying data like:
select products.id, products.title, productimages.imageurl
from products
join productimages on products.id = productimages.productid
ORDER BY products.id LIMIT 10
This case is not uncommon - you have two tables in a one to many relationship.
You should never nest an SQL call in a loop if you can possibly avoid it but there is a decision to be made about one SQL call or two.
A single SQL call could be:
SELECT id, title, imageURL
FROM products LEFT JOIN productImages ON id=productid
The disadvantage of this is that you are extracting the title several times for each product and this is wasteful.
Using two SQL statements you can download the titles once for each product:
SELECT id, title FROM products
The results of this query can be stored in an associative array - so that you can look up the title for each id.
The second query is:
SELECT productid, imageURL FROM productImages ORDER BY productid, imageURL
You can loop through the results of this query, spitting out the title as you go.
To save the images with product you can add a column imageurl in the products table.collect the image names with , and insert that image name string to the products table.
your table looks like below.
+--------------+--------------+---------------------------+
| id | title | imageurl |
+--------------+--------------+---------------------------+
| 1 | Laptop | lap1.png,lap2.png,lap3.png|
+--------------+--------------+---------------------------+
| 2 | Speakers | spe1.png |
Hope you understood what i explain.
I have two tables that hold information about a drawing that I join in my query. The first table contains the drawings unique number, title and who it was drawn by. The second table contains the revision and the date the drawing was revised.
Table 1
|dwg_id|project_no|sws_dwg_no|dwg_title|dwg_by|
|1 |153 |153-100 |Pipe... |JS |
Table 2
|dwg_id|dwg_rev|dwg_date |rev_id|
|1 |A |2015-07-15 11:00:00 |1 |
|1 |B |2015-07-23 12:00:00 |2 |
|1 |C |2015-08-06 10:00:00 |3 |
I want join the two tables and only show the most recent revision change for a drawing.
This is my current query.
SELECT
`drawings`.`dwg_id`,
`project_no`,
`sws_dwg_no`,
`client_dwg_no`,
`dwg_title`,
`dwg_by`,
`dwg_rev`.`dwg_rev`,
`dwg_rev`.`dwg_date`,
MAX(`dwg_rev`.`dwg_rev`) AS dwg_rev
FROM
(`drawings`)
JOIN `dwg_rev` ON `drawings`.`dwg_id` = `dwg_rev`.`dwg_id`
WHERE
`project_no` = '153'
GROUP BY
`sws_dwg_no`,
`dwg_rev`.`dwg_rev`
ORDER BY
`dwg_rev`.`dwg_date` ASC,
`dwg_rev`.`dwg_rev` ASC
The results that this query returns doesn't contain the latest revision numbers or it returns all the revision for each drawing.
You can use the following query:
SELECT d.*, dr.*
FROM drawings AS d
INNER JOIN (
SELECT dwg_id, MAX(rev_id) AS maxRevId
FROM dwg_rev
GROUP BY dwg_id
) AS t ON d.dwg_id = t.dwg_id
INNER JOIN dwg_rev AS dr ON t.dwg_id = dr.dwg_id AND t.maxRevId = dr.rev_id
WHERE project_no = 153
The key point in the above query is the usage of a derived table that returns the latest revision, i.e. MAX(rev_id), per dwg_id. Using an INNER JOIN on that derived table you get back exactly this row out of dwg_rev table.
Using something like the above is necessary if you have multiple dwg_id per project_no. In this case, the above query will fetch the most recent revision per drawing for project_no = 153.
Demo here
Sometimes MAX isn't the best way to go, instead use LIMIT Try this:
SELECT
`drawings`.`dwg_id`,
`project_no`,
`sws_dwg_no`,
`client_dwg_no`,
`dwg_title`,
`dwg_by`,
`dwg_rev`.`dwg_rev`,
`dwg_rev`.`dwg_date`,
`dwg_rev`.`dwg_rev` AS dwg_rev
FROM
(`drawings`)
JOIN `dwg_rev` ON `drawings`.`dwg_id` = `dwg_rev`.`dwg_id`
WHERE
`project_no` = '153'
GROUP BY
`sws_dwg_no`,
`dwg_rev`.`dwg_rev`
ORDER BY
`dwg_rev`.`dwg_date` DESC,
`dwg_rev`.`dwg_rev` DESC
LIMIT 1;
If you need the latest revision you should order DESC check code below.
and also you can order only by dwg_rev.rev_id` DESC , if this rev_id is populated.
SELECT
drawings.dwg_id,
project_no,
sws_dwg_no,
client_dwg_no,
dwg_title,
dwg_by,
dwg_rev.dwg_rev,
dwg_rev.dwg_date,
MAX(dwg_rev.dwg_rev) AS dwg_rev
FROM
(drawings)
JOINdwg_revONdrawings.dwg_id=dwg_rev.dwg_id
WHERE
project_no= '153'
GROUP BY
sws_dwg_no,
dwg_rev.dwg_rev
ORDER BY
dwg_rev.dwg_dateDESC,
dwg_rev.dwg_revDESC
LIMIT 1;
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.
I want to know how to display report by month using PHP and Mysql
Example of Report on the web page:
January 2011
==============
Store Name | Total Order Cost
SHOP A | £123
SHOP B | £100
February 2011
==============
Store Name | Total Order Cost
SHOP A | £123
SHOP B | £100
SHOP C | £99.40
I have mysql tables
tbl_shop
ShopID
ShopName
tbl_order
OrderID
ShopID
OrderDate
Total
You will need to iterate trough the result of the query and create an multidimensional array using the month/year combination as keys. The query below should be a good indication on how to fetch the required information from your database.
SELECT
MONTH(to.OrderDate),
YEAR(to.OrderDate),
SUM(to.Total),
to.*
FROM tbl_order as to
INNER JOIN tbl_shop as ts ON ts.ShopID = to.ShopID
GROUP BY to.ShopID, MONTH(to.OrderDate), YEAR(to.OrderDate)
Note that I havn't tested this query - please handle it as pseudo-code. You might need to throw around the GROUP BY fields a little and test if it works.
Use
SELECT ShopName,SUM(Total),MONTHNAME(OrderDate) from tbl_shop
left join tbl_order
on tbl_shop.ShopID = tbl_order.ShopID
GROUP By(ShopID),MONTHNAME(OrderDate)
will give you All months report.
Use this query:
SELECT monthname(o.OrderDate) as Month, year(o.OrderDate) as Year,
s.ShopName, sum(o.Total) as Total
FROM tbl_Shop s JOIN tbl_Order o
ON s.ShopID=o.ShopID
GROUP BY o.shopID,year(o.Orderdate),month(o.Orderdate)
ORDER BY year(o.OrderDate),month(o.OrderDate)
then for every row from result if month+year has changed (a variable $last_date could help) then print
$Month $Year
==============
Store Name | Total Order Cost
$ShopName | $Total
else print
$ShopName | $Total