I have a query that selects order info between a selected time period. I want to include a where clause that limits the order info to all orders that have only 1 order total(through out all time).
Here is what I have so far:
SELECT o.orders_id, o.customers_id, o.customers_name, o.payment_method, o.date_purchased,o.orders_status, o.shipping_status, ot.value
FROM orders as o
LEFT JOIN orders_total as ot ON o.orders_id = ot.orders_id
WHERE date_purchased between '2011-07-30' AND '2011-08-30 23:59:59'
AND ot.class = 'ot_total'
AND o.customer_service_id = ''
OR o.customer_service_id IS NULL
ORDER BY orders_id DESC
This query gives me all orders in the specified time period. I need to include a subquery(or something similar) that counts all previous(through out all time) orders(order_count) BY customers_id. Then include a 'HAVING order_count < 2' in the where clause.
Is this possible? Does this make sense?
Just add this in you where close:
AND (
SELECT COUNT(o.id)
FROM orders o2
WHERE o2.customers_id = o.customers_id
) < 2
Or if you want to return the orders count, add it in your SELECT clause, and add a HAVING clause:
SELECT o.orders_id, ..., (
SELECT COUNT(o.id)
FROM orders o2
WHERE o2.customers_id = o.customers_id
) as orders_count
...
HAVING orders_count < 2
Related
I have a SQL query which is for showing data list with paging and with sort order. The sort order have an "IN" query in tree to show the data more accurate.
Here is the code :
SELECT
s.ID id_sent,
SendingDateTime,
DestinationNumber,
TextDecoded,
UDH,
id_folder,
Status,
p.ID id_pbk,
Name,
Number
FROM
sentitems s LEFT JOIN pbk p
ON p.Number = s.DestinationNumber
WHERE
`id_folder` = 3
AND `Status` IN ('SendingOK', 'SendingOKNoReport')
AND SendingDateTime IN (
SELECT
MAX(SendingDateTime)
FROM sentitems
WHERE
id_folder = '3'
AND `Status` IN ('SendingOK', 'SendingOKNoReport')
GROUP BY DestinationNumber
)
ORDER BY `SendingDateTime` DESC LIMIT $posisi,$batas
My problem is, when i have a lot of datas in sentitems table; ex 3000 datas, although i LIMIT for 20 datas to show. The page is really need a long time to load and then get a timeout.
My question is, is it possible to improve my query??
or is it any other way to show my datas result exactly the as same as with my query?
thanks in advance
My guess is the slowdown is the IN statement where you are looking through the list of MAX(SendingDateTime). Instead you could try parsing down that list with another WHERE clause. So it would be something like:
SELECT
s.ID id_sent,
SendingDateTime,
DestinationNumber,
TextDecoded,
UDH,
id_folder,
Status,
p.ID id_pbk,
Name,
Number
FROM
sentitems s LEFT JOIN pbk p
ON p.Number = s.DestinationNumber
WHERE
`id_folder` = 3
AND `Status` IN ('SendingOK', 'SendingOKNoReport')
AND SendingDateTime = (
SELECT
MAX(SendingDateTime)
FROM sentitems si
WHERE
si.id_folder = '3'
AND s.Destination = si.Destination
AND si.`Status` IN ('SendingOK', 'SendingOKNoReport')
GROUP BY DestinationNumber
)
ORDER BY `SendingDateTime` DESC LIMIT $posisi,$batas
this may work for you
SELECT s.ID id_sent, SendingDateTime, DestinationNumber, TextDecoded, UDH, id_folder, Status, p.ID id_pbk, Name, Number
FROM sentitems s
LEFT JOIN pbk p ON p.Number = s.DestinationNumber
inner join
(
SELECT MAX(SendingDateTime) as maxSendingDateTime
FROM sentitems
WHERE id_folder = '3'
AND `Status`
IN (
'SendingOK', 'SendingOKNoReport'
)
GROUP BY DestinationNumber
) c as c.maxSendingDateTime = maxSendingDateTime
WHERE `id_folder` = 3
AND `Status`
IN (
'SendingOK', 'SendingOKNoReport'
)
ORDER BY `SendingDateTime` DESC
LIMIT $posisi,$batas;
I am trying to do a MYSQL query where I get the most recent purchase for a user and then see if falls within certain criteria. Here is the query I put together:
select
users_purch.purch_date as purchase_date,
users_purch.total_amount as purchase_amount,
users.*
from
users
left join
(
select
max(date) as purch_date,
user_id,
total_amount
from
users_purchases
group by
user_id
) as users_purch
on users_purch.user_id = users.id
where
users_purch.purch_date < '2016-11-01'
and users_purch.total_cost < 112.49
order by
users_purch.purch_date desc
It seems that the query works but fails in certain aspects. For example, if a user has more than one purchase entry it is getting the max date but the amount as total_cost that the query retrieves is not from the same row as the max date. How can I rewrite this query to give me the most recent purchase record in its entirety?
Thanks!
You have to join once more to user_purchases table in order to get the information about the date:
select
users_purch.purch_date as purchase_date,
users_purch.total_amount as purchase_amount,
users.*
from
users
left join
(
select
max(date) as purch_date,
user_id
from
users_purchases
group by
user_id
) as users_purch
on users_purch.user_id = users.id
left join
(
select
user_id,
date,
total_amount
from
users_purchases
) as users_purch2 on users_purch.user_id = users_purch2.user_id and
users_purch2.date = users_purch.purch_date
where
users_purch.purch_date < '2016-11-01'
and users_purch.total_cost < 112.49
order by
users_purch.purch_date desc
due the group by you have in this select
if you need the amount of the max(date) rewrite extecting the proper amount
select
users_purch.purch_date as purchase_date,
users_purch.total_amount as purchase_amount,
users.*
from
users
left join
(
select t1.purch_date, t1.user_id, t2.total_amount from (
select
max(date) as purch_date,
user_id
from users_purchases
group by user_id ) t1
inner join (
date,
user_id,
total_amount
from users_purchases
) t2 on t1.user_id= t2.user_id, t1.purch_date = t2.date
) as users_purch
on users_purch.user_id = users.id
where
users_purch.purch_date < '2016-11-01'
and users_purch.total_cost < 112.49
order by
users_purch.purch_date desc
You can try this one, mate:
SELECT
up.date AS 'purchase_date',
up.total_amount AS 'purchase_amount',
u.*
FROM
users u
INNER JOIN users_purchases up ON up.user_id = u.user_id
INNER JOIN (
# get max date per user_id
SELECT user_id, max(date) AS 'purch_date'
FROM users_purchases
GROUP BY user_id
) max_up ON
max_up.user_id
# join that date to get the correct total_amount
AND max_up.`purch_date` = up.`date`
WHERE
up.`date` < '2016-11-01'
AND up.total_amount < 112.49
GROUP BY u.user_id
ORDER BY up.`date` DESC;
Note
Max Date per user_id
Value of total_amount per Max Date
Grouped per user_id
Ordered descending using users_purchases.date
$listSQL = "SELECT op.name as prodname,count(*) total
FROM oc_order_product op
INNER JOIN oc_order o ON op.order_id = o.order_id
INNER JOIN oc_product_to_category p2c ON op.product_id = p2c.product_id
INNER JOIN oc_category_description cd ON cd.category_id = p2c.category_id ";
$listSQL = $listSQL."where lower(cd.name) LIKE '%".$category_name."%'
AND YEAR(o.date_added) = '".$StartDate."'
AND o.order_status_id > '0' ";
$listSQL = $listSQL."GROUP BY op.name ORDER BY o.date_added ASC";
I have this query where i am displaying product names and count by year.
I want to display Product name, and for each product, show month and count for that month for that particular year.
for example for year 2015, show all 12 months and under which show count of products for that month.
Thanks
Your original query looks pretty close, but using non-grouped, non-aggregated fields "outside" a group by is usually not a good idea unless they came from a table whose full primary key was part of the group by list. That said, this should give you what you want.
SELECT op.name AS prodname
, YEAR(o.date_added) AS oYear
, MONTH(o.date_added) AS oMonth
, COUNT(DISTINCT o.order_id) AS numOfOrders
FROM ...
GROUP BY prodname, oYear, oMonth
ORDER BY prodname, oYear, oMonth
;
[...] being your original FROM and WHERE
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
I'm trying to run a query to select customer audience, but it should select the customers who didn't get an email before. The email tracking comes from another table. This is the original query:
SELECT
c.customers_firstname,
c.customers_lastname,
o.orders_id,
o.customers_id,
c.customers_email_address
FROM
orders o,
customers c,
order_status s
WHERE
o.customers_id = c.customers_id
AND o.orders_id = s.orders_id
AND o.orders_status = s.orders_status_id
ORDER BY
o.orders_id ASC
Now, I need to check another table called tracking and see if the customer already exists in that table and if so, skip it.
This is what I've tried, but it doesn't seem to work:
SELECT
c.customers_firstname,
c.customers_lastname,
o.orders_id,
o.customers_id,
c.customers_email_address
FROM
orders o,
customers c
INNER JOIN
tracking t
ON
c.customers_id = t.customers_id,
order_status s
WHERE
o.customers_id = c.customers_id
AND o.orders_id = s.orders_id
AND o.orders_status = s.orders_status_id
AND c.customers_id NOT LIKE t.customers_id
ORDER BY
o.orders_id ASC
What am I doing wrong? Or is there any way to do this better?
ADDED: I totally forgot one more important factor - tracking table has "module" column and I need results only from "contact" module. So, in other words, I need to filter out customers who already exist in the tracking table, but only if associated with contact module, not any other module.
This is equivalent to your original query:
SELECT c.customers_firstname
, c.customers_lastname
, o.orders_id
, o.customers_id
, c.customers_email_address
FROM orders o
JOIN customers c
ON c.customers_id = o.customers_id
JOIN order_status s
ON s.orders_id = o.orders_id
AND s.orders_status_id = o.orders_status
ORDER
BY o.orders_id ASC
Add an anti-join
To meet your specification, you can use an "anti-join" pattern. We can add this to the query, before the ORDER BY clause:
LEFT
JOIN tracking t
ON t.customers_id = o.customers_id
WHERE t.customers_id IS NULL
What that's going to do is find all matching rows from the tracking table, based on the customers_id. For any rows that the query doesn't find a matching row(s) in the tracking table, it will generate a dummy row from tracking which consists of all NULL values. (That's one way of describing what an OUTER JOIN does.)
The "trick" now is to throw out all the rows that matched. And we do that by checking for a NULL value of customers_id from the tracking table (in the WHERE clause). For a match, that column won't be NULL. (The equals comparison in the join predicate guarantees us that.) So we know that if we get a NULL value for t.customers_id, that there wasn't a match.
So, this query returns the specified result set:
SELECT c.customers_firstname
, c.customers_lastname
, o.orders_id
, o.customers_id
, c.customers_email_address
FROM orders o
JOIN customers c
ON c.customers_id = o.customers_id
JOIN order_status s
ON s.orders_id = o.orders_id
AND s.orders_status_id = o.orders_status
LEFT
JOIN tracking t
ON t.customers_id = o.customers_id
WHERE t.customers_id IS NULL
ORDER
BY o.orders_id ASC
Other approaches
There are other approaches, but the anti-join is frequently the best performer.
Some other options are a NOT EXISTS predicate and a NOT IN predicate. I can add those, though I expect those solutions will be provided in other answers before I get around to it.
Starting with that first query (equivalent to the query in your question), we could also use a NOT EXISTS predicate. We'd add this before the ORDER BY clause:
WHERE NOT EXISTS
( SELECT 1
FROM tracking t
WHERE t.customers_id = o.customers_id
)
To use a NOT IN predicate, again, add this before the ORDER BY clause:
WHERE o.customers_id NOT IN
( SELECT t.customers_id
FROM tracking t
WHERE t.customers_id IS NOT NULL
)
(You may have some guarantee that tracking.customers_id is not null, but in the more general case, it's important that the subquery NOT return a NULL value, so we include a WHERE clause so that we have that guaranteed.)
With appropriate indexes, the anti-join pattern usually performs better than either the NOT EXISTS or the NOT IN, but not always.
Like spencer7593 suggested you can do the antijoin but instead of
LEFT JOIN tracking t ON t.customers_id = o.customers_id
WHERE t.customers_id IS NULL
you can write easier
JOIN tracking t ON t.customers_id = o.customers_id