How to fill a SQL column with data (calculated) from another table - mysql

I have a question and don't know how to approach the problem exactly.
I have two tables as following:
Clients
| c_id | name | reference |
| ---- | ------- | --------- |
| 1 | ClientA | 1 |
| 2 | ClientB | 1 |
| 3 | ClientC | 2 |
| 4 | ClientD | 2 |
| 5 | ClientE | 1 |
| 1 | ClientF | 3 |
Tour
| t_id | name | count |
| ---- | ------- | ----- |
| 1 | TourA | 3 |
| 2 | TourB | 2 |
| 3 | TourC | 1 |
"Reference" in the "Client" table is defined as foreign key.
Is it possible to fill the column "count" in the table "Tour" with an automated formula where it counts how many times the t_id appears in the "Client" table?
Something like: COUNT(c_id) FROM clients WHERE reference = t_id
I have read about to create a view but not sure how to fetch the data correctly.
Thanks for your help,
Raphael
UPDATE #1:
The workflow as described with the view works perfectly. I'm trying now to fill the column via a trigger but I'm getting an SQL error with the following code:
CREATE TRIGGER client_count
AFTER UPDATE
ON clients FOR EACH ROW
SELECT t.*,
(
SELECT COUNT(*) FROM clients c where c.tour_id = t.tour_id
) AS tours.tour_bookedspace
FROM tours t

The view you have referred to is indeed the way to go here. The view you need to create needs to join the two tables and perform a count aggregation as follows:
CREATE VIEW vwTour
AS
SELECT t.t_id,
t.name,
COUNT(t.name) AS Cnt
FROM tour t
JOIN Clients c
ON t.t_id = c.reference
GROUP BY t_id,
t.name

No you can't. Generated columns can only use data from the same table.
The options you have are:
1. Use a view
You can select from a view that computes the extra value(s) you want. For example:
create view tour_data as
select t.*,
(
select count(*) from clients c where c.reference = t.t_id
) as number_of_clients
from your t
2. Use a trigger
Alternatively, you can add the extra column number_of_clients and populate it using a trigger every time a row is added, modified, or deleted from the table clients.

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.

In mysql how can I get only rows from one table which do not link to any rows in another table with a specific ID

I have two tables with the following structures (unnecessary columns trimmed out)
----------------- ---------------------
| mod_personnel | | mod_skills |
| | | |
| - prs_id | | - prs_id |
| - name | | - skl_id |
----------------- | |
---------------------
There may be 0 to many rows in the skills table for each prs_id
What I want is all the personnel records which do NOT have an associated skill record with skill_id 1.
In plain English "I want all the people who do not have the skill x".
Currently, I have only been able to do it with the following nested select. But I am hoping to find a faster way.
SELECT * FROM `mod_personnel` WHERE `prs_id` NOT IN (
SELECT `prs_id` FROM `mod_skills` WHERE `skl_id` = 1 )
This may be faster:
SELECT `mod_personnel`.*
FROM `mod_personnel`
left outer join `mod_skills`
on `mod_skills`.`prs_id` = `mod_personnel`.`prs_id`
and `mod_skills`.`skl_id` = 1
WHERE `mod_skills`.`prs_id` is null;
Using a NOT EXISTS might be faster.
SELECT *
FROM `mod_personnel` p
WHERE NOT EXISTS (SELECT *
FROM `mod_skills` s
WHERE s.`prs_id` = p.`prs_id`
AND s.`skl_id` = 1 );

mySQL JOIN statement with COUNT not matching correctly

I am trying to write a mySQL statement that selects data from one table but counts up entries from another table with a matching ID in a specific field.
The two tables are jobs and job_cards. A job will always be a single entry which will have multiple job cards, so I need to write a singular statement that selects data from the job table but adds another field in the result which is a count of all related job cards.
Example:
jobs table:
| ID | customer | status | date_added |
|----------------------------------------|
| 1 | 3 | active | 2017-10-10 |
------------------------------------------
job_cards table is a bit more complex but includes a column called job_id which will be 1 in this case. But lets say there are 3 cards assigned to the job above. I wrote the following statement:
SELECT j.*, COUNT(jc.id) AS card_count FROM jobs j LEFT JOIN job_cards jc ON j.id = jc.job_id
But the count column only returns the TOTAL number of cards in the job_cards table, regardless of which job they are assigned to. Not only that, but it only ever returns a single result even though at the moment there are 4 entries in the jobs table.
Is there any way to do what I need to do with a single statement?
EDIT:
Sample data from the job_cards table:
| ID | job_id | customer | description | materials | notes |
|--------------------------------------------------------------|
| 1 | 1 | 3 | blah blah | none | test |
| 2 | 1 | 3 | something | pipes | n/a |
----------------------------------------------------------------
The result I would like to get is:
| ID | customer | date_added | card_count |
|-------------------------------------------|
| 1 | 3 | 2017-10-10 | 2 |
---------------------------------------------
Where the ID here is the ID of the job.
You can try this:
SELECT *, (select count(*)
from job_cards jc
where jc.job_id=j.id) as card_count
FROM jobs j

Split SQL field containing array into new table/rows

I need a list of user IDs (course_user_ids) that is currently stored in a single field of a larger table.
I have a table called courses that contains course information with course_id and course_students as such:
-----------------------------------------------------------
| course_id | course_students |
----------------------------------------------------------
| 1 | a:3:{i:0;i:12345;i:1;i:22345;i:2;i:323456;} |
-----------------------------------------------------------
| 2 | a:32:{ … } |
-----------------------------------------------------------
The course_students part contains 3 chunks of information:
the number of students (a:3:{…) -- not needed
the order/key for the array of each student ({i:0;… i:1;… i:2; …}) -- also not needed
the course_user_id (i:12345; … i:22345;… i:32345;)
I only need the course_user_id and the original course_id, resulting in a new table that i can use for joins/subqueries like this:
------------------------------
| course_id | course_user_id |
------------------------------
| 1 | 12345 |
------------------------------
| 1 | 22345 |
------------------------------
| 1 | 323456 |
------------------------------
(ideally able to continue to break out values for other course_ids and course_user_ids, but not a priority:)
| … | … |
------------------------------
| 2 | … |
------------------------------
| 2 | … |
------------------------------
| 97 | … |
------------------------------
| 97 | … |
------------------------------
| … | … |
------------------------------
Note: the course_user_id can vary in length (some are 5 digits, some are 6)
Any ideas would be much appreciated!
Update
My user table does have user_id which can be mapped to course_students or course_user_id, so that is a very helpful observation from below.
I also think I need to use a LEFT JOIN because some students are registered in multiple courses, and I'd like to see each instance/combo.
Let us assume that you have a table name users which contains all users data along with user_id.
Now you can join table courses and table users in following manner:
select c.course_id,u.user_id
from
courses c
join users u
on u.user_id=if(instr(c.course_students,concat(":",u.user_id,";"))>0,u.user_id,c.course_students)
You get the result as per your requirement.
Verify at http://sqlfiddle.com/#!9/3667d/2
Note: The above query works fine if no overlapping between user_id and array index. In case of overlapping, kindly filter data using where-clause
If I got your goal correctly you have users table. And {i:0;i:12345;i:1;i:22345;i:2;i:323456; equal users.id=12345,users.id=22345 etc.
If my guess is correct you can try this solution:
http://sqlfiddle.com/#!9/cfef27/5
SELECT * FROM courses
LEFT JOIN users u
ON courses.course_students LIKE CONCAT('%i:',u.id,';%')

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?