Generate table with columns dynamically - mysql

Say we have a table named questions with the following schema and content
id | name | text
1 | Q1 | Java: some text
2 | Q2 | Python: other text
3 | Q3 | C#: something else
then a table called answers with something like this
id | uid | qid | value
1 | 100 | 1 | true
2 | 100 | 3 | false
3 | 101 | 2 | false
4 | 101 | 3 | true
where qif is a FK to the question table, and uid a key to some other user table (not important here)
Now I'd like to generate the following table:
uid | Java | Python | C#
100 | true | null | false
101 | null | false | true
that is, a table that for each user contains all different questions taken from its table, with its column name generated dynamically based on its text (ie. using substring for instance)
My initial approach is the naive one, using something like this:
SELECT uid, java
FROM answer
LEFT JOIN
(SELECT a.uid, q.value AS "java"
FROM answer a
INNER JOIN question q ON q.id = a.qid
WHERE q.id=1) java
ON java.uid = u.id
LEFT JOIN
// same for "python", "c#"...
...
that is, generating each column by appending to it through a join. This of course works fine when having few items, but I was wondering if there is any alternative approach to do it more generically, without having to do these repetitive LEFT JOINS
Any help is greatly appreciated!

Related

SQL - get records from many-to-many relations by the user itself -OR- his group

I have two database tables, one as the main table and the other as the relation table.
The first table is a table of contents and the second table is a table that connects to users or groups.
Some data may also be modified in this second table.
I'm not sure about the structure and performance.
for example, we have User Id 160 which is under group id 7
So for the first, we have a post Table.
id | title | content | cover | status
------------------------------------------------
1 | first | content 1 | /img/... | 1
2 | second | content 2 | /img/... | 1
3 | another | content 3 | /img/... | 1
4 | four | content 4 | /img/... | 1
5 | five | content 5 | /img/... | 1
and for the second we have a post_rel Table:
id | group_id | user_id | post_id | title | cover | sort | status
---------------------------------------------------------------------------
1 | 7 | NULL | 1 | g title | img/... | 1 | 1
2 | NULL | 160 | 1 | u title | NULL | 2 | 1 *** selected for user_id
3 | 7 | NULL | 2 | NULL | img/... | 6 | 0
4 | NULL | 160 | 2 | NULL | img/... | 4 | 1 *** selected for user_id
5 | NULL | 160 | 3 | some | img/... | 3 | 1 *** selected for user_id
6 | 7 | NULL | 4 | NULL | img/... | 9 | 1 *** selected for group_id
7 | NULL | 165 | 5 | NULL | img/... | 5 | 0
This is the basic query we have.
select
`post_rel`.`title` as `custom_title`,
`post_rel`.`cover` as `custom_cover`,
`post_rel`.`group_id`,
`post_rel`.`user_id`,
`post`.*
from
`post`
inner join `post_rel` on `post`.`id` = `post_rel`.`post_id`
where
`post`.`status` = 1
and `post_rel`.`status` = 1
and (
`post_rel`.`user_id` = 160
or (
`post_rel`.`group_id` = 7
and `post_rel`.`post_id` not in (
select
`post_rel`.`post_id`
from
`post_rel`
where
`post_rel`.`user_id` = 160
)
)
)
order by
`post_rel`.`sort` asc
So, what you think about the basic query? Especially in the subquery, won't performance drop in a large table? Is it possible to write a better and simpler query or change the structure?
Edit: this is sqlfiddle example of my code and structure http://sqlfiddle.com/#!9/ed9d4b/1
I would change it to use "not exists" instead of "not in" and would use aliases so I could pull it off like so:
select
b.`title` as `custom_title`,
b.`cover` as `custom_cover`,
b.`group_id`,
b.`user_id`,
a.*
from
`post` a
inner join `post_rel` b on a.`id` = b.`post_id`
where
a.`status` = 1
and b.`status` = 1
and (
b.`user_id` = 160
or (
b.`group_id` = 7
and not exists (
select
'x'
from
`post_rel` c
where
c.`user_id` = 160 and c.`post_id`=b.`post_id`
)
)
)
order by
b.`sort` asc
Typically when managing users and group, there's this notion of an exception user who directly can get assigned to assets just like the whole group. This seems to be an example of that.
From a modeling-only perspective, there are 2 ways to deal with that:
Ensure that every user exists in a group and that you only assign assets to groups. For the exception user, create a group. You could even enforce that every user belongs to only one group. This way your post_rel table deals with only groups. Unfortunately, the relationship between group and user is not understood well enough to weigh in appropriately.
Driven only by the need to eliminate null values towards a good model which also reduces overhead, the other option is to use name value pairs and allows the User and Group to exist in the same field with another field besides it, denoting Group or User.
These are the SQL Fiddle:
NOT EXISTS version: http://sqlfiddle.com/#!9/1af8cf/2
NOT IN version: http://sqlfiddle.com/#!9/1af8cf/1
Some reading on nulls https://dev.mysql.com/doc/refman/5.6/en/data-size.html
Specifically:
Declare columns to be NOT NULL if possible. It makes SQL operations faster, by enabling better use of indexes and eliminating overhead for testing whether each value is NULL. You also save some storage space, one bit per column. If you really need NULL values in your tables, use them. Just avoid the default setting that allows NULL values in every column.

Select all values from a table plus a column returning 1/0 whether a record exists in other table [duplicate]

This question already has answers here:
Selecting boolean in MySQL based on contents of another table
(2 answers)
Closed 4 years ago.
I need some help with a MySQL query which is bringing me a headache.
Basically I have two tables which are related. The first table is called 'books' and it contains the basic information about a book. Then I have an other table called 'user_books' which is related to the previous table and other table (which is irrelevant in the question). This is how the books table looks like:
| b_id | b_name | b_description |
---------------------------------------------------
| 1 | Book1 | Description1 |
| 2 | Book2 | Description2 |
The 'user_books' table has this content:
| ub_userid | ub_bookid | ub_rating | ub_default |
------------------------------------------------------
| 10 | 1 | 5 | 1 |
The user_books table has two primary keys: ub_userid and ub_bookid.
Now I need to make a query which returns all books of the books table and for each book the rating of a given user and a column that in case that there is a record for the book in the user_books table return 1 but if there isn't any book with that bookid return 0.
My desired output given the user 10 would be this:
| b_id | b_name | b_description | ub_default | active |
----------------------------------------------------------
| 1 | Book1 | Description1 | 1 | 1 |
| 2 | Book2 | Description2 | 0 | 0 |
----------------------------------------------------------
I'm using MySQL 5.7
Thanks so much in advance for any kind of help.
select
b.b_id,
b.b_name,
b.b_description,
coalesce(ub.ub_default, 0) as ub_default,
case
when ub.ub_userid is null then 0
else 1
end as active
from books b left outer join
user_books ub
on ub.ub_bookid = b.b_id
where
ub.ub_userid = 10;
This doesn't do any aggregation, so if you have multiple user_books records for one books record, then the books record will be duplicated. But, it shows how to join against a missing row (outer join) and test for whether that outer join row is present or missing.
Here's a SQL Fiddle for MySQL 5.6 http://sqlfiddle.com/#!9/b70ff8/4/0

Mysql: how to return value or empty string from column ids in table (multi table)

I'M trying to extract all information into my table, but I need to change id, when available, to the name into another table.
I have 1 table like that:
|------------------------------|
|-id-|-systems-|-remote-|-deco-|
| 1 | NULL | 3 | |
| 2 | 21 | NULL | 2 |
|-------------------------------
each column like "systems" / "remote" / "deco" refer to an id into another table
I know how to use INNER JOIN. But if I use that, I got an empty result because the value need to be appears into the others tables.
ex.:
SELECT qd.id,s.name as systems,r.name as remote, d.name as deco
FROM `quote_data` qd
INNER JOIN systems s ON qd.systems=s.id
INNER JOIN remote r ON qd.remote=r.id
INNER JOIN deco d ON qd.deco=d.id
I got empty result.
In the best words, I need to do something like:
|------------------------------|
|-id-|-systems-|-remote-|-deco-|
| 1 | | R42 | |
| 2 | GTV | | B21 |
|-------------------------------
Also, I use innoDB table
Any Idea how to fix that?

comparing two SQL tables set cut-set to true and else to false

for my new project I decided to optimize all kind of inefficient SQL queries I've used on my older projects before importing it. After some hours on a problem that in my opinion seems to be very simple I am at a loss. I've got two different Tables that are holding user-IDs
table r table s
+-----+ +-----+
| user| | user|
+-----+ +-----+
| 2 | | 1 |
| 3 | | 4 |
| 4 | +-----+
+-----+
what I want now is to compare both Tables and setting a true or false flag in another column of the result table which shows me what value of table s is in table r. So what I want is just set the cut-set to true. the result table should look like this
result
+-----+-------+
| user| flag |
+-----+-------+
| 1 | FALSE |
+-----+-------+
| 4 | TRUE |
+-----+-------+
So the user 4 is the only user that is listed in table r. I already tried some solutions with IF clauses but I'm not pro in SQL but I do my best to get closer to that aim. My idea was this query
SELECT
r.user,
IF(r.user = s.user, TRUE, FALSE) AS flag
FROM
r,
s
I know that it isn't correct at all because the query gives me the following 6 rows.
result
+-----+-------+
| user| flag |
+-----+-------+
| 2 | 0 |
+-----+-------+
| 2 | 0 |
+-----+-------+
| 3 | 0 |
+-----+-------+
| 3 | 0 |
+-----+-------+
| 4 | 0 |
+-----+-------+
| 4 | 1 |
+-----+-------+
so the last row to each user seems to be the result I want but the problem is that the false table is combined with the flag and each user has two flags. I don't understand whats going wrong and I would be appreciated if someone could explain me how the SQL server understands my query and what I have to change to get the correct result
You have no join condition, so your query is simply creating a cartesian product between the two tables. It then reports true or false depending on whether the users match in a particular pairing. You would see this if you included s.user in your SELECT clause.
To do what you want, you should use an outer join.
SELECT s.user, r.user IS NOT NULL AS flag
FROM s
LEFT JOIN r
ON r.user = s.user
Try:
SELECT r.user FROM r,s flag WHERE r.user = s.user

Joining from another table multiple times in a MySQL query

I am trying to do multiple joins on the same MySQL table, but am not getting the results that I expect to get. Hopefully someone can point out my mistake(s).
Table 1 - cpe Table
|id | name
|----------
| 1 | cat
| 2 | dog
| 3 | mouse
| 4 | snake
-----------
Table 2 - AutoSelect
|id | name | cpe1_id | cpe2_id | cpe3_id |
|-----------------------------------------------
| 1 | user1 | 1 | 3 | 4 |
| 2 | user2 | 3 | 1 | 2 |
| 3 | user3 | 3 | 3 | 2 |
| 4 | user4 | 4 | 2 | 1 |
------------------------------------------------
I would like to see an output of
user1 | cat | mouse | snake |
user2 | mouse | snake | dog |
..etc
Here is what I have tried
SELECT * FROM AutoSelect
LEFT JOIN cpe ON
( cpe.id = AutoSelect.cpe1_id ) AND
( cpe.id = AutoSelect.cpe2_id ) AND
( cpe.id = AutoSelect.cpe3_id )
I get blank results. I thought i knew how to do these joins, but apparently when I'm trying to match cpe?_id with the name of the cpe table.
Thanks in advance for any assistance.
You need left join 3 times as well. Currently your query only joins 1 time with 3 critieria as to the join. This should do:
SELECT a.name, cpe1.name, cpe2.name, cpe3.name FROM AutoSelect as a
LEFT JOIN cpe as cpe1 ON ( cpe1.id = a.cpe1_id )
LEFT JOIN cpe as cpe2 ON ( cpe2.id = a.cpe2_id )
LEFT JOIN cpe as cpe3 ON ( cpe3.id = a.cpe3_id )
And you probably mean to INNER JOIN rather than LEFT JOIN unless NULL values are allowed in your AutoSelect table.
I think your design is wrong.
With tables like that, you get it the way it's meant to be in relational databases :
table 1 : animal
id name
1 cat
2 dog
3 mouse
4 snake
table 2 : user
|id | name |
|--------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
| 4 | user4 |
table 3 : association
|id_user | id_animal|
|--------------------
| 1 | 1 |
| 1 | 3 |
| 1 | 4 |
| 2 | 3 |
| 2 | 1 |
| 2 | 2 |
| 3 | 3 |
| 3 | 2
| 4 | 4 |
| 4 | 2 |
| 4 | 1 |
---------------------
Then :
select u.name, a.name from user u, animal a, association ass where ass.user_id = u.id and ass.animal_id = a.id;
In this case, your solution won't produce a good dynamic database. There are other ways to make combinations of multiple tables. I can show you by my own database what you should use and when you should use this solution. The scheme is in dutch, but you'll probably understand the keywords.
Like you, I had to combine my windmills with a kWh-meter, which has to measure the energyproduction of my windmills. What you should do, is this case, is making another table(in my case molenkWhlink). Make sure your tables are INNODB-types(for making Foreign keys). What I've done is combining my meters and mills by putting a pointer(a foreign key) of their ID(in Dutch Volgnummer) in the new table. An advantage you may not need, but I certainly did, is the fact I was able to extend the extra table with connection and disconnection info like Timestamps and metervalues when linking or unlinking. This makes your database way more dynamic.
In my case, I Also had a table for meassurements(metingoverzicht). As you can see in the scheme, I've got 2 lines going from Metingoverzicht to molenkwhlink. The reason for this is quite simple. All meassurements I take, will be saved in table Metingoverzicht. Daily meassurements(which are scheduled) will have a special boolean put on, but unscheduled meassurements, will also me saved here, with the bollean turned off. When switching meters, I need the endvalue from the leaving meter and the startvalue from the new meter, to calculate the value of todays eneryproduction. This is where your solution comes in and an extra table won't work. Usually, when you need just one value from another table a JOIN will be used. The problem in this case is, I've got 2 meassurementIDs in 1 link(1 for connecting and 1 for disconnecting). They both point to the same tablecolumn, because they both need to hold the same type of information. That is when you can use a double JOIN from one table towards the other. Because, both values will only be used once, and just needed to be saved in a different place to avoid having 1 action stored on different locations, which should always be avoided.
http://s1101.photobucket.com/user/Manuel_Barcelona/media/schemedatabase.jpg.html