MYSQL - Join 2 tables with full rows on left table - mysql

Could you please help me to resolve this issue?
I have 2 tables as below:
attributes
| id | name |
|----+--------------|
| 1 | first_name |
| 2 | last_name |
| 3 | email |
attribute_values
| uid | attr_id | value |
|-------+-----------|-----------|
| 1 | 1 | Hello |
| 1 | 2 | world |
|-------+-----------|-----------|
| 2 | 1 | A |
| 2 | 2 | B |
| 2 | 3 | a#xzy.com |
I want to write a query to select attributes of a user even if attribute is not exist in table attribute_values. Then, I would like to get result with null value.
For example for selecting user id 1
| name | value |
|---------------|-----------|
| first_name | Hello |
| last_name | world |
| email | NULL |
user id 2
| name | value |
|---------------|-----------|
| first_name | A |
| last_name | B |
| email | a#xzy.com |
user id 3 (not exist in attribute_values)
| name | value |
|---------------|-----------|
| first_name | NULL |
| last_name | NULL |
| email | NULL |
Is there best way with one query to get result as 3 above examples?
Thanks.

SELECT a.*, b.value
FROM attributes a
LEFT JOIN attribute_values b
ON a.ID = b.attr_id AND b.uid = 1
SQLFiddle Demo

Related

How do I query a three-level structure in two joined tables?

There are two tables,
Table A has a three-level structure that looks like
| id | name | level | up_level_id |
| :------- | :-------: | :------: | ----------:|
| 1 | lv1_name1 | 1 | null |
| 2 | lv1_name2 | 1 | null |
| 3 | lv2_name1 | 2 | 1 |
| 4 | lv2_name2 | 2 | 2 |
| 5 | lv3_name1 | 3 | 3 |
| 6 | lv3_name2 | 3 | 3 |
| 7 | lv3_name3 | 3 | 4 |
| 8 | lv3_name4 | 3 | 4 |
Table B looks like
| amount | org_id |
| -------- | -------- |
| 12,000 | 5 |
| 15,000 | 6 |
| 20,000 | 7 |
| 18,000 | 8 |
Table A and Table B can be joined on A.id=B.org_id, but they are all at the level-3 of Table A(Only level-3 has their amount)
I want to query the top-level name,level-1 name, and the summary amount that looks like
| sum_amount | top_lvl_name |
| -------- | -------- |
| 27,000 | lv1_name1 |
| 38,000 | lv1_name2 |
For Testing, I have already accomplished the query of the level-1 name from the level-3 id in TableA
The SQL is as follows
SELECT name
FROM TableA
WHERE id IN (
SELECT up_level_id
FROM Table A
WHERE id IN (
SELECT up_level_id
FROM Table A
WHERE id=5) --query the id:5's top-level name
);
But when I join these two tables as follows, it goes wrong
SELECT sum(amount) AS sum_amount, name AS top_lvl_name
FROM TableA, TableB
WHERE id = org_id
AND id IN (
SELECT up_level_id
FROM TableA
WHERE id IN (
SELECT up_level_id
FROM TableA
WHERE TableA.id IN(
SELECT org_id
FROM TABLEB
)
)
);
I get nothing as above
What can I do to make this query to be correct?
Thanks for everyone's answer and comment.
Finally, I find it very difficult to query the result as I wish. So, I've come up with a shortcut——create a new table that a three-level structure recorded horizontally, which looks like
| lv1_id | lv2_name | lv2_id | lv2_name | lv3_id | lv3_name |
| :------- | :-------: | :------: | :----------:| :------: | :----------:|
| 1 | lv1_name1 | 3 | lv2_name1 | 5 | lv3_name1 |
| 1 | lv1_name1 | 3 | lv2_name1 | 6 | lv3_name2 |
| 2 | lv1_name2 | 4 | lv2_name1 | 7 | lv3_name3 |
| 2 | lv1_name2 | 4 | lv2_name1 | 8 | lv3_name4 |
As above,I can easily connect two tables

MySQL how to combine not null fields that share the same ID into 1 row per ID

What is the most sufficient way to combine rows with null sharing the same id:
Having this table:
| UserID | FNAME | LNAME |
|--------|-------|-------|
| 1 | NULL | NULL |
| 1 | jhon | NULL |
| 1 | NULL | doe |
| 2 | NULL | NULL |
| 2 | Jarry | NULL |
| 2 | NULL | Lauf |
want to get:
| UserID | FNAME | LNAME |
|--------|-------|-------|
| 1 | jhon | doe |
| 2 | Jarry | Lauf |
Aggregate by user and then take the max of the two name columns:
SELECT UserID, MAX(FNAME) AS FNAME, MAX(LNAME) AS LNAME
FROM yourTable
GROUP BY UserID;

MySql query for displaying rows as column dyanmically

I've to join three tables the resultant output will be like this
+---------------+--------------+-------------+-------------+
| Category_Name | English | French | German |
+---------------+--------------+-------------+-------------+
| Clothing | english_name | french_name | german_name |
| Electronics | NULL | NULL | NULL |
| Ornaments | NULL | NULL | NULL |
+---------------+--------------+-------------+-------------+
Following is my table_structure
tbl_category
+----+---------------+
| id | category_name |
+----+---------------+
| 1 | Clothing |
| 2 | Electronics |
| 3 | Ornaments |
+----+---------------+
tbl_languages
+----+----------+
| id | language |
+----+----------+
| 1 | English |
| 2 | French |
| 3 | German |
+----+----------+
tbl_languages_data
+----+-------------+-------------+---------------+
| id | language_id | category_id | category_name |
+----+-------------+-------------+---------------+
| 1 | 1 | 1 | english_name |
| 2 | 2 | 1 | french_name |
| 3 | 3 | 1 | german_name |
+----+-------------+-------------+---------------+
I've two questions.
1. Are these relations are valid or there can be some other way to avoid deadlocks
2. What will be the query for getting this result.
Note: The output should be dynamic as I've to add more data to tbl_language.
Should be something along the lines of:
SELECT Category_Name
MAX(CASE when lan.language_id = 1 THEN lan.category_name END) as 'English',
MAX(CASE when lan.language_id = 2 THEN lan.category_name END) as 'French',
MAX(CASE when lan.language_id = 3 THEN lan.category_name END) as 'German'
FROM tbl_category cat
RIGHT JOIN tbl_languages_data lan ON cat.id = lan.category_id
GROUP BY Category_Name
NOTE: Did not notice your edit, I don't think you will be able to dynamically add columns to the expected result. Please somebody correct me if I am wrong.
I would omit the last column Category Name from tbl_languages_data. And refer the Language_id as an number, this way you will compare the two indexes with the same datatype (int to int). This will be faster than a string with an int. This will result in the following query:
SELECT cat.category_name
, lang.language
FROM tbl_category AS cat
LEFT JOIN tbl_languages_data AS lang_data
ON lang_data.category_id = cat.id
LEFT JOIN tbl_languages AS lang
ON lang_data.language_id = lang.id
Note that this is different than your expected result set. The three languages are now translated into one column. And here is your table design:
tbl_category
+----+---------------+
| id | category_name |
+----+---------------+
| 1 | Clothing |
+----+---------------+
| 2 | Electronics |
+----+---------------+
| 3 | Ornaments |
+----+---------------+
tbl_languages
+----+----------+
| id | language |
+----+----------+
| 1 | English |
+----+----------+
| 2 | French |
+----+----------+
| 3 | German |
+----+----------+
tbl_languages_data
+----+-------------+-------------+
| id | language_id | category_id |
+----+-------------+-------------+
| 1 | 1 | 1 |
+----+-------------+-------------+
| 2 | 2 | 1 |
+----+-------------+-------------+
| 3 | 3 | 1 |
+----+-------------+-------------+

Dsictint bi-directional relation

I have table that describes realtionships between other tables in database. Every user can have any file, and every file can have any user.
If I get one files' relations and I don't have this file relation to user, but user has relation to that file. I want to see that.
When both have relations to themselves I don't want to see twice records.
As an input I have record type and record id. How to achieve that?
Closure table:
+--------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| record_id | int(11) | NO | | NULL | |
| record_type | varchar(200) | NO | | NULL | |
| second_record_id | int(11) | NO | | NULL | |
| second_record_type | varchar(200) | NO | | NULL | |
+--------------------+--------------+------+-----+---------+----------------+
Sample data:
+----+-----------+-------------+------------------+--------------------+
| id | record_id | record_type | second_record_id | second_record_type |
+----+-----------+-------------+------------------+--------------------+
| 1 | 1 | files | 1 | users |
| 2 | 2 | users | 1 | files |
| 3 | 3 | users | 1 | files |
| 4 | 2 | files | 1 | users |
| 5 | 1 | users | 1 | files |
| 6 | 1 | files | 3 | users |
+----+-----------+-------------+------------------+--------------------+
I've tried
SELECT * FROM closure
WHERE record_id=1 OR second_record_id = 1
AND record_type="files" OR second_record_type="files"
GROUP BY "files"
HAVING record_id=1 OR second_record_id=1
but it gets me one relation:
+----+-----------+-------------+------------------+--------------------+
| id | record_id | record_type | second_record_id | second_record_type |
+----+-----------+-------------+------------------+--------------------+
| 1 | 1 | files | 1 | users |
+----+-----------+-------------+------------------+--------------------+
My desired result would be:
+----+-----------+-------------+------------------+--------------------+
| id | record_id | record_type | second_record_id | second_record_type |
+----+-----------+-------------+------------------+--------------------+
| 1 | 1 | files | 1 | users |
| 2 | 2 | users | 1 | files |
| 6 | 1 | files | 3 | users |
+----+-----------+-------------+------------------+--------------------+
Sql fiddle
EDIT
I've dropped id column finally.
I think you just want to compare both the "1" and the "files" at the same type, for each record type. This should produce your desired result:
SELECT c.*
FROM closure c
WHERE (record_id = 1 and record_type = 'files') OR
(second_record_id = 1 and second_record_type = 'files');
You don't have aggregation function so the group by and the having clause are unuseful if you wondt distinct add distinct clause (but the use of id don't permit a proper work)
SELECT *
FROM closure
WHERE ( record_id=1 OR second_record_id = 1 )
AND (record_type="files" OR second_record_type="files" )
or
SELECT distinct record_id, record_type, second_record_id, second_record_type
FROM closure
WHERE ( record_id=1 OR second_record_id = 1 )
AND ( record_type="files" OR second_record_type="files" )

Help with MySQL join 3 Tables

I am working on a registration system that uses the following tables:
Person (name etc.), Course (course date), Registration (association table pid, cid)
Person
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | text | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
Example Data:
select * from person;
+-----+--------+
| id | name |
+-----+--------+
| 101 | Graham |
| 102 | Lisa |
| 103 | John |
+-----+--------+
Course
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | text | YES | | NULL | |
| date | date | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
select * from course;
+----+---------+------------+
| id | name | date |
+----+---------+------------+
| 1 | Hip Hop | 2011-06-08 |
| 2 | Dancing | 2006-06-23 |
| 3 | Running | 2007-07-08 |
+----+---------+------------+
Registration
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| pid | int(11) | YES | | NULL | |
| cid | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
select * from registration;
+----+------+------+
| id | pid | cid |
+----+------+------+
| 1 | 101 | 1 |
| 2 | 101 | 2 |
| 3 | 103 | 2 |
+----+------+------+
I would like to find person(s) that have no registration records within the past two years. I am attempting to join the tables based on date calculation but it does not seem to work this way. Is this possible with mysql or is my approach of trying this with one query wrong?
query I have come up with:
select * from
(person left join registration on person.id = registration.pid)
left join course on course.id = registration.cid
AND DATE_FORMAT(`course`.`date`, "%m.%Y") > DATE_FORMAT( DATE_SUB(NOW(), INTERVAL 2 YEAR),"%m.%Y")
WHERE
registration.id IS NULL;
+-----+------+------+------+------+------+------+------+
| id | name | id | pid | cid | id | name | date |
+-----+------+------+------+------+------+------+------+
| 102 | Lisa | NULL | NULL | NULL | NULL | NULL | NULL |
+-----+------+------+------+------+------+------+------+
It should list person 102 and 103 since both registrations are older than 2 years and no other records of newer course dates can be found...
Give this a shot, using a NOT EXISTS clause:
select p.* from person p
where not exists (select 1 from person px
join registration rx on px.id = rx.pid
join course cx on rx.cid = cx.id
where px.id = p.id
and cx.date > DATE_SUB(NOW(), INTERVAL 2 YEAR))