Using left join with min - mysql

I am trying to connect two tables with left join and a date.
My SQL Query
SELECT
ord.`ordernumber` bestellnummer,
his.`change_date` zahldatum
FROM
`s_order` ord
LEFT JOIN
`s_order_history` his ON ((ord.`id`=his.`orderID`) AND (ord.`cleared`=his.`payment_status_id`)) #AND MIN(his.`change_date`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\'' ;
s_order
+----+---------------------+---------+-------------+
| id | ordertime | cleared | ordernumber |
+----+---------------------+---------+-------------+
| 1 | 2014-08-11 19:53:43 | 2 | 123 |
| 2 | 2014-08-15 18:33:34 | 2 | 125 |
+----+---------------------+---------+-------------+
s_order_history
+----+-------------------+-----------------+---------+---------------------+
| id | payment_status_id | order_status_id | orderID | orderID change_date |
+----+-------------------+-----------------+---------+---------------------+
| 1 | 1 | 5 | 1 | 2014-08-11 20:53:43 |
| 2 | 2 | 5 | 1 | 2014-08-11 22:53:43 |
| 3 | 2 | 7 | 1 | 2014-08-12 19:53:43 |
| 4 | 1 | 5 | 2 | 2014-08-15 18:33:34 |
| 5 | 1 | 6 | 2 | 2014-08-16 18:33:34 |
| 6 | 2 | 6 | 2 | 2014-08-17 18:33:34 |
+----+-------------------+-----------------+---------+---------------------+
Wanted result:
+-------------+---------------------+
| ordernumber | change_date |
+-------------+---------------------+
| 123 | 2014-08-11 22:53:43 |
| 125 | 2014-08-17 18:33:34 |
+-------------+---------------------+
The problem I have is getting only the date, where the cleared/payment_status_id value has been changed in s_order. I currently get all dates where the payment_status_id matches the current cleared value, but I only need the one, where it happend first.
This is only an excerpt of the actually query, since the original is a lot longer (mostly more left joins and a lot more tables).

You can group data by ordernumber
SELECT
ord.`ordernumber` bestellnummer,
MIN(his.`min_change_date`) as zahldatum
FROM
`s_order` ord
LEFT JOIN
`s_order_history` his ON ((ord.`id`=his.`orderID`) AND (ord.`cleared`=his.`payment_status_id`)) #AND MIN(his.`change_date`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\''
GROUP BY
ord.`ordernumber`;
or you can group data in a subquery:
SELECT
ord.`ordernumber` bestellnummer,
his.`min_change_date` zahldatum
FROM
`s_order` ord
LEFT JOIN (
SELECT
orderID, payment_status_id, MIN(change_date) as min_change_date
FROM
s_order_history
GROUP BY
orderID, payment_status_id
) his ON (ord.`id` = his.`orderID` AND ord.`cleared` = his.`payment_status_id`)
WHERE
ord.`ordertime` >= \''.$dateSTART.'\' AND ord.`ordertime` <= \''.$dateSTOP.'\'';

Try this:
select s_order.ordernumber, min(s_order_history.change_date)
from s_order left join s_order_history
on s_order.id = s_order_history.orderID
and s_order.cleared = s_order_history.payment_status_id
group by s_order.order_id

SELECT ord.`ordernumber` bestellnummer,
MIN( his.`change_date` ) zahldatum
...
GROUP BY ord.`ordernumber`

MIN is an aggregate function so you can't use it in a JOIN straight up like you've tried above. You also are not comparing it to a value in your JOIN.
You'll want to do something like:
his.`change_date` = (SELECT MIN(his.`change_date`) FROM s_order_history where ord.`id` = his.`orderID`)
in your JOIN.

Related

How come I get a syntax error when using a CTE to update a table?

I have a column (share_2pp) that needs to be updated with a calculated result from the table. This select query produces the column (share_2pp) I would like.
WITH cte
AS (
SELECT recipe
, SUM(meal_nr) AS meal_sum
FROM w03_forecast
GROUP BY recipe
)
SELECT w03_forecast.recipe
, w03_forecast.meal_nr
, meal_sum
, (meal_nr / meal_sum) AS share_2pp
FROM w03_forecast
INNER JOIN cte
ON w03_forecast.recipe = cte.recipe;
+--------+---------+----------+-----------+
| recipe | meal_nr | meal_sum | share_2pp |
+--------+---------+----------+-----------+
| 1 | 3842 | 4593 | 0.8365 |
| 2 | 4284 | 5130 | 0.8351 |
| 3 | 4166 | 4926 | 0.8457 |
| 4 | 2830 | 3382 | 0.8368 |
| 5 | 2495 | 2935 | 0.8501 |
| 1 | 751 | 4593 | 0.1635 |
| 2 | 846 | 5130 | 0.1649 |
| 3 | 760 | 4926 | 0.1543 |
| 4 | 552 | 3382 | 0.1632 |
| 5 | 440 | 2935 | 0.1499 |
+--------+---------+----------+-----------+
However, when I try to update the table I get a syntax error at FROM.
WITH cte
AS (
SELECT recipe
, SUM(meal_nr) AS meal_sum
FROM w03_forecast
GROUP BY recipe
)
UPDATE w03_forecast
SET w03_forecast.share_2pp = (meal_nr / meal_sum)
FROM w03_forecast
INNER JOIN cte
ON w03_forecast.recipe = cte.recipe;
I think you can just use JOIN:
UPDATE w03_forecast f JOIN
(SELECT recipe, SUM(meal_nr) AS meal_sum
FROM w03_forecast
GROUP BY recipe
) r
USING (recipe)
SET f.share_2pp = (meal_nr / meal_sum);
That said, there is no reason to store this in the table. It can easily be calculated on-the-fly using window functions:
select f.*,
(meal_nr / sum(meal_nr) over ()) as share_2pp
from w03_forecast f

How to get grand total of matched Left Join items in SQL Query

I am struggling to find a way to get a GRAND TOTAL count of the number of rows matched in a LEFT JOIN.
This is the current query:
SELECT * FROM renewal
LEFT JOIN appointment ON appointment.renewalid=renewal.id
I have tried:
SELECT *, COUNT(app.id) AS appcount FROM renewal
LEFT JOIN appointment ON appointment.renewalid=renewal.id
But that is obviously not the right way, as it only returns the total appointments for each renewal row returned.
I have also tried a subquery:
SELECT customer.*, app.totalcount FROM renewal
LEFT JOIN (SELECT COUNT(id) AS appcount FROM appointment) AS app ON app.renewalid=renewal.id
This also is not working.
Currently, I can get it to return the total for each customer, but not a GRAND TOTAL.
The reason I am not just querying the appointments table alone, is because I need it only to return a grand total of appointments linked to customers which follow the specified OUTER 'WHERE' statement.
Disclaimer: The queries above are a much more simplified version, just for readability sakes.
Here is the full query:
SELECT
renewal.id AS renid,
renewal.personid,
renewal.enddate,
renewal.assettype,
renewal.producttype,
renewal.vrm,
renewal.make,
renewal.model,
renewal.submodel,
renewal.derivative,
renewal.complete,
person.forename,
person.surname,
person.company,
appointment.id AS appid,
COUNT(appointment.renewalid) AS appointedcount,
appointment.renewalid,
n.latestnote,
(
SELECT
COUNT(complete)
FROM
renewal
WHERE
complete = 1 && enddate BETWEEN '2020-01-01' AND '2020-01-30' && dealershipid = '1' && assettype = 'N' && producttype NOT LIKE '%CH%' && complete = 1
) AS renewedcount
FROM
renewal
LEFT JOIN person ON person.id = renewal.personid
LEFT JOIN appointment ON appointment.renewalid = renewal.id
LEFT JOIN(
SELECT
note AS latestnote,
TIMESTAMP,
renewalid
FROM
renewal_note
ORDER BY
TIMESTAMP
DESC
) AS n
ON
n.renewalid = renewal.id
WHERE
enddate BETWEEN '2020-01-01' AND '2020-01-30' && renewal.dealershipid = '1' && assettype = 'N' && producttype NOT LIKE '%CH%'
GROUP BY
renid
ORDER BY
enddate ASC
This is what is being outputted with the Full query (Removed cluttering columns which arent relevant to this question):
| renid | appid | appointedcount | renewedcount | |
|--------|--------|-----------------|---------------|--|
| 60177 | 1096 | 6 | 5 | |
| 64704 | 2470 | 6 | 5 | |
| 43057 | | 0 | 5 | |
| 64626 | | 0 | 5 | |
| 11123 | | 0 | 5 | |
| 72469 | | 0 | 5 | |
| 76055 | 2879 | 7 | 5 | |
| 76001 | 2546 | 3 | 5 | |
| 72171 | 2769 | 6 | 5 | |
| 76073 | | 0 | 5 | |
| 73183 | 2093 | 8 | 5 | |
| 73114 | 2834 | 6 | 5 | |
| 43088 | | 0 | 5 | |
| 732 | | 0 | 5 | |
| 11157 | | 0 | 5 | |
| 60207 | | 0 | 5 | |
| 73103 | 2015 | 3 | 5 | |
| 75982 | | 0 | 5 | |
| 43076 | | 0 | 5 | |
It seems like the highest appointedcount value is 8, which if you count the number of rows with an appid (an appointment), adds up to 8.
I believe I am heading in the right direction, as it is returning 8 (at a random renewal row), but can't seem to get past this point.
Can someone please guide me in the right direction?
I think you want a window function:
SELECT *, COUNT(a.id) OVER () AS total_appcount
FROM renewal r LEFT JOIN
appointment a
ON a.renewalid = r.id ;
In older versions of MySQL, you can use a correlated subquery:
SELECT *,
(SELECT COUNT(*)
FROM renewal r JOIN
appointment a
ON a.renewalid = r.id
) AS total_appcount
FROM renewal r LEFT JOIN
appointment a
ON a.renewalid = r.id ;
Note that for the subquery, you don't need an outer join, because you only want matches.

Nested JOIN to create custom dynamic columns

I have a table veicoli (vehicles) like this:
-------------------------------
| ID | Modello | Targa |
-------------------------------
| 1 | IVECO | XA123WE |
-------------------------------
| 2 | IVECO | CF556XD |
-------------------------------
| 3 | FIAT | AS332ZZ |
-------------------------------
| 4 | GOLF | GF567YU |
-------------------------------
For each vehicle I have none, one or multiple revisioni_veicolo (revisions) (the one with bigger DateExpiring is the one I need to check if revision is still valid or not based on today date)
-------------------------------------------------------------------
| ID | veicoli_ID | DateExpiring | Pass_Success |
-------------------------------------------------------------------
| 1 | 1 | 2019-07-01 | 1
------------------------------------------------------------------
| 2 | 1 | 2020-10-01 | 0
-------------------------------------------------------------------
| 3 | 2 | 2019-11-25 | 1
-------------------------------------------------------------------
| 4 | 2 | 2018-10-20 | 1
-------------------------------------------------------------------
| 5 | 4 | 2017-10-20 | 1
-------------------------------------------------------------------
Based on my example above (today is 2019-10-29):
Vehicle: ID = 1 has a revision still active (2020-10-01) but not passed (Pass_success = 0)
Vehicle: ID = 2 has a revision still active (2019-11-25) and passed (Pass_success = 1)
Vehicle: ID = 3 has no revision yet
Vehicle: ID = 4 has revision, but no active revision (last expired on 2017-10-20) but the last one passed the check (Pass_success = 1)
What I need is to have 3 new custom columns created dynamically on my query result:
-------------------------------------------------------------------------------------------
| ID | Modello | Targa | RevisionPresent | RevisionStillActive | LastRevisionPassed |
-------------------------------------------------------------------------------------------
| 1 | IVECO | XA123WE | true | true | false
-------------------------------------------------------------------------------------------
| 2 | IVECO | CF556XD | true | true | true
-------------------------------------------------------------------------------------------
| 3 | FIAT | AS332ZZ | false | false | false
-------------------------------------------------------------------------------------------
| 4 | GOLF | GF567YU | true | false | true
-------------------------------------------------------------------------------------------
I tried to start with my old post: MYSQL INNER JOIN to get 3 types of result
But I'm very confused using nested JOIN
I tried starting a fiddle but i'm stuck on syntax error: http://sqlfiddle.com/#!9/3c70bf/2
You need a LEFT JOIN of the tables and conditional aggregation:
select v.ID, v.Modello, v.Targa,
max(r.DataScadenzaRevisione is not null) RevisionPresent,
coalesce(max(r.DataScadenzaRevisione >= current_date()), 0) RevisionStillActive,
max(case when r.DataScadenzaRevisione = g.maxdate then r.EsitoPositivo else 0 end) LastRevisionPassed
from veicoli v
left join revisioni_veicolo r on r.veicoli_ID = v.id
left join (
select veicoli_id, max(DataScadenzaRevisione) maxdate
from revisioni_veicolo
group by veicoli_id
) g on g.veicoli_ID = v.id
group by v.ID, v.Modello, v.Targa
See the demo.
Results:
| ID | Modello | Targa | RevisionPresent | RevisionStillActive | LastRevisionPassed |
| --- | ------- | ------- | --------------- | ------------------- | ------------------ |
| 1 | IVECO | XA123WE | 1 | 1 | 0 |
| 2 | IVECO | CF556XD | 1 | 1 | 1 |
| 3 | FIAT | AS332ZZ | 0 | 0 | 0 |
| 4 | GOLF | GF567YU | 1 | 0 | 1 |
...
LEFT JOIN (SELECT a.veicoli_ID, a.EsitoPositivo AS StatoUltimaRevisione,
a.DataScadenzaRevisione FROM revisioni_veicolo) a
...
There's two things wrong with this.
The alias a is defined for this subquery, so you can't reference it inside the subquery. But you don't need to qualify the columns in this subquery anyway - you didn't do this in other subqueries, so I'm not sure why you did it in this case.
You don't have any join condition for this join. MySQL is a little bit inconsistent about when join conditions are required. But in this case, you need one.
After I tested the query with these two corrections, it works.
Basically you just need to look at the last revision of each vehicule to produce that resultset.
You can do the filtering with a correlated subquery:
select
v.ID,
v.Modello,
v.Targa,
(DataScadenzaRevisione >= now()) RevisionPresent,
(DataScadenzaRevisione >= now() and EsitoPositivo = 1) RevisionStillActive,
(EsitoPositivo = 1) LastRevisionPassed
from
veicoli v
left join revisioni_veicolo r
on r.veicoli_ID = v.ID
and r.DataScadenzaRevisione = (
select max(DataScadenzaRevisione)
from revisioni_veicolo r1
where r1.veicoli_ID = v.ID
)
You can check the results with your sample data in this db fiddle.
Or you can use a window function (this requires MySQL 8.0):
select
v.ID,
v.Modello,
v.Targa,
(DataScadenzaRevisione >= now()) RevisionPresent,
(DataScadenzaRevisione >= now() and EsitoPositivo = 1) RevisionStillActive,
(EsitoPositivo = 1) LastRevisionPassed
from (
select
v.*,
r.*,
row_number() over(partition by ID order by r.DataScadenzaRevisione desc) rn
from veicoli v
left join revisioni_veicolo r on r.veicoli_ID = v.ID
) where coaelesce(rn, 1) = 1

MySQL: joining tables with multi results rows to one row

I have 4 tables:
secu_content
| id | created | modified |
| 910 | 26/12/1982 | 28/12/1984 |
| 911 | 24/12/1982 | 25/12/1984 |
secu_data
| element_id | field_id | data |
| 1 | 1 | 25/12/1984 |
| 2 | 1 | 26/12/1984 |
| 3 | 1 | 27/12/1984 |
| 4 | 1 | 25/12/1984 |
| 4 | 2 | google.com |
secu_elements
| id | item_id |
| 1 | 891 |
| 2 | 711 |
| 3 | 204 |
| 4 | 911 |
secu_fields
| id | type |
| 1 | date |
| 2 | input |
Table secu_content, contains many articles, where the id is the article id.
The other 3 tables gives additional information and I want to join them.
I want to get results that includes all secu_content rows and all the columns + calc_date + calc_link
calc_date <- the data column from secu_data where field_id=1 (see secu_fields)
calc_link <- the data column from secu_data where field_id=2 (see secu_fields)
The problem is that I get 2 rows where secu_content id=911 (one row with the correct calc_date and second row with the correct calc_link), and I need one row with both.
This is my SQL:
SELECT a.id
, a.created
, a.modified
, fe.item_id AS calc_date_item_id
, fd.data AS calc_date
, CASE WHEN fd.data IS NOT NULL AND ff.type = "date" THEN fd.data
WHEN a.modified = '0000-00-00 00:00:00' THEN a.created ELSE a.modified
END as calc_date
, CASE WHEN fd.data IS NOT NULL AND ff.type = "input" THEN fd.data
END as calc_link
FROM secu_content AS a
LEFT
JOIN secu_fieldsandfilters_elements AS fe
ON fe.item_id = a.id
AND fe.content_type_id=1
LEFT
JOIN secu_fieldsandfilters_data AS fd
ON fd.element_id = fe.id
LEFT
JOIN secu_fieldsandfilters_fields as ff
ON ff.id = fd.field_id
ORDER BY a.id DESC;
Thanks in advance
Israel
Fast and dirty solution is to use second join to secu_data like that (simplified, add logic you need)
SELECT id, d1.data as `calc_date`, d2.data as `calc_link`
FROM secu_content
LEFT JOIN secu_data d1 ON secu_content.id = d1.element_id AND field_id = 1
LEFT JOIN secu_data d2 ON secu_content.id = d2.element_id AND field_id = 2

Limiting records returned in MySQL

I have a one to many relationship between 2 tables and I would like to be able to limit how many records I return from the many table for each record in the one table.
RELATIONSHIP
tcontributors.CONTRIBUTOR_ID one or more records in table tclips.FK_CONTRIBUTOR_ID
I know this should be straight forward and it is using "TOP" in MS Access but I just can't work out the SQL for MySQL. Any help would be appreciated.
This is what I have at the moment...
select *
from tcontributors as qconts
inner join (SELECT * from tclips limit 3) as qclips
where CONTRIBUTOR_ID In (1922,2034,2099)
One way to limit the number of rows in a join is to do a regular join, followed by a WHERE clause that runs a coordinated subquery with a LIMIT clause, like this:
SELECT *
FROM tcontributors AS qconts
INNER JOIN tclips qclips
ON qclips.fk_contributor_id = qconts.contributor_id
WHERE qconts.contributor_id IN (1922,2034,2099)
AND qclips.clip_id IN (
SELECT clip_id
FROM tclips zclips
WHERE zclips.fk_contributor_id = qconts.contributor_id
LIMIT 3
)
Unfortunately, the current version of MySQL does not support LIMIT in coordinated subqueries, producing this error message (demo on sqlfiddle):
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
Other RDBMS engines do support this functionality. For example, Microsoft SQL Server produces correct results when the query is modified to fit its syntax:
SELECT *
FROM tcontributors AS qconts
INNER JOIN tclips qclips
ON qclips.fk_contributor_id = qconts.contributor_id
WHERE qconts.contributor_id IN (1922,2034,2099)
AND qclips.clip_id IN (
SELECT TOP 3 clip_id -- Use TOP 3 instead of LIMIT 3
FROM tclips zclips
WHERE zclips.fk_contributor_id = qconts.contributor_id
)
Here is a working demo on sqlfiddle.
Here is answer...
SELECT tcontributors.CONTRIBUTOR_ID, tcontributors.CONTRIBUTOR_NAME,c1.CLIP_ID, c1.CLIP_TITLE
FROM tcontributors
INNER JOIN tclips c1 ON c1.FK_CONTRIBUTOR_ID = tcontributors.CONTRIBUTOR_ID AND c1.FK_CONTRIBUTOR_ID IN (2731,3531,3371)
LEFT JOIN tclips c2 ON c2.FK_CONTRIBUTOR_ID = c1.FK_CONTRIBUTOR_ID AND c2.CLIP_ID < c1.CLIP_ID
GROUP BY tcontributors.CONTRIBUTOR_ID, tcontributors.CONTRIBUTOR_NAME, c1.CLIP_ID, c1.CLIP_TITLE
HAVING (COUNT(c2.CLIP_ID) < 2)
ORDER BY tcontributors.CONTRIBUTOR_ID DESC, c1.CLIP_ID DESC;
In your query you are not ordering subquery results, so you just need limit random clip_id per fk_contributor_id so my query does it. If you need specifically some clip_id you must write in that order to take them.
I think you can use this query with a little modification:
SQLFIDDLEExample
SELECT qclips.*,
#row_num := IF(#prev_value=qclips.fk_contributor_id ,#row_num+1,1) AS RowNumber,
#prev_value := qclips.fk_contributor_id
FROM tcontributors AS qconts
INNER JOIN tclips qclips
ON qclips.fk_contributor_id = qconts.contributor_id,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
WHERE qconts.contributor_id IN (1922,2034,2099)
AND IF(#prev_value=qclips.fk_contributor_id ,#row_num+1,1) < 4
ORDER BY contributor_id
Result without row number:
| CLIP_ID | FK_CONTRIBUTOR_ID | CLIP_TITLE |
|---------|-------------------|------------|
| 5 | 1922 | five |
| 2 | 1922 | two |
| 3 | 1922 | three |
| 4 | 1922 | four |
| 1 | 1922 | one |
| 6 | 2034 | six |
| 7 | 2034 | seven |
| 11 | 2099 | go |
| 8 | 2099 | all |
| 9 | 2099 | good |
| 10 | 2099 | children |
Result with row number:
| CLIP_ID | FK_CONTRIBUTOR_ID | CLIP_TITLE | ROWNUMBER | PRRW |
|---------|-------------------|------------|-----------|------|
| 2 | 1922 | two | 2 | 1922 |
| 3 | 1922 | three | 3 | 1922 |
| 1 | 1922 | one | 1 | 1922 |
| 6 | 2034 | six | 1 | 2034 |
| 7 | 2034 | seven | 2 | 2034 |
| 9 | 2099 | good | 2 | 2099 |
| 10 | 2099 | children | 3 | 2099 |
| 8 | 2099 | all | 1 | 2099 |