I need to calculate the re-bookings rate percentage by teacher for a given time range, my table structure is fairly standard for an e-commerce platform, tables include booking, customer and teacher, included below:
Re-bookings rate: a customer can create a booking for any teacher but each booking can only have one teacher at a time. The re-bookings rate is when a teacher is booked more than once by the same customer. The teacher with the highest percentage means the teacher has more repeat bookings than any other teacher.
I wondered whether a temporary table needs to be created for each teacher with a list bookings?
table: Booking
id, customer_id, teacher_id
table: Customer
id, email, forename, surname
table: Teacher
id, name, email
I have the following query but I'm not sure how to calculate the re-bookings rate!?
SELECT t.id, t.email
FROM booking b
JOIN customer c on c.id = b.customer_id
JOIN teacher t on t.id = b.teacher_id
WHERE b.time_created = "..."
Sample dataset
Booking
id, customer_id, teacher_id
1, 1, 1
2, 2, 1
3, 6, 2
4, 8, 4
5, 4, 3
6, 1, 1
Customer
id, email, forename, surname
1, tom#test.com, tom, smith
2, rachel#test.com, rachel, green
3, jeff#lycos.com, jeff, price
4, max#google.com, max, cooper
5, tom#msn.com, tom, white
6, pete#gmail.com, pete, tinner
7, lenny#hotmail.com, lenny, allen
8, noel#gmail.com, noel, ashton
Teacher
id, name, email
1, john, john#schoolexample1.edu
2, gavin, gavin#schoolexample1.edu
3, gordon, gordon#schoolexample1.edu
4, hazel, hazel#schoolexample1.edu
Desired data output
Latest query version update
My thoughts on this:
SELECT
t_ct.teacher_id, teacher.forename, t_ct.customer_id, customer.forename, ct,
SUM(ct) OVER(PARTITION BY t_ct.teacher_id)
FROM
(SELECT
teacher_id, customer_id, count(*) AS ct
FROM
booking
JOIN
teacher
ON
booking.teacher_id = teacher.id
GROUP BY
teacher_id, customer_id) AS t_ct
JOIN
teacher
ON
t_ct.teacher_id = teacher.id
JOIN
customer
ON
t_ct.customer_id = customer.id
ORDER BY
teacher.forename, ct desc;
teacher_id | forename | customer_id | forename | ct | sum
------------+----------+-------------+----------+----+-----
4 | jennifer | 1 | oliver | 1 | 1
3 | max | 1 | oliver | 1 | 2
3 | max | 2 | chandler | 1 | 2
2 | terry | 2 | chandler | 3 | 5
2 | terry | 3 | sarah | 1 | 5
2 | terry | 4 | vicky | 1 | 5
Changing the ORDER BY:
sum desc, ct desc, teacher.forename;
would get a better picture:
teacher_id | forename | customer_id | forename | ct | sum
------------+----------+-------------+----------+----+-----
2 | terry | 2 | chandler | 3 | 5
2 | terry | 3 | sarah | 1 | 5
2 | terry | 4 | vicky | 1 | 5
3 | max | 1 | oliver | 1 | 2
3 | max | 2 | chandler | 1 | 2
4 | jennifer | 1 | oliver | 1 | 1
The SUM(ct) OVER(PARTITION BY t_ct.teacher_id) portion is a Window function that calculates the sum of bookings a teacher has, per 'total_number_of_bookings' request from the dbfiddle. Basically it sums the bookings per teacher_id. It repeats for each row for a given teacher_id.
UPDATE 12/10/2020. Using tablefunc module to do a crosstab on a temporary table. Not complete as it does not deal with 4+ bookings.
CREATE TEMP TABLE booking_rate AS
SELECT
t_ct.teacher_id, teacher.forename, ct,
SUM(ct) OVER(PARTITION BY t_ct.teacher_id) AS total
FROM
(select teacher_id, ct, count(*) from (SELECT
teacher_id, customer_id, count(*) AS ct
FROM
booking
JOIN
teacher
ON
booking.teacher_id = teacher.id
GROUP BY
teacher_id, customer_id) as t_c_ct group by teacher_id, ct) AS t_ct
JOIN
teacher
ON
t_ct.teacher_id = teacher.id
ORDER BY
teacher.forename, ct desc;
select * from booking_rate ;
teacher_id | forename | ct | total
------------+----------+----+-------
4 | jennifer | 1 | 1
3 | max | 1 | 1
2 | terry | 3 | 4
2 | terry | 1 | 4
SELECT t_id, name, total, round(coalesce(ct/total::numeric, 0), 2) * 100 AS one,
round(coalesce(ct2/total::numeric, 0), 2) * 100 AS two, round(coalesce(ct3/total::numeric, 0) ,2) * 100 as three,
round(coalesce(ct4/total::numeric, 0), 2) * 100 AS four
FROM
(SELECT * FROM crosstab (
'SELECT teacher_id, forename, total, ct, ct FROM booking_rate ORDER BY 1',
'SELECT booking from generate_series(1,4) AS booking'
) AS (
t_id int,
name varchar,
total int,
ct int,
ct2 int,
ct3 int,
ct4 int
)
)AS c_tab
;
t_id | name | total | one | two | three | four
------+----------+-------+--------+------+-------+------
2 | terry | 4 | 25.00 | 0.00 | 75.00 | 0.00
3 | max | 1 | 100.00 | 0.00 | 0.00 | 0.00
4 | jennifer | 1 | 100.00 | 0.00 | 0.00 | 0.00
(3 rows)
Related
I have a table as follows
id | group | value
------+-------------+----------
1 | 1 | 2
2 | 1 | 4
3 | 1 | 3
4 | 2 | 2
5 | 2 | 9
6 | 2 | 5
I want to group the rows by 'group' with the order of 'id' and create a new column that reverses the 'value' column as follows
id | group | value | reversedvalue
------+-------------+---------+---------
1 | 1 | 2 | 3
2 | 1 | 4 | 4
3 | 1 | 3 | 2
4 | 2 | 2 | 5
5 | 2 | 9 | 9
6 | 2 | 5 | 2
Try the following:
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n
FROM your_table
) q1
JOIN
(
SELECT *,ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n
FROM your_table
) q2
ON q1.group_id=q2.group_id AND q1.n=q2.n
You also can use CTE:
WITH cte AS(
SELECT *,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id) n1,
ROW_NUMBER()OVER(PARTITION BY group_id ORDER BY id DESC) n2
FROM your_table
)
SELECT q1.id,q1.group_id,q1.value,q2.value
FROM
cte q1
JOIN
cte q2
ON q1.group_id=q2.group_id AND q1.n1=q2.n2;
I have chapters table like this:
id | title | sort_number | book_id
1 | 'Chap 1' | 3 | 1
5 | 'Chap 2' | 6 | 1
8 | 'About ' | 1 | 1
9 | 'Chap 3' | 9 | 1
10 | 'Attack' | 1 | 2
Id is unique, sort_number is unique for same book(book_id)
1)How can load all data (3 rows) for 3 chapters (current, next and prev) sorted by sort_number if i have only current chapter id?
2)How can i load current chapter data (1 row) and only id's of next, prev if they exist?
This can be done using window functions
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter,
lead(id) over w as next_chapter
from chapters
window w as (partition by book_id order by sort_number);
With your sample data that returns:
id | title | sort_number | book_id | prev_chapter | next_chapter
---+--------+-------------+---------+--------------+-------------
8 | About | 1 | 1 | | 1
1 | Chap 1 | 3 | 1 | 8 | 5
5 | Chap 2 | 6 | 1 | 1 | 9
9 | Chap 3 | 9 | 1 | 5 |
10 | Attack | 1 | 2 | |
The above query can now be used to answer both your questions:
1)
select id, title, sort_number, book_id
from (
select id, title, sort_number, book_id,
--first_value(id) over w as first_chapter,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where 1 in (id, prev_chapter_id, next_chapter_id)
2)
select *
from (
select id, title, sort_number, book_id,
lag(id) over w as prev_chapter_id,
lead(id) over w as next_chapter_id
from chapters
window w as (partition by book_id order by sort_number)
) t
where id = 1
I have 4 tables
Table1
id | name
1 | A
2 | B
Table2
id | name1
1 | C
2 | D
Table3
id | name2
1 | E
2 | F
Table4
id | name1_id | name2_id | name3_id
1 | 1 | 2 | 1
2 | 2 | 2 | 2
3 | 1 | 2 | 1
4 | 2 | 1 | 1
5 | 1 | 1 | 2
6 | 2 | 2 | 1
7 | 1 | 1 | 2
8 | 2 | 1 | 1
9 | 1 | 2 | 1
10 | 2 | 2 | 1
Now I want to join all tables with 4 and get this type of output
name | count
{A,B} | {5, 5}
{C,D} | {5, 6}
{E,F} | {7, 3}
I tried this
select array_agg(distinct(t1.name)), array_agg(distinct(temp.test))
from
(select t4.name1_id, (count(t4.name1_id)) "test"
from table4 t4 group by t4.name1_id
) temp
join table1 t1
on temp.name1_id = t1.id
I am trying to achieve this. Anybody can help me.
Calculate the counts for every table separately and union the results:
select
array_agg(name order by name) as name,
array_agg(count order by name) as count
from (
select 1 as t, name, count(*)
from table4
join table1 t1 on t1.id = name1_id
group by name
union all
select 2 as t, name, count(*)
from table4
join table2 t2 on t2.id = name2_id
group by name
union all
select 3 as t, name, count(*)
from table4
join table3 t3 on t3.id = name3_id
group by name
) s
group by t;
name | count
-------+-------
{A,B} | {5,5}
{C,D} | {4,6}
{E,F} | {7,3}
(3 rows)
I have two tables namely match and player. I am trying to find the total number of matches played by each player by adding no_of_wins and no_of_loses columns.
player:
id | name
----|----
1 | Suhas
2 | Srivats
3 | James
4 | Watson
match:
id | winner | loser
----|--------|-------
1 | 1 | 2
2 | 1 | 3
3 | 1 | 4
4 | 2 | 4
5 | 4 | 3
6 | 3 | 2
I tried the following SQL command:
select p.id, p.name, count(m.winner) as no_of_wins,count(m.loser) as no_of_loses from player as p left join match as m on p.id=m.winner group by p.id order by p.id;
This command shows the wrong output for the number of loses.
id | name | no_of_wins | no_of_loses
----|---------|------------|-------------
1 | Suhas | 3 | 3
2 | Srivats | 1 | 1
3 | James | 1 | 1
4 | Watson | 1 | 1
Kindly help.
Calculate aggregated numbers of wins and loses for a player in two queries and (full) join them by a player id:
select
name,
coalesce(wins, 0) as no_of_wins,
coalesce(loses, 0) as no_of_loses,
coalesce(wins, 0) + coalesce(loses, 0) as total
from (
select winner as id, count(*) as wins
from match
group by 1
) w
full join (
select loser as id, count(*) as loses
from match
group by 1
) l using (id)
full join player using(id)
order by id;
name | no_of_wins | no_of_loses | total
---------+------------+-------------+-------
Suhas | 3 | 0 | 3
Srivats | 1 | 2 | 3
James | 1 | 2 | 3
Watson | 1 | 2 | 3
(4 rows)
Your query will cause an error because you didn't add p.name to the GROUP BY clause.
You'll have to join match twice, because these are two independent joins:
SELECT p.id,
p.name,
COALESCE(w.wins, 0) no_of_wins,
COALESCE(l.losses, 0) no_of_losses
FROM player p
LEFT JOIN
(SELECT winner id,
count(*) wins
FROM match
GROUP BY winner
) w
USING (id)
LEFT JOIN
(SELECT loser id,
count(*) losses
FROM match
GROUP BY loser
) l
USING (id);
I have this table named Samples. The Date column values are just symbolic date values.
+----+------------+-------+------+
| Id | Product_Id | Price | Date |
+----+------------+-------+------+
| 1 | 1 | 100 | 1 |
| 2 | 2 | 100 | 2 |
| 3 | 3 | 100 | 3 |
| 4 | 1 | 100 | 4 |
| 5 | 2 | 100 | 5 |
| 6 | 3 | 100 | 6 |
...
+----+------------+-------+------+
I want to group by product_id such that I have the 1'th sample in descending date order and a new colomn added with the Price of the 7'th sample row in each product group. If the 7'th row does not exist, then the value should be null.
Example:
+----+------------+-------+------+----------+
| Id | Product_Id | Price | Date | 7thPrice |
+----+------------+-------+------+----------+
| 4 | 1 | 100 | 4 | 120 |
| 5 | 2 | 100 | 5 | 100 |
| 6 | 3 | 100 | 6 | NULL |
+----+------------+-------+------+----------+
I belive I can achieve the table without the '7thPrice' with the following
SELECT * FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY Product_Id ORDER BY date DESC) r, * FROM Samples
) T WHERE T.r = 1
Any suggestions?
You can try something like this. I used your query to create a CTE. Then joined rank1 to rank7.
;with sampleCTE
as
(SELECT ROW_NUMBER() OVER (PARTITION BY Product_Id ORDER BY date DESC) r, * FROM Samples)
select *
from
(select * from samplecte where r = 1) a
left join
(select * from samplecte where r=7) b
on a.product_id = b.product_id