MySQL nested, nested subquery not getting outter variable - mysql

I have a spendings table and a dates table, that are joined by date_id and id...
What I'm trying to do, is get from 1 query all the info from spendings, plus the sum of all the spendings but with a limit and/or offset
This is the query right now
SELECT spendings.id, spendings.price, spendings.title,
dates.date, users.username, currencies.value,
( SELECT SUM(sum_table.price)
FROM (
SELECT s.price
FROM spendings s, dates d
WHERE s.date_id = d.id
AND day(d.date) = 25
LIMIT 2 OFFSET 0
) as sum_table
) AS sum_price
FROM spendings, dates, users, currencies
WHERE spendings.date_id = dates.id
AND day(dates.date) = 25
AND spendings.user_id = users.id
AND spendings.curr_id = currencies.id
LIMIT 2 OFFSET 0
Output
id price title date username value sum_price
3 6.00 title1 2013-11-25 alex € 21.00
4 15.00 title2 2013-11-25 alex € 21.00
It works, but only if the date here day(d.date) = 25 is the same as the outer one here day(dates.date) = 25
If instead I put day(d.date) = day(dates.date) which seems the logic thing to do, I get #1054 - Unknown column 'dates.date' in 'where clause'
If anyone has an idea to make this simpler let me know :)

Try to join instead of using nested correlated subqueries:
SELECT spendings.id, spendings.price, spendings.title,
dates.date, users.username, currencies.value,
y.sum_price
FROM spendings, dates, users, currencies
JOIN (
SELECT day, SUM(sum_table.price) As sum_price
FROM (
SELECT day(d.date) As day,
s.price
FROM spendings s, dates d
WHERE s.date_id = d.id
AND day(d.date) = 25
LIMIT 2 OFFSET 0
) sum_table
GROUP BY day
) y
ON y.day = day(dates.date)
WHERE spendings.date_id = dates.id
-- AND day(dates.date) = 25 <== commented since it's redundant now
AND spendings.user_id = users.id
AND spendings.curr_id = currencies.id
Some remarks:
Using old join syntax with commas is not recommended: FROM table1,table2,table2 WHERE
The recommended way of expressing joins is "new" ANSI SQL join syntax:
FROM table1
[left|right|cross|[full] outer|natural] JOIN table2 {ON|USING} join_condition1
[left|right|cross|[full] outer|natural] JOIN table3 {ON|USING} join_condition2
....
Actually this "new syntax" is quite old now, since is has been published, as I remember, in 1992 - 22 years ago. In IT industry 22 years is like 22 ages.

Related

How to query all records from multiple tables with joins

I have my main table employee_rates_year:
tier skill rate year
1 28 110 2019
2 28 101 2019
my column skill is connected to an employees table and subcontractors table, for simplicity here is a shortened version of tblEmployees:
EMPLOYEEID Skill FullName
1 28 Employee One
2 28 Employee Two
and tblSubcontractors:
SUBCONTRACTORID Skill FullName
1 28 Sub One
2 28 Sub Two
I am trying to get a tier, rate and year show up for every individual employee & subcontractor.
I've tried using different joins but nothing seems to give me information with every employee/subcontractor.
Here is my query:
SELECT *
FROM employee_rates_year
left join tblsubcontractors on employee_rates_year.skill = tblsubcontractors.skill
left join tblemployees on employee_rates_year.skill = tblemployees.skill
The problem is that my query shows results like this:
tier skill rate year tblEmployees.FullName tblSubcontractors.Fullname
1 28 110 2019 Employee One Sub One
2 28 101 2019 Employee Two Sub Two
Whereas I am trying to get:
tier skill rate year tblEmployees.FullName tblSubcontractors.FullName
1 28 110 2019 Employee One
1 28 110 2019 Sub One
2 28 101 2019 Employee Two
2 28 101 2019 Sub Two
I have also tried using WHERE to try and differentiate between the two tables and pick individual records.
you should never use SELECT *, because.... 😜
Try:
SELECT
tier,
skill,
rate,
year,
tblEmployees.FullName As Employee
'' As Subcontractor
FROM employee_rates_year
left join tblsubcontractors on employee_rates_year.skill = tblsubcontractors.skill
union all
SELECT
tier,
skill,
rate,
year,
'' As Employee
tblSubcontractors.Fullname As Subcontractor
FROM employee_rates_year
left join tblemployees on employee_rates_year.skill = tblemployees.skill
You seem to need UNION ALL and not a double join:
SELECT e.*, t.FullName EmployeesFullName, NULL SubcontractorsFullName
FROM employee_rates_year e
left join tblsubcontractors t on e.skill = t.skill
UNION ALL
SELECT e.*, NULL, t.FullName
FROM employee_rates_year e
left join tblemployees t on e.skill = t.skill
ORDER BY e.tier, e.skill
Each query of the 2 uses a join of employee_rates_year with 1 of the other tables.
Finally the 2 resultsets are merged with UNION ALL.
Think the problem (or part of the problem) is because of the "left join", which creates one row in result for each row in the "left" table. Since employee_rates_year only has two rows, your result for query will only have 2 rows.
Use a union all to bring the employees and subcontactors together and then join and group by. To get values in "vertical lists", you can use row_number():
select er.skill, er.rate, er.year,
max(e_fullname) as e_fullname,
max(s_fullname) as s_fullname
from ((select e.skill, e.fullname as e_fullname, null as s_fullname,
row_number() over (partition by e.skill order by e.fullname) as seqnum
from tblemployees e
) union all
(select s.skill, null as e_fullname, s.fullname as s_fullname,
row_number() over (partition by s.skill order by s.fullname) as seqnum
from tblsubcontractors s
)
) es left join
employee_rates er
on es.skill = er.skill
group by er.skill, er.rate, er.year, seqnum;
If you just want the values in separate lists (with NULLs), then you don't need the row_number() trick:
select er.skill, er.rate, er.year, e_fullname, s_fullname
from ((select e.skill, e.fullname as e_fullname, null as s_fullname
from tblemployees e
) union all
(select s.skill, null as e_fullname, s.fullname as s_fullname
from tblsubcontractors s
)
) es left join
employee_rates er
on es.skill = er.skill;

Query to get the current values

I've got the following tables
menu_supp
menu_id supp_id supp_weight supp_load_follow
1 29 10.00 1
1 31 20.00 2
supps
supp_id user_id supp_name
29 1 Test supp 1
31 1 Test supp 2
supps_prop
supp_id supp_dry_w supp_price supp_date
29 95.00 125.00 2015-10-25
29 94.00 124.00 2015-11-06
29 94.00 128.00 2015-11-12
31 25.00 200.00 2015-06-25
Now I've got this query:
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price, sp.supp_dry_weight
FROM menu_supp ms
LEFT JOIN supps s ON ms.supp_id = s.supp_id
LEFT JOIN supps_prop sp ON ms.supp_id = sp.supp_id
WHERE menu_id = 1
GROUP BY s.supp_id
ORDER BY ms.supp_load_follow ASC
Which gives me this result:
supp_id supp_name supp_weight supp_price supp_dry_weight
29 Test supp 1 10.00 125.00 95.00
31 Test supp 2 20.00 200.00 25.00
From supp 29 it gets the oldest value. Where it should take the value based on the current date. How can I achieve that?
If the supp_date is unique for a supp_id then you can use the following to get the value for the latest date:-
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price, sp.supp_dry_weight
FROM menu_supp ms
LEFT JOIN supps s
ON ms.supp_id = s.supp_id
LEFT JOIN
(
SELECT supp_id, MAX(supp_date) AS max_supp_date
FROM supps_prop
GROUP BY supp_id
) sub0
ON ms.supp_id = sub0.supp_id
LEFT OUTER JOIN supps_prop sp
ON sub0.supp_id = sp.supp_id
AND sub0.max_supp_date = sp.supp_date
WHERE menu_id = 1
ORDER BY ms.supp_load_follow ASC
This gets the max supp_date for each supp_id and joins that back to the supps_prop table to get the other fields from it.
EDIT - Coping with either the highest date, or the lowest date after today is a bit more complicated.
I would suggest having 2 sub queries. One to get the highest date for each supp_id and one to get the lowest date on or after today for each supp_id. If the 2nd is found then use that, if not use the first. Not tested but:-
SELECT s.supp_id, s.supp_name, ms.supp_weight, COALESCE(sp1.supp_price, sp0.supp_price), COALESCE(sp1.supp_dry_weight, sp0.supp_dry_weight)
FROM menu_supp ms
LEFT JOIN supps s
ON ms.supp_id = s.supp_id
LEFT JOIN
(
SELECT supp_id, MAX(supp_date) AS max_supp_date
FROM supps_prop
GROUP BY supp_id
) sub0
ON ms.supp_id = sub0.supp_id
LEFT OUTER JOIN supps_prop sp0
ON sub0.supp_id = sp0.supp_id
AND sub0.max_supp_date = sp0.supp_date
LEFT JOIN
(
SELECT supp_id, MIN(supp_date) AS max_supp_date
FROM supps_prop
WHERE supp_date >= CURDATE()
GROUP BY supp_id
) sub1
ON ms.supp_id = sub1.supp_id
LEFT OUTER JOIN supps_prop sp1
ON sub1.supp_id = sp1.supp_id
AND sub1.max_supp_date = sp1.supp_date
WHERE menu_id = 1
ORDER BY ms.supp_load_follow ASC
EDIT - An explanation of GROUP BY, etc:-
GROUP BY is used for aggregate functions; these are functions that give a value over a range of rows which share common field values. For example, SUM would be used to add up the values of the fields over multiple rows often for a shared value (ie, maybe the SUM of order values for a customer id). The shared value field is used given in the GROUP BY field.
In normal standard SQL all the returned non aggregate fields returned by the SELECT statement must be mentioned in the GROUP BY statement. This makes logical sense as if they are not mentioned then the values for a group of rows could be different and then there is the problem of which one to choose.
However there are times when this can be a bit too restrictive. For example if you are grouping by a customer id then the customer name is directly related to this customer id. MySQL does allow you to return non aggregate fields in the SELECT statement that are not specified in the GROUP BY clause, but if the values vary over the rows that are grouped together then which value is chosen is not specified; it could be from any of the rows, and indeed there is no reason that it might not change in the future or when using a different storage engine.
Sometimes GROUP BY is abused to return unique rows, in the way that DISTINCT is meant to be used.
In your original query
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_weight FROM menu_supp ms LEFT JOIN supps s ON ms.supp_id =
s.supp_id LEFT JOIN supps_prop sp ON ms.supp_id = sp.supp_id WHERE
menu_id = 1 GROUP BY s.supp_id ORDER BY ms.supp_load_follow ASC
you are using GROUP BY s.supp_id. While s.supp_name is dependent on this, ms.supp_weight and sp.supp_price are not. There could be numerous values of each of these for any s.supp_id. MySQL has just used the value from one of the grouped rows for these and doesn't really care which row it chose to use.
Here is your query without the group by and using inner joins. It appears to me that no supp_id would be inserted into menu_supp that is not already defined in supps. I suppose it would be possible to have no entry in supps_prop but that looks doubtful also. If I am wrong, simply change it back.
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_w, sp.supp_date
FROM menu_supp ms
JOIN supps s
ON s.supp_id = ms.supp_id
JOIN supps_prop sp
ON sp.supp_id = ms.supp_id
WHERE menu_id = 1
ORDER BY ms.supp_load_follow;
I've also added the date to make it easier to follow. The results are all four possible rows:
supp_id supp_name supp_weight supp_price supp_dry_w supp_date
------- --------- ----------- ---------- ---------- ---------
29 Test supp 1 10.00 125.00 95.00 2015-10-25
29 Test supp 1 10.00 124.00 94.00 2015-11-06
29 Test supp 1 10.00 128.00 94.00 2015-11-12
31 Test supp 2 20.00 200.00 25.00 2015-06-25
Obviously, you only want to join with the prop information contained in the row with the current or most recent date. That date is the largest value still in the past. Which can be found like this:
SELECT s.supp_id, s.supp_name, ms.supp_weight, sp.supp_price,
sp.supp_dry_w, sp.supp_date
FROM menu_supp ms
JOIN supps s
ON s.supp_id = ms.supp_id
JOIN supps_prop sp
ON sp.supp_id = ms.supp_id
and sp.supp_date =(
select Max( supp_date )
from supps_prop
where supp_id = ms.supp_id
and supp_date <= NOW() )
WHERE menu_id = 1
ORDER BY ms.supp_load_follow;
Don't let the subquery concern you. Since the combination of supp_id and supp_date are the most obvious PK for the prop table, those fields should already be indexed, making this an impressively fast query.
See it in action at sqlfiddle.

Get product total sales per moth, with 0 in the gaps

I have been stuck in a recent problem with a SQL Query. What I'm trying to archieve is to get each product in the store and show how many of them has been sold each month. However, sometimes there are some months where these products were not sold, which means they won't be displayed.
For instance, this is the result I'm getting right now
Article Month Sold
CN140027 6 312
CN140027 7 293
CN140027 12 122
CN140186 1 10
CN140186 4 2
While I want to get something more like this
Article Month Sold
CN140027 6 312
CN140027 7 293
CN140027 8 0
CN140027 9 0
CN140027 10 0
CN140027 11 0
CN140027 12 122
CN140186 1 10
CN140186 2 0
CN140186 3 0
CN140186 4 2
And here is the query I'm using at the moment
SELECT k.artikelnr, Months.datefield as `Months`, IFNULL(SUM(k.menge),0) as `Quantity`
FROM store_shop_korb as k LEFT OUTER JOIN office_calendar AS Months
ON Months.datefield = month(k.date_insert)
WHERE k.date_insert BETWEEN "2014-12-01" AND "2015-12-31"
group by k.artikelnr, Months.datefield
What am I missing? Or what am I doing wrong? Any help is really appreciated.
Thanks in advance.
EDIT:
Additional information:
office_calendar is the calendar table. It only contains the months as registry, from 1 to 12.
Additionally, I'm taking the article/product ID from a table called 'store_shop_korb', which contains all the lines of a made order (so it contains the article ID, its price, the quantity for each order..)
This works for me:
SELECT k.artikelnr, c.datefield AS `Month`, COALESCE(s.Quantity, 0) AS Sold
FROM (
SELECT artikelnr
FROM store_shop_korb
GROUP BY artikelnr
) k
JOIN office_calendar c
LEFT JOIN (
SELECT artikelnr, MONTH(date_insert) AS monthfield, SUM(menge) AS Quantity
FROM store_shop_korb
GROUP BY artikelnr, MONTH(date_insert)
) s ON k.artikelnr = s.artikelnr AND c.datefield = s.monthfield
ORDER BY k.artikelnr, c.datefield
If you have a table of articles, you can use it in the place of subquery k. I'm basically normalizing on the fly.
Explanation:
There's basically 3 sets of data that get joined. The first is a distinct set of articles (k), the second is a distinct set of months (c). These two are joined without restriction, meaning you get the cartesian product (every article x every month). This result is then left-joined to the sales per month (s) so that we don't lose 0 entries.
Add another where condition , i think it will solve your problem
SELECT k.artikelnr, Months.datefield as `Months`, IFNULL(SUM(k.menge),0) as `Quantity`
FROM store_shop_korb as k LEFT OUTER JOIN office_calendar AS Months
ON Months.datefield = month(k.date_insert)
WHERE IFNULL(SUM(k.menge),0)>0 AND k.date_insert BETWEEN "2014-12-01" AND "2015-12-31"
group by k.artikelnr, Months.datefield
I have tried this in MSAccess and it seems to work OK
SELECT PRODUCT, CALENDAR.MONTH, A
FROM CALENDAR LEFT JOIN (
SELECT PRODUCT, MONTH(SALEDTE) AS M, SUM(SALEAMOUNT) AS A
FROM SALES
WHERE SALEDTE BETWEEN #1/1/2015# AND #12/31/2015#
GROUP BY PRODUCT, MONTH(SALEDTE) ) AS X
ON X.M = CALENDAR.MONTH
If you already have a calender table then use this.
SELECT B.Article,
A.Month,
COALESCE(c.Sold, 0)
FROM (SELECT DISTINCT Months.datefield --Considering this as months feild
FROM office_calendar AS Months) A
CROSS JOIN (SELECT DISTINCT article
FROM Yourtable) B
LEFT OUTER JOIN Yourtable C
ON a.month = c.Month
AND b.Article = c.Article
Else you need a months table. Try this.
SELECT *
FROM (SELECT 1 AS month UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12) A
CROSS JOIN (SELECT DISTINCT article
FROM Yourtable) B
LEFT OUTER JOIN Yourtable C
ON a.month = c.Month
AND b.Article = c.Article

Get sum of value with inner join mysql

I have a table for terminal
Id status
1 Online
2 Offline
3 Offline
and I have a separate table where I can find the total hours/date of the up and downtime.
total_time
Id up down
1 10 14
2 20 4
3 15 9
1 5 19
2 4 20
3 10 14
I want to display the terminal id, status and the TOTAL up and downtime(1 = 15(up), 2 = 24(up), 3 = 25(up). I'm using inner join and I have no idea how i will be able to get the sum of the up and downtime..
SELECT terminal.Id, terminal.status, total_time.Id, SUM(total_time.up),SUM(total_time.down)
FROM terminal
INNER JOIN total_time
ON terminal.Id = total_time.Id
WHERE terminal.Id = total_time.Id
Something like this should do the trick. I am interpreting your question as asking for only the sum of the time of the current status. If this is not what you want (and maybe you want the sum of both times), please let me know.
SELECT t.id, t.status, IF(t.status = 'Online', ttlTime.upTime, ttlTime.downTime) as totalTime
FROM terminal t
JOIN
(SELECT tt.id, SUM(tt.up) as upTime, SUM(tt.down) AS downTime
FROM total_time tt
GROUP BY tt.id) ttlTime ON t.id = ttlTime.id
See the SQLFiddle
You get "inner sums" by using the GROUP BY clause.
Try:
SELECT terminal.Id, SUM(total_time.up), SUM(total_time.down)
FROM terminal
INNER JOIN total_time
ON terminal.Id = total_time.Id
GROUP BY terminal.Id
SELECT DISTINCT terminal.Id, terminal.status, total_time.Id, SUM(total_time.up),SUM(total_time.down)
FROM terminal
INNER JOIN total_time
ON terminal.Id = total_time.Id
Should do it. The DISTINCT clause acts as a filter to remove duplicate records from a result set.

Multiple Select to insert 1 colum with 1 result

SELECT
C.CompanyId,
CompanyName,
Server,
ServerUsers,
ServerUptime,
ServerHostName,
ServerType
FROM
CUSTOMERS AS C
INNER JOIN
USERS ON C.CompanyId = USERS.CompanyId
WHERE
USERS.UserEmail='matt' AND
USERS.UserPin='5153' AND
(SELECT Status FROM 4321_BlackBerryServices LIMIT 0,1)
LIMIT 0, 8
Currently my table is below
4321 T1 Solutions EXCH-01 392 47 days, 17 min exch01.myCorp.com ExchangeServices
4321 T1 Solutions EXCH-02 685 47 days, 17 min exch02.myCorp.com ExchangeServices
4321 T1 Solutions Lync-01 368 47 days, 17 min lync01.myCorp.com LyncServices
4321 T1 Solutions Lync-02 890 458 days, 58 min lync02.myCorp.com LyncServices
What i would like to do is add the last result from Status FROM 4321_BlackBerryServices so i would want to add the latest status for each server in my table
how is this possible ?
If I understand what you need, try this:
SELECT
C.CompanyId, CompanyName, Server, ServerUsers,
ServerUptime, ServerHostName, ServerType,
(SELECT Status FROM 4321_BlackBerryServices
ORDER BY field_you_know DESC LIMIT 0,1) AS BBS_Status
FROM CUSTOMERS AS C
INNER JOIN USERS
ON C.CompanyId = USERS.CompanyId
AND USERS.UserEmail = 'matt'
AND USERS.UserPin = '5153'
LIMIT 0, 8
In my query field_you_know field is the one you know you can sort the table to take the last one.