Query with a subquery where results could be many - mysql

I have a main table, tbl_vluchtgegevens which is the "main" table I'm looking at. From this, I want to JOIN tbl_photos and show a "random" result from this table.
My problem is that in the tbl_vluchtgegevens there is only 1 column value that would equal a column value in tbl_photos, however, there is a second column that is stored in tbl_photos which is similar to a second column in tbl_vluchtgevevens that it needs to look at. There is a 3rd table where the value in tbl_photos would have the value for tbl_vluchtgegevens, tbl_luchtvaartmaatschappij
I just can't figure out the MySQL code for MariaDB. I'll try to display this below.
tbl_vluchtgegevens | tbl_luchtvaartmaatschappij | tbl_photos
luchtvaartmaatschappij luchtvaartmaatschappij
IATACode img_lvm
inschrijvingnmr img_nmr
SAMPLE DATA:
tbl_vluchtgegevens
gegevenID | luchtvaartmaatschappij | inschrijvingnmr | vertrekdatum2
1 911 N803NW 2018-01-01 12:00:00
2 1702 PH-AON 2018-01-15 17:00:00
3 911 N853NW 2018-01-17 11:00:00
tbl_luchtvaartmaatschappij
luchtvaartmaatschappijID | IATACode
911 DL
1702 KL
1803 LH
tbl_photos
photoID | img_lvm | img_nmr | file
1 DL N853NW somefile.jpg
2 DL N803NW somefile2.jpg
3 DL N853NW somefile3.jpg
4 KL PH-AON somefile4.jpg
5 KL PH-AON somefile5.jpg
6 LH D-AUBC somefile6.jpg
7 DL N805NW somefile7.jpg
Query would result:
gegevenID | vertrekdatum2 | luchtvaartmaatschappij | inschrijvingnmr | file
1 2018-01-15 12:00:00 911 N803NW somefile.jpg
2 2018-01-15 17:00:00 1702 PH-AON somefile4.jpg
3 2018-01-17 11:00:00 911 N853NW somefile3.jpg
sqlfiddle: http://www.sqlfiddle.com/#!9/19e222/1
At one point, I've tried using the code below, but if multiple rows exist in tbl_photos, then it displays each row from tbl_vluchtgegevens with all of the rows in tbl_photos.
SELECT DISTINCT vg.gegevenID, vg.vertrekdatum2, vg.inschrijvingnmr, lvm.luchtvaartmaatschappij, lvm.luchtvaartmaatschappijID, p.*
FROM tbl_vluchtgegevens vg
LEFT JOIN tbl_luchtvaartmaatschappij lvm
ON vg.luchtvaartmaatschappij = lvm.luchtvaartmaatschappijID
LEFT JOIN tbl_photos p
ON lvm.IATACode = p.img_lvm
AND vg.inschrijvingnmr = p.img_nmr
WHERE vg.vertrekdatum2 <=NOW()
ORDER BY vg.vertrekdatum2 DESC
I've tried to do a subquery, too, but I've only done one and I can't get this to work no matter how I rework the code.
SELECT vg.gegevenID, vg.vertrekdatum2, vg.inschrijvingnmr, lvm.luchtvaartmaatschappij, lvm.luchtvaartmaatschappijID, p.*
FROM tbl_vluchtgegevens vg
LEFT JOIN tbl_luchtvaartmaatschappij lvm
ON vg.luchtvaartmaatschappij = lvm.luchtvaartmaatschappijID
( SELECT p.*, lvm.IATACode, lvm.luchtvaartmaatschappijID
FROM tbl_photos p
LEFT JOIN tbl_luchtvaartmaatschappij lvm
ON vg.luchtvaartmaatschappij = lvm.luchtvaartmaatschappijID
ORDER BY RAND()
LIMIT 1 ) pho
WHERE vg.vertrekdatum2 <=NOW() AND vg.luchtvaartmaatschappij = pho.luchtvaartnamatschappij AND vg.inschrijvingnmr = pho.img_nmr
ORDER BY vg.vertrekdatum2 DESC

One way to do it is with a co-related subquery
Query
SELECT
tbl_vluchtgegevens.gegevenID
, tbl_vluchtgegevens.vertrekdatum2
, tbl_vluchtgegevens.luchtvaartmaatschappij
, tbl_vluchtgegevens.inschrijvingnmr
, (
SELECT
tbl_photos.file
FROM
tbl_photos
WHERE
tbl_photos.img_nmr = tbl_vluchtgegevens.inschrijvingnmr
ORDER BY
RAND()
LIMIT 1
) AS `file`
FROM
tbl_vluchtgegevens
WHERE
tbl_vluchtgegevens.vertrekdatum2 <=NOW()
ORDER BY
tbl_vluchtgegevens.vertrekdatum2 DESC
One Possible Result
| gegevenID | vertrekdatum2 | luchtvaartmaatschappij | inschrijvingnmr | file |
|-----------|----------------------|------------------------|-----------------|---------------|
| 2 | 2018-01-01T17:00:00Z | 1702 | PH-AON | somefile5.jpg |
| 1 | 2018-01-01T12:00:00Z | 911 | N803NW | somefile2.jpg |
| 4 | 2017-03-01T17:00:00Z | 911 | N809NW | (null) |
| 3 | 2017-01-17T11:00:00Z | 911 | N853NW | somefile7.jpg |
| 4 | 2016-03-01T17:00:00Z | 1702 | PH-AON | somefile3.jpg |
see demo http://www.sqlfiddle.com/#!9/be9f7/29

Related

MySQL select N latest rows for each product from 3 relational tables

Now i have this code which return latest record for each product. But i don't know how to modify this to get for example 3 latest rows for each product.
I want to compare latest product prices and i need few latest rows of each.
shops
id | shopId
-----------
1 | 2345
2 | 6573
products
id | shopId | title | active | pDateAdded | pDateUpdate
---------------------------------------------------------------------------
18 | 1 | Honda | 1 | 2021-03-07 01:56:34 | 2021-03-07 04:36:34
19 | 2 | Subaru | 1 | 2021-03-07 03:43:34 | 2021-03-08 04:36:34
20 | 1 | VW | 1 | 2021-03-07 07:21:34 | 2021-03-09 04:36:34
21 | 2 | Ford | 0 | 2021-03-07 11:37:34 | 2021-03-10 04:36:34
prices
id | shopId | productId | price | dDateAdded
-----------------------------------------------------
224 | 1 | 18 | 2385 | 2021-03-09 12:39:57
225 | 2 | 19 | 1523 | 2021-03-09 13:14:44
226 | 1 | 20 | 5489 | 2021-03-09 17:32:18
227 | 1 | 18 | 2256 | 2021-03-10 18:22:13
228 | 2 | 19 | 1600 | 2021-03-10 21:33:21
229 | 1 | 20 | 5321 | 2021-03-10 14:15:56
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
231 | 2 | 19 | 1666 | 2021-03-11 17:31:49
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
This command return only 1 latest record from prices table for every product from products table for specific shopId
SELECT s.*, c.*, d.*
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN (
SELECT productId, MAX(dDateAdded) MaxDate
FROM prices
GROUP BY productId
) MaxDates
ON MaxDates.productId = c.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id AND MaxDates.MaxDate = d.dDateAdded
WHERE s.id = ".$shopId."
For example if shopId=1 this command get only that records (I omitted here the data from the other tables that are retrieved):
230 | 1 | 18 | 2137 | 2021-03-11 05:55:25
232 | 1 | 20 | 5001 | 2021-03-11 20:18:01
But i want to get for example 2 latest records for every product where shopId=1, so the records which i want to get:
(shops)id | (shops)shopId | title | active | price | dDateAdded
1 | 2345 | Honda | 1 | 2256 | 2021-03-10 18:22:13
1 | 2345 | Honda | 1 | 2137 | 2021-03-10 14:15:56
1 | 2345 | VW | 1 | 5321 | 2021-03-11 05:55:25
1 | 2345 | VW | 1 | 5001 | 2021-03-11 20:18:01
To select N latest rows needs to allocate row number and to filter by N rows. However, the ROW_NUMBER function is not supported in MySQL 5.7.
So that you need to simulate the ROW_NUMBER function like the follwing:
You can get the desired result by adding subquery with row number to your query like the below:
DB Fiddle
SELECT
s.id,
s.shopId,
c.title,
c.active,
d.price,
d.dDateAdded
FROM shops AS s
LEFT JOIN products AS c ON c.shopId = s.id
LEFT JOIN prices AS d ON d.productId = c.id AND d.shopId = s.id
--
LEFT JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY p1.id, p1.shopId, p1.productId, p1.dDateAdded
) AS w
ON d.id=w.id
--
WHERE
s.id = 1 AND
w.row_num <= 2
DB Fiddle
SELECT
id,
shopId,
productId,
price,
dDateAdded
FROM (
SELECT p1.*,
(
SELECT COUNT(*)+1 FROM prices p2
WHERE
p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
) row_num
FROM prices p1
) p
WHERE
shopId = 1 AND
row_num <= 2
ORDER BY id
DB Fiddle
SELECT p.* FROM prices p
INNER JOIN (
SELECT
p1.id,
COUNT(p2.dDateAdded) + 1 row_num
FROM prices p1 LEFT JOIN prices p2
ON p1.shopId = p2.shopId AND
p1.productId = p2.productId AND
p1.dDateAdded < p2.dDateAdded
GROUP BY
p1.id,
p1.shopId,
p1.productId,
p1.dDateAdded
) w
ON p.id=w.id
WHERE
p.shopId = 1 AND
w.row_num <= 2
ORDER BY p.id
Other way using a variable

Sum is not done if joined table is empty with MySQL

I have the following tables structure and trying to make a report from these:
___BillableDatas
|--------|------------|---------|--------------|------------|
| BIL_Id | BIL_Date |BIL_Rate | BIL_Quantity | BIL_Status |
|--------|------------|---------|--------------|------------|
| 1 | 2018-03-01 | 105 | 1 | charged |
| 2 | 2018-03-01 | 15 | 2 | notcharged |
| 3 | 2018-03-01 | 5 | 1 | notcharged |
|--------|------------|---------|--------------|------------|
___SalesTaxes
|--------|--------------|------------|
| STX_Id | STX_TaxeName | STX_Amount |
|--------|--------------|------------|
| 8 | Tax 1 | 5.000 |
| 9 | Tax 2 | 15.000 |
|--------|--------------|------------|
STX_Amount is a percentage.
___ApplicableTaxes
|-----------|-----------|
| ATX_BILId | ATX_STXId |
|-----------|-----------|
| 1 | 8 |
| 1 | 9 |
|-----------|-----------|
ATX_BILId is the item ID link with ___BillableDatas.
ATX_STXId is the tax ID link with ___SalesTaxes.
I need to get to sum of the items per day
- without tax
- with tax
So mething like this:
|------------------|---------------|------------|
| BIL_RateNonTaxed | BIL_RateTaxed | BIL_Status |
|------------------|---------------|------------|
| 105.00 | 126.00 | charged | <- Taxes #8, #9 applicable
| 35.00 | 35.00 | notcharged | <- No taxes here
|------------------|---------------|------------|
Explications on the totals:
105 = 105*1 -- (total of the charged item multiply by the quantity)
35 = (15*2)+5 -- (total of the notcharged items multiply by the quantity)
126.00 = 105+(105*(5+15)/100)
35.00 = as no taxe, put the non taxed value.
My last try was this one:
SELECT BIL_Status
, SUM(BIL_Rate*BIL_Quantity) BIL_RateNonTaxed
, IFNULL(SUM((BIL_Rate*BIL_Quantity)+(BIL_Rate*BIL_Quantity*total_sales_tax/100)), SUM(BIL_Rate*BIL_Quantity)) BIL_RateTaxed
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY ATX_BILId
) x
GROUP
BY BIL_Status
This query works just when each item has a linked taxe (case of my item #1). When item has no linked taxes (item #2 and #3), the sum is not made.
Please see this SQLFiddle to help you if needed:
http://sqlfiddle.com/#!9/433a3f/2
The only one error with the link is I should have 35 and not 30.
Thanks.
The subquery was grouping by the wrong thing. You are grouping by ATX_BILId, but I think you really wanted to get all the unique billable data bill_ids. At least the following query returns what you expected. The only difference is changing "GROUP BY ATX_BILId" to "GROUP BY BIL_Id"
SELECT BIL_Status
, SUM(BIL_Rate*BIL_Quantity) BIL_RateNonTaxed
, IFNULL(SUM((BIL_Rate*BIL_Quantity)+(BIL_Rate*BIL_Quantity*total_sales_tax/100)), SUM(BIL_Rate*BIL_Quantity)) BIL_RateTaxed
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY BIL_Id
) x
GROUP
BY BIL_Status
Link to SQL Fiddle
In general, when you are troubleshooting this type of query, the first thing to do is examine the returned rows without the group bys. It becomes easier to see the problem when you run the following query.
SELECT BIL_Status,
BIL_Rate,
BIL_Quantity,
total_sales_tax
FROM
( SELECT b.*
, SUM(t.STX_Amount) total_sales_tax
FROM ___BillableDatas b
LEFT JOIN ___ApplicableTaxes bt
ON bt.ATX_BILId = b.BIL_Id
LEFT JOIN ___SalesTaxes t
ON t.STX_Id = bt.ATX_STXId
GROUP
BY ATX_BILId
) x

MySQL - Update table with row number per group

Sample Data
id | order_id | instalment_num | date_due
---------------------------------------------------------
1 | 10000 | 1 | 2010-07-09 00:00:00
2 | 10000 | 1 | 2010-09-06 11:39:56
3 | 10001 | 1 | 2014-04-25 15:46:52
4 | 10002 | 1 | 2010-01-11 00:00:00
5 | 10003 | 1 | 2010-01-04 00:00:00
6 | 10003 | 1 | 2016-05-31 00:00:00
7 | 10003 | 1 | 2010-01-08 00:00:00
8 | 10003 | 1 | 2010-01-06 09:06:26
9 | 10004 | 1 | 2010-01-11 11:25:07
10 | 10004 | 1 | 2010-01-12 07:06:42
Desired Result
id | order_id | instalment_num | date_due
---------------------------------------------------------
1 | 10000 | 1 | 2010-07-09 00:00:00
2 | 10000 | 2 | 2010-09-06 11:39:56
3 | 10001 | 1 | 2014-04-25 15:46:52
4 | 10002 | 1 | 2010-01-11 00:00:00
5 | 10003 | 1 | 2010-01-04 00:00:00
8 | 10003 | 2 | 2010-01-06 09:06:26
7 | 10003 | 3 | 2010-01-08 00:00:00
6 | 10003 | 4 | 2016-05-31 00:00:00
9 | 10004 | 1 | 2010-01-11 11:25:07
10 | 10004 | 2 | 2010-01-12 07:06:42
As you can see, I have an instalment_num column which should show the number/index of each row belonging to the order_id, determined by the date_due ASC, id ASC order.
How can I update the instalment_num column like this?
Additional Notes
The date_due column is not unique, and there may be many ids or order_ids with the exact same timestamp.
If the timestamp is the same for two rows belonging to the same order_id, it should order them by id as a fallback.
I require a query which will update this column.
This is how I would do it:
SELECT a.id,
a.order_id,
COUNT(b.id)+1 AS instalment_num,
a.date_due
FROM sample_data a
LEFT JOIN sample_data b ON a.order_id=b.order_id AND (a.date_due>b.date_due OR (a.date_due=b.date_due AND a.id>b.id))
GROUP BY a.id, a.order_id, a.date_due
ORDER BY a.order_id, a.date_due, a.id
UPDATE version attempt:
UPDATE sample_data
LEFT JOIN (SELECT a.id,
COUNT(b.id)+1 AS instalment_num
FROM sample_data a
JOIN sample_data b ON a.order_id=b.order_id AND (a.date_due>b.date_due OR (a.date_due=b.date_due AND a.id>b.id))
GROUP BY a.id) c ON c.id=sample_data.id
SET sample_data.instalment_num=c.instalment_num
For the numbering to begin with 1:
UPDATE sample_data
LEFT JOIN (SELECT a.id,
COUNT(b.id) AS instalment_num
FROM sample_data a
JOIN sample_data b ON a.order_id = b.order_id AND (a.date_due > b.date_due OR (a.date_due=b.date_due AND a.id + 1 > b.id))
GROUP BY a.id) c ON c.id = sample_data.id
SET sample_data.instalment_num = c.instalment_num
You are trying to achieve what ROW_NUMBER with a partition would do using something like SQL Server or Oracle. You can simulate this with an approriate query:
SELECT t.id, t.order_id,
(
SELECT 1 + COUNT(*)
FROM sampleData
WHERE (date_due < t.date_due OR (date_due = t.date_due AND id < t.id)) AND
order_id = t.order_id
) AS instalment_num,
t.date_due
FROM sampleData t
ORDER BY t.order_id, t.date_due
This query will order the instalment_num by due_date in ascending order. And in the case of a tie in due_date, it will order by the id in ascending order.
Follow the link below for a demo:
SQLFiddle
select
sub.order_id, sub.date_due,
#group_rn:= case
when #group_order_id=sub.order_id then #group_rn:=#group_rn:+1
else 1
end as instalment_num,
#group_order_id:=sub.order_id
FROM (select #group_rn:=0, group_order_id=0) init,
(select *
from the_table
order by order_id, date_due) sub

Using left join with min

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.

how to join two tables using group by order by and limit

I have two tables
tblXYZ
patId | Name | DOB
---------------------------
1 | xyz | 10-05-1986
2 | abc | 12-06-01978
3 | lmn | 12-04-1975
tblABC
apptId | patId | status | otherinfo
-------------------------------------
1 | 1 | single | jmdfh
2 | 1 | sds | dfdf
3 | 2 | fdf | sdwed
4 | 2 | fdf | sdwed
I want join these two table to get result as:
result
patId | apptId | Name | DOB
--------------------------------
1 | 2 | single | jmdfh
2 | 4 | sds | dfdf
3 | null | fdf | sdwed
apptId should be the last entered value from tblABC
try something like that
select patId, apptId, Name, DOB
join -- or left join if you want patId that doesn't have match in the second table
(
select patId AS patIdBis, max(apptId) AS apptId
from tblABC group by patId
)
on patId = patIdBis
order by patId;
If by "last entered" you mean largest apptId, then the following query will do what you want.
SELECT tblXYZ.patId, tblABC.apptId, tblXYZ.name, tblXYZ.DOB
FROM tblXYZ
LEFT JOIN
(
(SELECT patId, MAX(apptId) mx FROM tblABC GROUP BY patId) maxes
INNER JOIN tblABC
ON maxes.patId = tblABC.patId AND maxes.mx = tblABC.apptId
) ON tblXYZ.patId = tblABC.patId;
UPDATE: Valentin Clement's query is shorter and is better if you only need the apptId from the tblABC. If you need any other data from the tblABC, then you need to use the query from my answer.
Use this query to get your result
SELECT x.patid, a.apptid, x.name, x.dob
FROM tblxyz x INNER JOIN tblabc a ON
x.patid=a.patid
patid apptid name DOB
1 1 xyz 1986-10-05 00:00:00.000
1 2 xyz 1986-10-05 00:00:00.000
2 3 abc 1978-12-06 00:00:00.000
2 4 abc 1978-12-06 00:00:00.000