Query is not showing up 0 values mysql - php

In my query I am listing all of the theater ticket sales and movie ticket sales of different customers. The issue I'm running into is that all of the '0' ticket sales, so those users who haven't boughten a theater ticket or movie ticket is not showing up.
Here's a picture for a visual aspect: table
I believe I need to be doing a union to return the users who haven't boughten any tickets. I just can't seem to figure this out.
Thanks in advance.
Here's my code so far:
select customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
count(ticketdetails.eventtype) as 'Theater Tickets',
0 as 'Movie Tickets'
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType ='T'
Group by Customer.hippcode
union
select customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
0 as 'Theater Tickets', count(ticketdetails.eventtype) as 'Movie Tickets'
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType ='M'
Group by Customer.hippcode
order by `theater tickets` + `movie tickets` desc;
select
customer.hippcode, customer.LastName, customer.Firstname, customer.Email,
sum(case when ticketdetails.eventtype = 'T' then 1 else 0 end) as TheaterTickets,
sum(case when ticketdetails.eventtype = 'M' then 1 else 0 end) as MovieTickets
from customer
inner join ticketdetails on ticketdetails.hippcode = customer.hippcode
where ticketdetails.hippcode is not null
and ticketdetails.eventType in ('T', 'M')
Group by customer.hippcode, customer.LastName, customer.Firstname, customer.Email
Order by 'TheaterTickets' + 'MovieTickets' desc

inner join => bring the line only if you got a record on both table.
I think you should use a LEFT JOIN somewhere to chose the master table
http://dev.mysql.com/doc/refman/5.7/en/join.html and
http://dev.mysql.com/doc/refman/5.7/en/left-join-optimization.html

I think the last query is the only one you want. A left join is appropriate, but you need to be careful about the where clause:
select c.hippcode, c.LastName, c.Firstname, c.Email,
sum(td.eventtype) as TheaterTickets,
sum(td.eventtype) as MovieTickets
from customer c left join
ticketdetails td
on td.hippcode = c.hippcode and
td.eventType in ('T', 'M')
Group by c.hippcode, c.LastName, c.Firstname, c.Email
Order by count(t.hippcode) desc;
Notes:
Table aliases make the query easier to read and write.
Conditions on ticketdetails go in the on clause, not the where clause.
The condition on td.hippcode is not null is unnecessary, because NULL will not match in the join (note: you might want to check for the customer column).
case is the standard way to do the conditional sum (and hence correct). However, MySQL offers a much simpler and intuitive syntax.
Your order by was doing nothing, because it was adding two strings (hence equivalent to order by 0. Don't use single quotes ever for column names, and you won't have a problem like that.

Related

Left Join - Select ALL from left table but select only the latest from right table

I have 2 tables, borrowers and loans. I want to display on the main page the list of ALL borrowers with or without loans. If with loan, display the newest one.
I have the following sql query, basically it returns the above description except it displays the very first loan of the borrower instead of the latest one.
(Side note: I used GROUP BY to avoid duplicates. Without it the query returns duplicated borrower names if they have multiple loans. Just wanted to know if this is an efficient way of doing so.)
SELECT b.b_id,
b.isdeleted,
b.picture,
b.firstname,
b.middlename,
b.lastname,
b.address,
b.contactno,
b.birthday,
b.businessname,
b.occupation,
b.comaker,
b.comakerno,
b.remarks,
b.datecreated,
b.activeloan,
l.l_id,
l.amount,
l.payable,
l.balance,
l.mode,
l.term,
l.interestrate,
l.amortization,
l.releasedate,
l.duedate,
l.status,
l.c_id
FROM borrowers as b
LEFT JOIN loans as l ON b.b_id = l.b_id
WHERE b.isdeleted = 0
GROUP BY b.b_id
It seems the below query does exactly what i wanted.
I added the below subquery on the "ON" clause.
(SELECT MAX(l_id)
FROM jai_db.loans as l2
WHERE l2.b_id = b.b_id LIMIT 1)
SELECT b.b_id, b.isdeleted, b.picture, b.firstname, b.middlename, b.lastname, b.address, b.contactno,
b.birthday, b.businessname, b.occupation, b.comaker, b.comakerno, b.remarks, b.datecreated, b.activeloan,
l.l_id, l.amount, l.payable, l.balance, l.mode, l.term, l.interestrate, l.amortization,
l.releasedate, l.duedate, l.status, l.c_id
FROM jai_db.borrowers as b
LEFT JOIN jai_db.loans as l
ON l.l_id = (SELECT MAX(l_id)
FROM jai_db.loans as l2
WHERE l2.b_id = b.b_id LIMIT 1)
WHERE b.isdeleted = 0

Ordering search of joined tables in 2 ways

I am trying to search for the oldest outstanding invoice for each customer and then present the results in descending order of that invoice date however the result gives me the date of the most recent invoice. How can I ensure that it picks up the earliest invoice date? Many thanks in advance.
Here is my query:
$checkDebtors = "select
a.accountNumber as accountNumber, a.balanceOutstanding as balanceOutstanding, a.companyName as companyName, b.invoiceDate as invoiceDate, b.netOutstanding as netOutstanding
from
customersQQuote a
Right JOIN invoices b ON a.accountNumber = b.accountNumber
WHERE netOutstanding > 0 AND balanceOutstanding > 0
group by
a.accountNumber
order by
b.invoiceDate ASC";
Did you mean the result by following sql? Try it, if it does not work for you, leave a comment for me;)
select
a.accountNumber as accountNumber,
a.balanceOutstanding as balanceOutstanding,
a.companyName as companyName,
b.invoiceDate as invoiceDate,
c.netOutstanding as netOutstanding
from
(SELECT accountNumber, MIN(invoiceDate) AS invoiceDate FROM invoices GROUP BY accountNumber) b
LEFT JOIN customersQQuote a ON a.accountNumber = b.accountNumber
LEFT JOIN invoices c ON c.accountNumber = b.accountNumber AND c.invoiceDate = b.invoiceDate
WHERE c.netOutstanding > 0 AND a.balanceOutstanding > 0
group by
a.accountNumber
order by
b.invoiceDate ASC
Above code has certain mistakes such as, alias name using in where clause and misuse of group by clause. you can apply this piece of code..
select a.accountNumber as accountNumber, a.balanceOutstanding as balanceOutstanding,
a.companyName as companyName, b.invoiceDate as invoiceDate, b.netOutstanding as netOutstanding
from customersQQuote a Right JOIN invoices b ON a.accountNumber = b.accountNumber
WHERE b.netOutstanding > 0 AND a.balanceOutstanding > 0 order by b.invoiceDate;
Your current code does not work as you have not specified which invoice to pull back. You have used GROUP BY accountNumber, so it will return one row for each accountNumber value. Other fields should be aggregate values (ie, the result of a aggregate function such as MIN() or MAX()). If fields are neither mentioned in the GROUP BY clause nor the result of an aggregate function then MySQL will return a value for that column, but which row that value comes from is not defined. It could be the first or the last rows value, or any in between (and could change between different versions of MySQL).
Most flavours of SQL do not allow a query like this and would error. MySQL has a feature that does allow this. But this feature can be configured to be turned off.
To do this in standard SQL and give you a consistent value you use a sub query to get the first invoiceDate for each accountNumber, using the MIN() aggregate function. Then you join this sub query against the invoices table to get the other columns.
SELECT a.accountNumber,
a.balanceOutstanding,
a.companyName,
b.invoiceDate,
b.netOutstanding
FROM
(
SELECT accountNumber,
MIN(invoiceDate) as invoiceDate
FROM invoices
GROUP BY accountNumber
) sub0
INNER JOIN invoices b ON sub0.account_number = b.account_number AND sub0.invoiceDate = b.invoiceDate
INNER JOIN customersQQuote a ON sub0.account_number = a.account_number
Note that if an accountNumber could have 2 identical first invoiceDate values then you will need to pick another field to minimise (ie, maybe your unique id field) for each date. Gets messy but something like this:-
SELECT a.accountNumber,
a.balanceOutstanding,
a.companyName,
b.invoiceDate,
b.netOutstanding
FROM
(
SELECT accountNumber,
MIN(invoiceDate) as invoiceDate
FROM invoices
GROUP BY accountNumber
) sub0
INNER JOIN
(
SELECT accountNumber,
invoiceDate,
MIN(id) as id
FROM invoices
GROUP BY accountNumber, invoiceDate
) sub1 ON sub0.accountNumber = sub1.accountNumber AND sub0.invoiceDate = sub1.invoiceDate
INNER JOIN invoices b ON sub1.account_number = b.account_number AND sub1.invoiceDate = b.invoiceDate AND sub1.id = b.id
INNER JOIN customersQQuote a ON sub0.account_number = a.account_number
Thank you for your advice. I got some good tips from your answers which helped me solve the problem. The main thing being the use of MIN() and also that I was using Right join rather than Left. Additionally I just used the invoiceDate keyword in the ORDER BY rather than the b.invoiceDate. I guess this ensured the MIN rule was used here as well. Thanks to you I ended up getting the result I needed.
select
a.accountNumber as accountNumber, a.balanceOutstanding as balanceOutstanding, a.companyName as companyName, MIN(b.invoiceDate) as invoiceDate, b.netOutstanding as netOutstanding
from
customersQQuote a
Left JOIN invoices b ON a.accountNumber = b.accountNumber
WHERE netOutstanding > 0 AND balanceOutstanding > 0
group by
a.accountNumber
order by invoiceDate ASC

Optimizing my mysql query

I clearly know that there are plenty of questions already asked on this topic but i couldn't optimize mine.
So below is my query:
select tc.id, tc.name tc, s.name state, d.name district,
count(distinct case when curdate() between b.starttime and b.endtime then b.id end) as active,
(case when count(distinct case when curdate() between b.starttime and b.endtime then b.id end) > 0 then 'active' when tc.status = 'Archived-I' then 'transitioned' when count(distinct case when curdate() between b.starttime and b.endtime then b.id end) = 0 and tc.status != 'Archived-I' then 'Idle' end ) as _status,
count(distinct(b.id)) as batches, sum(case when sb.status = 'active' then 1 else 0 end) as in_training, count(distinct case when sb.status = 'complete' then sb.student_id end) as trained,
count(distinct(sa.student_id)) as assessed, count(distinct(sp.student_id)) as placed
from training_centers tc left join batches b on b.training_center_id = tc.id
left join student_batches sb on b.id = sb.batch_id
left join student_assessments sa on sa.batch_id = b.id
left join student_placements sp on sp.batch_id = b.id
left join states s on s.id = tc.state_id
left join districts d on d.id = tc.district_id
where tc.implementing_agency_id = 28
group by tc.id
order by tc.name
Output of EXPLAIN is below:
id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra
1|SIMPLE|tc|ref|implementing agency|implementing agency|4|const|201|Using temporary; Using filesort
1|SIMPLE|b|ALL|NULL|NULL|NULL|NULL|11018|
1|SIMPLE|sb|ref|batch id|batch id|4|ministry_mis.b.id|10|
1|SIMPLE|sa|ref|batch|batch|4|ministry_mis.b.id|7|
1|SIMPLE|sp|ALL|NULL|NULL|NULL|NULL|78799|
1|SIMPLE|s|eq_ref|PRIMARY|PRIMARY|4|ministry_mis.tc.state_id|1|
1|SIMPLE|d|eq_ref|PRIMARY|PRIMARY|4|ministry_mis.tc.district_id|1|1|
I have already tried query caching and since i am using this query in a php application, i have tried memcache as well.
Please help me understand, how can i optimize my query or if something is wrong here.
Add an appropriate index on the students_placements table (aliased as sp in the query).
CREATE INDEX student_placements_IX1
ON student_placements (batch_id, student_id)
The EXPLAIN output is showing a full scan of that table, ("ALL"). With that index, we'd expect the EXPLAIN output to show a ref operation.
Also, on the batches table (aliased as b in the query) add an index like:
CREATE INDEX batches_IX1
ON batches (training_center_id, id, starttime, endtime)
If all of the columns referenced in the query for a table come from the index, the index is called a "covering index" for the query, and the the EXPLAIN output will show "Using index" in the Extra column.
If id is a UNIQUE or PRIMARY KEY on the training_centers table (aliased as tc in the questy), you might also consider changing the query to do this:
GROUP BY tc.name, tc.id
ORDER BY tc.name
A covering index on training_centers, may also be of benefit:
CREATE INDEX training_centers_IX1
ON training_centers (implementing_agency_id, name, id, state_id, district_id, status)
We'd need to look at the EXPLAIN after the indexes are added, and go from there. (Sometimes, MySQL can make use of an index to avoid a "Using filesort" operation, if an appropriate index is available.)

MySQL query (28K rows) overloads the server

We are running OpenCart store and now we have to export order data for 6 months (around 17K orders).
Opencart has a builtin solution, when you select the orders you need and export, but it works fine with less than 500 orders.
I've decided to make a standalone script based on the query it uses for its original export.
I'm not publishing the export part, since my dedicated server and the store freezes when I run the following query and consequent loop for mysql_fetch_assoc.
$query = "SELECT o.*, op.name, op.model,op.order_product_id, op.product_id, op.quantity, op.price, op.total AS ptotal, op.tax,
(SELECT ot.value FROM order_total ot WHERE ot.order_id = o.order_id AND ot.code = 'sub_total') AS sub_total,
(SELECT ot.value FROM order_total ot WHERE ot.order_id = o.order_id AND ot.code = 'credit') AS store_credit,
(SELECT `name` FROM order_history oh INNER JOIN `order_status` os on oh.order_status_id=os.order_status_id WHERE oh.order_id = o.order_id and os.language_id='2'
ORDER BY order_history_id DESC LIMIT 1) as order_status
FROM `order` o inner join `order_product` op on o.order_id=op.order_id WHERE o.date_added BETWEEN '2014-01-01 00:00:00' AND '2014-06-30 03:59:59'";
$sql = mysql_query($query);
do {
echo $o['model']."<br/>"; // here we will have a part using PEAR Excel basically
} while ($o=mysql_fetch_assoc($sql));
$endtime = microtime();
echo $endtime-$starttime;
Indexes are set. Additional index was set on oh.order_id. No luck. The same request for 1 particular order id runs in 0.003ms.
My process list shows "Sending data" for the above shown query and "Waiting for table level lock" for anothers.
Could you kindly assist?
I think you could optimize your SQL a bit to prevent locks. The following might help, but recognize that I don't have your schema or data to test this on, so you may need to tweak.
SELECT o.*, op.name, op.model,op.order_product_id, op.product_id,
op.quantity, op.price, op.total AS ptotal, op.tax,
ot.sub_total, ot.store_credit, os.os_name
FROM `order` o inner join `order_product` op on o.order_id=op.order_id
INNER JOIN (SELECT t.order_id,
MAX(case when t.code = 'sub_total' t.value else 0 end case) as sub_total,
MAX(case when t.code = 'credit' t.value else 0 end case) as store_credit
FROM order_total t GROUP BY t.order_id) ot ON ot.order_id = o.order_id
INNER JOIN (SELECT * FROM (SELECT oh.order_id, `name` AS os_name
FROM order_history oh
INNER JOIN `order_status` s on oh.order_status_id=s.order_status_id
WHERE s.language_id='2' ORDER BY order_history_id DESC) t1
GROUP BY t1.order_id) as os ON os.order_id = o.order_id
WHERE o.date_added BETWEEN '2014-01-01 00:00:00' AND '2014-06-30 03:59:59'
I would also probably do a create temp table temp_order_history from select ... and then select * from temp_order_history Also, you may need to do outer join instead of inner join if either of those sub selects have missing data. In those cases, you'd just get NULLs for those columns.

MySQL Where to put in this statement?

This is a bit beyond my skills and I had a lot of help from the good people at SO to get this far. What I need now is to put in a MATCH() ... AGAINST() but I don't know where to insert it?
The query I have is (and this is the short version):
SELECT
SQL_CALC_FOUND_ROWS i.idItems RowCount,
i.* Items,
# Create a JSON formatted field
CONCAT('{',GROUP_CONCAT('"',Attributes.key, '":"', CONVERT(Attributes.value,CHAR),'"'),'}') as Attributes,
IF (te.Key IS NULL,tp.Key,te.Key) as Type,
tc.Value Color,
l.* Location,
c.Name,
c.Mobile,
c.Mail
FROM
(SELECT ItemID, ats.Key, ats.Value FROM attributeStrings as ats
UNION ALL
SELECT ItemID, ati.Key, ati.Value FROM attributeIntegers as ati
) Attributes
JOIN Items i ON
i.idItems = Attributes.ItemID
AND CheckIn >= DATE_SUB('2011-02-16 00:00:00',INTERVAL 90 DAY)
AND CheckIn <= DATE_ADD('2011-02-16 23:59:59',INTERVAL 90 DAY)
AND Checkout IS NULL
LEFT JOIN Customers c ON c.idCustomers = i.CustomerID
LEFT JOIN attributeintegers atli ON atli.itemid = i.idItems AND atli.key = 'Location'
LEFT JOIN locations l ON l.id = atli.value
LEFT JOIN attributestrings atts ON atts.itemid = i.idItems AND atts.key = 'Type' LEFT
JOIN Lists tp ON tp.value = atts.value
LEFT JOIN attributestrings attes ON attes.itemid = i.idItems AND attes.key = 'Tech' LEFT
JOIN Lists te ON te.value = attes.value
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT
JOIN Strings tc ON tc.StringID = atci.value
GROUP BY Attributes.ItemID
ORDER BY CheckIn DESC
Now I need to get this statement in here somewhere
MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
As you can see there is a table called attributestrings and it has 3 columns: ItemID,*Key* and Value. I need to search the column Value for the words in the AGAINST() and only show results matching this and the other criterias such as the Date and Checkout above.
I tried to add the statement after the AND Checkout IS NULL like this:
AND Checkout IS NULL
AND MATCH(Attributes.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I had to use the Attributes.Value instead of attributestrings because it didn't found the table. This only resulted in the CONCATENATED column Attributes only contained the value "Nokia", even if there where more to CONCATENATE.
I hope someone are willing to take on this challenge...
// Tank you.
[EDIT]
I tried to put in the WHERE before the GROUP as Tim Fultz sugested, but I get the error
Unknown column 'attributestrings.Value' in 'Where clause'
LEFT JOIN attributeintegers atci ON atci.itemid = i.idItems AND atci.key = 'Color' LEFT JOIN Strings tc ON tc.StringID = atci.value
WHERE MATCH(attributestrings.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
GROUP BY Attributes.ItemID
Typically this is put in the Where clause:
WHERE MATCH(attributestrings.Value) AGAINST("Nokia" IN BOOLEAN MODE)
I think I came up with a solution...
I added another:
LEFT JOIN attributestrings fts ON fts.itemid = i.idItems AND MATCH(fts.Value) AGAINST("Nokia Sony" IN BOOLEAN MODE)
Just before the GROUP BY... and then included fts.Value ftsv in the main select statement. Now I could insert a HAVING ftsv IS NOT NULL between GROUP BY... and ORDER BY...
This gave me the result I wanted but the query starts to get a bit slow now...
There is a problem in your query when you are assigning table names with as use every where in you query the name you assigned. Like you gave attributestrings as ats now use ats everywhere and this will work.

Categories