Query to combine two tables into one based on timestamp - postgresql

I have three tables in Postgres. They are all about a single event (an occurrence, not "sports event"). Each table is about a specific item during the event.
table_header columns
gid, start_timestamp, end_timestamp, location, positions
table_item1 columns
gid, side, visibility, item1_timestamp
table_item2 columns
gid, position_id, name, item2_timestamp
I've tried the following query:
SELECT h.gid, h.location, h.start_timestamp, h.end_timestamp, i1.side,
i1.visibility, i2.position_id, i2.name, i2.item2_timestamp AS timestamp
FROM tablet_header AS h
LEFT OUTER JOIN table_item1 i1 on (i1.gid = h.gid)
LEFT OUTER JOIN table_item2 i2 on (i2.gid = i1.gid AND
i1.item1_timestamp = i2.item2_timestamp)
WHERE h.start_timestamp BETWEEN '2016-03-24 12:00:00'::timestamp AND now()::timestamp
The problem is that I'm losing some data from rows when item1_timestamp and item2_timestamp do not match.
So if I have in table_item1 and table_item2:
gid | item1_timestamp | side gid | item2_timestamp | name
---------------------------- -----------------------------------
1 | 17:00:00 | left 1 | 17:00:00 | charlie
1 | 17:00:05 | right 1 | 17:00:03 | frank
1 | 17:00:10 | left 1 | 17:00:06 | dee
I would want the final output to be:
gid | timestamp | side | name
-----------------------------
1 | 17:00:00 | left | charlie
1 | 17:00:03 | | frank
1 | 17:00:05 | right |
1 | 17:00:06 | | dee
1 | 17:00:10 | left |
based purely on the timestamp (and gid). Naturally I would have the header info in there too, but that's trivial.
I tried playing around with the query I posted used different JOINs and UNIONs, but I cannot seem to get it right. The one I posted gives the best results I could manage, but it's incomplete.
Side note: every minute or so there will be a new "event". So the gid will be unique to each event and the query needs to ensure that each dataset is paired with data from the same gid. Which is the reason for my i1.gid = h.gid lines. Data between different events should not be compared.

select t1.gid, t1.timestamp, t1.side, t2.name
from t1
left join t2 on t2.timestamp=t1.timestamp and t2.gid=t1.gid
union
select t1.gid, t1.timestamp, t1.side, t2.name
from t2
left join t1 on t2.timestamp=t1.timestamp and t2.gid=t1.gid

Related

How to query parent child in PostgreSQL?

I have the following table structure :
place_id | parent_place_id | name
---------|-----------------|------------
1 | 2 | child
---------|-----------------|------------
2 | 3 | dad
---------|-----------------|------------
3 | | grandfather
......
I am trying to write a query so that my output data is as follows :
id_Grandfather | name_Grandfather | id_Dad | name_Dad | id_Child | name_child
----------------|------------------|--------|----------|----------|-----------
3 | grandfather | 2 | dad | 1 | child
I have tried many ways but not getting the expected result. Can anyone help me to solve it? Thank !
There is a way to do it with double join. But does it make any sense is totally different question.
SELECT
gf.place_id as id_Grandfather,
gf.name as name_Grandfather,
d.place_id as id_Dad,
d.name as name_Dad,
c.place_id as id_Child,
c.name as name_Child
FROM
your_table c
LEFT JOIN your_table d ON c.parent_place_id = d.place_id
LEFT JOIN your_table gf ON d.parent_lace_id = gf.place_id
-- Add this if you want to have only lines which has Dad and Grandfather fields populated
WHERE d.place_id IS NOT NULL
;

LEFT JOIN in Postgres when there is a WHERE clause [duplicate]

This question already has answers here:
Left Outer Join doesn't return all rows from my left table?
(3 answers)
Closed 9 months ago.
I've been using PosgreSQL almost daily for over 11 years now, and today I wrote what I though was a very simple query with a LEFT JOIN that doesn't behave the way that I expected. I'm lucky I caught the bug, but it has me concerned that there is something fundamental here that I a missing. Please look at the following to be able reproduce.
CREATE TEMP TABLE tbl_a(date date);
INSERT INTO tbl_a VALUES ('2022-01-01'), ('2022-01-02'), ('2022-01-03'), ('2022-01-04');
CREATE TEMP TABLE sale(date date, item_id int);
INSERT INTO sale VALUES ('2022-01-02', 2), ('2022-01-03', 2), ('2022-01-04', 3);
When I run the following query I get the results I expect with a LEFT JOIN
SELECT t.*, s.item_id FROM tbl_a AS t LEFT JOIN sale AS s ON t.date = s.date;
+------------+---------+
| date | item_id |
+------------+---------+
| 2022-01-01 | NULL |
| 2022-01-02 | 2 |
| 2022-01-03 | 2 |
| 2022-01-04 | 3 |
+------------+---------+
I get every record in tbl_a and since I have no sale records for 2022-01-01, I get a NULL.
However, when I add a WHERE to the query I get an unexpected result.
SELECT t.*, s.item_id FROM tbl_a AS t LEFT JOIN sale AS s ON t.date = s.date WHERE s.item_id = 2;
+------------+---------+
| date | item_id |
+------------+---------+
| 2022-01-02 | 2 |
| 2022-01-03 | 2 |
+------------+---------+
Note: there is no record for 2022-01-01 or 2022-01-04.
However, if I rewrite the query with a CTE, I get the results I expect.
WITH s AS (select * from sale WHERE item_id = 2) SELECT t.*, s.item_id FROM tbl_a AS t LEFT JOIN s ON t.date = s.date ORDER BY t.date;
+------------+---------+
| date | item_id |
+------------+---------+
| 2022-01-01 | NULL |
| 2022-01-02 | 2 |
| 2022-01-03 | 2 |
| 2022-01-04 | NULL |
+------------+---------+
My question is why do the above two queries yield different results.
Note:
SELECT version();
+-----------------------------------------------------------------------------------------------------------------------------------+
| version |
+-----------------------------------------------------------------------------------------------------------------------------------+
| PostgreSQL 13.7 (Ubuntu 13.7-1.pgdg20.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, 64-bit |
+-----------------------------------------------------------------------------------------------------------------------------------+
Thats due to the order of execution from postgres.
Whenever you run the 1st query you are joining both tables then filtering it with the where item_id = 2).
In the second query you are filtering tbl_a then joining the result with b.
The equivalent of the 1st query would be something like:
WITH s AS
(select * from sale)
SELECT t.*, s.item_id
FROM tbl_a AS t
LEFT JOIN s ON t.date = s.date
WHERE s.item_id = 2
ORDER BY t.date;

Mysql- SELECT Column 'A' even with NULLS

Table A contains student names, table B and C contain classes and the presence of students.
I would like to display all students and attend their presence. The problem is that I can not display all students who did not have a checked presence.
Where I checked the presence of students it is ok, but if there is no checked presence in a given class, on a given day and in a given subject- nothing is displayed.
My query:
SELECT student.id_student, CONCAT(student.name,' ' ,student.surname) as 'name_surname',pres_student_present, pres_student_absent, pres_student_justified, pres_student_late, pres_student_rel, pres_student_course, pres_student_delegation, pres_student_note FROM student
LEFT JOIN class ON student.no_classes = class.no_classes
LEFT JOIN pres_student ON student.id_student = pres_student.id_student
WHERE (class.no_classes = '$class' OR NULL AND pres_student_data = '$data' AND pres_student_id_subject = $id_subject OR NULL)
GROUP BY student.surname
ORDER BY student.surname ASC
I want to display name_surname always and any other column should have NULL or 1
like:
Name | present | absent | just | late | rel | delegation | note |
Donald Trump | 1 | | | | | | |
Bush | | | | | | | |
Someone | 1 | | | | | | |
etc...
You should move restrictions on class and pres_studenttables from the WHERE clause to the ON (LEFT join).
In your case when you perform a restriction in the WHERE clause on a table with an outer join, the sql engine consider you are performing an INNER join
SELECT student.id_student
, CONCAT(student.name, ' ', student.surname) AS 'name_surname'
, pres_student_present
, pres_student_absent
, pres_student_justified
, pres_student_late
, pres_student_rel
, pres_student_course
, pres_student_delegation
, pres_student_note
FROM student
LEFT JOIN class
ON student.no_classes = class.no_classes
AND class.no_classes = '$class'
LEFT JOIN pres_student
ON student.id_student = pres_student.id_student
AND pres_student_data = '$data'
AND pres_student_id_subject = $id_subject
GROUP BY student.surname
ORDER BY student.surname ASC

SELECT DISTINCT on a ordered subquery's table

I'm working on a problem, involving these two tables.
books
isbn | title | author
------------+-----------------------------------------+------------------
1840918626 | Hogwarts: A History | Bathilda Bagshot
3458400871 | Fantastic Beasts and Where to Find Them | Newt Scamander
9136884926 | Advanced Potion-Making | Libatius Borage
transactions
id | patron_id | isbn | checked_out_date | checked_in_date
----+-----------+------------+------------------+-----------------
1 | 1 | 1840918626 | 2012-05-05 | 2012-05-06
2 | 4 | 9136884926 | 2012-05-05 | 2012-05-06
3 | 2 | 3458400871 | 2012-05-05 | 2012-05-06
4 | 3 | 3458400871 | 2018-04-29 | 2018-05-02
5 | 2 | 9136884926 | 2018-05-03 | NULL
6 | 1 | 3458400871 | 2018-05-03 | 2018-05-05
7 | 5 | 3458400871 | 2018-05-05 | NULL
the query "Make a list of all book titles and denote whether or not a copy of that book is checked out." so pretty much just the first table with a checked out column.
im trying to SELECT DISTINCT on a sub query with the checkout books first, but that doesn't work. I've researched and others say to accomplish this use a GROUP BY clause instead of DISTINCT but the examples they provide are one column queries and when more columns are added it doesn't work.
this is my closest attempt
SELECT DISTINCT ON (title)
title, checked_out
FROM(
SELECT b.title, t.checked_in_date IS NULL AS checked_out
FROM transactions t
natural join books b
ORDER BY checked_out DESC
) t;
or you can join only transactions where books are not checked in:
SELECT b.title, t.isbn IS NOT NULL AS checked_out
, t.checked_out_date
FROM books b
LEFT JOIN transactions t ON t.isbn = b.isbn AND t.checked_in_date IS NULL
ORDER BY checked_out DESC
I adjusted your attempt a little bit. Basically I changed the way your data is joined
SELECT DISTINCT ON (title)
title, checked_out
FROM(
SELECT b.title, t.checked_in_date IS NULL AS checked_out
FROM books b
LEFT OUTER JOIN transactions t USING (isbn)
ORDER BY checked_out DESC
) t;

Joining sequential records in TSQL

I've got a database of messages which contain various pieces of information that come in. One such piece is a state change messages so my table looks like the following.
+-----------+--------------+---------+
| MessageId | RecievedUTC | State |
+-----------+--------------+---------+
| 1 | 1/1/2010 5pm | Off |
+-----------+--------------+---------+
| 2 | 1/2/2010 8am | Idle |
+-----------+--------------+---------+
| 3 | 1/2/2010 9am | Working |
+-----------+--------------+---------+
I'd like get a list of records which state how long I was in each state something like for a report and a maybe a pretty bar chart of how time was spent.
+---------+---------------+--------------+
| State | StartUTC | StopUTC |
+---------+---------------+--------------+
| Off | 1/1/2010 5pm | 1/2/2010 8am |
+---------+---------------+--------------+
| Idle | 1/1/2010 8am | 1/2/2010 9am |
+---------+---------------+--------------+
etc. In my mind its no harder than a join of the table with itself, offset by 1 record ordered by the RecievedUTC.
The best TSQL I could come up with is something to the effect of
SELECT m1.State, m1.RecievedUTC as StartUTC, MIN(m2.RecievedUTC) as StopUTC
FROM MessageStates as m1
INNER JOIN MessageStates as m2 ON MessageStates ON m2.RecievedUTC > m1.RecievedUTC
GROUP BY m1.MessageId, m1.State, m1.RecievedUTC
Or as a sub query to get StopUTC but both perform horribly with only 30-40k records taking almost 5 minutes to do this join.
If I wrote this in C# I would keep the track of the previous RecievedUTC and state so when I saw the next record I could combine the next RecievedUTC with it and in linear time have the data as I wanted it.
Try this:
WITH MsgStates AS
(
SELECT a.*, ROW_NUMBER() OVER(ORDER BY RecievedUTC ) RN
FROM MessageStates a
)
SELECT a.State, a.RecievedUTC StartUTC, b.RecievedUTC StartUTC
FROM MsgStates a, MsgStates b
WHERE a.rn = b.rn+1