Optimizing custom Wordpress SQL query for fetching usermeta data - mysql

I have the following query and it works. But it takes insanely long to process due to it's buildup. I'm therefor in the need of assistance to get this query faster.
SQL Query
In the query PRODUCT_ID should be replaced by ' ' and the productID number.
SELECT
b.order_id,
customer_meta.meta_value AS customer_id,
users.user_email,
qty_table.meta_value AS qty,
user_meta1.meta_value AS firstname,
user_meta2.meta_value AS lastname,
user_meta3.meta_value AS company,
user_meta4.meta_value AS address,
user_meta5.meta_value AS city,
user_meta6.meta_value AS postcode,
user_meta7.meta_value AS state,
user_meta8.meta_value AS user_phone
FROM
wp_woocommerce_order_itemmeta a,
wp_woocommerce_order_items b,
wp_postmeta customer_meta,
wp_users users,
wp_woocommerce_order_itemmeta qty_table,
wp_usermeta user_meta1,
wp_usermeta user_meta2,
wp_usermeta user_meta3,
wp_usermeta user_meta4,
wp_usermeta user_meta5,
wp_usermeta user_meta6,
wp_usermeta user_meta7,
wp_usermeta user_meta8
WHERE
a.meta_key = '_product_id'
AND a.meta_value = PRODUCT_ID
AND a.order_item_id = b.order_item_id
AND customer_meta.meta_key = '_customer_user'
AND customer_meta.post_id = b.order_id
AND user_meta1.meta_key = 'first_name'
AND user_meta1.user_id = users.id
AND user_meta2.meta_key = 'last_name'
AND user_meta2.user_id = users.id
AND user_meta3.meta_key = 'billing_company'
AND user_meta3.user_id = users.id
AND user_meta4.meta_key = 'billing_address_1'
AND user_meta4.user_id = users.id
AND user_meta5.meta_key = 'billing_city'
AND user_meta5.user_id = users.id
AND user_meta6.meta_key = 'billing_postcode'
AND user_meta6.user_id = users.id
AND user_meta7.meta_key = 'billing_state'
AND user_meta7.user_id = users.id
AND user_meta8.meta_key = 'billing_phone'
AND user_meta8.user_id = users.id
AND users.ID = customer_meta.meta_value
AND qty_table.meta_key = '_qty'
AND qty_table.order_item_id = b.order_item_id
ORDER BY user_meta3.meta_value ASC
I need all the information, since I want to list all the users with their Firstname, lastname, company, address, postcode, etc for a given product which has been bought. So the query in itself works, but is a killer in process time.
I could use max( CASE WHEN ... ... THEN ...END ) as a_name but I only know how to do it successfully if I use a left join.
Any tips on how to get this query to run better?

WP, are you listening? WooCommerce, are you listening? I am getting tired of optimizing your database app.
First and foremost, EAV is a terrible schema design. But I won't rant about that. I'll just point out the index(es) that are probably missing or mal-formed:
wp_usermeta: PRIMARY KEY(user_id, meta_key)
Without any (191) tacked on.
Similarly for wp_woocommerce_order_itemmeta.
I may have more abuse to dish out; please provide SHOW CREATE TABLE for the tables being used.

Years later, after talking to #RickJames, here's another answer to your question.
You have refactored the query from comma-join to LEFT JOINs. That is good. Your comma-join syntax omitted users with missing metadata from the result set.
If you haven't already done so, make sure you upgrade your MySQL server so it's version 5.7 or later. If you use MariaDB, upgrade it so it's version 10.3 or later The version is important for performance.
Install and use the WordPress plugin Index WP MySQL for Speed. Rick and I put it together to address precisely your problem. It reindexes wp_usermeta (and some other WordPress tables) to make these sorts of queries more efficient.
Or, if you prefer not to use a plugin to reindex your tables, you can do it yourself via phpmyadmin or some other SQL client. (These statements won't work if you're stuck on an old version of MySQL. The plugin contains a workaround for old versions that's a bit too complex to explain in a StackOverflow answer.)
ALTER TABLE wp_usermeta ENGINE=InnoDB, ROW_FORMAT=Dynamic;
ALTER TABLE wp_usermeta ADD UNIQUE KEY umeta_id (umeta_id);
ALTER TABLE wp_usermeta DROP PRIMARY KEY;
ALTER TABLE wp_usermeta ADD PRIMARY KEY (user_id, meta_key, umeta_id);
ALTER TABLE wp_usermeta DROP KEY user_id;
ALTER TABLE wp_usermeta DROP KEY meta_key;
ALTER TABLE wp_usermeta ADD KEY meta_key (meta_key, user_id);
This set of indexes allows your queries to retrieve those left-joined meta_value attributes much more efficiently. Why? In brief, the table needs an index on (user_id, meta_key) for the purpose. And in InnoDB, the data in the table is actually stored in the primary key's index (it's called a clustered index). So MySQL can very quickly retrieve the meta_values you need.

Adding the indexes described by Rick James will make the original query much faster.
But I have managed to change it to use LEFT JOIN and this is by far the fastest and is shown here if anybody else would need it:
SELECT
b.order_id,
customer_meta.meta_value as customer_id,
u.display_name as customer_name,
u.user_email,
qty_table.meta_value as qty,
um_last_name.meta_value as last_name,
um_first_name.meta_value as first_name,
um_address1.meta_value as address1,
um_address2.meta_value as address2,
um_address_city.meta_value as city,
um_address_state.meta_value as state,
um_company.meta_value as company,
um_email.meta_value as billing_email
FROM wp_woocommerce_order_itemmeta a
LEFT JOIN wp_woocommerce_order_items b on a.order_item_id = b.order_item_id
LEFT JOIN wp_postmeta customer_meta on customer_meta.post_id = b.order_id AND customer_meta.meta_key = '_customer_user'
LEFT JOIN wp_users u on u.ID = customer_meta.meta_value
LEFT JOIN wp_woocommerce_order_itemmeta qty_table on qty_table.order_item_id = b.order_item_id and qty_table.meta_key = '_qty'
LEFT JOIN wp_usermeta um_last_name on u.ID = um_last_name.user_id and um_last_name.meta_key = 'last_name'
LEFT JOIN wp_usermeta um_first_name on u.ID = um_first_name.user_id and um_first_name.meta_key = 'first_name'
LEFT JOIN wp_usermeta um_address1 on u.ID = um_address1.user_id and um_address1.meta_key = 'billing_address_1'
LEFT JOIN wp_usermeta um_address2 on u.ID = um_address2.user_id and um_address2.meta_key = 'billing_address_2'
LEFT JOIN wp_usermeta um_address_city on u.ID = um_address_city.user_id and um_address_city.meta_key = 'billing_city'
LEFT JOIN wp_usermeta um_address_state on u.ID = um_address_state.user_id and um_address_state.meta_key = 'billing_state'
LEFT JOIN wp_usermeta um_email on u.ID = um_email.user_id and um_email.meta_key = 'billing_email'
LEFT JOIN wp_usermeta um_company on u.ID = um_company.user_id and um_company.meta_key = 'billing_company'
WHERE
a.meta_key = '_product_id' AND a.meta_value = PRODUCT_ID

Related

Retrieving Records from Multiple Tables in MySQL

How to select records from multiple table
Table "users":
user_id|username|email|login|password
Table "users_log"
log_id|user_id|status|timestamp|message
Table "users_balance"
balance_id|user_id|status|amount
Table "users_roles"
role_id|status|role_name
Table "users_role"
role_id|user_id
how to retrieving all records by user email and log status and user role
Output:
log-timestamp|user_id|Username|email|role_name|balance-amount|log-message
Something like this I'm guessing.
SELECT
log.timestamp AS log_timestamp,
u.user_id,
u.username AS user_name,
u.email AS user_email,
role.name AS role_name,
bal.amount AS balance_amount,
log.message AS log_message
FROM users AS u
JOIN users_role AS ur ON ur.user_id = u.user_id
JOIN users_roles AS role ON role.role_id = ur.role_id
JOIN users_log AS log ON u.user_id = log.user_id
LEFT JOIN users_balance AS bal ON bal.user_id = u.user_id
WHERE u.email = #UserEmail
AND role.role_name = #RoleName
AND log.status = #LogStatus
And then set or replace the #UserEmail, #RoleName, #LogStatus variables by what you're looking for.
But depending on the data you might want to add a GROUP BY and an aggregate function like MAX or SUM, and perhaps extra criteria in the WHERE or HAVING clause.
You can use JOIN query to get data from multiple tables
Please try this,
SELECT usr.*,usrlg.*,usrbl.*,usrrl.*,usrrls.* FROM users AS usr JOIN users_log AS usrlg ON(usrlg.user_id=usr.user_id) JOIN users_balance AS usrbl ON (usrbl.user_id=usr.user_id)
JOIN users_role AS usrrl ON(usrrl.user_id=usr.user_id) JOIN users_roles AS usrrls ON(usrrls.role_id=usrrl.role_id)
WHERE usr.email='value' AND usrlg.status='value' AND usrrls.role_name='value'
You can do it through joins. Can refer below links.
https://www.guru99.com/joins.html
http://www.mysqltutorial.org/mysql-join/
https://www.tutorialspoint.com/mysql/mysql-using-joins.htm

Table MYSQL query

I have used WPAllImport to import data from a CSV-file to Advanced Custom Fields.
I now want to put them back together with a SQL query, but dont know how to do it.
I've tried WPDataTables, but when I choose 5 or more tables, WPDataTables stops.
If I pick 2, I get this code
SELECT posts_podukter.post_title AS podukter_post_title,
podukter_meta_produkter_0_pris_tbl.meta_value AS podukter_meta_produkter_0_pris
FROM beta_h3L_posts AS posts_podukter
INNER JOIN (SELECT podukter_meta_produkter_0_pris_tbl_posts.ID as id, meta_value, meta_key FROM beta_h3L_postmeta AS podukter_meta_produkter_0_pris_tbl_postmeta INNER JOIN beta_h3L_posts AS podukter_meta_produkter_0_pris_tbl_posts ON podukter_meta_produkter_0_pris_tbl_postmeta.post_id = podukter_meta_produkter_0_pris_tbl_posts.ID AND podukter_meta_produkter_0_pris_tbl_posts.post_type = 'podukter') AS podukter_meta_produkter_0_pris_tbl
ON podukter_meta_produkter_0_pris_tbl.meta_key = 'produkter_0_pris' AND podukter_meta_produkter_0_pris_tbl.id = posts_podukter.ID
WHERE 1=1
AND posts_podukter.post_type = 'podukter'
I think this is too much code.
Can someone help me to get on the right way.... :-)
This is what the table should look like
Here is a capture how the table should look like
I would agree that this is "too much code" which sounds sort of ridiculous, but in this case totally applies. That SQL statement that was produced could be written as:
SELECT
post.post_title as podukter_post_title,
postmeta.meta_value as podukter_meta_produkter_0_pris
FROM beta_h3L_posts AS posts
INNER JOIN beta_h3L_postmeta AS postmeta
ON postmeta.post_id = post.ID
AND postmeta.meta_key = 'produkter_0_pris'
WHERE posts.post_type = 'podukter'
If there is another metavalue that you need you can join again to your meta table:
SELECT
post.post_title as podukter_post_title,
postmeta.meta_value as podukter_meta_produkter_0_pris,
postmeta2.meta_value as tilbudspris
FROM beta_h3L_posts AS posts
INNER JOIN beta_h3L_postmeta AS postmeta
ON postmeta.post_id = post.ID
AND postmeta.meta_key = 'produkter_0_pris'
INNER JOIN beta_h3L_postmeta AS postmeta2
ON postmeta.post_id = post.ID
AND postmeta2.meta_key = 'tilbudspris'
WHERE posts.post_type = 'podukter'
I don't know what any of these words mean (besides post and postmeta) so I'm just going to assume that this is right/helpful.
The only thing is that you may want to switch to using a LEFT OUTER JOIN to your postmeta table just in case the meta_key you are after doesn't exist for the post.id you are querying. In that case, with an INNER JOIN the id/post will be dropped from the result set where a LEFT OUTER JOIN will show the id/post record with a blank for whatever that corresponding meta_value is that you are joining in.

Performance problems with an ordered view in MySQL

I am new to performance tuning in MySQL & need your help concerning a view that will replace a table later-on in our design.
The table to be replaced is called users and has the following attributes:
The users2 view has the following attributes:
When I execute a normal SELECT on both objects, they respond at the same time:
SELECT *
FROM `users`
SELECT *
FROM `users2`
But an ordered version of these queries result in a different performance: The table is a little slower (takes less than two seconds), the view need about ten times this time:
SELECT *
FROM `users`
ORDER BY `lastName`, `firstName`
SELECT *
FROM `users2`
ORDER BY `lastName`, `firstName`
To find out the reason, I let EXPLAIN the two comments:
Obviously, an ALL on table 'a' (addresses) on the attribute Countries_ID is making trouble, so I made the following:
ALTER TABLE addresses ADD INDEX (Countries_ID);
This index didn't change anything at all. So, I ask you for your opinion what can be done better.
Notice 1: Is there a way to create an index on temporary column Countries_ID_2?
Notice 2: The users2 view was created with the following SQL query:
CREATE OR REPLACE VIEW users2 AS
(SELECT p.username
, p.password
, p.firstName
, p.lastName
, p.eMail AS email
, a.settlement AS city
, s.name AS country
, pl.languages
, p.description
, p.ID AS ID
, p.phone1
, p.phone2
, CONCAT_WS(' ', a.street, a.addition) AS address
, p.status
, p.publicMail
, ad.name AS Betreuer
FROM addresses a
INNER JOIN addresses_have_persons ap ON a.ID = ap.Addresses_ID
INNER JOIN countries c ON a.Countries_ID = c.ID
INNER JOIN persons p ON a.ID = p.addressID
AND ap.Persons_ID = p.ID
INNER JOIN states s ON a.States_ID = s.ID
INNER JOIN persons_language pl ON p.ID = pl.ID
LEFT JOIN advisors ad ON p.advisorID = ad.ID
-- LEFT JOIN titles t ON t.ID = ad.titleID
);
Notice 3: Although a lot of fields in the persons table are NULL, there is not a single row where these fields are altogether NULL.
EDIT:
CREATE OR REPLACE VIEW persons_language AS
(SELECT lp.Persons_ID AS ID
, GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
FROM languages l
, languages_have_persons lp
WHERE l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID);
Without the ORDER BY, the language names are not alphabetically ordered, which I currently want. Perhaps, we could decide to get them in any order, but we'll see.
Currently, I made the following modifications without any performance improvement:
ALTER TABLE addresses ADD INDEX (Countries_ID);
ALTER TABLE addresses ADD INDEX (States_ID);
ALTER TABLE addresses_have_persons ADD INDEX (Addresses_ID);
ALTER TABLE languages ADD INDEX (name);
ALTER TABLE persons ADD INDEX (addressID);
ALTER TABLE persons ADD INDEX (address2ID);
ALTER TABLE persons ADD INDEX (address3ID);
ALTER TABLE persons ADD INDEX (advisorID);
EDIT 2:
I discuss this issue also on another site. The discussions there let me do the following changes to be nearer to the third normal form:
CREATE OR REPLACE TABLE accounts
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
, username VARCHAR(50) NOT NULL UNIQUE
, password VARCHAR(255) NOT NULL
, eMail VARCHAR(100) NOT NULL
, Persons_ID INT NOT NULL
);
INSERT INTO accounts (username, password, eMail, Persons_ID)
SELECT username, password, eMail, ID
FROM persons;
The table persons does contain only the most necessary things and has the following structure now:
The new table persons_information carries all additional information:
I recreated the users2 with the following command:
CREATE OR REPLACE VIEW users2 AS
(SELECT ac.username
, ac.password
, p.firstName
, p.lastName
, ac.eMail AS email
, adr.settlement AS city
, s.name AS country
, pl.languages
, pi.description
, ac.Persons_ID AS ID
, pi.phone1
, pi.phone2
, CONCAT_WS(' ', adr.street, adr.addition) AS address
, p.status
, pi.publicMail
, adv.name AS Betreuer
FROM accounts ac
INNER JOIN persons p ON ac.Persons_ID = p.ID
INNER JOIN persons_information pi ON p.ID = pi.ID
INNER JOIN addresses adr ON adr.ID = pi.addressID
INNER JOIN addresses_have_persons ap ON adr.ID = ap.Addresses_ID
AND ap.Persons_ID = p.ID
INNER JOIN countries c ON adr.Countries_ID = c.ID
INNER JOIN states s ON adr.States_ID = s.ID
INNER JOIN persons_language pl ON p.ID = pl.ID
LEFT JOIN advisors adv ON pi.advisorID = adv.ID
-- LEFT JOIN titles t ON t.ID = adv.titleID
);
The SELECT _ FROM users2 is fast, but if I add an ORDER BY lastName, firstName, it takes about 25 seconds to get the response.
Here are the results of the *EXPLAIN SELECT * FROM users2* command:
And here for the other command:
I also (re)created following indexes:
ALTER TABLE addresses ADD INDEX (Countries_ID);
ALTER TABLE addresses ADD INDEX (States_ID);
ALTER TABLE addresses_have_persons ADD INDEX (Persons_ID);
ALTER TABLE languages ADD INDEX (name);
ALTER TABLE persons_information ADD INDEX (addressID);
ALTER TABLE persons_information ADD INDEX (address2ID);
ALTER TABLE persons_information ADD INDEX (address3ID);
ALTER TABLE persons_information ADD INDEX (advisorID);
I think one reason for the problem is the persons_language view that is created as follows:
CREATE OR REPLACE VIEW persons_language AS
(SELECT lp.Persons_ID AS ID
, GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
FROM languages l
INNER JOIN languages_have_persons lp ON l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID);
EDIT 3:
For those interested, I add the EXPLAIN for the persons_language view:
EDIT 4:
After the database meeting today, we decided to delete all objects related to the address information & recreated the view with
CREATE OR REPLACE VIEW `users2` AS
(SELECT ac.username
, ac.password
, p.firstName
, p.lastName
, ac.eMail AS email
, pl.languages
, pi.description
, ac.Persons_ID AS ID
, pi.phone1
, pi.phone2
, p.status
, pi.publicMail
, adv.name AS Betreuer
FROM accounts ac
INNER JOIN persons p ON ac.Persons_ID = p.ID
INNER JOIN persons_information pi ON p.ID = pi.ID
INNER JOIN persons_language pl ON p.ID = pl.ID
INNER JOIN advisors adv ON pi.advisorID = adv.ID
WHERE ac.password IS NOT NULL
);
I also created an index with
CREATE INDEX LanguagesPersonsIndex ON `languages_have_persons` (`Languages_ID`, `Persons_ID`);
The EXPLAIN command shows that the new indices are in use and that the delay after a SELECT with an ORDER BY clause with the new, smaller view is about 18 s. Here is the new result:
My question is: What could I do more to improve the performance?
The key fault must be the problem.
But depending on data volume on the joined tables, it'll anyway be slower.
Try To:
Implement KeyIndexes on ALL attributes used to stablish relationships. (ap.Addresses_ID, a.Countries_ID, p.addressID, ap.Persons_ID, a.States_ID, p.advisorID).
Declare PK on All 'ID' columns.
Don't use ORDER or GROUP in the views construction.
Declare Key Index for attributes that are most used on searches, ordering or grouping.
Tip: The 'INNER' (INNER JOIN) isn't necessary. Is the same of 'JOIN'
Your VIEW "persons_language" would be better like this:
SELECT lp.Persons_ID AS ID, GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
FROM languages_have_persons lp
JOIN languages l ON l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID;
It's more appropriate because the clauses 'FROM' and 'JOIN' are processed before 'WHERE' clause.
You may boost your mysql memory and cache configurations.
Look the my mysql server's configurations (Runs an ERP with weight tables and views):
join_buffer_size= 256M
key_buffer = 312M
key_buffer_size = 768M
max_allowed_packet = 160M
thread_stack = 192K
thread_cache_size = 8
query_cache_limit = 64M
innodb_buffer_pool_size = 1512M
table_cache = 1024M
read_buffer_size = 4M
query_cache_size = 768M
query_cache_limit = 128M
SELECT *
FROM `users`
ORDER BY `lastName`, `firstName`
needs
INDEX(last_name, first_name) -- in that order
Beware of VIEWs; some VIEWs optimize well, some do not.
Please provide SHOW CREATE TABLE for both addresses and addresses_have_persons.
In persons_language, why do you need DISTINCT? Doesn't it have PRIMARY KEY(person, language) (or in the opposite order)? Let's see SHOW CREATE TABLE.
Please provide the EXPLAIN for any query you want to discuss.

how to optimize the mysql Query

how to increase the performance of this mysql query
SELECT '' AS sharedid,
hubber_posts.userID AS postowner,
hubber_posts.*,
'' AS sharedby,
hubber_posts.userID AS userID,
hubber_posts.posted_date AS DATE,
'' AS sharebyusr,
'' AS sharebyusrimg,
Concat_ws(' ', firstname, lastname) AS fullname,
username AS postedBy,
hubber_user.image,
hubber_user.gender AS gender,
(SELECT accounttype
FROM hubber_user_security us
WHERE hubber_user.ID = us.userID
AND hubber_posts.userID = us.userID) AS accounttype,
'' AS sharebyusrtype
FROM hubber_posts
INNER JOIN hubber_user
ON hubber_posts.userID = hubber_user.ID
WHERE hubber_posts.status = 1
Your example code has a correlated subquery. MySQL performs poorly with those, as of late 2016.
Try converting it to a JOINed table.
SELECT all that stuff,
us.accounttype
FROM hubber_posts
JOIN hubber_user ON hubber_posts.userID = hubber_user.ID
LEFT JOIN hubber_user_security us ON hubber_user.ID = us.userID
WHERE hubber_posts.status = 1
I used LEFT JOIN. Without the LEFT, any rows without a matching entry in that table will be suppressed from the result set.
You query is essentially this:
SELECT . . .
(SELECT accounttype
FROM hubber_user_security us
WHERE u.ID = us.userID AND
p.userID = us.userID
) AS accounttype,
. . .
FROM hubber_posts p INNER JOIN
hubber_user u
ON p.userID = u.ID
WHERE p.status = 1 ;
For this query, the optimal indexes are:
hubber_posts(status, userId)
hubber_user(Id)
hubber_user_security(userId)
I would note that the subquery has an extra correlation condition that is not necessary -- the user ids are already equal. And, you run the risk of getting an error if there are multiple account types.
You may intend:
(SELECT GROUP_CONCAT(accounttype)
FROM hubber_user_security us
WHERE u.ID = us.userID
) as accounttypes,
My suggestion is to support a join where hubber_posts is the base table and the 2 other tables are joined using nested loops.
No need to index hubber_posts for the join.
hubber_user.ID should be a PK.
hubber_user_security.userID should be indexed (and defined as a FK references hubber_user.ID).
As for the WHERE clause - only if you have relatively few rows where hubber_posts.status = 1 then you should add an index on hubber_posts.status
P.s.
since the join contain the condition -
ON hubber_posts.userID = hubber_user.ID
There is no need to compare both hubber_posts.userID and hubber_user.ID to us.userID

Join and nullable values

I have three tables in my MySql database :
Vehicle
Id_vehicle_pk( int,auto-increment,pk)
id_driver_fk(varchar,fk, nullable)
User
Id_user_pk(varchar,pk)
mail(varchar)
Collaborator
Id_coll_pk(int,pk)
Id_user_fk(varchar,fk)
First_Name(varchar)
Last_Name(varchar)
I'd like to create a view which join these three tables, so i try this
select Id_vehicle_pk, id_driver_fk,mail, Id_coll_pk, First_Name,Last_Name
from
Vehicle join User join
Collaborator
where
`Vehicle`.`id_driver_fk` = `User`.`Id_user_pk`
and `Collaborator`.`Id_user_fk` = `User`.`Id_user_pk`
group by Id_vehicle_pk
Having Id_vehicle_pk> 0
But I have wrong result caused by null values of id_driver_fk. So I need to know :
How can I fix my query?
I think you are looking for the LEFT JOIN clause which allows to get all records from the main table even if there are no foreign key matches to other tables:
SELECT
v.Id_vehicle_pk,
v.id_driver_fk,
u.mail,
c.Id_coll_pk,
c.First_Name,
c.Last_Name
FROM Vehicle v LEFT JOIN
User u ON v.id_driver_fk = u.Id_user_pk LEFT JOIN
Collaborator c ON c.Id_user_fk = u.Id_user_pk
WHERE Id_vehicle_pk> 0
Also it is a best practice to put join condition inside the join rather than WHERE clause.
You are missing the on clause in both of your joins.
select Id_vehicle_pk,
id_driver_fk,
mail, Id_coll_pk,
First_Name,
Last_Name
from Vehicle
join User on id_driver_fk = Id_user_pk
join Collaborator on Id_user_pk = Id_user_fk
group by Id_vehicle_pk
Having Id_vehicle_pk> 0