grouping relationships of many-to-many-to many - mysql

I have a requirement to map a tax rate to a person based on the country the person was resident at that time.
tbl: person
| p_id | name_first | name_last |
=======++========================
| 1 | john | smith |
| 2 | joanne | smyth |
tbl: person_in_country
| p_id | iso | arrived |
===========================
| 1 | GB | 1980-01-01 |
| 2 | FR | 1987-03-21 |
| 1 | FR | 2003-06-17 |
| 1 | JP | 2008-07-02 |
| 2 | GB | 2008-10-01 |
| 1 | GB | 2009-01-10 |
tbl: country
| iso | ctry_name |
========================
| GB | United Kingdom |
| FR | France |
| JP | Japan |
tbl: tax_rates
| iso | tax_rate | tax_date |
===============================
| GB | 17.5 | 1970-01-01 |
| FR | 15.0 | 1977-03-21 |
| JP | 12.0 | 1977-06-17 |
| FR | 15.0 | 1994-03-21 |
| JP | 18.5 | 2008-07-02 |
| GB | 15 | 2008-04-01 |
| GB | 20 | 2010-05-01 |
So I need tuples containing the person in the country and the tax rate that they should have at a given time..
Something along the lines of:
select p.p_id, p.name_first, p.name_last,
pic.arrived,
c.iso, c.ctry_name,
t.tax_rate
from people p
left join (select * from person_in_country order by arrived desc) pic using (p_id)
left join country c on c.iso = pic.iso
left join (select * from tax_rates order by tax_date desc) t on t.iso = c.iso
where t.tax_date <= NOW()
group by p.pid, pic.arrived, t.tax_date
Hope this make sense... and many thanks in advance

Actually, you have to do a query basicly in three steps. First one you are going to retrieve a kind of "raw data" with all desired columns, joining the relating tables, whatever these columns will be used to join or to retrieve choosen data.
After that, you have to group data in order to turn only last dates from the matching join.
Finally, you have to query again tax table to retrieve tax in the tax date current at the moment of arrival.
It is possible that there is a more easy or ellegant way to do so, but this query is working. Check your system performance depending on the query demmands. It seems a bit hard at a first glance, but it isn't when taking a more carefull look. The SQL code:
SELECT
c02.iso,
c02.p_id,
c02.name_first,
c02.name_last,
c02.ctry_name,
c02.arrived,
c02.mtax_date,
tax_rates.tax_rate
FROM (
SELECT
c01.iso,
c01.p_id,
c01.name_first,
c01.name_last,
c01.ctry_name,
c01.arrived,
Max(c01.tax_date) AS mtax_date
FROM (
SELECT
country.iso,
person.p_id,
person.name_first,
person.name_last,
country.ctry_name,
person_in_country.arrived,
tax_rates.tax_date
FROM
tax_rates
INNER JOIN (
country
INNER JOIN (
person
INNER JOIN
person_in_country
ON
person.p_id = person_in_country.p_id
)
ON
country.iso = person_in_country.iso
)
ON
tax_rates.iso = person_in_country.iso
GROUP BY
country.iso,
person.p_id,
person.name_first,
person.name_last,
country.ctry_name,
person_in_country.arrived,
tax_rates.tax_date
HAVING (((tax_rates.tax_date)<=[arrived]))
) as c01
GROUP BY
c01.iso,
c01.p_id,
c01.name_first,
c01.name_last,
c01.ctry_name,
c01.arrived
) as c02
INNER JOIN
tax_rates ON (
c02.mtax_date = tax_rates.tax_date
)
AND
(
c02.iso = tax_rates.iso
);
Output:
iso p_id name_first name_last ctry_name arrived mtax_date tax_rate
GB 1 john smith United Kindom 01/01/1980 01/01/1970 18
FR 2 joanne smyth France 21/03/1987 21/03/1977 15
FR 1 john smith France 17/06/2003 21/03/1994 15
JP 1 john smith Japan 02/07/2008 02/07/2008 18
GB 1 john smith United Kindom 10/01/2009 01/04/2008 15
GB 2 joanne smyth United Kindom 01/10/2008 01/04/2008 15

Related

How can I group by latest dates and IDs, yet take into account all data from previous dates?

So my example table is like this -
I have a mysql version 5.7 database which I can connect to. Read-only rights.
My table goes like this:
human_id | dog_id | dog_bought_at | amount_paid_for_dog | purchase_place | buyer_has_criminal_past
1 | 1 | 27-12-2019 | 100 | Tokyo | 0
1 | 2 | 03-01-2020 | 200 | Moscow | 0
2 | 3 | 03-01-2020 | 200 | Los Angeles | 0
3 | 4 | 03-01-2020 | 50 | Washington | 0
3 | 3 | 05-01-2020 | 30 | Dallas | 0
4 | 2 | 06-01-2020 | 150 | Texas | 1
What I need to show is this:
dog_id | last_owner_id | total_amount_paid_for_dog | last_purchase_date | last_purchase_place
1 | 1 | 100 | 27-12-2019 | Tokyo
2 | 4 | 350 | 06-01-2020 | Moscow
3 | 3 | 230 | 05-01-2020 | Dallas
4 | 3 | 50 | 03-01-2020 | Washington
Last_purchase_place is shown only for those humans, which do not have criminal past.
what I have tried:
SELECT
e.dog_id
,MAX(e.human_id) last_owner_id
,SUM(e.amount_paid_for_dog) total_amount_paid_for_dog
,MAX(e.dog_bought_at) last_purchase_date
,e_filter.purchase_place last_purchase_place
FROM example e
LEFT JOIN (
SELECT
dog_id
,dog_bought_at
,purchase_place
,human_id
FROM example
WHERE buyer_has_criminal_past != 1
) e_filter ON e.dog_id = e_filter.dog_id AND e.dog_bought_at = e_filter.dog_bought_at
But I am stuck on the logic, that allows to sum up ALL amounts, yet filter out unneeded values.
This is my first question here, so if this is a duplicate or not well written, please say it. Any help appreciated.
SELECT e1.dog_id,
e1.human_id last_owner_id,
sq1.total_amount_paid_for_dog,
e1.dog_bought_at last_purchase_date,
e2.purchase_place last_purchase_place
FROM example e1
JOIN ( SELECT dog_id,
MAX(dog_bought_at) dog_bought_at,
SUM(amount_paid_for_dog) total_amount_paid_for_dog
FROM example
GROUP BY dog_id ) sq1 ON e1.dog_id = sq1.dog_id
AND e1.dog_bought_at = sq1.dog_bought_at
LEFT JOIN example e2 ON e1.dog_id = e2.dog_id
JOIN ( SELECT dog_id,
MAX(dog_bought_at) dog_bought_at
FROM example
WHERE buyer_has_criminal_past = 0
GROUP BY dog_id ) sq2 ON e2.dog_id = sq2.dog_id
AND e2.dog_bought_at = sq2.dog_bought_at
fiddle

How group primary keys with different value but same name and using datediff

So I have three tables:
Guest can make a reservation and in the reservation table you can see what kind of reservation the guest made based on the primary key 'pID'. I want to show only the reservation of luxe rooms and the amount of days the guest spends there. I don't even know what I am doing is the right way. I hope someone can help me.
reservation:
pID |begindate | enddate |
------------------------------------------------------
COD12 | 2014-07-15 | 2014-07-18 |
COD400 | 2014-07-20 | 2014-07-21 |
KOD12 | 2014-07-01 | 2014-07-07 |
Luxe room table:
pID |city |
---------------------------------
COD12 | Corona |
COD400 | Corona |
KOD12 | Kentucky |
Small room table:
pID |city |
---------------------------------
COD600 | Corona |
MOD10 | Madrid |
KOD20 | Kentucky |
What I want:
Amount of days spent from guest in Luxe rooms, note that Corona has two rooms:
city |amountofdays |
---------------------------------
Corona | 4 |
Kentucky | 6 |
What I tried to do:
SELECT datediff(reservation.enddate,reservation.begindate) as amountofdays, luxeroom.city FROM reservation
INNER JOIN luxeroom ON reservation.pID = luxeroom.pID
GROUP BY luxeroom.pID, luxeroom.city;
this resulted in:
city |amountofdays |
---------------------------------
Corona | 3 |
Corona | 1 |
Kentucky | 6 |
You need group by and sum
SELECT sum(datediff(a.enddate, a.begindate)) as amountofdays, b.city
FROM reservation a
JOIN luxeroom b ON a.pID = b.pID
GROUP BY b.city;
Fix the group by:
SELECT sum(datediff(r.enddate, r.begindate)) as amountofdays, l.city
FROM reservation r JOIN
luxeroom l
ON r.pID = l.pID
GROUP BY l.city;
You want one room per city, so the GROUP BY should only include city.

INNER and LEFT JOIN on mysql

I have the needing to chain some joins queries, and I am not able to get the result that I want.
I have many tables to save some product info:
-Products: Name and description in many languages.
-Products_attributes: Some common attributes of the products like the provider or the buying price.
-Products_locations: Info concerning the locations that sell a certain product, like stock, or the selling price.
-Other important table is the Companies one: some of this companies could be a provider.
Well, this is my query:
SELECT p.id
, p.name nameProduct
, p.reference refProduct
, a.buy_price priceProduct
, l.active
, l.sell_price
, l.stock stockProduct
, c.title p_name
FROM products p
LEFT JOIN products_location l
ON l.product_reference = p.reference
AND l.active = 1
AND l.location_id = 4
JOIN products_attributes a
ON a.product_reference = p.reference
AND p.lang = 'es'
AND a.provider = 6
JOIN companies c
ON a.provider = c.id
AND c.id = 6
What I want to do is get all products of a certain provider, if the location from where is executing the query has this product, the row of the result must return too the concerning at this product<->location, in contrary it must return NULL in the column related to this relation.
At the moment, with this query, all what I am getting is the products of a provider where has a relation between the product<->location (through products_location table).
Any way to do that??
Thank you.
EDIT
An example about what I trying to get could be something like this:
TABLE: Companies
id | Title
1 | SomeName
6 | ProviderName
TABLE: Products
id | reference | name | lang
1 | 11111 | 1_es | es
2 | 11111 | 1_en | en
3 | 22222 | 2nam | es
4 | 33333 | 3nam | es
5 | 44444 | 4nam | es
6 | 55555 | 5nam | es
TABLE: Products_atributte
id | product_reference | buy_price | provider
1 | 11111 | 10 | 6
1 | 22222 | 15 | 6
1 | 33333 | 20 | 6
1 | 44444 | 12 | 1
1 | 55555 | 13 | 1
TABLE: Products_locations
id | product_reference | location_id | sell_price | stock | active
1 | 11111 | 4 | 26 | 10 | 1
1 | 11111 | 5 | 25 | 13 | 1
1 | 22222 | 5 | 20 | 13 | 1
1 | 44444 | 5 | 21 | 1 | 1
1 | 55555 | 5 | 22 | 2 | 1
AND THE RESULT MUST BE SOMETHING LIKE THIS:
nameProduct | refProduct | priceProduct | active | sell_price | stockProduct | p_name
1_es | 11111 | 10 | 1 | 26 | 10 | ProviderName
2nam | 22222 | 15 | NULL | NULL | NULL | ProviderName
3nam | 33333 | 20 | NULL | NULL | NULL | ProviderName
If I use a LEFT JOIN only in the products_locations table, I donĀ“t get the two last rows, and if I use LEFT JOIN with all the tables I get duplicates product references, also I get products provided by other providers (in the example 1-> SomeName).
You were correct to LEFT JOIN the products and products_location tables. However, you used INNER JOIN for the other two tables in the query and I believe that this may be the reason why you are only seeing records which have a relation between product and location. The logic would be that a product which does not have a location also does not have an entry in, for example, the products_attributes table. Hence, the non matching records you want to retain would be filtered off downstream by an INNER JOIN. To remedy this, use LEFT JOIN everywhere:
SELECT products.id,
products.name AS nameProduct,
products.reference AS refProduct,
products_attributesbuy_price AS priceProduct,
products_location.active,
products_location.sell_price,
products_location.stock AS stockProduct,
provider.title AS p_name
FROM products
LEFT JOIN products_location
ON products_location.product_reference = products.reference AND
products_location.active = 1 AND
products_location.location_id = 4
LEFT JOIN products_attributes
ON products_attributes.product_reference = products.reference AND
products.lang = 'es' AND
products_attributes.provider = 6
LEFT JOIN companies AS provider
ON products_attributes.provider = provider.id AND
provider.id = 6

Combine information from 3 tables in one table (MySQL)

I have three tables like this
couples
+----------+-------------+---------------+-----------------+
|couple_id | clubname | agegroup | group |
+----------+-------------+---------------+-----------------+
| 36 | club_1 | adults | C |
| 37 | club_2 | youth | A |
+----------+-------------+---------------+-----------------+
users:
+----------+-------------+---------------+
|couple_id | firstname | lastname |
+----------+-------------+---------------+
| 36 | andy | hort |
| 36 | katrin | wilson |
| 37 | hans | gertes |
| 37 | julia | menz |
+----------+-------------+---------------+
locations for training:
+----------+-------------+
|couple_id | location |
+----------+-------------+
| 36 | Paris |
| 37 | Berlin |
| 37 | Paris |
+----------+-------------+
The resulting table should look like this:
+---------+------------------------+--------+-----+----------------+
|couple_id| couple |agegroup|group|location |
+---------+------------------------+--------+-----+----------------+
| 36 |andy hort, katrin wilson| adults | C | Paris |
| 37 |hans gertes, julia menz | youth | A | Paris, Berlin |
+---------+------------------------+--------+-----+----------------+
Is there an elegant query to join these information in one query?
You should use group_concat with distinct:
select c.couple_id,
group_concat(distinct concat(u.firstname, " ", u.lastname)) couple,
c.agegroup,
c.groupd,
group_concat(distinct l.location) location
from couple c
join users u on c.couple_id = u.couple_id
join locations l on c.couple_id = l.couple_id
group by c.couple_id
If you have records in the couple table that may not exist in the users or locations table, then you may need to use an outer join instead.
#spencer7593 makes a great point -- you can move the aggregation to subqueries to include potential duplicates being removed by distinct:
select c.couple_id,
u.couple,
c.agegroup,
c.groupd,
l.location
from couple c
join (
select couple_id,
group_concat(concat(firstname, " ", lastname)) couple
from users
group by couple_id
) u on c.couple_id = u.couple_id
join (
select couple_id,
group_concat(location) location
from locations
group by couple_id
) l on c.couple_id = l.couple_id

MySQL join same table twice on same column with different value returning most recent row only

I'm stuck trying to solve a small part of what is otherwise a complex JOIN.
We have an 'instructions' table and an 'estimates' table. In the 'estimates' we have multiple rows for different types of estimates for a given instruction.
Instructions Table
id | address | status
1 | 27 TAYLOR ROAD, ALBION PARK NSW 2527 | InProgress
Estimates Table
id | instruction_id | basis | basis_date | basis_value
1 | 1 | ContractPrice | 2012-04-05 | 124000
2 | 1 | CAMV | 2012-02-01 | 120000
3 | 1 | CustomerEstimate | 2012-06-07 | 132000
4 | 1 | ContractPrice | 2013-01-03 | 140000
5 | 1 | CustomerEstimate | 2013-02-09 | 145000
What we want is actually 2 joins of 'instructions' on 'estimates' based on instructions.id = estimates.instruction_id and estimates.basis for 1) the most recent 'CustomerEstimate' (aliasing basis_date and basis_value as estimate_date and estimate_value) and 2) most recent 'ContractPrice' (again, aliasing basis_date and basis_value as contact_date and contract_value).
The intended result would be as follows;
id | address | status | contract_price | contract_date | estimate_date | estimate_value
1 | 27 TAYLOR ROAD, ALBION PARK NSW 2527 | InProgress | 2013-01-03 | 140000 | 2013-02-09 | 145000
I would really appreciate some assistance from the SQL gurus out there.
Many thanks,
Trent.
Try
SELECT i.id,
i.address,
i.status,
p.max_date contract_date,
p.basis_value contract_price,
e.max_date estimate_date,
e.basis_value estimate_value
FROM Instructions i LEFT JOIN
(
SELECT q1.instruction_id, max_date, basis_value
FROM Estimates e JOIN
(
SELECT instruction_id, MAX(basis_date) max_date
FROM Estimates
WHERE basis = 'CustomerEstimate'
GROUP BY instruction_id
) q1 ON e.instruction_id = q1.instruction_id AND e.basis_date = q1.max_date
) e ON i.id = e.instruction_id LEFT JOIN
(
SELECT q2.instruction_id, max_date, basis_value
FROM Estimates e JOIN
(
SELECT instruction_id, MAX(basis_date) max_date
FROM Estimates
WHERE basis = 'ContractPrice'
GROUP BY instruction_id
) q2 ON e.instruction_id = q2.instruction_id AND e.basis_date = q2.max_date
) p ON i.id = p.instruction_id
Output:
| ID | ADDRESS | STATUS | CONTRACT_PRICE | CONTRACT_DATE | ESTIMATE_VALUE | ESTIMATE_DATE |
----------------------------------------------------------------------------------------------------------------------------
| 1 | 27 TAYLOR ROAD, ALBION PARK NSW 2527 | InProgress | 140000 | 2013-01-03 | 145000 | 2013-02-09 |
Here is SQLFiddle demo.
What is the contract_price here?
You can try the below
select inst.id
, inst.address
, inst.status
, est.basis_value as estimate_value
, est.basis_date as estimate_date
from instructions inst
, estimates est
where inst.id=est.instruction_id
and (est.basis='CustomerEstimate' or est.basis='ContractPrice')
order
by est.basis
, est.basis_date desc;