show all records which contain the latest edited version - mysql

I have a table with students names, auto increased NR record, but on each edit of the record, a new one is created copying the NR to the ID field.
But when I try to group the ID records when MAX(NR) it shows me the max number of that ID but when I ask for the remaining of the rocord, it doesn't show me that last record of that group of ID's in
SELECT MAX(`NR`) AS 'mNr',`NR`,`ID`,`Name1`,`Name3`,`Gender`
FROM `Kids` GROUP BY `ID`
This produces results like:
mNr NR ID Name1 Name3 Gender
252 1 1 Alice Carper f
179 2 2 Dorah Fisher f
189 3 3 Racheal King f
173 4 4 Frank Smith m
192 5 5 Patrick Fay m
305 6 6 Gloria Sing f
299 7 7 Bridget Young f
But as you can see, the query shows the highest edit NR, but then continues to give the lowest of the rest of the record, not the record details belonging to that lastest NR...
What am I doing wrong?
This is the sample data:
NR ID Name1 Name3 Gender
1 1 Alice Achand f
2 2 Dorah Achieng f
3 3 Racheal Achieng f
4 4 Francisca Adikin f
5 5 Patrick Adilu m
6 6 Gloria Ajwang f
7 7 Bridget Aketch f
130 5 Patrick Adilu m
129 4 Francisca Adikin f
128 2 Dorah Achieng f
153 4 Francisca Adikin f
173 4 Francisca Adikin f
179 2 Dorah Achieng f
189 3 Racheal Achieng f
192 5 Patrick Adilu m
252 1 Alice Wor f
299 7 Bridget Aketch f
305 6 Gloria Ajwang f

Perhaps without knowing it, you are using an arcane feature of MySQL. MySQL allows you to include columns in the select statement of an aggregation query that are not in aggregation functions or in the group by clause. The engine puts in arbitrary values for these columns.
The correct way to do what you want is with a join:
SELECT k.*
FROM `Kids` k join
(select id, max(nr) as maxnr
from kids
group by id
) m
on k.id = m.id and nr = maxnr;
Here is the explicit explanation in the documentation:
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause. This means
that the preceding query is legal in MySQL. You can use this feature
to get better performance by avoiding unnecessary column sorting and
grouping. However, this is useful primarily when all values in each
nonaggregated column not named in the GROUP BY are the same for each
group. The server is free to choose any value from each group, so
unless they are the same, the values chosen are indeterminate.
Furthermore, the selection of values from each group cannot be
influenced by adding an ORDER BY clause. Sorting of the result set
occurs after values have been chosen, and ORDER BY does not affect
which values within each group the server chooses.
which you can read in more detail here.

Although #Gordon Linoff's answer is technically correct, using sub-queries may be more resource and time consuming on large data sets.
Depending on the case, I would usually separate the data into two tables students and student_details where
students's table structure is
CREATE TABLE students (
student_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT
);
The idea of this table is to create a unique number for the student and store any other student data that you may not want to save in the revisions.
student_details's table structure is:
CREATE TABLE student_details (
revision_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
student_id INTEGER NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
gender VARCHAR(1),
is_history BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT FOREIGN KEY(student_id) REFERENCES students(student_id) ON DELETE RESTRICT
);
And this table stores the actual data of the student. When the data for a certain student is updated, you just need to update the is_history column to true for the records of that student. Then, when selecting your student data, you'd simply use SELECT student_details.* FROM students LEFT JOIN student_details ON (student_details.student_id = students.student_id AND student_details.is_history = false). This will always return the latest revision of a student's detail.
Inserting a new student
Insert a student INSERT INTO students(student_id) VALUES('');
Get the lasted insert id SELECT LAST_INSERT_ID(); (let's say it returns 1 for the sake of this example)
Insert the student details INSERT INTO student_details(student_id, first_name, last_name, gender) VALUES('1', 'Alice', 'Carper', 'f')
Updating an existing student (let's say student_id = 1)
Set all previous student_detail "revisions" as "history": UPDATE student_details SET is_history = true WHERE student_details.student_id = 1 AND is_history = false
Add the new revision: INSERT INTO student_details(student_id, first_name, last_name, gender) VALUES('1', 'Alice', 'Achand', 'f')
Getting a student and his latest student details
SELECT student_details.* FROM students LEFT JOIN student_details ON (student_details.student_id = students.student_id AND student_details.is_history = false)

Related

remove duplicate rows from a table given certain criteria in mysql 5.7

I have a table, named books, with rows like:
+----+----------+---------+--------+-----------+
Id PNo Gid Genre Trailer
+----+----------+---------+--------+------------+
1 1234567 55 NULL NULL
2 1234567 64 Fiction NULL
3 8763525 64 Fiction NULL
4 8763525 73 Fiction NULL
5 5555555 18 Fiction NULL
6 5555555 64 Fiction NULL
+---------------------------------------------------
What i need to do, is the following:
i want to remove all rows, that have duplicate data, based on column PNo, and where the Gid value = 64, and also Pno is not 5555555
The following query returns to me the results i need (the duplicate rows), but i need to know how i could make the deletion based also on the above criteria:
SELECT
COUNT(*) as repetitions,
group_concat(PNo, ' (', Gid, ', ', Genre, ') ' SEPARATOR ' | ')
as row_data
FROM books
GROUP BY PNo
HAVING repetitions > 1;
DELETE m1 FROM books m1
INNER JOIN books m2
ON m1.PNo = m2.PNo
WHERE m1.PNo = '64' AND m1.PNo != 5555555
The above will delete all rows, that have a m1.PNo = '64', when i need to remove only the duplicate rows, based on the value of the PNo column, that also have in the column Gid the value 64. How my delete query should be?
You are almost there: you just need a condition that avoids that a record can be joined with itself. Assuming that id is the primary key of your table (or at least a unique key), you could do:
delete m1
from books m1
inner join books m2 on m1.pno = m2.pno and m1.id <> m2.id
where m1.gid = 64 and m1.pno != 5555555
Note: I think that there is a glitch in your where clause, where m1.PNo = '64' should be m1.gid = 64 (also, if gid is of a numeric datatype, you should not quote the value).

Select From table name obtained dynamically from the query

I have 3 Tables
campaign1 (TABLE)
id campaign_details
1 'some detail'
campaign2 (TABLE)
id campaign_details
1 'some other detail'
campaign_list (TABLE)
id campaign_table_name
1 'campaign1'
2 'campaign2'
Campaign list table contains the table name of the two tables described above. I want to Select from the Campaign List table and get the record count using the table name i get from this select
For eg.
using select i get campaign1(Table name). Then i run select query on campaign1 to count number of records.
What i'm doing right now is .
-Select from campign_list
-loop through all campaign_table_names and run select query individually
Is there a way to do this using a single query
something like this
select campaign_name,(SELECT COUNT(*) FROM c.campaign_name) as campcount from campaign_list c
SQLFiddle : http://sqlfiddle.com/#!9/b766d/2
It's not possible inside a single query to build it dynamically but it's possible to cheat. Especially if there are only two linked tables.
I've listed two options
left outer join both tables
select campaign_name,
coalesce(c1.campaign_details, c2.campaign_details)
from campaign_list c
left join campaign1 c1 using (id)
left join campaign2 c2 using (id);
union all two different selects
select campaign_name,
campaign_details
from campaign_list c
join campaign1 c1 using (id)
union all
select campaign_name,
campaign_details
from campaign_list c
join campaign2 c2 using (id);
sqlfiddle
Combine your campaign tables to 1 table and add an column named 'type' (int).
campaign_items tables:
item_id item_details item_type
1 'some detail' 1
2 'some detail' 1
3 'some other detail' 2
4 'some other detail' 2
campaign_lists table
campaign_id campaign_name
1 'campaign1'
2 'campaign2'
Then you can use the following select statement:
SELECT campaign_name, (SELECT COUNT(*) FROM campaign_items WHERE item_type = campaign_id) as campaign_count
FROM campaign_lists
Oops, writing took me so long that you got this answered by Colin Raaijmakers already. Well, I'll post my answer anyway in spite of being more or less the same answer. Maybe my elaboration helps you see the problem.
Your problem stems from a bad database design. A database is made to order data and its relations. A CD database holds albums, songs, artists, etc. A business database may hold items, warehouses, sales and so on. Your database holds table names. [... time for thinking :-) ]
(When writing a DBMS you would want to store table names, column names, constraints etc., but I guess I am right supposing that you are not writing a new DBMS.)
So create tables that deal with your actual data. E.g.:
campain_type (id_campain_type, description, ...)
campain (id_campain, id_campain_type, campain_date, ...)
campain_type
id_campain_type description
1 Type A
2 Type B
3 Type C
campain
id_campain id_campain_type date
33 1 2015-06-03
85 2 2015-10-23
97 2 2015-12-01
query
select
ct.description,
(select count(*) from campain c where c.id_campain_type = ct.id_campain_type) as cnt
from campain_type ct;
result
description cnt
Type A 1
Type B 2
Type C 0

How do I compare values from different column and different row but same table?

ID Name FatherID Birthyear
1 Bart NULL 1756
2 Franz 1 1796
3 Josef 2 1835
4 Zohan 3 1887
Suppose I have this table, I would like to know if Zohan is the son of Bart, which can be gotten if I compare the values from the column "FatherID" with the ID of the previous rows up until I get to Bart. But how do I compare the values in the same table but of different rows and columns
You could self join the table:
SELECT s.name AS son_name, f.name AS father_name
FROM mytable s
JOIN mytable f ON s.fatherID = f.id
-- possibly add a where clause with conditions on son/father names

MySQL Generate a list of names that appear in more than one column, grouped alphabetically

I need to create a digest of records which are grouped by names in a table. The problem is, my table has two columns for names and the same names can be in either column. The digest should list all names and the records in which they exist, regardless of which column.
I can see doing this by first generating a unique list of names from both columns, then scanning through the result to see if the names appear in either column. I want to know if MySQL can do this in one query.
Example, here's my table:
Id ColumnA Column B
1 Bill NULL
2 NULL Dennis
3 Adam Carl
4 NULL Adam
5 Adam Bill
6 Dennis NULL
7 Frank Bill
The result of the query (NULL sorts to the top)
NULL 4 NULL Adam
NULL 1 Bill NULL
NULL 2 NULL Dennis
NULL 6 Dennis NULL
Adam 5 Adam Bill
Adam 7 Adam Carl
Bill 1 Bill NULL
Bill 5 Adam Bill
Bill 6 Frank Bill
Carl 3 Adam Carl
Dennis 2 NULL Dennis
Dennis 6 Dennis NULL
Frank 7 Frank Bill
It would be nice if I could eliminate the NULL entries at the top of the list. I only put them there because everything I'm trying so far has them at the top. I'd rather the result looks like this:
Adam 5 Adam Bill
Adam 7 Adam Carl
Bill 1 Bill NULL
Bill 5 Adam Bill
Bill 6 Frank Bill
Carl 3 Adam Carl
Dennis 2 NULL Dennis
Dennis 6 Dennis NULL
Frank 7 Frank Bill
You need to do a union of ID, ColumnA with ID, ColumnB, then summarize and order the result.
Like so http://sqlfiddle.com/#!2/eeec6/5/0:
SELECT name, GROUP_CONCAT(DISTINCT id ORDER BY id) AS ids
FROM (
SELECT id, a AS name
FROM names
UNION
SELECT id, b AS name
FROM names
) AS t
WHERE name IS NOT NULL
GROUP BY name
ORDER BY name
This will give you one row per name, with a list of IDs (row numbers) containing that name.
Or you can simply display the name, id columns one at a time(http://sqlfiddle.com/#!2/eeec6/6/0):
SELECT name, id
FROM (
SELECT id, a AS name
FROM names
UNION
SELECT id, b AS name
FROM names
) AS t
WHERE name IS NOT NULL
ORDER BY name, id
Finally, if you want to show the whole row with each name, you can do this (http://sqlfiddle.com/#!2/eeec6/8/0):
SELECT name, n.id, n.a, n.b
FROM (
SELECT id, a AS name
FROM names
UNION
SELECT id, b AS name
FROM names
) AS t
JOIN names AS n ON t.id = n.id
WHERE name IS NOT NULL
ORDER BY name, n.id
One approach:
SELECT s.ColumnA AS digest_name
, s.ID
, s.ColumnA
, s.ColumnB
FROM mydata s
WHERE s.ColumnA IS NOT NULL
UNION ALL
SELECT t.ColumnB
, t.ID
, t.ColumnA
, t.ColumnB
FROM mydata t
WHERE t.ColumnB IS NOT NULL
ORDER BY 1, 3, 4
One query gets all the rows where ColumnA is not null, the second query gets the rows where Column B is not null. (This effectively omits rows where the digest name would have been NULL.)
The UNION ALL set operator combines the two sets of rows into a single set. (NOTE: We prefer the more efficient UNION ALL operator over the UNION operator, because it doesn't perform the extra work of identifying and removing duplicate rows, when that operation isn't required.)
The ORDER BY specifies that the rows are to be ordered by the digest name first, then by the names in ColumnA and ColumnB.
This produces ten rows of output, and includes row:
digest Id ColumnA ColumnB
-------- -- -------- --------
Adam 4 NULL Adam
It's not clear why this row is omitted from the resultset specified in the question. (That resultset in the question shows only nine rows.)
So, I'm not sure if I'm missing something about the requirement. It's entirely possible I've not understood the specification.

mysql select matching results

I'm trying to find the leagues (lid) where two users are apart of.
Here are my tables:
Table leagues:
*id* lname
--------------
1 Hard C
3 Fun
5 Crazy
Table match:
*userid* *lid*
-----------------
1 1
4 5
1 3
2 1
4 1
4 3
*Are primary keys
match.lid is foreign key to leagues.id (a user cannot not be part of the same league twice)
Here's what I have so far (a start):
SELECT t1.lid, t2.lname
FROM match t1
JOIN leagues t2 on t1.lid = t2.id
So far I managed to join the two tables and get the names. My ultimate goal is to show the lid's where two users are part of the same league, say userid 1 and 4.
userid 1 is a member of lid 1 and 3
userid 4 is a member of lid 5, 1, and 3
Both users meet in league(lid) 1 and 3
So I need a query that shows only the league where both users meet. Like this:
lid lname
--------------
1 Hard C
3 Fun
Since userid 1 and 4 meet in league 1 and 3, the results should show that. I can run two queries for each user and check which leagues both users meet via php, but I think it's more efficient to run one query.
SELECT m1.lid, l.lname FROM
`match` m1, `match` m2, leagues l
WHERE m1.lid = m2.lid AND m1.lid = l.id
AND m1.userid = 1
AND m2.userid = 4
There are a few ways. The most straightforward is:
SELECT id AS lid,
lname
FROM leagues
WHERE id IN
( SELECT lid
FROM match
WHERE userid = 1
)
AND id IN
( SELECT lid
FROM match
WHERE userid = 4
)
;
Another way, which is a bit less direct, but may perform better — you can try it and see — is to use JOIN:
SELECT id AS lid,
lname
FROM leagues
JOIN match AS match1
ON match1.lid = leagues.id
AND match1.userid = 1
JOIN match AS match2
ON match2.lid = leagues.id
AND match2.userid = 4
;