SQL query to combine tables (n:n) - php

I have these tables:
CATEGORIES:
id
name
KIBE_TRAEGER:
id
name
COUNTIES:
id
region_id
country_id
county_code
county_de
REGIONS:
id
country
region_de
CONTACTS:
id
firstname
surname
company
zipcode
city
region --> =regions.id --> should show regions.region_de
county --> =counties.id --> should show counties.county_de
tel1
tel2
mobile
fax
email1
email2
homepage
categories --> =categories.id BUT in table "contacts" it's written
with ;catnr; (e.g. an entry can have more than 1 entry:
";6;;7;;8;;16;") --> should show categories.name
notes
active
pernr
KIBE_CONTACTS:
id
openinghours
costs
groups
handicapplaces
freeplaces
traeger --> =kibe_traeger.id same as categories ;traegerId; (e.g.";1;;5;") --> should show kibe_traeger.name
and I want to combine all of them to export it to csv.
Btw. i tried my poor queries so far directly in phpmyadmin with left join and inner join but I'm relly poor in SQL stuff and wont learn it anymore :P ...
My code so far (edited according to Matt Cremeen's answers):
Working:
SELECT
contacts.id, contacts.firstname, contacts.surname, contacts.company,
contacts.zipcode, contacts.city, contacts.country, contacts.region,
contacts.county, contacts.tel1, contacts.tel2, contacts.mobile,
contacts.fax, contacts.email1, contacts.email2, contacts.homepage,
contacts.active, contacts.pernr, kibe_contacts.id as contacts_id,
kibe_contacts.openinghours, kibe_contacts.costs, kibe_contacts.groups,
kibe_contacts.handicapplaces, kibe_contacts.freeplaces,
kibe_contacts.traeger, kibe_traeger.id as traeger_id, kibe_traeger.name,
counties.id as counties_id, counties.region_id as region_id,
counties.country_id as country_id, counties.county_code, counties.county_de,
regions.id as regions_id, regions.country as regions_country, regions.region_de
FROM
contacts
INNER JOIN
kibe_contacts ON contacts.id = kibe_contacts.contact_id
INNER JOIN
regions ON contacts.region = regions.id
INNER JOIN
counties ON contacts.county = counties.id
Not working:
INNER JOIN
kibe_traeger ON kibe_contacts.traeger = kibe_traeger.id
INNER JOIN
categories ON contacts.categories = categories.id;
I'm getting a "Null-Result" I think I know why I get this error: because in the column kibe_contacts.traeger and contacts.categories there are more than one values which are separated with ; e.g. an entry can have more than 1 entry: ;6;;7;;8;;16; but in categories.id there is always just a single id e.g. 4.
Is there any solution to query this out?
Last Edit: Solved the second problem by myself: i exported the list and did it with search & replace.
Thanks again to all the helpers!

Assuming that the id field corresponds to each table, you are going to want to join each table on that field. I won't write all of the code here, but it'll start like this
SELECT contacts.id, contacts.firstname, contacts.surname, ...
FROM contacts
INNER JOIN
kibe_contacts
ON contacts.id = kibe_contacts.id
INNER JOIN
regions
on contacts.region_id = regions.id
...

try to use GROUP_CONCAT function of MySQL. Here my sample SQL
I've 2 tables, clientes ('Id' int, 'nome' varchar(50) )
clientes
1 Rafael
2 Jony
3 Smith
and clientes_devices ('Id' int, 'id_cliente' int, 'device' varchar(50) )
clientes_devices
1 1 AsusZenFone
2 1 MotorolaG
3 2 WindowsPhone
Here is the select command
select c.nome, GROUP_CONCAT(DISTINCT d.device SEPARATOR ',') AS Device
from clientes c
join clientes_dispositivos d on (c.id = d.id_cliente)
where c.id = 1
this is the output
Without the function, I got this

Related

How to retrieve data with 3 tables in mysql?

I am having 2 tables :
1.internal_employee_master
id employee_name unique_id
1 Noah ABCD
2 Liam ABCD
3 William ABCD
4 Benjamin ABCD
5 Jacob EFGH
2.external_employee_master
id name unique_id
1 Elijah ABCD
2 Ethan ABCD
3 Alexander EFGH
I am using UNION query to get both tables data into single table and display this data into html table.
select id
, employee_name
, unique_id
from internal_employee_master
where unique_id = 'ABCD'
union
select id
, employee_name
, unique_id
from external_employee_master
where unique_id = 'ABCD'
I want to store payslips of both employees into single table.
I have one table payslips with emp_id and emp_type columns.
I am storing data into payslips data like:
id pay_slip emp_id emp_type
1 Noah_payslip.pdf 1 internal
2 Liam_payslip.pdf 2 internal
3 Lia_payslip.pdf 1 External
as you can see in above table i am storing emp_id and emp_type of
both the tables in single columns each.
Now, i dont undestand how to split data of internal employee and
external employee from pay_slip table and show data in html table.
Currently, i am writing below sql joins to get employee_names of
internal and external employee tables but it doesnt work for me.
$id = $_GET['id];
SELECT ps.id,ps.pdf,ps.emp_id,ps.emp_type,external_employee.name as comemp,
internal_employee.comp_empl_name as comemp
FROM pay_slip as ps
INNER JOIN internal_employee_master as internal_employee ON internal_employee.comp_trad_id = ps.trade_id
INNER JOIN external_employee_master as external_employee ON external_employee.trad_id = ps.trade_id
where ps.is_deleted = 1 AND ps.id = '".$id."'"
Please help me to join query to get name and employee_name with respect to emp_type form pay_slip table.
How about using UNION again?
SELECT
ps.id,
ps.pdf,
ps.emp_id,
ps.emp_type,
external_employee.name AS comemp,
internal_employee.comp_empl_name AS comemp
FROM
pay_slip AS ps
INNER JOIN
internal_employee_master AS internal_employee ON internal_employee.comp_trad_id = ps.trade_id
WHERE
ps.is_deleted = 1 AND ps.id = '".$id."'
AND ps.type = 'internal'
UNION ALL
SELECT
ps.id,
ps.pdf,
ps.emp_id,
ps.emp_type,
external_employee.name AS comemp,
internal_employee.comp_empl_name AS comemp
FROM
pay_slip AS ps
INNER JOIN
external_employee_master AS external_employee ON external_employee.trad_id = ps.trade_id
WHERE
ps.is_deleted = 1 AND ps.id = '".$id."'
AND ps.type = 'external'
You could try this
SELECT ps.id, ps.pay_slip, ps.emp_type, COALESCE(i.employee_name, e.name) AS name
FROM payslips ps
LEFT JOIN internal_employee_master i ON i.id = ps.emp_id AND ps.emp_type = 'internal'
LEFT JOIN external_employee_master e ON e.id = ps.emp_id AND ps.emp_type = 'External'
AND ps.id = :ID
You can see this in action here http://sqlfiddle.com/#!9/53a195/7/0
I would mention that there are a number of issues in your included tables and queries. For example, irregular column names between tables (name vs. employee_name), you've missed the is_deleted column from your example schema, and you have capitalised and non-capitalised values in the emp_type column which is confusing.

Getting data from 3 MySQL tables

I'm having a struggle here trying to grab some data from 3 database tables..
The idea is that users can fill out their profile with several fields, and I'm storing every profile field, field values and the users selected value in separate tables.
The structure of the tables look like this:
Table 'profile_fields'
- id
- name
- sort
- status (enum '0', '1')
Table 'profile_field_values'
- id
- profile_field_id
- name
Table 'user_profile_fields'
- user_id
- profile_field_id
- profile_field_value_id
Would be really nice if you could tell me how to construct this query, and why you used the JOIN you did.
Also, how would this table layout scale when the userbase grows?
Thank you so much in advance!
Edit:
OK, I still can't figure out how to make it return all the fields from 'profile_fields' along with the users selected option from 'user_profile_fields'. If the user hasn't selected a value, it should just be null.
This is my (non-functional) query so far:
SELECT PF.id AS field_id, PF.name AS field_name, UPF.profile_field_value_id AS value_id, PF.type, PFV.name
FROM profile_fields PF
LEFT JOIN profile_fields_values PFV ON PFV.profile_field_id = PF.id
LEFT JOIN user_profile_fields UPF ON UPF.user_id=1 AND PF.id = UPF.profile_field_id
WHERE length(PF.name) > 0 and PF.status = '1'
ORDER BY PF.sort
This query seems to work, but it does not fetch the name of the value from 'profile_field_values':
SELECT PF.id AS field_id, PF.name AS field_name, UPF.profile_field_value_id AS value_id, PF.type
FROM profile_fields PF
LEFT JOIN user_profile_fields UPF ON UPF.user_id =1
AND PF.id = UPF.profile_field_id
WHERE LENGTH( PF.name ) >0
AND PF.status = '1'
ORDER BY PF.sort
I think you have some unnecessary complexity in there. Maybe you should try
Table 'profile_fields'
id
name
sort
status (enum '0', '1')
Table 'profile_field_values'
id
user_id
profile_field_id
value
why are there 3 tables?
Seems like simple JOINs should work:
SELECT pf.id, pf.name, pf.sort, pf.status,
pfv.id, pfv.profile_field_id, pfv.name,
upf.user_id, upf.profile_field_id, upf.profile_field_value_id
FROM profile_fields pf
INNER JOIN profile_field_values pfv
ON pf.id = pfv.profile_field_id
INNER JOIN user_profile_fields upf
ON upf.profile_field_value_id = pfv.id AND upf.profile_field_id = pf.id
A Visual Explanation of SQL Joins
This uses an INNER JOIN to select all matching records from each table -- review the post to tell the difference between an INNER and an OUTER join.

Hyphen-delimited values and LEFT_JOIN

I have a row with some values hyphen-delimited:
table: live_customers
row: areas
id | areas
1 | 10-20-30
2 | 40-50-60
...
Using this...
LEFT JOIN $table5 AS table5 ON live.areas REGEXP CONCAT('(^|-) ?',table5.id,' ?($|-)')
My results looks like:
(tab id:1) area: 10
(tab id:1) area: 20
...
(tab id:2) area: 40
...
But i expect:
(tab id:1) area: 10,20,30
(tab id:2) area: 40,50,60
How could i solve that?
EDIT:
The full query looks like:
SELECT live.*,
live.id AS lid,
table1.id, table1.value AS tn_val,
table2.id, table2.value AS tp_val,
table3.id, table3.value AS ht_val,
table5.id, table5.value AS ar_val
FROM $dblist AS live
LEFT JOIN $table1 AS table1 ON live.town = table1.id
LEFT JOIN $table2 AS table2 ON live.htype = table2.id
LEFT JOIN $table3 AS table3 ON live.ht = table3.id
LEFT JOIN $table5 AS table5 ON live.areas REGEXP CONCAT('(^|-) ?',table5.id,' ?($|-)')
ORDER BY live.id ASC
PHP echoes:
...
if ($post['areas']){ // Debugging areas stuff
echo '<strong>'.$_areas.': (ar_val)</strong> '.$post['ar_val'].'<p>';
echo '<strong>'.$_areas.': (areas)</strong> '.$post['areas'].'<p>';
}
...
EDIT2:
It's quite hard for me to explain my issue in English, but i'm trying the best i can :)
in the table "live_customers" i does have this:
id | areas
1 | 10-20-30
2 | 40-50-60
...
in the table "areas" (that is a completely different table):
id | value
38 | Zone1
39 | Zone2
40 | Zone3
...
In the SQL query you see just tables variables because i previousvly declared them at the top of page:
$table5 = 'areas';
$dblist = 'live_customers';
etc..
Solution
Thanks anyone for their answers and for let me know "GROUP_CONCAT".
Here is my solution:
SELECT live.*,
live.id AS lid,
table1.id, table1.value AS tn_val,
table2.id, table2.value AS tp_val,
table3.id, table3.value AS ht_val,
table5.id, GROUP_CONCAT(table5.value) AS ar_val
FROM $dblist AS live
LEFT JOIN $table1 AS table1 ON live.town = table1.id
LEFT JOIN $table2 AS table2 ON live.htype = table2.id
LEFT JOIN $table3 AS table3 ON live.ht = table3.id
LEFT JOIN $table5 AS table5 ON FIND_IN_SET(table5.id, REPLACE(live.areas, '-', ','))
GROUP BY live.id
Result is what i expected ^^
Take it together with GROUP_CONCAT()
First thing to say is that your schema violates First Normal Form (1NF) in that the column areas is not atomic. You should not be putting 3 different values in one column.
Next you say you have a table called live_customers with a row called areas. This is nonsense. Rows do not have names, columns do. You show a bit of table with 2 columns id and areas. What table is this?
Next in the query there is no mention of a table called live_customers.
Next, if there is a column called areas in the table with the alias of live, then the output should contain that column since you are selecting live.*. That being the case, your results cannot be what you showed us, since it would contain a results column with data like 10-20-30
Finally those cannot be the results of the posted query since I can see a results column of lid specified.
If you would care to take some time over ensuring that the questionyou post makes sense, then you might get a reasonable answer.

MySQL Join and create new column value

I have an instrument list and teachers instrument list.
I would like to get a full instrument list with id and name.
Then check the teachers_instrument table for their instruments and if a specific teacher has the instrument add NULL or 1 value in a new column.
I can then take this to loop over some instrument checkboxes in Codeigniter, it just seems to make more sense to pull the data as I need it from the DB but am struggling to write the query.
teaching_instrument_list
- id
- instrument_name
teachers_instruments
- id
- teacher_id
- teacher_instrument_id
SELECT
a.instrument,
a.id
FROM
teaching_instrument_list a
LEFT JOIN
(
SELECT teachers_instruments.teacher_instrument_id
FROM teachers_instruments
WHERE teacher_id = 170
) b ON a.id = b.teacher_instrument_id
my query would look like this:
instrument name id value
--------------- -- -----
woodwinds 1 if the teacher has this instrument, set 1
brass 2 0
strings 3 1
One possible approach:
SELECT i.instrument_name, COUNT(ti.teacher_id) AS used_by
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
GROUP BY ti.teacher_instrument_id
ORDER BY i.id;
Here's SQL Fiddle (tables' naming is a bit different).
Explanation: with LEFT JOIN on instrument_id we'll get as many teacher_id values for each instrument as teachers using it are - or just a single NULL value, if none uses it. The next step is to use GROUP BY and COUNT() to, well, group the result set by instruments and count their users (excluding NULL-valued rows).
If what you want is to show all the instruments and some flag showing whether or now a teacher uses it, you need another LEFT JOIN:
SELECT i.instrument_name, NOT ISNULL(teacher_id) AS in_use
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
AND ti.teacher_id = :teacher_id;
Demo.
Well this can be achieved like this
SELECT
id,
instrument_name,
if(ti.teacher_instrument_id IS NULL,0,1) as `Value`
from teaching_instrument_list as til
LEFT JOIN teachers_instruments as ti
on ti.teacher_instrument_id = til.id
Add a column and check for teacher_instrument_id. If found set Value to 1 else 0.

Join tables with comma values

I have a hard nut to crack with joing 3 tables.
I have a newsletter_items, newsletter_fields and newsletter_mailgroups which I want to be joined to get a list of newsletters.
The newsletter_items contains the fields:
letter_id, letter_date, receivers, template, status
That can look like
1, 1234567899, 1,2 (comma separated), standard.html, 1
newsletter_fields contains the fields:
field_uid, field_name, field_content, field_letter_uid
That can look like
1, letter_headline, A great headline, 1
where field_letter_uid is the newsletter for which the field belongs to.
and newsletter_mailgroups contains the fields:
mailgroup_id, mailgroup_name, number_of_members
That can look like
1, Group1, 233
2, Group2, 124
3, Group3, 54
What I want is to combine these 3 tables to that I can get a list of all the newsletter like this:
Letter date | Letter headline | Receivers | Status
2008-01-01 12:00:00 | A great headline | Group1, Group 2 | 1
So in short I want my SQL query to join the 3 tables and in that process select the receivers from the mailgroup table and display them comma separated like Group1, Group 2
This what I got now
SELECT A.*, B.* FROM newsletter_items A, newsletter_fields B, WHERE B.field_letter_uid = A.letter_id AND field_name = 'letter_headline' AND A.template = '". $template ."';
But I can't seem to figure out how to get the mailgroups into that.
I recommend that you make your joins explicit.
It makes it easier to debug your query and to change inner with left joins.
There is absolutely never a good reason to use SQL '89 implicit join syntax.
SELECT ni.*
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
Regarding your database design.
I recommend you normalize your database, that means that you move the comma separated fields into a different table.
So you make a table receivers
Receivers
----------
id integer auto_increment primary key
letter_id integer not null foreign key references newsletter_items(letter_id)
value integer not null
You then remove the field receiver from the table newsletter_items
Your query then changes into:
SELECT ni.*
, group_concat(r.value) as receivers
, nf.*
, group_concat(nm.mailgroup_name) as mailgroups
FROM newsletter_items ni
INNER JOIN newsletter_fields nf
ON (nf.field_letter_uid = ni.letter_id)
INNER JOIN newsletter_mailgroups nm
ON (find_in_set(nm.mailgroup_id, ni.receivers))
LEFT JOIN receiver r ON (r.letter_id = ni.letter_id)
WHERE
nf.field_name = 'letter_headline'
ni.template = '". $template ."'
GROUP BY ni.letter_id;
This change should also speed up your query significantly.
If it's allowed, why don't you create a new table called newsletter_item_receivers where you could store letter_id, receiver_id fields?
Having comma separated values in a field like this usually means you're missing a table :)
Edit:
By using CSV, you are making your life miserable when you want to retrieve an answer to "give me all newsletters that receiver_id=5 receives" :)
Here's a good answer to a similar question on SO: Comma separated values in a database field
Edit2:
If I understand your table relationships correctly then it would be something like this:
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items_receivers b
LEFT OUTER JOIN newsletter_items a ON (a.letter_id = b.letter_id)
LEFT OUTER JOIN newsletter_mailgroups m ON (m.mailgroup_id = b.receiver_id)
NOTE! This query WILL NOT return a newsletter when there are no receivers of that newsletter.
If you need that functionality you can try something like this:
SELECT
x.letter_date,
y.mailgroup_name,
x.status
FROM (
SELECT
a.letter_date,
b.receiver_id,
a.status
FROM newsletter_items a
LEFT OUTER JOIN newsletter_items_rec b ON (b.letter_id = a.letter_id)) x
LEFT OUTER JOIN newsletter_mailgroups y ON (y.mailgroup_id = x.receiver_id)
I don't have access to SQL right now so I might have made some syntax errors (hopefully not logical ones :)).
As for why we are doing it like this, as #Konerak pointed out, you'd be well advised to read up on database normalization and why it's important.
You can start with this article from about.com, just glanced over it seems an OK read
http://databases.about.com/od/specificproducts/a/normalization.htm
Also, it would be good if you'd keep fields names the same across multiple tables.
For example you have letter_id in newsletter_items, but you have field_letter_uid in newsletter_fields. Just a thought :)
Try to use
SELECT A.*, B.*, group_concat(C.mailgroup_name SEPARATOR ',')
FROM newsletter_items A, newsletter_fields B, newsletter_mailgroups C
WHERE B.field_letter_uid = A.letter_id
AND field_name = 'letter_headline'
AND A.template = '". $template ."'
and find_in_set(c.mailgroup_id, A.receivers)
group by A.letter_id;

Categories