SQL Query Retrieving Latest Row When Other Columns Are Equal - sql

I'm having trouble figuring out the SQL statement to retrieve a specific set of data. Where all columns are equal except the last update date, I want the most recent. For example.
Book Author Update
John Foo 1/21/2010
John Foo 1/22/2010
Fred Foo2 1/21/2010
Fred Foo2 1/22/2010
What's the query that retrieves the most recent rows? That is, the query that returns:
Book Author Update
John Foo 1/22/2010
Fred Foo2 1/22/2010
TIA,
Steve

SELECT
book,
author,
MAX(update)
FROM
My_Table
GROUP BY
book,
author
This only works in this particular case because all of the other columns have the same value. If you wanted to get the latest row by book, but where the author (or some other column that you will retrieve) might be different then you could use:
SELECT
T.book,
T.author,
T.update
FROM
(SELECT book, MAX(update) AS max_update FROM My_Table GROUP BY book) SQ
INNER JOIN My_Table T ON
T.book = SQ.book AND
T.update = SQ.max_update

Fixed it up
DROP TABLE #tmpBooks
CREATE TABLE #tmpBooks
(
Book VARCHAR(100),
Author VARCHAR(100),
Updated DATETIME
)
INSERT INTO #tmpBooks VALUES ('Foo', 'Bar', '1/1/1980')
INSERT INTO #tmpBooks VALUES ('Foo', 'Bar', '1/1/1990')
INSERT INTO #tmpBooks VALUES ('Foo', 'Bar', '1/1/2000')
INSERT INTO #tmpBooks VALUES ('Foo', 'Bar', '1/1/2010')
INSERT INTO #tmpBooks VALUES ('Foo2', 'Bar2', '1/1/1980')
INSERT INTO #tmpBooks VALUES ('Foo2', 'Bar2', '1/1/1990')
INSERT INTO #tmpBooks VALUES ('Foo2', 'Bar2', '1/1/2000')
SELECT Book, Author, Max(Updated) as MaxUpdated
FROM #tmpBooks
GROUP BY Book, Author
Results:
Book Author MaxUpdated
--------------- --------------- -----------------------
Foo Bar 2010-01-01 00:00:00.000
Foo2 Bar2 2000-01-01 00:00:00.000
(2 row(s) affected)

This will get what you asked for, but something tells me it's not what you want.
SELECT Book, Author, MAX(Update)
FROM BookUpdates
GROUP BY Book, Author
Is there more to the table schema?

Try this (don't have the data to test on but it should work):
SELECT
bu.Book,
bu.Author
FROM
BookUpdates bu
JOIN
(SELECT MAX(Updated) as Date FROM BookUpdates) max
WHERE
bu.Updated = max.Date;

Related

Joining a table with dictionary to unique id column in different table

I have two tables, Table A and Table B that I am trying to extract information from and create a resulting table.
Table A
user_ids value
{user_123, user_234} apples
{user_456, user_123} oranges
{user_234} kiwi
Table B
id name
123 John Smith
234 Jane Doe
456 John Doe
I want to join the two tables in a way that will result in the following:
Table C
user_ids value user_names
{user_123, user_234} apples {John Smith, Jane Doe}
{user_456, user_123} oranges {John Doe, John Smith}
{user_234} kiwi {Jane Doe}
Any help would be really appreciated!
Others have already encouraged you to normalize your design and there are numerous posts on why this is recommended. Using your current shared dataset, the following was done using postgres where the user_ids was treated as a text array. I also tested with the user_ids as text as used cast(user_ids as text[]) to convert it to a text array
See fiddle and result below:
Schema (PostgreSQL v11)
CREATE TABLE table_a (
"user_ids" text[],
"value" VARCHAR(7)
);
INSERT INTO table_a
("user_ids", "value")
VALUES
('{user_123, user_234}', 'apples'),
('{user_456, user_123}', 'oranges'),
('{user_234}', 'kiwi');
CREATE TABLE table_b (
"id" INTEGER,
"name" VARCHAR(10)
);
INSERT INTO table_b
("id", "name")
VALUES
('123', 'John Smith'),
('234', 'Jane Doe'),
('456', 'John Doe');
The first CTE user_values creates a row for each user_id and value. The second CTE merged_values joins table_b on the pattern user_<user_id> if it exists and ensures unique results using DISTINCT. The final projection groups based on values and users array_agg to collect all user_ids or names into a single row.
Query #1
WITH user_values AS (
SELECT
unnest(a.user_ids) user_id,
a.value
FROM
table_a a
),
merged_values AS (
SELECT DISTINCT
a.user_id,
a.value,
b.name
FROM
user_values a
LEFT JOIN
table_b b ON a.user_id = CONCAT('user_',b.id)
)
SELECT
array_agg(user_id) user_ids,
value,
array_agg(name) "names"
FROM
merged_values
GROUP BY
value;
user_ids
value
names
user_123,user_456
oranges
John Smith,John Doe
user_123,user_234
apples
John Smith,Jane Doe
user_234
kiwi
Jane Doe
View on DB Fiddle

Where condition based on link table

I have a table of Users. Each User can be in multiple Disciplines, and they are linked by a link table, User_Discipline. The tables are pretty straight forward:
User
ID Name more...
3 | John Doe | ...
7 | Jane Smith | ...
12 | Joe Jones | ...
Discipline
ID name
1 | Civil
2 | Mechanical
3 | Piping
User_Discipline
UserID DisciplineID
3 | 2
3 | 1
7 | 2
12 | 3
Say John Doe is the logged in user. He needs to be able to select a list of all of the users in any of his disciplines. In the given example, I need a query that would return a list with John and Jane, since John is both Civil and Mechanical, and Jane is in Mechanical.
I think sub-selects are involved, but all the reading I've done so far have shown how to do subselects checking for one value (say, John's Civil Discipline). I need to be able to perform a query that runs a WHERE condition but matches any of John's Disciplines many-to-many with others' Disciplines.
I'm using the DataTables Editor .NET library to do the SQL, but I can translate an answer in regular SQL markup to that library. The only limitation of the library that I might encounter here is that everything would have to be done in one SQL statement. I appreciate any help!
Something like this?
SELECT DISTINCT [User].ID, [User].Name
FROM [User]
JOIN User_Discipline
ON [User].ID = User_Discipline.UserID
WHERE
User_Discipline.DisciplineID IN (
SELECT DisciplineID
FROM User_Discipline
WHERE UserID = <<John Doe's userID>>
)
You can do it all with inner joins:
declare #users table (id int, fullname varchar(50))
declare #disciplines table (id int, discname varchar(50))
declare #userdisciplines table (userid int, discid int)
insert into #users VALUES (3, 'John Doe')
insert into #users VALUES (7, 'Jane Smith')
insert into #users VALUES (12, 'Joe Jones')
insert into #disciplines VALUES (1, 'Civil')
insert into #disciplines VALUES (2, 'Mechanical')
insert into #disciplines VALUES (2, 'Piping')
insert into #userdisciplines VALUES (3, 2)
insert into #userdisciplines VALUES (3, 1)
insert into #userdisciplines VALUES (7, 2)
insert into #userdisciplines VALUES (12, 3)
SELECT distinct id, fullname from #users u
INNER JOIN #userdisciplines ud ON ud.userid = u.id
INNER JOIN
(SELECT ud.discid FROM #users u
inner join #userdisciplines ud on ud.userid = u.id
WHERE u.fullname = 'John Doe') d ON d.discid = ud.discid

oracle search using duplicate values - how do I return duplicate results

I have a requirement to return values from a SELECT statement, regardless of if they are duplicate or not.
Example:
SELECT * FROM PEOPLE_TABLE
WHERE PERSON_ID = 1
AND PERSON_ID = 1;
It obviously just returns a single record e.g.
(person_id, name)
"1", "Henry"
I would like my results to return
"1", "Henry"
"1", "Henry"
What's the best way to achieve this? My actual joins a few tables and uses WHERE IN and then specifies about 600 values (200 unique values).
Using WHERE IN will suppress duplicates. If you're doing something like this:
CREATE TABLE PEOPLE_TABLE (PERSON_ID NUMBER, NAME VARCHAR2(10));
INSERT INTO PEOPLE_TABLE VALUES (1, 'Henry');
INSERT INTO PEOPLE_TABLE VALUES (2, 'George');
INSERT INTO PEOPLE_TABLE VALUES (3, 'Jane');
SELECT PERSON_ID, NAME
FROM PEOPLE_TABLE
WHERE PERSON_ID IN (1, 1, 3);
PERSON_ID NAME
---------- ----------
1 Henry
3 Jane
Then you can join instead, against a table collection that contains the same target values:
SELECT PT.PERSON_ID, PT.NAME
FROM TABLE(SYS.ODCINUMBERLIST(1, 1, 3)) T
JOIN PEOPLE_TABLE PT
ON PT.PERSON_ID = T.COLUMN_VALUE;
PERSON_ID NAME
---------- ----------
1 Henry
1 Henry
3 Jane
SQL Fiddle demo. (Or with strings, from a comment).
You can create your own schema-level table collection type if you prefer and have privileges to do that, or use a built-in one like SYS.OCDINUMBERLIST or SYS.ODCIVARCHAR2LIST, depending on your actual data type.
To repeat the result, use UNION ALL:
SELECT * FROM PEOPLE_TABLE
WHERE PERSON_ID = 1
UNION ALL
SELECT * FROM PEOPLE_TABLE
WHERE PERSON_ID = 1
Actually, I think you're asking for something else, but I can't figure out what.
I would assume your PERSON_ID is a number
You only need one criteria
select * from PEOPLE_TABLE
where PERSON_ID = 1
http://sqlfiddle.com/#!15/3ff40/5

Pulling one record per id by record type sql

I am new to SQL and I'm trying to write a query to do the following.
I have a table containing records types and IDs. You can have duplicate and multiple types per ID.
Type ID
History 1
History 1
Geography 1
Geography 2
French 2
French 3
English 3
English 4
History 4
History 4
I want to create one record per ID with a 'Type Hierarchy' of History, Geography, All Others. So in the above example, I am looking for the following:
History 1
Geography 2
French 3
History 4
In other words give me all history records, if an ID does not have history give me the geography records, and for the IDs without either give me one of any record type.
Any ideas?
Thanks.
You didn't tell your RDMS but the following code works for Oracle (and SQL Server, I suppose). For others, there are emulations for row_number function.
--creating table..
CREATE TABLE "YOURTABLE"
( "TIP" VARCHAR2(20),
"IDE" NUMBER
)
--inserting experimental values..
insert into YOURTABLE (TIP, IDE)
values ('History', 1);
insert into YOURTABLE (TIP, IDE)
values ('History', 1);
insert into YOURTABLE (TIP, IDE)
values ('Geography', 1);
insert into YOURTABLE (TIP, IDE)
values ('Geography', 2);
insert into YOURTABLE (TIP, IDE)
values ('French', 2);
insert into YOURTABLE (TIP, IDE)
values ('French', 3);
insert into YOURTABLE (TIP, IDE)
values ('English', 3);
insert into YOURTABLE (TIP, IDE)
values ('English', 4);
insert into YOURTABLE (TIP, IDE)
values ('History', 4);
insert into YOURTABLE (TIP, IDE)
values ('History', 4);
--code..
select m.*
from (select *
from YOURTABLE
where tip = 'History'
union
select *
from YOURTABLE
where tip = 'Geography'
and ide not in (select ide from YOURTABLE where tip = 'History')
union
select tip, ide
from (select s.*,
ROW_NUMBER() OVER(PARTITION BY ide ORDER BY ide) ord
from YOURTABLE s
where tip not in ('History', 'Geography')
and ide not in
(select ide
from YOURTABLE
where tip in ('History', 'Geography')))
where ord = 1) m
Resulting table:
TIP IDE
-------------------- ----------
History 1
Geography 2
English 3
History 4
You said:
'Type Hierarchy' of History, Geography, All Others
In my example English got into the result, not French, but this shouldn't be a problem as "all others" are created equal.
I think something along the lines of
SELECT Type, ID
GROUP BY Type, ID
should work.

Postgresql aggregate array

I have a two tables
Student
--------
Id Name
1 John
2 David
3 Will
Grade
---------
Student_id Mark
1 A
2 B
2 B+
3 C
3 A
Is it possible to make native Postgresql SELECT to get results like below:
Name Array of marks
-----------------------
'John', {'A'}
'David', {'B','B+'}
'Will', {'C','A'}
But not like below
Name Mark
----------------
'John', 'A'
'David', 'B'
'David', 'B+'
'Will', 'C'
'Will', 'A'
Use array_agg: http://www.sqlfiddle.com/#!1/5099e/1
SELECT s.name, array_agg(g.Mark) as marks
FROM student s
LEFT JOIN Grade g ON g.Student_id = s.Id
GROUP BY s.Id
By the way, if you are using Postgres 9.1, you don't need to repeat the columns on SELECT to GROUP BY, e.g. you don't need to repeat the student name on GROUP BY. You can merely GROUP BY on primary key. If you remove the primary key on student, you need to repeat the student name on GROUP BY.
CREATE TABLE grade
(Student_id int, Mark varchar(2));
INSERT INTO grade
(Student_id, Mark)
VALUES
(1, 'A'),
(2, 'B'),
(2, 'B+'),
(3, 'C'),
(3, 'A');
CREATE TABLE student
(Id int primary key, Name varchar(5));
INSERT INTO student
(Id, Name)
VALUES
(1, 'John'),
(2, 'David'),
(3, 'Will');
What I understand you can do something like this:
SELECT p.p_name,
STRING_AGG(Grade.Mark, ',' ORDER BY Grade.Mark) As marks
FROM Student
LEFT JOIN Grade ON Grade.Student_id = Student.Id
GROUP BY Student.Name;
EDIT
I am not sure. But maybe something like this then:
SELECT p.p_name, 
    array_to_string(ARRAY_AGG(Grade.Mark),';') As marks
FROM Student
LEFT JOIN Grade ON Grade.Student_id = Student.Id
GROUP BY Student.Name;
Reference here
You could use the following:
SELECT Student.Name as Name,
(SELECT array(SELECT Mark FROM Grade WHERE Grade.Student_id = Student.Id))
AS ArrayOfMarks
FROM Student
As described here: http://www.mkyong.com/database/convert-subquery-result-to-array/
Michael Buen got it right. I got what I needed using array_agg.
Here just a basic query example in case it helps someone:
SELECT directory, ARRAY_AGG(file_name)
FROM table
WHERE type = 'ZIP'
GROUP BY directory;
And the result was something like:
| parent_directory | array_agg |
+-------------------------+----------------------------------------+
| /home/postgresql/files | {zip_1.zip,zip_2.zip,zip_3.zip} |
| /home/postgresql/files2 | {file1.zip,file2.zip} |
This post also helped me a lot: "Group By" in SQL and Python Pandas.
It basically says that it is more convenient to use only PSQL when possible, but that Python Pandas can be useful to achieve extra functionalities in the filtering process.