SELECT using three tables w/ 1000+ entries - mysql

For transaction listing I need to provide the following columns:
log_out.timestamp
items.description
log_out.qty
category.name
storage.name
log_out.dnr ( Representing the users id )
Table structure from log_out looks like this:
| id | timestamp | storageid | itemid | qty | categoryid | dnr |
| | | | | | | |
| 1 | ........ | 2 | 23 | 3 | 999 | 123 |
As one could guess, I only store the corresponding ID's from other tables in this table. Note: log_out.id is the primary key in this table.
To get the the corresponding strings, int's or whatever back, I tried two queries.
Approach 1
SELECT i.description, c.name, s.name as sname, l.*
FROM items i, categories c, storages s, log_out l
WHERE l.itemid = i.id AND l.storageid = s.id AND l.categoryid = c.id
ORDER BY l.id DESC
Approach 2
SELECT log_out.id, items.description, storages.name, categories.name AS cat, timestamp, dnr, qty
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id
ORDER BY log_out.id DESC
They both work fine on my developing machine, which has approx 99 dummy transactions stored in log_out. The DB on the main server got something like 1100+ tx stored in the table. And that's where trouble begins. No matter which of these two approaches I run on the main machine, it always returns 0 rows w/o any error *sigh*.
First I thought, it's because the main machine uses MariaDB instead of MySQL. But after I imported the remote's log_out table to my dev-machine, it does the same as the main machine -> return 0 rows w/o error.
You guys got any idea what's going on ?

If the table has the data then it probably has something to do with JOIN and related records in corresponding tables. I would start with log_out table and incrementally add the other tables in the JOIN, e.g.:
SELECT *
FROM log_out;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id;
SELECT *
FROM log_out
INNER JOIN items ON log_out.itemid = items.id
INNER JOIN storages ON log_out.storageid = storages.id
INNER JOIN categories ON log_out.categoryid = categories.id;
I would execute all the queries one by one and see which one results in 0 records. Additional join in that query would be the one with data discrepancy.

You're queries look fine to me, which makes me think that it is probably something unexpected with the data. Most likely the ids in your joins are not maintained right (do all of them have a foreign key constraint?). I would dig around the data, like SELECT COUNT(*) FROM items WHERE id IN (SELECT itemid FROM log_out), etc, and seeing if the returns make sense. Sorry I can't offer more advise, but I would be interested in hearing if the problem is in the data itself.

Related

MySQL - return single record by joining multiple tables with multiple WHERE/OR clauses

Did many searches and were able to write all the JOIN queries except this one. I have 3 tables, which look like:
TABLE - accounts
account_id | account_email
1 | aa#bb.com
2 | cc#dd.com
TABLE - products
product_id | product_name
1 | name1
2 | name2
TABLE - licenses
license_id | account_id | product_id | license_code
1 | 1 | 1 |
2 | 1 | 2 |
3 | 0 | 1 | abc123
I know account_email, product_id and license_code (this one can be empty) variables, and need to check if client has license for selected product (searching by account_email or license_code).
The problem is that account_id sometimes can be 0 (in other words, client has license, but client's profile is not stored in the accounts table).
Trying to use this one, but it returns wrong (duplicated) results:
SELECT * FROM licenses
INNER JOIN products ON licenses.product_id=products.product_id AND products.product_id='X'
INNER JOIN accounts ON licenses.account_id=accounts.account_id AND accounts.account_email='XYZ' OR licenses.license_code='ZZZ'
The question: how do I rewrite query, so I can find a record by account_email or license_code field? Put simply, if account_id is not 0 (profile exists), I should see data from 3 tables (accounts, products, licenses). If account_id is 0, I should see data from 2 tables (values from accounts table should be displayed as empty/null).
Needless to say, account_email and license_code fields are unique.
You just need to adjust the parentheses. However, I would use a separate WHERE clause:
SELECT *
FROM licenses l INNER JOIN
products p
ON l.product_id = p.product_id INNER JOIN
accounts a
ON l.account_id = a.account_id
WHERE p.product_id = 'X' AND
(a.account_email='XYZ' OR l.license_code = 'ZZZ')
You can keep the filters in the ON clauses -- there is nothing wrong with that. I just prefer to separate join conditions from filtering conditions. The important part are the parentheses, so the ON clause is not interpreted as:
ON (l.account_id = a.account_id AND a.account_email = 'XYZ') OR
licenses.license_code = 'ZZZ'
This is the interpretation without parens.
After some more code modification and testing, here's the code which works perfectly. It's a modified Gordon Linoff's code (original code didn't work when only license_code was known, and email address wasn't known). So most of credits go to Gordon Linoff for his efforts.
SELECT *
FROM licenses l JOIN
products p
ON l.product_id = p.product_id LEFT JOIN
accounts a
ON l.account_id = a.account_id
WHERE p.product_id = 'X' AND
(a.account_email='XYZ' OR l.license_code = 'ZZZ')

COUNT(*) FROM different tables with LEFT JOIN

I have 1 table of users, and 10 tables (articles, news, ...) where I save user's publications. I want to show how many publications has each user, in one query:
| ID_USER | COUNT(id_article) | COUNT(id_news) | etc...
-------------------------------------------------
| 1 | 0 | 3 |
| 2 | 2 | 9 |
| 3 | 14 | 5 |
| 4 | 0 | 0 |
If I use this query to show the number of articles...
SELECT id_user,COUNT(articles.id_article) FROM users
LEFT JOIN articles ON articles.id_user_article=users.id_user
GROUP BY users.id_user
... it shows the information correctly. But if I start to add the second table...
SELECT id_user,COUNT(articles.id_article),COUNT(news.id_news) FROM users
LEFT JOIN articles ON articles.id_user_article=users.id_user
LEFT JOIN news ON news.id_user_news=users.id_user
GROUP BY users.id_user
... it doesn't show the correct information.. and if I join all the rest tables, if shows really strange result (thousands of articles for first user, and NULL for the rest).
Which is the correct way of show this information using only one query? Thank you!
You can use a subselect instead of a left join for each table. The final result will be the same but maybe in that way is clearer.
SELECT u.id_user,
(SELECT COUNT(a.id_article)
FROM articles a
WHERE a.id_user_article = u.id_user) AS articles,
(SELECT COUNT(n.news)
FROM news n
WHERE n.id_user_news = u.id_user) AS news
FROM users u
Also if you only uses one column of each table, the subselect is a better option than multiple left joins.
Your problem is that you are joining along different dimensions, which creates cartesian products for each user. The solution by #rafa is actually a fine solution in MySQL. The use of count(distinct) works okay, but only when the counts are not very large. Another approach is to pre-aggregate the results along each dimension:
SELECT u.id_user, a.articles, n.news
FROM users u left outer join
(select id_user_article, count(*) as articles
from articles
group by id_user_article
) a
on u.id_user = a.id_user_article left outer join
(select id_user_news, count(*) as news
from news
group by id_user_news
) n
on u.id_user = n.id_user_news;
EDIT:
If you are using the count(distinct) approach, then you are generating a cross product. If every user had 3 articles and 4 news items, then the users would be multiplied by 12. Probably feasible.
If every user had 300 articles and 400 news items, then every user would be multiplied by 120,000. Probably not feasible.

Trouble joining two tables - No results

I have a table named phpbb_pcp_market with these rows: http://pastebin.com/ZAFjawD8 (There are more obviously)
And I have another table named phpbb_pcp_market_cart that looks like this:
+----+---------+-----------+------------+
| id | item_id | player_id | time |
+----+---------+-----------+------------+
| 14 | 49 | 3 | 1384806292 |
+----+---------+-----------+------------+
I need to join these two tables based on item_id, but for some reason it's not working.
This is the query I've used:
SELECT m.*, c.* FROM (phpbb_pcp_market_cart c)
LEFT JOIN phpbb_pcp_market m
ON (c.item_id = m.item_id)
WHERE c.player_id = 3
ORDER BY c.time
For some reason, it's returning nothing.
I can't figure what I did wrong in the query. And no, I'm not good at SQL.
Everything looks fine with your SQL-code.
Look in the rest of your PHP-code if there is something wrong. The bug is not related to the SQL-part ;)
First double check your data, your query seems to be OK.
If you want to select all items for specific player_id don't use LEFT JOIN but simple JOIN, because you will never get rows where it could be NULL.
Also braces can be left out for simplicity:
SELECT m.*, c.* FROM phpbb_pcp_market_cart c
JOIN phpbb_pcp_market m
ON c.item_id = m.item_id
WHERE c.player_id = 3
ORDER BY c.time

MySQL join multiple rows from one table while selecting only a single row from the others

Maybe a bit of a strange title description, but i basically want to achieve something the GROUP_CONCAT() function does, only then keep the double entries.
I have four tables i want to join, client, doctor, physio and records
Depending on the variable $client i want to get the client details, attending doctor and therapist (one single row from three tables) and join all records for that user.
Say that in this case the $client = 1. The records table has five records where the column r_client_id = 1. If i run a query like below i only get one record from the records table, namely the first occurrence where r_client_id = 1 (which makes sense of course):
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
records.r_record
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
Now assume the five records where r_client_id = 1 are like so:
+------+-------------+-------------------+----------+
| r_id | r_client_id | r_record | r_date |
+------+-------------+-------------------+----------+
| 1 | 1 | regular visit | 10/10/12 |
+------+-------------+-------------------+----------+
| 3 | 1 | emergency control | 24/10/12 |
+------+-------------+-------------------+----------+
| 7 | 1 | regular visit | 08/09/12 |
+------+-------------+-------------------+----------+
| 18 | 1 | delivery | 03/01/12 |
+------+-------------+-------------------+----------+
| 20 | 1 | health checkup | 10/12/11 |
+------+-------------+-------------------+----------+
I want my output to be in an array like so:
Client 1
- Name Doctor
- Name Physio
Records
- Emergency control, 24/10/12
- Regular visit, 10/10/12
- Regular visit, 08/09/12
- Delivery, 03/01/12
- Health checkup, 10/12/11
The closest one i can image is a to add a GROUP_CONCAT() on the records, but that, of course, groups the 'regular visit', so i'll get 4 rows instead of 5
GROUP_CONCAT(DISTINCT records.r_record SEPARATOR '|')
[..]
echo(str_replace("|","<br>",$show->r_record));
Anybody an idea how to display all the matching records? I have the feeling i'm close, but i'm out of options by now..
Edit:
I forgot to mention that when i remove the DISTINCT, it displays all the records twice..
SOLVED:
Got it working like so:
GROUP_CONCAT(DISTINCT
CONCAT (records.r_date, '~', records.r_record, '~', records.r_paraph)
SEPARATOR '|') AS clientDoctorRecords,
Try:
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
GROUP_CONCAT(records.r_record)
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
GROUP BY
client.c_id
If you want r_date to come along with record in one column, then you can use plain CONCAT first and then do a GROUP_CONCAT on it.

mysql error in my query

i have to check in my products i am selling (mostly gaming consoles and games)
i want to see which products has which categories and this is my query:
select * From products left join products_categories on (product_id=id) ;
+------+------+------------+-------------+----------
| id | name | product_id | category_id | and more
+------+------+------------+-------------+----------
| 4 | Xbox | 4 | 2 |
| 5 | PS3 | 5 | 2 |
| 7 | BAD | NULL | NULL |
etc...
+------+------+------------+-------------+---------
here i have a product (#7 - BAD) that i don'T want to see since i removed the category,
I don't want to see the product without categories?
The LEFT JOIN command is used to combines null matching rows which are
stored in related tables In order to join these tables, the join table
require a common field (commonly called foreign key) from the left
table. This type of join requires keywords ON or USING.
Example:
SELECT *
From products
LEFT JOIN products_categories ON (product_id=id)
WHERE product_id IS NOT NULL;
Or you can use the INNER JOIN:
The JOIN or INNER JOIN command is used to combines non-null matching
rows which are stored in related tables In order to join these tables,
the join table require a common field (commonly called foreign key)
from the left table. This type of join requires keywords ON or USING.
Example:
SELECT * From products INNER JOIN products_categories ON (product_id=id);
Now, I would recommend to add a flag for inactive or active product, this way you don't need to remove the categories for a product if it's inactive. This way, if you want to re-activate it, simply turn the flag back to 1 or whatever flag you use.
Example:
SELECT *
FROM products
INNER JOIN products_categories ON (product_id=id)
WHERE products.is_active = 1;