Count() on joins for pagination, getting additional rows - mysql

I have this problem that when i run this one.
$results = mysqli_query($connecDB,"SELECT COUNT(*) FROM customers
RIGHT JOIN orders on customers.serial=orders.serial
RIGHT JOIN order_detail on orders.serial=order_detail.orderid
LEFT JOIN inventory on order_detail.productid=inventory.prod_id");
$get_total_rows = mysqli_fetch_array($results); //total records
it shows additional pages even if it's blank. i've tried on other tables that doesnt require me to join and it works on them. But with this one, it does not work perfectly.
How do i fix my count function?
here is my original query before tweaking the pagination codes.
SELECT DISTINCT customers.order_status,customers.serial,customers.name,customers.address,customers.phone,customers.email,customers.payment,customers.carrier,customers.tracking_no, orders.date, order_detail.productid, order_detail.quantity, order_detail.price, inventory.prod_name
FROM customers
RIGHT JOIN orders on customers.serial=orders.serial
RIGHT JOIN order_detail on orders.serial=order_detail.orderid
LEFT JOIN inventory on order_detail.productid=inventory.prod_id
GROUP BY orders.date, order_detail.orderid
ORDER BY order_detail.orderid DESC";

To much to put in a comment:
There are three ways to get the total records for use with pagination.
Execute the query without the limit and get the number of records in the resultset. It maybe obvious that this isn't the way to go.
Execute the query with the limit and use SQL_CALC_FOUND_ROWS.
Replace the colums in the SELECT clause with COUNT(1) and fetch the (single) row.
In many situations the third is the best choice, however sometimes the second can out perform the third.
Finally, see this predicates
A product which isn't in inventory can't be ordered
An order can't be submitted by non-customers
I think inner joins are sufficient

Related

How to use left outer join in mysql to get my desirable results?

I have made an sql fiddle. Users with id 100 and 118 should have 0 records for assigned_scopes, assigned_qa, failed_qa and assigned_canvass. But it does not display that data; it just shows the record for the user with id 210. What I really need is to display all the users, with 0 in each column if they have nothing. How can I do this?
What I have tried since is in this fiddle. It works according to my requirements but there is problem with query optimization; its execution time is not what I want. It took more than 33 seconds to load the page (which is not good at all) when I used the second fiddle query because of the huge data. The query in the first fiddle executed in 2 seconds even with huge data. How can correct the first query to give the results of the second query while (hopefully) staying fast?
The "ten second explanation" for why most queries with LEFT JOIN do not display rows that you expect is that you've put one of the left joined tables into the where clause, demanding a value. This automatically converts any left join into an inner join behaviour
You either have to say WHERE leftjoinedtable.column =value OR leftjoinedtable.column is null or preferentially put the statement in the ON condition (doesn't need a qualifying 'or xxx is null which makes your query simpler and more readable
Move all the statements in your where clause out, and into the respective ON conditions so your query no longer has a where clause section at all. Throw your latter attempt away; it has a Cartesian join and sub selects in the main selection list; in a production system it could be generating millions more rows than needed, selecting extra data based on them, and then discarding them. A very difficult query style to optimise and usually unnecessary
Edit.. Had another look at your fiddle, noticed you also GROUP BY a column that could be null.. When it is, those rows disappear from the output
In terms of how you should write queries, consider first which tables will always have all the values. In your case these are the user and teams tables. These should be inner joined together first, then other tables which may not always have a matching row should be added LEFT join. Right join is seldom needed
Alter the order of your tables so it's teams, inner join users, then left join the two tables you're counting stars on. When you group by, group on the users Id from the users table, not the stats table
I did this with your fiddles but couldn't find a way to save them as a new fiddle on the iPad, couldn't copy the text, but here's a screenshot of a query that does what you require, I'll leave the typing as an exercise for the reader :) , don't forget to adjust the group by
Note, now the users table is inner joined, the =15 filter could go in the where clause... I'll leave the general advice though, to encourage and remind you that a) it works just as well in the ON clause and b) to prefer putting these things in the ON because it means left joins work as you expect
Read a definition of left join on. It returns rows that inner join on does plus unmatched rows in the left table extended by nulls.
Re your 1st link query: If you want all the records from user then it must be the leftmost table in your left joins. You must not remove rows that might have null values by tests requiring non-null values in where--but, why would you?--ie, why do you? The following returns rows only for users with role 15, per the 2nd link query; you don't explain why 15 tested for in the 1st.
select u.user_id,
ut.name as team_name,
/* ... */
count(case when a.status = 2 AND a.qc_id = 0 and o.class_id= 3 then 1 else null end)
AS assigned_canvass
from am_user u
left join user_team ut
on u.user_team_id = ut.user_team_id
left join am_ts_assignment a
on u.user_id = a.tech_id
left join am_ts_order o
on o.assignment_id = a.assignment_id
where u.user_role_id = 15
group by u.user_id
order by u.user_id asc
Compose your queries incrementally and test as you add joins & columns.
(This is the same query I gave on your last post re essentially your 1st link query except it returns a row for every am_user instead of every am_ts_assignment, per each post. The where might have been an and in the first left join with am_user; either will do here.)

something wrong with the result of mysql query with joins and select

Good day,
I am trying to join 3 tables for my inventory report but I am getting weird results out of it.
my query
SELECT i_inventory.xid,
count(x_transaction_details.xitem) AS occurrence,
i_inventory.xitem AS itemName,
SUM(i_items_group.or_qty) AS `openingQty`,
avg(x_transaction_details.cost) AS avg_cost,
SUM(x_transaction_details.qty) AS totalNumberSold,
SUM(i_items_group.or_qty) - SUM(x_transaction_details.qty) AS totalRemQty
FROM x_transaction_details
LEFT JOIN i_inventory ON x_transaction_details.xitem = i_inventory.xid
LEFT JOIN i_items_group ON i_inventory.xid = i_items_group.xitem
WHERE (x_transaction_details.date_at BETWEEN '2015-01-18 03:14:54' AND '2015-10-18 03:14:54')
AND i_inventory.xid = 3840
GROUP BY x_transaction_details.xitem
ORDER BY occurrence DESC
This query gives me this result:
See the openingQty column, I then tried to do a simple query to verify the result,
here's my query for checking the openingQty with joining only 2 tables i_items_group table (batches are stored) and i_inventory table (item Information are stored).
SELECT i_inventory.xid,
i_inventory.xitem,
SUM(i_items_group.or_qty) AS openingQty,
i_items_group.cost
FROM i_inventory
INNER JOIN i_items_group ON i_inventory.xid = i_items_group.xitem
WHERE i_inventory.xid = 3840
AND (i_items_group.date_at BETWEEN '2015-01-18 03:14:54' AND '2015-10-18 03:14:54')
my result was:
which is the correct data.
I also made a query on my x_transaction_details table also to verify if its correct or not.
heres my query:
select xitem, qty as qtySold from x_transaction_details where xitem = 3840
AND (date_at BETWEEN '2015-01-18 03:14:54' AND '2015-10-18 03:14:54')
result:
Which would total to: 15-quatitySold.
I'm just confused on how did I get 3269 as a result of my query where as the true openingQty should be only 467.
I guess the problem was in my query with joins, its messing up with number of transactions then it sums it up (I really dont know though).
Can you please help me identify it, and help me come up with the correct query.
This is a common problem with multiple SUM statements in a single query. Keep in mind how SQL does aggregation: first it generates a set of data that is not aggregated, then it aggregates it. Try your query without the GROUP BY or aggregate functions, and you'll be surprised what you turn up. There aren't enough of the right details in your post for me to determine where the breakdown is, but I can guess.
It looks like you have an xitem corresponding to some kind of product, then you have joined that to both transactions and items groups. Suppose a particular xitem matches with 3 transactions and 5 item groups. You'll get 15 records from that join. And when you sum it, any SUM calculations based on fields from the transaction table will be 5x higher than you expect, and any SUM calculations from the item groups table will be 3x higher than you expect. The key symptom here is the aggregate result being a multiple of the correct value, but seemingly different multiples for different rows.
There are multiple ways to address this kind of error. Some developers like to calculate one of hte aggregates in a subquery, then do the other aggregate in the main query and group by the already correct result from the subquery. Others like to write in-line queries to do the aggregate right in the expression:
SELECT xitem, (SELECT SUM(i_items_group.or_qty) FROM i_items_group WHERE i_inventory.xid = i_items_group.xitem) AS `openingQty`
, -- select more fields
Find what approach works best for you. But if you want to see the evidence for yourself, run this query with the aggregates gone and you'll see why those SUMs are doing what they are doing:
SELECT i_inventory.xid,
x_transaction_details.xitem AS occurrence,
i_inventory.xitem AS itemName,
i_items_group.or_qty,
x_transaction_details.cost,
x_transaction_details.qty,
i_items_group.or_qty - x_transaction_details.qty AS RemainingQty
FROM x_transaction_details
LEFT JOIN i_inventory ON x_transaction_details.xitem = i_inventory.xid
LEFT JOIN i_items_group ON i_inventory.xid = i_items_group.xitem
WHERE (x_transaction_details.date_at BETWEEN '2015-01-18 03:14:54' AND '2015-10-18 03:14:54')
AND i_inventory.xid = 3840
ORDER BY occurrence DESC

MySQL SELECT from two tables with COUNT

i have two tables as below:
Table 1 "customer" with fields "Cust_id", "first_name", "last_name" (10 customers)
Table 2 "cust_order" with fields "order_id", "cust_id", (26 orders)
I need to display "Cust_id" "first_name" "last_name" "order_id"
to where i need count of order_id group by cust_id like list total number of orders placed by each customer.
I am running below query, however, it is counting all the 26 orders and applying that 26 orders to each of the customer.
SELECT COUNT(order_id), cus.cust_id, cus.first_name, cus.last_name
FROM cust_order, customer cus
GROUP BY cust_id;
Could you please suggest/advice what is wrong in the query?
You issue here is that you have told the database how these two tables are 'connected', or what they should be connected by:
Have a look at this image:
~IMAGE SOURCE
This effectively allows you to 'join' two tables together, and use a query between them.
so you might want to use something like:
SELECT COUNT(B.order_id), A.cust_id, A.first_name, A.last_name
FROM customer A
LEFT JOIN cust_order B //this is using a left join, but an inner may be appropriate also
ON (A.cust_id= B.Cust_id) //what links them together
GROUP BY A.cust_id; // the group by clause
As per your comment requesting some further info:
Left Join (right joins are almost identical, only the other way around):
The SQL LEFT JOIN returns all rows from the left table, even if there are no matches in the right table. This means that if the ON clause matches 0 (zero) records in right table, the join will still return a row in the result, but with NULL in each column from right table. ~Tutorials Point.
This means that a left join returns all the values from the left table, plus matched values from the right table or NULL in case of no matching join predicate.
LEFT joins will be used in the cases where you wish to retrieve all the data from the table in the left hand side, and only data from the right that match.
Execution Time
While the accepted answer in this case may work well in small datasets, it may however become 'heavy' in larger databases. This is because it was not actually designed for this type of operation.
This was the purpose of Joins to be introduced.
Much work in database-systems has aimed at efficient implementation of joins, because relational systems commonly call for joins, yet face difficulties in optimising their efficient execution. The problem arises because inner joins operate both commutatively and associatively. ~Wikipedia
In practice, this means that the user merely supplies the list of tables for joining and the join conditions to use, and the database system has the task of determining the most efficient way to perform the operation. A query optimizer determines how to execute a query containing joins. So, by allowing the dbms to choose the way your data is queried, you can save a lot of time.
Other Joins/Summary
AN INNER JOIN will return data from both tables where the keys in each table match
A LEFT JOIN or RIGHT JOIN will return all the rows from one table and matching data from the other table.
Use a join when you want to query multiple tables.
Joins are much faster than other ways of querying >=2 tables (speed can be seen much better on larger datasets).
You could try this one:
SELECT COUNT(cus_order.order_id), cus.cust_id, cus.first_name, cus.last_name
FROM cust_order cus_order, customer cus
WHERE cus_order.cust_id = cus.cust_id
GROUP BY cust_id;
Maybe an left join will help you
SELECT COUNT(order_id), cus.cust_id, cus.first_name, cus.last_name ]
FROM customer cus
LEFT JOIN cust_order co
ON (co.cust_id= cus.Cust_id )
GROUP BY cus.cust_id;

How do I sum [add] multiple columns from joined tables

I need to write a query that joins several tables and totals several columns in different tables & can't seem to figure out how to do it:
here are the tables:
The result I am trying to get is a result with the contract budgets for a particular budget name added: i.e. [this does not work but gives an idea]
select c.contract_budget_f1, c.contract_budget_f2, cb.budget_type_id, c.id, sum(cb.budget_f1) as bf1, sum(cb.budget_f2) as bf2
from `flow_contract` c
left join `flow_contract_budget` cb on cb.contract_id = c.id
where c.program_id = '69'
group by cb.budget_type_id
with the whole result set looking like:
[budget_type_id]
[contract_budget_f1]
[contract_budget_f2]
[bf1]
[bf2]
where it will return 3 rows with the budgets for each budget type added
How can I do this, is it even possible?
Here are links to the tables - sorry, didn't realize you couldn't click on them...
http://media.bigblockstudios.ca/stack/program-name.gif
http://media.bigblockstudios.ca/stack/contract-budget.gif
http://media.bigblockstudios.ca/stack/contracts.gif
UPDATE
I got it working like this:
select c.id, c.program_id, c.contract_budget_f1, c.contract_budget_f2, cb.budget_f1, cb.budget_f2, cb.budget_type_id, c.id,
sum(cb.budget_f1) as bf1, sum(cb.budget_f2) as bf2
from `flow_contract` c
left join `flow_contract_budget` cb on cb.contract_id = c.id
where c.program_id = '".$formfields['program_id']."'
group by cb.budget_type_id
order by cb.budget_type_id
I think what you want is:
group by cb.budget_type_id, cb.contract_id
Or the other way around. Could you make an sql fiddle?
Short Answer: subquery
Try this:
Start with a subquery.
Write a query that produces the column values that you want to sum. This is now the subquery.
Write an outter query that sums the desired columns in the inner query.
After you get it working, review it to see if you can optimize out the subquery and just have one query.

Mysql Query not working

I have three tables that are structured like this:
http://i41.tinypic.com/2bt9aq.png
What I am trying to do is retrieve the joke id, title, and average rating of all jokes in a certain category and order them alphabetically. I have this query:
$result = mysql_query("
SELECT jokedata.id AS joke_id,
jokedata.joketitle AS joke_title,
SUM(ratings.rating) / COUNT(ratings.rating) AS average
FROM jokedata
INNER JOIN ratings ON ratings.content_type = 'joke' AND ratings.relative_id = jokedata.id
WHERE jokecategory = '$cur_category'
GROUP BY jokedata.id
ORDER BY jokedata.joketitle
LIMIT $offset, $jokes_per_page
");
However it is not selecting any jokes.
What is wrong with that query? Thankyou.
First, you probably want to use AVG() instead of SUM()/COUNT().
Your problem is the inner join - if no ratings where submitted for a joke then that joke would not be returned as only jokes with a rating value match the inner join.
I would recommend using a left join instead, or even a sub-select. While I normally prefer JOINs as they are usually faster, I would have tried something like this:
SELECT id AS joke_id,
joketitle AS joke_title,
(
SELECT AVG(rating) AS avgrating FROM ratings
WHERE content_type = 'joke' AND relative_id = joke_id
GROUP BY relative_id
) AS average
FROM jokedata
WHERE jokecategory = '$cur_category'
GROUP BY id
ORDER BY joketitle
LIMIT $offset, $jokes_per_page
An inner join will not return a row if one of the joins cannot be fulfilled. So odds are good that the criteria ratings.relative_id = jokedata.id is causing the query to return 0 jokes. Try replacing the INNER JOIN with LEFT JOIN and you'll see how many of the jokedata's rows don't have matching id's in ratings.relative_id.
I would first narrow down the problem by taking out everything except the join and seeing what the result is:
SELECT *
FROM jokedata
INNER JOIN ratings ON ratings.content_type = 'joke' AND ratings.relative_id = jokedata.id
If this gives you results, I would then add back in the WHERE criteria. Do this step by step, and whenever you get a query where you have no rows returned, then look at the previous set of results, and think about what is happening when you add the additional criteria. Being able to see this "intermediate" set of results will help you identify and understand the problem.
Continue adding back to the query piece-wise until you start getting no results, at least then you've narrowed down to what aspect of the query is causing the problem.