Getting data from multiple tables results with less records - php

I have Joomla tables (shown bellow) with some records in it (records are about companies). What I want to achieve is to get all records and group them to new table (I am using PHP for this). Problem is that there is 548 records (companies) and I got 439 records (after grouping).
Does someone knows where's the problem?
Here is database:
And here is query:
SELECT a.itemid as ID, a.title as Name, c.name as Categories, d.data_txt as Description, d.fieldid as FieldID FROM `jos_sobi2_item` as a
INNER JOIN `jos_sobi2_cat_items_relations` as b ON b.itemid=a.itemid
INNER JOIN `jos_sobi2_categories` as c ON c.catid = b.catid
INNER JOIN `jos_sobi2_fields_data`as d ON a.itemid=d.itemid

The issue is inner join is filtering out the records which are not in the related tables but in jos_sobi2_item
You may want to do left join something as
select
a.itemid as ID,
a.title as Name,
c.name as Categories,
d.data_txt as Description,
d.fieldid as FieldID
FROM `jos_sobi2_item` as a
left join `jos_sobi2_cat_items_relations` as b ON b.itemid=a.itemid
left join `jos_sobi2_categories` as c ON c.catid = b.catid
left join `jos_sobi2_fields_data`as d ON a.itemid=d.itemid
Here is an illustration of this
create table table1 (t1id int , name varchar(100));
insert into table1 values (1,'aa'),(2,'cc'),(3,'dd'),(4,'ee'),(5,'ff'),(6,'bb'),(7,'gg');
create table table2 (t2id int, description varchar(100));
insert into table2 values (1,'desc1'),(2,'desc2'),(3,'desc3');
create table table3 (t3id int, t1id int , t2id int);
insert into table3 values (1,1,2),(2,3,2),(3,2,2),(4,7,3);
create table table4 (t4id int , t1id int ,fieldid int);
insert into table4 values (1,1,10),(2,3,23),(3,4,34),(4,5,50);
select
t1.t1id,
t1.name,
t2.description,
t4.fieldid
from table1 t1
left join table3 t3 on t3.t1id = t1.t1id
left join table2 t2 on t2.t2id = t3.t2id
left join table4 t4 on t4.t1id = t1.t1id;
+------+------+-------------+---------+
| t1id | name | description | fieldid |
+------+------+-------------+---------+
| 1 | aa | desc2 | 10 |
| 2 | cc | desc2 | NULL |
| 3 | dd | desc2 | 23 |
| 4 | ee | NULL | 34 |
| 5 | ff | NULL | 50 |
| 6 | bb | NULL | NULL |
| 7 | gg | desc3 | NULL |
+------+------+-------------+---------+
The above example is similar to what you have.

If you still want to use INNER
1. Try a LEFT JOIN instead a INNER JOIN, and put a condition in WHERE
SELECT a.itemid as ID, a.title as Name, c.name as Categories, d.data_txt as Description, d.fieldid as FieldID FROM `jos_sobi2_item` as a
LEFT JOIN `jos_sobi2_cat_items_relations` as b ON b.itemid=a.itemid
LEFT JOIN `jos_sobi2_categories` as c ON c.catid = b.catid
LEFT JOIN `jos_sobi2_fields_data`as d ON a.itemid=d.itemid
WHERE b.itemid IS NULL OR c.catid IS NULL OR d.itemid IS NULL
Look at your results and see where you have nulls, after that you will discover what records from the jos_sobi2_item don't have a connection in the other tables.
Or if you want to use LEFT JOIN :
1.Run the query:
SELECT a.itemid as ID, a.title as Name, c.name as Categories, d.data_txt as Description, d.fieldid as FieldID,
COUNT(b.itemid), COUNT(c.catid), COUNT(d.itemid)
FROM `jos_sobi2_item` as a
LEFT JOIN `jos_sobi2_cat_items_relations` as b ON b.itemid=a.itemid
LEFT JOIN `jos_sobi2_categories` as c ON c.catid = b.catid
LEFT JOIN `jos_sobi2_fields_data`as d ON a.itemid=d.itemid
GROUP BY a.itemid
Analyze the counts for duplicate records, you can put a condition where count > 1 on every count.

Related

a complex sql query with outer join

there are two tables
a
------------------------------
id | Name
------------------------------
1 | Alpha
-----------------------------
2 | Beta
-----------------------------
3 | Gamma
-----------------------------
4 | Delta
-----------------------------
and another table b with foreign key of table a
b
-----------------------------
id | a_id | Film
-----------------------------
1 | 1 | Bladerunner
-----------------------------
2 | 1 | Star Wars
-----------------------------
3 | 3 | Superman
-----------------------------
4 | 4 | Rollerball
-----------------------------
Write an SQL query using outer join to get all names from table ā€œaā€ that don't have a film starting with ā€œSā€.
Query result should be:
Beta
--------
Delta
Use correlated subquery with not exists
DEMO
SELECT *
FROM tablea a
LEFT JOIN tableb b
ON a.id = b.a_id
WHERE NOT EXISTS (SELECT 1
FROM tableb b1
WHERE b.a_id = b1.a_id
AND film LIKE 'S%')
OR you can use below query to avoid subquery
SELECT NAME,
Sum(CASE
WHEN film LIKE 'S%' THEN 1
ELSE 0
END)
FROM t1 a
LEFT JOIN t2 b
ON a.id = b.a_id
GROUP BY NAME
HAVING Sum(CASE
WHEN film LIKE 'S%' THEN 1
ELSE 0
END) = 0
OUTPUT:
name
Delta
Beta
SELECT a.NAME
FROM a
LEFT JOIN b
ON a.id = b.a_id
WHERE a.NAME NOT IN (SELECT a.NAME
FROM a
LEFT JOIN b
ON a.id = b.a_id
WHERE b.film LIKE 's%')
GROUP BY a.NAME
output
Beta
Delta
according to the OPs question which he has not mentioned and just gave output part in his question... he wants all the name of the table a where film name do not start with letter S from table b. the above query fullfilled the requirements of OPs completely and in most standard way.
OP just mentioned the join in the question but he do not mentioned that he want those id name also which do not exist in table b in a_id i.e the output part beta.
use substring and join
select a.name from t1 a left join
t2 b on a.id=b.a_id
where a.id not in ( select b.a_id from t2 b
where
upper(substring(b.Film,1,1)) like '%S%' and b.a_id is not null
)
output
name
Delta
Beta
demo link

MySQL Join two tables on multiple records

I have two tables:
Table1:
- id
- name
- table2_id1
- table2_id2
- table2_id3
Table2:
- id
- name
Table1:
id | name | table2_id1 | table2_id2 | table2_id3
1 | blabla | 1 | 2 | 3
2 | blabla2 | 2 | 3 | 1
Table2:
id | name
1 | aaa
2 | bbb
3 | ccc
I would like to display a name from Table1 and many names from Table2 which are joined, example:
*blabla | aaa | bbb | ccc*
I hope you get what I mean.
EDIT:
I tried something like this:
SELECT Table1.name, Table2.name, Table2.name, Table2.name<BR>
FROM Table1 JOIN Table2 ON<BR>
You can use left join queries:
SELECT a.name as "name", b.name as "table2_id1_name", c.name as "table2_id2_name", d.name as "table2_id3_name"
FROM Table1 a
LEFT JOIN Table2 b ON (a.table2_id1 = b.id)
LEFT JOIN Table2 C ON (a.table2_id2 = c.id)
LEFT JOIN Table2 d ON (a.table2_id3= d.id)
Hope it work as you expect. :)
SELECT a.name, b.name, c.name, d.name
FROM Table1 a, Table2 b, Table2 c, Table2 d
WHERE a.table2_id1 = b.id
AND a.table2_id2 = c.id
AND a.table2_id3= d.id

Issue with adding fields of multiple tables

I have three tables with same structure.
table1
id | email | count
1 | test1#abc.com | 5
2 | test2#abc.com | 5
3 | test3#abc.com | 5
table2
id | email | count
1 | test1#abc.com | 50
2 | test1#abc.com | 50
3 | test3#abc.com | 50
table3
id | email | count
1 | test1#abc.com | 40
2 | test1#abc.com | 45
3 | test1#abc.com | 50
Now what i want is for table1, for first record "test1#abc.com", I need sum of "count" field of next two tables. So i used below query
SELECT (IFNULL(sum(distinct(table2.count)), 0) +
IFNULL(sum(distinct(table3.count)), 0)) as total
FROM table1
LEFT JOIN table2 ON table1.email = table2.email
LEFT JOIN table3 ON table1.email = table3.email
WHERE table1.email = 'test1#abc.com'
This query gives me below record:
185
But the result should be as below:
235
This is because i have used distinct when adding field. But if i don't use distinct, it gives me 285.
Please help. What should i do?
Your issue is because, first, you're using LEFT JOIN (no sense with summation since NULL-records will provide nothing), second, that's how JOIN works. Illustrate with query:
SELECT
t1.id AS id_1,
t1.email AS email_1,
t1.count AS count_1,
t2.id AS id_2,
t2.email AS email_2,
t2.count AS count_2,
t3.id AS id_3,
t3.email AS email_3,
t3.count AS count_3
FROM
table1 AS t1
INNER JOIN table2 AS t2 ON t1.email=t2.email
INNER JOIN table3 AS t3 ON t1.email=t3.email
WHERE
t1.email='test1#abc.com'
(fiddle is here). As you can see, you'll get repeated id's from second and third tables - and - yes, that's because there are multiple rows for joining condition.
To resolve your issue you may add distinction by id into join (and later filtering that with variables or like that), but I would not recommend it. JOIN is simply not the thing for your issue. Use UNION, like:
SELECT
SUM(`count`) AS s
FROM
(
SELECT
table2.count
FROM
table2
WHERE
email='test1#abc.com'
UNION ALL
SELECT
table3.count
FROM
table3
WHERE
email='test1#abc.com'
) AS u
(see the fiddle)

MySQL inner join AND turn rows into colums

I'm having difficulty figuring out how to create the proper syntax for my query.
Here is what i'm pulling. I have 2 tables.
Table 1 : Fields (user_id, name)
Table 2 : Fields (user_id, type, are_code, phone_number).
Table 1 can only have 1 record per user_id.
1 | John Doe
Table 2 can have up to 3 records per user_id:
1 | Home | 123 | 456.4567
1 | Work | 000 | 987.1467
1 | Mobi | 098 | 987.1756
How can i select everything so that my table will result in 1 record pulled like so :
user_id | name | home# | work# | mobi#
I tried this, which duplicates and doubles rows based on amount of entries within Table 2.
SELECT a.user_id,
b.area_code, b.phone_number
FROM users a
INNER JOIN user_contact_phones b ON a.user_id = b.user_id
That unfortunately returned 3 rows which is not good :(.
1 | John Doe | area | home# |
1 | John Doe | area | work# |
1 | John Doe | area | mobi# |
Any help and or pointers would be greatly appreciated.
Try this out:
SELECT
u.user_id,
u.name,
MAX(CASE WHEN p.type = 'Home' THEN phone_number END) HomeNumber,
MAX(CASE WHEN p.type = 'Work' THEN phone_number END) WorkNumber,
MAX(CASE WHEN p.type = 'Mobi' THEN phone_number END) MobiNumber
FROM phones p
JOIN users u ON p.user_id = u.user_id
GROUP BY u.user_id, u.name
Output:
| USER_ID | NAME | HOMENUMBER | WORKNUMBER | MOBINUMBER |
|---------|----------|------------|------------|------------|
| 1 | John Doe | 456.4567 | 987.1467 | 987.1756 |
Fiddle here.
Also note that you can remove u.name if u.user_id determines u.name... which is most likely the case as it seems to be a primary key. That would speed things up a little bit.
Note: This assumes that you cant have more than one same type for the same user (as it is in your example data, which only has one column for home, work and mobile.
Use user_contact_phones.type to get exact what you want, like-
SELECT a.user_id,
b.area_code, b.phone_number
FROM users a
INNER JOIN user_contact_phones b ON a.user_id = b.user_id where b.type='Home'
Here's a solution that will work:
select u.user_id, u.name,
thome.area_code as home_area_code, thome.phone_number as home_phone_number,
twork.area_code as work_area_code, twork.phone_number as work_phone_number,
tmobi.area_code as mobi_area_code, tmobi.phone_number as mobi_phone_number
from table1 u
left outer join table2 thome on u.user_id = thome.user_id and thome.type = 'Home'
left outer join table2 twork on u.user_id = twork.user_id and twork.type = 'Work'
left outer join table2 tmobi on u.user_id = tmobi.user_id and tmobi.type = 'Mobi'
Please note the use of left outer join instead of inner join in case the record for a particular type does not exist. You will get null values for those columns in your result set with left outer join. With inner join, you would not get a result for a user that did not have all three types. Good luck!

IF statement in mySQL query?

I have this query:
SELECT a.id as alert_id,a.user_id,a.date,a.msg_title,a.message,a.alert_type,a.school_or_contact_id,
u.id as user_id, u.full_name,
c.id as contact_id, concat(c.f_name,' ',c.l_name) as contact_name
FROM alerts a
LEFT OUTER JOIN users u ON a.user_id = u.id
LEFT OUTER JOIN contacts c ON a.school_or_contact_id = c.id
LEFT OUTER JOIN schools s ON a.school_or_contact_id = s.school_id
ORDER BY a.date
This works, but I need it to do one more thing, and I can't seem to figure it out. I need to select some data from the "schools" table IF data in alerts.alert_type (alerts table) == "claim".
If "claim" is not found in alerts.alerts_table, then it needs to do nothing different than the query above. alerts.alert_table
This is what I've tried, but it doesn't seem to work:
SELECT a.id as alert_id,a.user_id,a.date,a.msg_title,a.message,a.alert_type,a.school_or_contact_id,
u.id as user_id, u.full_name,
c.id as contact_id, concat(c.f_name,' ',c.l_name) as contact_name,
IF(a.alert_type = 'claim', select s.* from schools where school_id = a.school_or_contact_id)
FROM alerts a
LEFT OUTER JOIN users u ON a.user_id = u.id
LEFT OUTER JOIN contacts c ON a.school_or_contact_id = c.id
LEFT OUTER JOIN schools s ON a.school_or_contact_id = s.school_id
ORDER BY a.date
EDIT
For clarification, I'm building a tool that has front page "update" kind of like Facebook. Depending on what the users are doing, the "alerts" will say different things.
The schools table has 3,000 rows and will only apply to the alerts table when the row alerts_type.alerts == "claim". Otherwise, it won't matter what what's in the schools table. If alert_type.alerts != "claim", the "contacts" table will be where the rest of the data comes from.
I wanted to have cleaner data when doing the query (ie -- not "school" table data when alerts_type.alerts != "claim") but I can easily do this in PHP. I just didn't want to pull data that I wouldn't use.
Thank you everyone for all the help and advice!
2nd edit
I will change the table schema. Right now, it looks like this:
mysql> desc alerts;
+----------------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(12) | YES | | NULL | |
| date | timestamp | NO | | CURRENT_TIMESTAMP | |
| msg_title | varchar(100) | YES | | NULL | |
| message | longtext | YES | | NULL | |
| alert_type | varchar(255) | YES | | NULL | |
| school_or_contact_id | int(12) | YES | | NULL | |
+----------------------+--------------+------+-----+-------------------+----------------+
7 rows in set (0.00 sec)
I will edit the alerts table to this (below), then JOIN alerts.school_id = schools.school_id. This should fix the problem.
+----------------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(12) | YES | | NULL | |
| date | timestamp | NO | | CURRENT_TIMESTAMP | |
| msg_title | varchar(100) | YES | | NULL | |
| message | longtext | YES | | NULL | |
| alert_type | varchar(255) | YES | | NULL | |
| school_id | int(12) | YES | | NULL | |
| contact_id | int(12) | YES | | NULL | |
+----------------------+--------------+------+-----+-------------------+----------------+
You can't really do an optional JOIN like you're trying in the above SQL.
You'll need an IF clause for each column, i.e IF (a.alert_type = 'claim', s.col, NULL)
As you've already joined on the schools table, there shouldn't be any difference in performance, and fetching data all in one query will be better than running multiple queries.
An example:
SELECT a.id as alert_id,a.user_id,a.date,a.msg_title,a.message,a.alert_type,a.school_or_contact_id,
u.id as user_id, u.full_name,
c.id as contact_id, concat(c.f_name,' ',c.l_name) as contact_name,
IF (a.alert_type = 'claim', s.col1, NULL) AS col1,
IF (a.alert_type = 'claim', s.col2, NULL) AS col2
FROM alerts a
LEFT OUTER JOIN users u ON a.user_id = u.id
LEFT OUTER JOIN contacts c ON a.school_or_contact_id = c.id
LEFT OUTER JOIN schools s ON a.school_or_contact_id = s.school_id
ORDER BY a.date
If it happens that you have a lot of fields in the schools table you might as well just fetch s.*, avoid the IF parts, and simply skip over those values in your PHP script.
Probably the best way would be to check the alert_type using PHP and run a second query if needed. You could then merge the two results together.
You might try this though:
SELECT a.id as alert_id,a.user_id,a.date,a.msg_title,a.message,a.alert_type,a.school_or_contact_id,
u.id as user_id, u.full_name,
c.id as contact_id, concat(c.f_name,' ',c.l_name) as contact_name, s.*
FROM alerts a
LEFT OUTER JOIN users u ON a.user_id = u.id
LEFT OUTER JOIN contacts c ON a.school_or_contact_id = c.id
LEFT OUTER JOIN schools s ON a.school_or_contact_id = s.school_id AND a.alert_type = 'claim'
ORDER BY a.date
You can't embed queries into IF() calls. Any reason you can't just do the sub-query unconditionally and then filter the value in your client app? Regardless of this, you cannot have a subquery return multiple fields as you are when the subquery is substituting for a field. So even if the IF() call were possible, the sub-queries have to return a single field/row.
Let me introduce you to UNION SELECT.
Note this will be a long query, and depends on the exact structure of schools; the below assumes two columes xs.foo and xs.bar:
SELECT * FROM
(SELECT a.id as alert_id,a.user_id,a.date,a.msg_title,a.message,
a.alert_type,a.school_or_contact_id,
u.id as user_id, u.full_name,
c.id as contact_id, concat(c.f_name,' ',c.l_name) as contact_name,NULL,NULL
FROM alerts a
LEFT OUTER JOIN users u ON a.user_id = u.id
LEFT OUTER JOIN contacts c ON a.school_or_contact_id = c.id
WHERE xa.alert_type!='claim'
UNION SELECT xa.id as alert_id,xa.user_id,xa.date,xa.msg_title,xa.message,
xa.alert_type,xa.school_or_contact_id,
xu.id as user_id, xu.full_name,
xc.id as contact_id, concat(xc.f_name,' ',xc.l_name) as contact_name,xs.foo,xs.bar
FROM alerts xa
LEFT OUTER JOIN users xu ON xa.user_id = xu.id
LEFT OUTER JOIN contacts xc ON xa.school_or_contact_id = xc.id
LEFT OUTER JOIN schools xs ON xa.school_or_contact_id = xs.school_id
WHERE xa.alert_type='claim')
ORDER BY date
A caveat: That this is complicated is a good sign your database is poorly designed. If you inherited this...problem, then so be it, but if you're creating new code that works this way, let me strongly recommend that you model your data so a full outer join does the right thing.
As others explained, it's notpossible to have variable number of columns in a result set.
The closest you can get to what you want may be this:
SELECT a.id as alert_id
, a.user_id
, a.date
, a.msg_title
, a.message
, a.alert_type
, a.school_or_contact_id
, u.id as user_id
, u.full_name
, c.id as contact_id
, concat(c.f_name,' ',c.l_name) as contact_name
, s.*
FROM alerts a
FROM alerts a
LEFT OUTER JOIN users u
ON a.user_id = u.id
LEFT OUTER JOIN contacts c
ON a.school_or_contact_id = c.id
LEFT OUTER JOIN schools s
ON a.school_or_contact_id = s.school_id
AND a.alert_type = 'claim'
ORDER BY a.date

Categories