Lets say I have a database containing the following rows:
id (auto_incremented) | id_person | first_name | last_name | phone | email
-----------------------------------------------------------------------------
1 | 12 | kevin | smith | | kevin#hotmail.com
2 | 12 | kevin | smith | 1-800-123-4567 |
3 | 33 | joe | jones | 1-800-765-4321 |
4 | 33 | joe | thompson | | joe#hotmail.com
5 | 33 | joe | thompson | | newjoe#hotmail.com
Based on id_person, I want to output the following in a single query:
id | id_person | first_name | last_name | phone | email
-----------------------------------------------------------------------------
2 | 12 | kevin | smith | 1-800-123-4567 | kevin#hotmail.com
5 | 33 | joe | thompson | 1-800-765-4321 | newjoe#hotmail.com
So basically, I just want to take the newest row values, and if they are empty, grab the values from the first row which isn't empty.
How do I do this? Hope this makes sense.
SELECT id_person, subQ.lastID
, GROUP_CONCAT(IF(subQ.lastFieldXId = tblFields.ID, tblFields.fieldX, NULL)) AS fieldX
, GROUP_CONCAT(IF(subQ.lastFieldYId = tblFields.ID, tblFields.fieldY, NULL)) AS fieldY
[, ...]
FROM
(SELECT id_person
, MAX(id) AS lastID
, MAX(IF(IFNULL(fieldX, '') = '', 0, id) AS lastFieldXId
, MAX(IF(IFNULL(fieldY, '') = '', 0, id) AS lastFieldYId
[, ....]
FROM theTable
GROUP BY id_person) AS subQ
LEFT JOIN theTable AS tblFields USING (id_person)
;
The LEFT JOIN may or may not be faster as
LEFT JOIN theTable AS tblFields
ON tblFields.ID IN (subQ.lastFieldXId, subQ.lastFieldYId [, ...])
There is probably also a way using session variables, but I don't have the energy...
select * from(
select p1.id,p1.id_person,p1.first_name,p1.last_name,
ifnull(p1.phone,p2ph.phone) phone ,ifnull(p1.email,p3em.email) email
from person p1
left join person p2ph on(p1.id_person=p2ph.id_person and p2ph.phone is not null)
left join person p3em on(p1.id_person=p3em.id_person and p3em.email is not null)
order by id_person,p1.id desc,p2ph.id desc,p3em.id desc
)t group by id_person;
verify at http://sqlfiddle.com/#!9/d2f44/6
Related
If I run a SELECT query and it returns rows that have redundant information in them, how can I present that information in one row?
Currently my query is like this:
SELECT email, first_name, group FROM membership
And that returns a result set like so:
------------------------------------
| email | first_name | group |
------------------------------------
| bob#co.uk | bob | g1 |
| jim#co.uk | jim | g1 |
| jim#co.uk | jim | g2 |
| jane#co.uk | henry | g2 |
------------------------------------
Instead, I need the result set to look like this:
--------------------------------------------
| email | first_name | group | group |
--------------------------------------------
| bob#co.uk | bob | g1 | |
| jim#co.uk | jim | g1 | g2 |
| jane#co.uk | henry | g2 | |
--------------------------------------------
It's important that the column name "group" is not named differently despite being redundant.
edit: for clarity, the number of groups may change.
edit2: The full code I'm using, for the initial query, is:
SELECT
persons.mail AS email,
persons.fname AS 'first_name',
persons.lname AS 'last_name',
groups.name as add_list
FROM group_membership
LEFT JOIN groups ON group_membership.gid = groups.gid
LEFT JOIN persons ON group_membership.uid = persons.uid
WHERE persons.mail != ""
ORDER BY last_name
What you are asking for is impossible. If there were four rows with the value:
| jim#co.uk | jim | g2 |
would you expect the result to have 4 columns named group instead of the two columns of group that you currently show?
What you probably want is:
select email, first_name, group_concat(`group`) group by email, first_name
For your sample data this will do:
select email, first_name,
min(`group`) `group`,
case when max(`group`) <> min(`group`) then max(`group`) end `group`
from membership
group by email, first_name
See the demo.
Results:
> email | first_name | group | group
> :--------- | :--------- | :---- | :----
> bob#co.uk | bob | g1 |
> jane#co.uk | henry | g2 |
> jim#co.uk | jim | g1 | g2
As per the above sample the below would work
SELECT email, first_name, max(case
when
group ='g1' then group else null end)
as groupg1,
max(case
when
group ='g2' then group else null end)
as groupg2
FROM membership group by email,
first_name
I have a table of students with temporary test values like this:
Table students
+----+-------------+-------+-----------+
| id | section_id | age | name |
+----+-------------+-------+-----------+
| 1 | 1 | 18 | Justin |
+----+-------------+-------+-----------+
| 2 | 2 | 14 | Jillian |
+----+-------------+-------+-----------+
| 3 | 2 | 16 | Cherry |
+----+-------------+-------+-----------+
| 4 | 3 | 19 | Ronald |
+----+-------------+-------+-----------+
| 5 | 3 | 21 | Marie |
+----+-------------+-------+-----------+
| 6 | 3 | 21 | Arthur |
+----+-------------+-------+-----------+
I want to query the table such that I want to get all the maximum ages of each section. However, if two students have the same age, the table produced will return the student with smallest id.
Return:
+----+------------+-----+--------+
| id | section_id | age | name |
+----+------------+-----+--------+
| 1 | 1 | 18 | Justin |
+----+------------+-----+--------+
| 3 | 2 | 16 | Cherry |
+----+------------+-----+--------+
| 5 | 3 | 21 | Marie |
+----+------------+-----+--------+
I tried this query:
SELECT ANY_VALUE(id), ANY_VALUE(section_id), MAX(age), ANY_VALUE(name) FROM
(SELECT id, section_id, age, name FROM students ORDER BY id) as X
GROUP BY section_id
Unfortunately, there are instances that id does not match the age and name.
I have on my end:
sql_mode = only_full_group_by
and I don't have a privilege to edit that, hence the any_value function but I have no idea how to use it.
This will do what you want.
It starts by finding the maximum age per section (including duplicates).
Then it joins those results with the minimum id per section (to eliminate duplicates).
And finally, select all fields for the matching id and section combinations.
SELECT s3.*
FROM students s3
INNER JOIN (
SELECT MIN(s2.id) AS id, s2.section_id
FROM students s2
INNER JOIN (
SELECT s1.section_id, MAX(s1.age) AS age
FROM students s1
GROUP BY s1.section_id
) s1 USING (section_id, age)
GROUP BY s2.section_id
) s2 USING (id, section_id);
Working SQL fiddle: https://www.db-fiddle.com/f/aezgAYM6A5KnXykceB7At1/0
I would simply use a correlated subquery:
select s.*
from students s
where s.id = (select s2.id
from students s2
where s2.section_id = s.section_id
order by s2.age desc, s2.id asc
limit 1
);
This is pretty much the simplest way to express the logic. And with an index on students(section, age, id), it should be the most performant as well.
I have two queries that pull data from two different tables, but I need them to pull in the same report. I have a shared key between them, and the first table has one entry that corresponds to many entries in the second table.
My first query:
SELECT Proposal_ID,
substr(Proposal_Name, 1, 3) AS Prefix,
substr(Proposal_Name, 4, 6) AS `Number`,
Institution,
CollegeCode,
DepartmentCode,
Proposer_FirstName,
Proposer_LastName
FROM proposals.proposal
WHERE Institution = 'T';
Sample Data:
+----+--------+--------+-------+----------+----------+-----------+----------+
| ID | Prefix | Number | Inst. | CollCode | DeptCode | FirstName | LastName |
+----+--------+--------+-------+----------+----------+-----------+----------+
| 18 | SYP | 4675 | T | AS | SOC | Linda | McGaff |
+----+--------+--------+-------+----------+----------+-----------+----------+
| 20 | GEO | 4340 | T | AS | SGS | Teddy | Graham |
+----+--------+--------+-------+----------+----------+-----------+----------+
My second query:
SELECT Parent_Proposal,
SUBSTRING_INDEX(GROUP_CONCAT(`status`.`Status_Code` ORDER BY `status`.`Status_Time` DESC), ',', 1) AS status_code,
SUBSTRING_INDEX(GROUP_CONCAT(`status`.`Status_Time` ORDER BY `status`.`Status_Time` DESC), ',', 1) AS status_timestamp
FROM proposals.`status`
GROUP BY `status`.Parent_Proposal
Sample Data:
+-----------------+-------------+----------------------+
| Parent_Proposal | Status_Code | Status_Time |
+-----------------+-------------+----------------------+
| 18 | 40 | 2016-11-09 06:30:35 |
+-----------------+-------------+----------------------+
| 20 | 11 | 2017-03-20 10:26:31 |
+-----------------+-------------+----------------------+
I basically need to pull the most recent Status_Code and Status_Timestamp based on the Status_Timestamp and then relate that to the first table with the Parent_Proposal column.
Is there a way to group a subset of results without grouping all of the data together?
Expected Result:
+----+--------+--------+-------+----------+----------+-------+--------+-------------+----------------------+
| ID | Prefix | Number | Inst. | CollCode | DeptCode | FName | LName | Status_Code | Status_Time |
+----+--------+--------+-------+----------+----------+-------+--------+-------------+----------------------+
| 18 | SYP | 4675 | T | AS | SOC | Linda | McGaff | 40 | 2016-11-09 06:30:35 |
+----+--------+--------+-------+----------+----------+-------+--------+-------------+----------------------+
| 20 | 11 | GEO | 4340 | AS | SGS | Teddy | Graham | 11 | 2017-03-20 10:26:31 |
+----+--------+--------+-------+----------+----------+-------+--------+-------------+----------------------+
Thanks for any help and insight!
I think you want this. Just join your two tables together, and then do an additional join to a subquery on the status table to find the latest record for each parent proposal.
SELECT
p.Proposal_ID,
SUBSTR(p.Proposal_Name, 1, 3) AS Prefix,
SUBSTR(p.Proposal_Name, 4, 6) AS Number,
p.Institution,
p.CollegeCode,
p.DepartmentCode,
p.Proposer_FirstName,
p.Proposer_LastName,
s1.Status_Code,
s1.Status_Time
FROM proposals.proposal p
LEFT JOIN proposals.status s1
ON p.ID = s1.Parent_Proposal
INNER JOIN
(
SELECT Parent_Proposal, MAX(Status_Time) AS Max_Status_Time
FROM proposals.status
GROUP BY Parent_Proposal
) s2
ON s1.Parent_Proposal = s2.Parent_Proposal AND s1.Status_Time = s2.Max_Status_Time
WHERE
p.Institution = 'T';
I have a two tables.
work:
+----+----------+
| id | position |
+----+----------+
| 1 | 1 |
| 2 | 2 |
+----+----------+
content:
+----+---------+------+-------------+
| id | work_id | name | translation |
+----+---------+------+-------------+
| 1 | 1 | Kot | 1 |
| 2 | 1 | Cat | 2 |
| 3 | 2 | Ptak | 1 |
| 4 | 2 | Bird | 2 |
| 5 | 2 | Ssss | 3 |
+----+---------+------+-------------+
I want to get result like this:
+----+------+----------+
| id | name | sortName |
+----+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+----+------+----------+
My not working query is here:
select
w.id,
c.name,
cSort.name as sortName
from
work w
LEFT JOIN
content c
ON
(w.id=c.work_id)
LEFT JOIN
content cSort
ON
(w.id=cSort.work_id)
WHERE
c.translation IN(1,2) AND
cSort.translation=3
ORDER BY
sortName
I want to get for each work at least one translation and secound if exist (translation=1 always exist). And for every row I want special column with translation used to sort. But Not always this translation exist for work.id. In this example I want to sort work by translation=3.
Sorry for my not fluent english. Any ideas?
Best regards
/*
create table work ( id int, position int);
insert into work values
( 1 , 1 ),
( 2 , 2 );
create table content(id int, work_id int, name varchar(4), translation int);
insert into content values
( 1 , 1 , 'Kot' , 1),
( 2 , 1 , 'Cat' , 2),
( 3 , 2 , 'Ptak' , 1),
( 4 , 2 , 'Bird' , 2),
( 5 , 2 , 'Ssss' , 3);
*/
select w.id,c.name,(select c.name from content c where c.work_id = w.id and c.translation = 3) sortname
from work w
join content c on w.id = c.work_id
where c.translation <> 3;
result
+------+------+----------+
| id | name | sortname |
+------+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+------+------+----------+
So translation is also a work_id and you consider translation = 3 a translation in your example and translation <> 3 an original. You want to join each original record with every translation record where the latter's work_id matches the former's translation.
I think you are simply confusing IDs here. It should be ON (w.translation = cSort.work_id).
Another way to write the query:
select o.work_id as id, o.name, t.name as sortname
from (select * from content where translation <> 3) o
left join (select * from content where translation = 3) t
on t.work_id = o.translation
order by t.name;
There seems to be no need to join table work.
I'd like to add that the table design is a bit confusing. Somehow it is not clear from it what is a translation for what. In your example you interpret translation 3 as a translation for the non-three records, but this is just an example as you say. I don't find this readable.
UPDATE: In order to sort your results by work.position, you can join that table or use a subquery instead. Here is the order by clause for the latter:
order by (select position from work w where w.id = o.work_id);
I have a table which has 45 columns. Column SrNo has a primary key. It has millions of records. It contains so many duplicates. I actually want to find unique values and want to insert them into another table. Columns First-Middle-Last Name, Emails(Office,Personal),MobileNumbers contain so many duplicates. I am trying to find unique values using below query but it still showing result with duplicates. Can anyone help me where am I mistaking?
SELECT ft.first_name
, ft.middle_name
, ft.last_name
, ft.designation
, ft.company_name
, ft.email_office
, ft.email_personal1
, ft.email_personal2
, ft.personal_mobile1
, ft.personal_mobile2
FROM mytable ft
JOIN
( SELECT First_Name
, Middle_Name
, Last_Name
, designation
, Company_Name
, Email_Office
, Email_Personal1
, Email_Personal2
, Personal_Mobile1
, Personal_Mobile2
, COUNT(*)
FROM mytable
GROUP
BY First_Name
, Middle_Name
, Last_Name
, designation
, Company_Name
, Email_Office
, Email_Personal1
, Email_Personal2
, Personal_Mobile1
, Personal_Mobile2
HAVING COUNT(*) = 1
) temp
ON temp.first_name = ft.First_Name
AND temp.middle_name = ft.Middle_Name
AND temp.last_name = ft.Last_Name
AND temp.designation = ft.Designation
AND temp.company_name = ft.Company_Name
AND temp.email_office = ft.Email_Office
AND temp.email_personal1 = ft.Email_Personal1
AND temp.email_personal2 = ft.Email_Personal2
AND temp.personal_mobile1 = ft.Personal_Mobile1
AND temp.personal_mobile2 = ft.Personal_Mobile2
WHERE srno <= 1000;
Its taking so much time to execute a query on fulldata, as it has millions. Thats why I've to put where condition to show records upto 1000.
Its showing duplicates in Emails,3 columns of Names too. Can anyone help me whats wrong in this query? Or Am I making it more complicate this way?
Also how to get those records whose count(*) > 1, how do I take only one record of them while ignoring its other duplicates?
Below is my sample table representation.
+-----------+------------+----------+-------------------+-------------+----------------+-----------------+---------------------+------------+------------+
| FirstName | MiddleName | LastName | Designation | CompanyName | Email(o) | Email(P1) | Email(P2) | Mobile(pl) | Mobile(p2) |
+-----------+------------+----------+-------------------+-------------+----------------+-----------------+---------------------+------------+------------+
| Anil | | | Personnel Manager | ABC | anilgabc.com | anilggmail.com | | 9898989898 | |
| Anil | D | Gupta | Personnel Manager | ABC | anilggmail.com | | | | |
| Anil | D | | | ABC | anilg#abc.com | | | | |
| Anil | | Gupta | Personnel Manager | | | | anilgreditfmail.com | 9898989898 | |
| Anil | | Gupta | | ABC | | | | 9898989898 | |
| Anil | D | Gupta | Personnel Manager | ABC | anilgabc.com | anilg#gmail.com | anilgrediffmail.com | 9898989898 | |
+-----------+------------+----------+-------------------+-------------+----------------+-----------------+---------------------+------------+------------+
And here's the desired result...
Use distinct for the table that has multiple the same records first then join the other table you need: like this,,
select distinct ft.first_name,ft.middle_name,ft.last_name,ft.designation,ft.company_name,ft.emai l_office,ft.email_personal1, ft.email_personal2, ft.personal_mobile1, ft.personal_mobile2
from mytable ft and so on