I have a MySQL table:
+======+=========+============+======+======+
| name | surname | other_name | year | date |
+======+=========+============+======+======+
| John | Foo | NULL | 2000 | 2017 |
+------+---------+------------+------+------+
| John | Foo | Bar | 2000 | 2018 |
+------+---------+------------+------+------+
| John | Bar | NULL | 2000 | 2018 |
+------+---------+------------+------+------+
| John | Bar | Bar | 2000 | 2018 |
+------+---------+------------+------+------+
| John | Foo | NULL | 1990 | 2018 |
+------+---------+------------+------+------+
I'm trying to group the records for same person. Same person is identified by name, surname and year of birth.
One can however change his surname (Foo -> Bar). Then the old rows' other_name column should be updated with the new name. Unfortunately the data I have are incomplete and when one changed his name, the other_name might have been updated, but it also might not.
I can easily group by the three basic columns.
What I need to do as well though is to cross compare the surname and other_name and if they match and so do the name and year columns, group them under the most recent surname (decided by date when the row was recorded).
The final print result should look like this:
+======+===========+======+
| name | surname | year |
+======+===========+======+
| John | Bar (Foo) | 2000 |
+------+-----------+------+
| John | Foo | 1990 |
+------+-----------+------+
I realize it's rather a complex task for an SQL query. So if you have a simpler solution accomplished in the program (PHP), I would appreciate it as well.
Hmmm . . . This does what you want in the limited case that surnames are only changed once:
select t.name, t.year, group_concat(distinct t.surname) as surnames
from t left join
t tother
on t.surname = tother.other_name and t.name = tother.name and t.year = tother.year
group by t.name, t.year, coalesce(tother.surname, t.surname);
Here is a db<>fiddle (it uses Postgres because I find that easier to set up but all is the same except the group_concat()).
Related
I have two different tables with different column name
Table 1 users
+-----+----------+---------+---------+------------+
| id | name | username| image | email |
+-----+----------+---------+---------+------------+
| 1 | John1 | exmple1 | img1 |a#gmail.com |
| 2 | John2 | exmple2 | img2 |b#gmail.com |
| 3 | John3 | exmple3 | img3 |c#gmail.com |
| 4 | John4 | exmple4 | img4 |d#gmail.com |
+-----+----------+---------+---------+------------+
Table 2nd Company
+-----+----------+------------+---------=------+-----------+
| id |company_name | username| description | founded |
+-----+-------------+---------+----------------+-----------|
| 1 | john1 | exmple1 |description1 | 2016 |
| 2 |CompanyName2 | exmple2 |description2 | 2016 |
| 3 |CompanyName3 | exmple3 |description3 | 2016 |
| 4 |CompanyName4 | exmple4 |description4 | 2016 |
+-----+-------------+---------+---------=------+-----------+
Now whenever a user type any input in search bar an ajax request is made to php file which checks for if that name of user exist in database or not
so for example if user type input john then a query should run which will check john in users table and company table & if there is a user name john & if there is a company named john it should fetch both the result.
how can i achieve this, i tried using UNION in my query but it said columns are different
Currently this is my query
$go = mysqli_query($connecDB, "SELECT name, img,username FROM users WHERE name LIKE '$q%' LIMIT 0,10");
Now people might be thinking what i really want!
i want a single query that will check for input in both table & fetch their details
You can use union
select id, name ,username, image, email, null, null
from users
where name LIKE concat('$q', '%')
union all
select id, company_name as name ,username, null, null, description, founded
from Company
where name LIKE concat('$q, '%')
order by name limit 10
I have a table in my MySQL like the below
============================
id | courses | r_number
----------------------------
1 | English | C/009
2 | Maths | C/009
3 | English | C/003
4 | Maths | C/002
============================
How do I ouput this to be like the HTML table below
====================================
id | courses | r_number
------------------------------------
1 | English, Maths | C/009
2 | English | C/003
3 | Maths | C/002
====================================
SELECT MIN(ID) ID,
GROUP_CONCAT(Courses SEPARATOR ', ') Courses,
r_Number
FROM TableName
GROUP BY r_Number
ORDER BY ID
SQLFiddle Demo
I'm wondering why in your example the ID doesn't match with the record, why is that?
I have a table like the one below that currently has no values for rating, lib_id or votes.
library
id | title | year | rating | votes | lib_id |
---------------------------------------------
1 | book1 | 1999 | | | |
2 | book2 | 2010 | | | |
3 | book3 | 2009 | | | |
4 | book4 | 2007 | | | |
5 | book5 | 1987 | | | |
I then have the classifications table which looks like this.
classifications
id | title | year | rating | votes | lib_id |
---------------------------------------------
108 | book154 | 1929 | | | |
322 | book23 | 2011 | | | |
311 | book3 | 2009 | 9.3 | 4056 | 10876 |
642 | book444 | 2001 | | | |
533 | book567 | 1981 | | | |
It can happen that entries in the library table may not appear in the classifications table and vice-versa. There can also be the possibility that the title of the book is not unique. So what I want to do is go through each row in the library table, take the title and year columns, go to the classifications table and find the row that has these two values, retrieve the corresponding rating, votes and lib_id columns and update the entry in the library table.
I also want to use PDOs. Below is a non-working example of what i'm trying to achieve.
$update_vals_STH =
$DBH->prepare(
"UPDATE library SET lib_id=?, rating=?, votes=?
FROM (SELECT lib_id, rating, votes)
FROM classifications WHERE title=? AND year=?";
Any help would be appreciated. I'm quite new to MySQL and have been struggling with this one for a while.
You can join tables on update statement too.
UPDATE library a
INNER JOIN classifications b
ON a.title = b.title AND
a.year = b.year
SET a.rating = b.rating,
a.votes = b.votes,
a.lib_id = b.lib_id
// WHERE clause // if you want to have extra condition.
SQLFiddle Demo
UPDATE
For better performance, you need to add indexes on the following field.
ALTER TABLE library ADD INDEX (title, year);
ALTER TABLE classifications ADD INDEX (title, year);
I have three tables: years, employees, positions. Suppose that I already have these data in those tables.
years:
----------------
| id | name |
----------------
| 1 | 2011 |
----------------
positions:
------------------------------
| id | name | year_id |
------------------------------
| 1 | Director | 1 |
| 2 | Manager | 1 |
------------------------------
employees:
---------------------------------------------------------
| id | name | position_id | year_id |
---------------------------------------------------------
| 1 | Employee A (Director) | 1 | 1 |
| 2 | Employee B (Manager) | 2 | 1 |
---------------------------------------------------------
========================================
The years table is a central point.
If I insert a new year record, I must also copy all positions and employees which are related to the previous year.
So if I insert year 2012 into the years table, the data is suppose to be like this:
years:
----------------
| id | name |
----------------
| 1 | 2011 |
| 2 | 2012 |
----------------
positions:
------------------------------
| id | name | year_id |
------------------------------
| 1 | Director | 1 |
| 2 | Manager | 1 |
| 3 | Director | 2 |
| 4 | Manager | 2 |
------------------------------
employees:
---------------------------------------------------------
| id | name | position_id | year_id |
---------------------------------------------------------
| 1 | Employee A (Director) | 1 | 1 |
| 2 | Employee B (Manager) | 2 | 1 |
| 3 | Employee A (Director) | 3 (?) | 2 |
| 4 | Employee B (Manager) | 4 (?) | 2 |
---------------------------------------------------------
NOTICE the question marks in the third and fourth row of employees table.
I use these queries to insert a new year and copy all related positions and employees:
// Insert new year record
INSERT INTO years (name) VALUES (2012);
// Get last inserted year ID
$inserted_year_id = .......... // skipped
// Copy positions
INSERT INTO positions (name, year_id) SELECT name, $inserted_year_id AS last_year_id FROM positions WHERE year_id = 1;
// Copy employees
INSERT INTO employees (name, position_id, year_id) SELECT name, position_id, $inserted_year_id AS last_year_id FROM employees WHERE year_id = 1;
The problem is at copying employees query. I can't find a method to get or track the new ID of positions.
Is there a simple method to do this?
Thank you very much.
Your data model is seriously flawed and probably needs a complete overhaul, but if you insist on copying the data like you describe, this should do the trick:
// Copy employees
INSERT INTO employees (name, position_id, year_id)
SELECT name, new_positions.id, $inserted_year_id AS last_year_id
FROM employees
JOIN positions AS old_positions ON old_positions.id = employees.position_id
AND old_positions.year_id = employees.year_id
JOIN positions AS new_positions ON new_positions.name = old_positions.name
AND new_positions.year_id = $inserted_year_id
WHERE employees.year_id = 1
I think you should read about database normalization. Copying the data leads to maintenance issues and erroneous reporting.
If you went with a different design like the following, then there would be nothing to insert, until an employee changes position, is terminated, or a position is discontinued. There are plenty of other ways to approach this, too, but you should minimize redundancy (i.e. have only one copy of each Employee), and then keep track of data that changes over time, separately. Also read about foreign keys before you try to implement something like this.
positions:
-- If you are keeping track of the years that each position is active,
-- using dates provides simplicity. Note: this design assumes that positions
-- are never reactivated after being deactivated.
------------------------------------------------
| id | name | DateActive | DateInactive |
------------------------------------------------
| 1 | Director | 01/01/2011 | |
| 2 | Manager | 01/01/2011 | |
------------------------------------------------
employees:
---------------------------------------------------------------
| id | name | DateHired | DateTerminated |
---------------------------------------------------------------
| 1 | Employee A | 01/01/2011 | |
| 2 | Employee B | 01/01/2011 | |
| 3 | Employee C | 01/01/2011 | 10/01/2012 |
---------------------------------------------------------------
EmployeePositionRelationships
--If you are keeping track of time that each employee held a position
-- Employee A has been a Director since 1/1/2011
-- Employee B was a Manager from 1/1/2011 to 10/6/2012. Then they became a Director
-- Employee B was a Manager from 1/1/2011 to 10/1/2012. Then they were terminated
--------------------------------------------------------
EmployeeId | PositionId | DateStarted | DateEnded |
--------------------------------------------------------
1 | 1 | 01/01/2011 | |
2 | 2 | 01/01/2011 | 10/6/2012 |
3 | 2 | 01/01/2011 | 10/1/2012 |
2 | 1 | 10/6/2012 | |
--------------------------------------------------------
Preface: Yes i realize this is bad design, but i can't change this.
Question
I have a customers table, and within that a field 'products'. Here is an example of what is in a sample customers products field:
36;40;362
Each of those numbers reference a record from the products table. I'm trying to do a
(SELECT group_concat(productName) from products where productID=???)
but am having trouble with the delimiters. I know how to remove the semi colons, and have tried 'where INSTR' or IN but am having no luck.
Is the best approach to return the whole field to PHP and then explode / parse there?
You can use FIND_IN_SET function in MySQL.
You just need to replace semicolons with a comma and the use it in your query:
SELECT group_concat(productName)
FROM products
WHERE FIND_IN_SET(productID, ???) > 0
Just remember that ??? should be comma-separated!
Like you said, this isn't the way to do it. But since it's an imperfect world:
Assuming a database structure like so:
+-PRODUCTS---------+ +-CUSTOMERS---------+------------+
| ID | productName | | ID | customerName | productIDs |
+----+-------------+ +----+--------------+------------+
| 1 | Foo | | 1 | Alice | 1;2 |
+----+-------------+ +----+--------------+------------+
| 2 | Bar | | 2 | Bob | 2;3 |
+----+-------------+ +----+--------------+------------+
| 3 | Baz | | 3 | Charlie | |
+----+-------------+ +----+--------------+------------+
Then a query like this:
SELECT customers.*,
GROUP_CONCAT(products.id) AS ids,
GROUP_CONCAT(productName) AS names
FROM customers
LEFT JOIN products
ON FIND_IN_SET(products.id, REPLACE(productIDs, ";", ","))
GROUP BY customers.id
Would return:
+-RESULT------------+------------+-----+---------+
| ID | customerName | productIDs | ids | names |
+----+--------------+------------+-----+---------+
| 1 | Alice | 1;2 | 1,2 | Foo,Bar |
+----+--------------+------------+-----+---------+
| 2 | Bob | 2;3 | 1,2 | Bar,Baz |
+----+--------------+------------+-----+---------+
| 3 | Charlie | | 1,2 | NULL |
+----+--------------+------------+-----+---------+
FIND_IN_SET( search_value, comma_separated_list ) searches for the value in the given comma separated string. So, you need to replace the semicolons with commas, which is obviously what REPLACE() does. The return value of this function is the position where it found the first match, so for example:
SELECT FIND_IN_SET(3, '1,3,5') = 2
SELECT FIND_IN_SET(5, '1,3,5') = 3
SELECT FIND_IN_SET(7, '1,3,5') = NULL