condition in SELECT in mysql - mysql

I have a query that looks like this
SELECT customer, totalvolume
FROM orders
WHERE deliverydate BETWEEN '2020-01-01' AND CURDATE()
Is there any way to select totalvolume for specific date range and make it a separate column?
So for example, I already have totalvolume. I'd like to also add totalvolume for the previous month as a separate column (totalvolume where deliverydate BETWEEN '2020-08-01' AND '2020-08-31'). Is there a function for that?

Simply use 2 table copies:
SELECT t1.customer, t1.totalvolume, t2.totalvolume previousvolume
FROM orders t1
LEFT JOIN orders t2 ON t1.customer = t2.customer
AND t1.deliverydate = t2.deliverydate + INTERVAL 1 MONTH
WHERE t1.deliverydate BETWEEN '2020-08-01' AND '2020-08-31';

You can do it with case/when construct in your columns and just expand your WHERE clause. Sometimes I would do it by having a secondary #variables to simplify my clauses. Something like
SELECT
o.customer,
sum( case when o.deliveryDate < #beginOfMonth
then o.TotalVolume else 0 end ) PriorMonthVolume,
sum( case when o.deliveryDate >= #beginOfMonth
then o.TotalVolume else 0 end ) ThisMonthVolume,
sum( o.totalvolume ) TwoMonthsVolume
FROM
( select #myToday := date(curdate()),
#beginOfMonth := date_sub( #myToday, interval dayOfMonth( #myToday ) -1 day ),
#beginLastMonth := date_sub( #beginOfMonth, interval 1 month ) ) SqlVars,
orders o
WHERE
o.deliverydate >= #beginLastMonth
group by
o.customer
To start, the "from" clause of the query alias "SqlVars" will dynamically create 3 variables and return a single row for that set. With no JOIN condition, is always a 1:1 ratio for everything in the orders table. Nice thing, you don't have to pre-declare variables and the #variables are available for the query.
By querying for all records on or after the beginning of the LAST month, you get all records for both months in question. The sum( case/when ) can now use those variables as the demarcation point for the respective volume totals.
I know you mentioned this was a simplified query, but masking that might not be a perfect answer to what you need, but may help you look at it from a different querying perspective.

Related

Mysql case join query assistance

I'm trying to create a query which gets all columns and a case column which denotes availablity, however I need to include a one to many relationship in the mix and it's messing with my head.
My query without the relationship:
SELECT storage_rooms.*,
CASE
WHEN release_date <= CURDATE() THEN 1
WHEN release_date <= ADDDATE(CURDATE(), INTERVAL 30 DAY) THEN release_date
ELSE 0
END AS available
FROM storage_rooms
Which is fine and returns something like this:
Now I need to check if it is already booked.
To give you an idea of what I'm thinking (doesn't work):
SELECT storage_rooms.*,
(SELECT
CASE
WHEN release_date <= CURDATE() OR orders.status_id IN (3, 4) THEN 1
WHEN release_date <= ADDDATE(CURDATE(), INTERVAL 30 DAY) THEN release_date
ELSE 0
END AS available
FROM storage_rooms
LEFT OUTER JOIN orders
ON storage_rooms.id = orders.storage_room_id)
FROM storage_rooms
I gather I might need another subquery with a groupBy?
I don't find joins super intuitive and I'm a bit lost so any assistance would be welcome.
I'm not totally sure it will work but you can try this:
SELECT storage_rooms.*,
CASE
WHEN release_date <= CURDATE() OR availibility.order_status_id IN (3, 4) THEN 1
WHEN release_date <= ADDDATE(CURDATE(), INTERVAL 30 DAY) THEN release_date
ELSE 0
END AS available
FROM storage_rooms
LEFT JOIN
( SELECT id AS order_id, status_id AS order_status_id, storage_room_id AS order_storage_room_id
FROM orders WHERE status_id IN (3, 4)
GROUP BY storage_room_id
) availibility
ON (storage_rooms.id = availibility.order_storage_room_id)
The thing here is that the LEFT JOIN is adding to the results the columns needed to check your availability. But the trick is you need a subquery so that the table joined contains only one entry for each room or else the results are duplicated. The subquery must be limited to the positive matches you want to take in consideration with a WHERE, because the GROUP BY will only keep the first row found and you don't want this information lost in the grouping.
FROM OP'S COMMENTS THE FINAL QUERY IS:
SELECT storage_rooms.*,
CASE
WHEN release_date <= CURDATE() OR aggregate.order_status_id IS NULL THEN 1
WHEN release_date <= ADDDATE(CURDATE(), INTERVAL 30 DAY) THEN release_date
ELSE 0
END AS available
FROM storage_rooms
LEFT JOIN
(SELECT storage_room_id, order_status_id
FROM orders WHERE order_status_id IN (1, 2)
GROUP BY storage_room_id, order_status_id
) aggregate
ON storage_rooms.id = aggregate.storage_room_id

Is there a MySQL Statement for this or are multiple statements needed?

I have a table with MLSNumber, ListingContractDate, CloseDate.
I want to summarize the activity grouped my month starting with the current month and going back to January 2000.
I have this statement which summarizes the ListingContractDate by month.
SELECT COUNT(MLSNumber) AS NewListings, DATE_FORMAT(ListingContractDate,'%M %Y')
FROM Listings
WHERE Neighbourhood = 'Beachside'
AND ListingContractDate >= '2000-01-01'
GROUP BY YEAR(ListingContractDate), MONTH(ListingContractDate)
ORDER BY ListingContractDate DESC
The two problems with this statement are if there is nothing found in a specific month it skips that month, and I would need to return a 0 so no months are missing, and I am not sure how to get the same count on the CloseDate field or if I just have to run a 2nd query and match the two results up by month and year using PHP.
An exceptionally useful item to have is a "tally table" which simply consists on a set of integers. I used a script found HERE to generate such a table.
With that table I can now LEFT JOIN the time related data to it as shown below:
set #startdt := '2000-01-01';
SELECT COUNT(MLSNumber) AS NewListings, DATE_FORMAT(T.Mnth,'%M %Y')
FROM (
select
tally.id
, date_add( #startdt, INTERVAL (tally.id - 1) MONTH ) as Mnth
, date_add( #startdt, INTERVAL tally.id MONTH ) as NextMnth
from tally
where tally.id <= (
select period_diff(date_format(now(), '%Y%m'), date_format(#startdt, '%Y%m')) + 1
)
) t
LEFT JOIN Temp On Temp.ListingContractDate >= T.Mnth and Temp.ListingContractDate < T.NextMnth
GROUP BY YEAR(T.Mnth), MONTH(T.Mnth)
ORDER BY T.Mnth DESC
Logc,
define a stating date
calculate the number of months from that date until now (using
PERIOD_DIFF + 1)
choose that number of records from the tally table
create period start and end dates (tally.Mnth & tally.NextMnth)
LEFT JOIN the actual data to the tally table using
Temp.ListingContractDate >= T.Mnth and Temp.ListingContractDate < T.NextMnth
group and count the data
see this sqlfiddle`

MySQL COUNT rows in a subquery

I've got this MySQL statement that returns a list of all of our dealers, along with total amount of stores they have. I'm trying to make it so that I can add additional columns that are returned depending on the stores joineddate column.
This is the basic statement that returns the list of dealers (accountgroup) and the total / COUNT of stores that are assigned to accountgroup.
SELECT accountgroup, COUNT(storename) AS TotalAmountOfStores
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup
I thought using the below statement in a SELECT subquery would let me have these additional columns that I want. This statement works on it's own, but I haven't been able to encorporate it into the above statement as a subquery successfully.
SELECT COUNT( storename ) AS '2months'
FROM `accounts`
WHERE joineddate > date_sub(now(), interval 2 month)
AND accountgroup <> ''
AND othercode = 'Site'
GROUP BY accountgroup
This is the statement that I thought would work, but I get the error "#1242 - Subquery returns more than 1 row, which confuses me as it should be returning more than one row":
SELECT accountgroup, COUNT(storename) AS TotalAmountOfStores,
(SELECT COUNT(storename)
FROM `accounts`
WHERE joineddate > date_sub(now(), interval 2 month) AND accountgroup <>'' AND othercode='Site'
GROUP BY accountgroup ) AS '2months'
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup
I'm starting to feel that a subquery isn't the right approach, and am reading up on JOIN' s. Can anybody confirm this, and possibly point me in the right direction?
Any help is greatly appreciated. Thanks in advance.
You can SUM the times this condition is true using an if. Example:
SELECT
accountgroup,
COUNT(storename) AS TotalAmountOfStores,
SUM(
IF(joineddate > date_sub(now(), interval 2 month), 1, 0)
) AS '2months'
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup

1242 - Subquery returns more than 1 row in subquery

I'm trying to get the total of pending amount of each staff. The below query works fine:
SELECT SUM(amount)
FROM pending
WHERE MONTH < DATE_SUB(curdate() , INTERVAL 1 MONTH)
GROUP BY class
but when I try to add it as a subquery it gives me the error below:
1242 - Subquery returns more than 1 row
SELECT
(period_diff(date_format(now(), '%Y%m'),
date_format(MONTH, '%Y%m'))) AS months,
pending.amount,
pending.admission_numb,
pending.month,
staff.name,
staff.class, (
SELECT SUM(amount)
FROM pending
WHERE MONTH < DATE_SUB(curdate(), INTERVAL 1 MONTH)
GROUP BY class
)
FROM
pending JOIN staff
ON pending.admission_numb = staff.admission
GROUP BY admission
ORDER BY CAST( staff.class AS UNSIGNED ) , staff.class
any help will be appreciated..
Since your subquery returns more than one row (i expect that it will return a row for each class), you need do join your subquery in the from clause:
SELECT
(period_diff(date_format(now(), '%Y%m'), date_format(MONTH, '%Y%m'))) AS months,
pending.amount,
pending.admission_numb,
pending.month,
staff.name,
staff.class,
sums.tot
FROM
pending JOIN staff ON pending.admission_numb = staff.admission
JOIN (
SELECT class, SUM(amount) as tot
FROM pending
WHERE MONTH < DATE_SUB(curdate(), INTERVAL 1 MONTH)
GROUP BY class
) sums on staff.class = sums.class
GROUP BY admission
ORDER BY CAST( staff.class AS UNSIGNED ) , staff.class
It's pretty simple, really. Subqueries can only return one row. That's what the error message tells you. The query you gave most likely returns more than one row (one for each class, in fact). Since you provide no output, I can only guess that this is true.
To fix it, you need to change that query to return one row. Perhaps you get rid of the GROUP BY, or perhaps you pick the largest (or smallest) sum and return that. It's up to you based on your business requirements.

MySQL query to count items by week for the current 52-weeks?

I have a query that I'd like to change so that it gives me the counts for the current 52 weeks. This query makes use of a calendar table I've made which contains a list of dates in a fixed range. The query as it stands is selecting max and min dates and not necessarily the last 52 weeks.
I'm wondering how to keep my calendar table current such that I can get the last 52-weeks (i.e, from right now to one year ago). Or is there another way to make the query independent of using a calendar table?
Here's the query:
SELECT calendar.datefield AS date, IFNULL(SUM(purchaseyesno),0) AS item_sales
FROM items_purchased join items on items_purchased.item_id=items.item_id
RIGHT JOIN calendar ON (DATE(items_purchased.purchase_date) = calendar.datefield)
WHERE (calendar.datefield BETWEEN (SELECT MIN(DATE(purchase_date))
FROM items_purchased) AND (SELECT MAX(DATE(purchase_date)) FROM items_purchased))
GROUP BY week(date)
thoughts?
Some people dislike this approach but I tend to use a dummy table that contains values from 0 - 1000 and then use a derived table to produce the ranges that are needed -
CREATE TABLE dummy (`num` INT NOT NULL);
INSERT INTO dummy VALUES (0), (1), (2), (3), (4), (5), .... (999), (1000);
If you have a table with an auto-incrementing id and plenty of rows you could generate it from that -
CREATE TABLE `dummy`
SELECT id AS `num` FROM `some_table` WHERE `id` <= 1000;
Just remember to insert the 0 value.
SELECT CURRENT_DATE - INTERVAL num DAY
FROM dummy
WHERE num < 365
So, applying this approach to your query you could do something like this -
SELECT WEEK(calendar.datefield) AS `week`, IFNULL(SUM(purchaseyesno),0) AS item_sales
FROM items_purchased join items on items_purchased.item_id=items.item_id
RIGHT JOIN (
SELECT (CURRENT_DATE - INTERVAL num DAY) AS datefield
FROM dummy
WHERE num < 365
) AS calendar ON (DATE(items_purchased.purchase_date) = calendar.datefield)
WHERE calendar.datefield >= (CURRENT_DATE - INTERVAL 1 YEAR)
GROUP BY week(datefield) -- shouldn't this be datefield instead of date?
I too typically "simulate" a table on the fly by using #sql variables and just join to ANY table in your system that has AT least as many weeks as you want. NOTE... when dealing with dates, I like to typically use the date-part only which implies a 12:00:00 am. Also, by advancing the start date by 7 days for the "EndOfWeek", you can now apply a BETWEEN clause for records within a given time period... such as your weekly needs.
I've applied such a sample to coordinate the join based on date association to the per week basis... Since your
select
DynamicCalendar.StartOfWeek,
COALESCE( SUM( IP.PurchaseYesNo ), 0 ) as Item_Sales
from
( select
#weekNum := #weekNum +1 as WeekNum,
#startDate as StartOfWeek,
#startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from
( select #weekNum := 0,
#startDate := date(date_sub(now(), interval 1 year ))) sqlv,
AnyTableThatHasAtLeast52Records,
limit
52 ) DynamicCalendar
LEFT JOIN items_purchased IP
on IP.Purchase_Date bewteen DynamicCalendar.StartOfWeek
AND DynamicCalendar.EndOfWeek
group by
DynamicCalendar.StartOfWeek
This is under the premise that your "PurchaseYesNo" value is in your purchased table directly. If so, no need to join to the ITEMS table. If the field IS in the items table, then I would just tack on a LEFT JOIN for your items table and get value from that.
However you could use the dynamicCalendar context in MANY conditions.