mySQL SELECT in between dates and with conditions - php

I'm having a melt down and my experience with SQL is somehow limited, for these kind of purposes. Say I have the two following tables:
customer_list:
id | email
-----------------------------------
1 | mail1#mail.com
2 | mail2#mail.com
3 | mail3#mail.com
payment_log
customer_id | payment_date | payment_type_id
-------------------------------------------------------------
1 | 2016-01-01 | 3
1 | 2016-01-05 | 3
1 | 2016-01-02 | 2
1 | 2016-04-01 | 1
1 | 2016-04-12 | 2
2 | 2016-01-13 | 1
2 | 2016-01-19 | 1
2 | 2016-01-07 | 1
2 | 2016-01-04 | 1
3 | 2016-04-15 | 2
The customers I wish to select must in this example live up to the following criteria:
Must have made a payment before 2016-03-22, with any payment type, EXCEPT payment_type_id = 3.
Must have made at least one payment after 2016-03-22, using payment_type_id = 3.
The customer I need as a result of the query is customer_id = 1.

I think you should use an inner join on the same table
SELECT distinct a.customer_id FROM payment_log as a
INNER JOIN payment_log as b
on (a.customer_id = b.customer_id and a.payment_date = b.payment_date)
WHERE (a.payment_date < '2016-03-22' AND a.payment_type_id!=3)
AND (b.payment_date>2016-03-22 AND b.payment_type_id=3)";

SELECT * FROM customer
WHERE id IN (
SELECT DISTINCT L1.customer_id AS id
FROM payment_log L1
LEFT JOIN payment_log L2 ON L1.customer_id = L2.customer_id
WHERE
L1.payment_date < '2016-03-22'
AND L2.payment_date > '2016-03-22' AND L2.payment_type_id = 3
)

Try something like:
SELECT
DISTINCT customer_id
FROM
payment_log
WHERE
(payment_date < '2016-03-22' AND payment_type_id != 3)
AND
customer_id IN (SELECT DISTINCT customer_id FROM payment_log WHERE payment_date > 2016-03-22 AND payment_type_id = 3);
You might have to massage the sql syntax a little, but it should give you what you need.

Try This.
SELECT customer_id
FROM payment_log
WHERE payment_date =< '2016-03-22' AND payment_type_id != '3');

Related

Get rows above and below (neighbouring rows) a certain row, based on two criteria SQL

Say I have a table like so:
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 3 | 1 | 90 | 2021-01-12 15:38:0 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 10| 2 | 99 | 2021-01-15 10:50:38 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+
What I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). Also where level=1. This will be like a score board but only showing the user's score with the two above and two below.
So because scores can be the same, the timestamp column will also need to be used - so if two scores are equal, then the first person to get the score is shown above the other person.
E.g. say the user is id=5, I want to show
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+
Note that id=7 is below id=5
I am wondering does anyone know a way of doing this?
I have tried this below but it is not outputting what I need (it is outputting where level_id=2 and id=5, and the other rows are not in order)
((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
UNION ALL
(SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2))
order by score
If it is easier to output all rows in the table, say where level = 1, so it is a full score board.. and then do the getting a certain row and two above and below it using PHP I'd also like to know please :) ! (possibly thinking this may keep the SQL simpler)?
You can use cte and inner join as follows:
With cte as
(select t.*,
dense_rank() over (order by score) as dr
from your_table t)
Select c.*
From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
Where cu.id = 5
Ordwr by c.dr, c.timestamp
I would suggest window functions:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2;
Note: This returns 5 distinct score values, which may result in more rows depending on duplicates.
Here is a db<>fiddle.
EDIT:
If you want exactly 5 rows using the timestamp, then:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score, timestamp) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;
Note: This still treats equivalent timestamps as the same, but they seem to be unique in your data.

How to compare columns values with sum function in SQL?

I have three tables :
mls_category
points_martix
mls_entry
My first table (mls_category) is like below:
*--------------------------------*
| cat_no | store_id | cat_value |
*--------------------------------*
| 10 | 101 | 1 |
| 11 | 101 | 4 |
*--------------------------------*
My second table (points_martix) is like below:
*----------------------------------------------------*
| pm_no | store_id | value_per_point | max_distance |
*----------------------------------------------------*
| 1 | 101 | 1 | 10 |
| 2 | 101 | 2 | 50 |
| 3 | 101 | 3 | 80 |
*----------------------------------------------------*
My third table (mls_entry) is like below:
*-------------------------------------------*
| user_id | category | distance | status |
*-------------------------------------------*
| 1 | 10 | 20 | approved |
| 1 | 10 | 30 | approved |
| 1 | 11 | 40 | approved |
*-------------------------------------------*
I am using the following query to show the sum of distance with some condition:
SELECT SUM(t1.totald/c.cat_value)
AS total_distance
FROM mls_category c
JOIN
(SELECT SUM(distance) totald, user_id, category
FROM mls_entry
WHERE user_id = 1
AND status = 'approved'
GROUP BY user_id, category) t1
ON c.cat_no = t1.category
This gives me sum 60 as total_distance, that is correct which I wanted.
Now, I want to include the third table (points_matrix) and want to compare my sum(60) is less than or equal to 80(max_distance) then my new value would be 60*3=180.
So, suppose my sum comes 10 then my new value will be 10*1=10 and if my sum comes 25 then my new value will be according to point matrix 25*2=50.
Yon can using MIN() to calculate what value_per_point you need, and the whole sql is like this:
SELECT MIN(b.value_per_point) * d.total_distance FROM points_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category
) d ON b.store_id = d.store_id AND b.max_distance >= d.total_distance
Use Correlated Subquery:
SELECT
dt.total_distance * dt.max_points
FROM (
SELECT SUM(t1.totald/c.cat_value) AS total_distance,
(
SELECT value_per_point
FROM points_martix
WHERE SUM(t1.totald/c.cat_value) >= max_distance
ORDER BY max_distance ASC LIMIT 1
) AS max_points
FROM mls_category AS c
JOIN (
SELECT SUM(distance) AS totald,
user_id,
category
FROM mls_entry
WHERE user_id= 1 AND
status = 'approved'
GROUP BY user_id, category
) AS t1 on c.cat_no = t1.category
) AS dt

Get the most popular result from theses tables

Here's my table definition:
Table ___Rooms:
|--------|-----------|--------|----------|
|ROO_Id |ROO_HotelId|ROO_Name|ROO_Number|
|--------|-----------|--------|----------|
| 1|AAA00 |Room 12 | 12|
| 2|AAA00 |Room 14 | 14|
| 3|AAA00 |Room 16 | 16|
| 4|ZZZ99 |Room 11 | 11|
| 5|ZZZ99 |Room 22 | 22|
| 6|ZZZ99 |Room 33 | 33|
|--------|-----------|--------|----------|
Table ___Bookings:
|--------|-----------|----------|
|BOO_Id |BOO_HotelId|BOO_RoomId|
|--------|-----------|----------|
| 1|AAA00 | 1|
| 2|AAA00 | 1|
| 3|AAA00 | 3|
| 4|ZZZ99 | 5|
| 5|ZZZ99 | 5|
| 6|ZZZ99 | 5|
|--------|-----------|----------|
Actually, I have:
Number of booking for AAA00 = 3
Number of rooms for AAA00 = 3
I want to list rooms for the property AAA00 only and rank them by the most popular in them of number of bookings.
So I use this query:
SELECT r.ROO_Number BOO_RoomId,
( ( ifnull(cnt_book,0)*100)/(SELECT count(*) FROM ___Bookings)) percentage,
ifnull(cnt_book,0) `count`
FROM ___Rooms r
LEFT JOIN (
SELECT BOO_RoomId, count(*) cnt_book
FROM ___Bookings
WHERE BOO_HotelId='AAA00'
GROUP BY BOO_RoomId
) cnt ON r.ROO_Id=cnt.BOO_RoomId
ORDER BY percentage DESC
The expecting result of this query was:
1 - Room 2 - 2 bookings - 66.66%
2 - Room 3 - 1 booking - 33.33%
3 - Room 2 - 0 booking - 00.00$
But it returns me all the rooms.
Could you please help me with that ?
Thanks.
Solution
Use CASE with SUM to add together all bookings per room. Then, JOIN to subquery to make the hotel total-bookings available to every row.
SELECT r.ROO_Name
, Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END) NumBookings
, Concat(
Format(
Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END)
/ TotalBookings
* 100
, 0)
, '%') AS PercentageTotal
FROM ( __Rooms r LEFT JOIN __Bookings b ON r.ROO_Id = b.BOO_RoomId
) INNER JOIN (SELECT BOO_HotelId
, Count(*) AS TotalBookings
FROM __Bookings
GROUP BY BOO_HotelId
) AS TotalHotelBookings
ON r.ROO_HotelId = TotalHotelBookings.BOO_HotelId
WHERE r.ROO_HotelId = 'AAA00'
GROUP BY r.ROO_Name
ORDER BY r.ROO_Name
;
Result Set
ROO_Name NumBookings PercentageTotal
-------- ----------- ---------------
Room 12 2 67%
Room 14 0 0%
Room 16 1 33%
Key point
Sum(CASE WHEN BOO_id IS NULL THEN 0 ELSE 1 END)
Pretty much should be something like this:
SELECT
foo.ROO_Id,
foo.ROO_Name,
foo.cnt,
(foo.cnt * 100.0) / (SELECT count(*) FROM ___Bookings WHERE BOO_HotelId = foo.ROO_HotelId) AS percentage
FROM (
SELECT
ROO_Id,
ROO_Name,
ROO_HotelId,
(SELECT count(*) FROM ___Bookings b WHERE b.BOO_RoomId = r.ROO_Id) AS cnt
FROM ___Rooms r
WHERE ROO_HotelId = 'AAA00'
) AS foo
ORDER BY cnt DESC

Insert `COUNT(*)` based on separate table

In MySQL is it possible to select columns from one table while also creating a column for COUNT(*) based on other tables? That way a summary of the results from all tables can be returned. This might be a bit confusing to explain in words so I made some sample tables instead:
events_tbl
----------------------------
id | eventname
1 | Anime Festival
2 | Food Festival
----------------------------
booths_tbl
-------------------------
id | boothname
1 | Walmart
2 | Pizza Hut
3 | Nike
4 | North Face
-------------------------
participants_tbl
-----------------------------
id | participantname
1 | John
2 | Mike
3 | Rambo
4 | Minnie
-----------------------------
event_booths_tbl
--------------------------------
event_id | booth_id
1 | 1
1 | 2
1 | 5
2 | 3
2 | 4
--------------------------------
event_participants_tbl
-------------------------------------
event_id | booth_id
1 | 1
1 | 2
1 | 3
1 | 4
-------------------------------------
Is there a way to get results like this in MySQL:
summary_tbl
------------------------------------------------------------------------
id | eventname | booth_count | participant_count
1 | Anime Festival | 3 | 4
2 | Food Festival | 2 | 0
------------------------------------------------------------------------
The event_participants_tbl should contain participant_id instead of booth_id.
Its irrelevant otherwise.
Your MySQL query would be like this :
select
et.id,
et.eventname,
count(distinct ebt.booth_id) as booth_count,
count(distinct ept.participant_id) as participant_count
from
event_booths_tbl ebt
left join events_tbl et on et.id=ebt.event_id
left join event_participants_tbl ept on ept.event_id=ebt.event_id
group by et.event_id;
Join with subqueries that count in each table:
SELECT e.id, e.event_name,
IFNULL(b.booth_count, 0) AS booth_count,
IFNULL(p.participant_count, 0) AS participant_count
FROM events_table AS e
LEFT JOIN (SELECT event_id, COUNT(*) AS booth_count
FROM event_booths_table
GROUP BY event_id) AS b ON e.id = b.event_id
LEFT JOIN (SELECT event_id, COUNT(*) AS participant_count
FROM event_participants_table
GROUP BY event_id) AS p ON e.id = p.event_id
Try this :
select event.id,
event.name,
count(distinct eventBooth.booth_id),
count(distinct eventParitcipant.booth_id)
from events_tbl event
LEFT JOIN event_booths_tbl eventBooth on eventBooth.event_id=event.id
LEFT JOIN event_participants_tbl eventParitcipant
on eventParitcipant.event_id=event.id
group by event.id

mySQL "Rank in Highscore"-Query

Hi there coders around the world,
I'm working on a project where users can do certain things and gain points for it. To simplify this question let's say we got 2 tables user and points.
-- table user -- table points
+---------------+ +-----------------------------+
| id | name | | id | points | user_id |
+---------------+ +-----------------------------+
| 1 Tim | | 1 5 1 |
| 2 Tom | | 2 10 1 |
| 3 Marc | | 3 5 1 |
| 4 Tina | | 4 12 2 |
| 5 Lutz | | 5 2 2 |
+---------------+ | 6 7 1 |
| 7 40 3 |
| 8 100 1 |
+-----------------------------+
Now to get the complete highscore-list I use the following query
SELECT u.*, SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
ORDER BY sum_points DESC
resulting in a fine highscore-list with all users from first to last
+------------------------------+
| id | name | sum_points |
+------------------------------+
| 1 Tim 127 |
| 3 Marc 40 |
| 2 Tom 14 |
| 4 Tina 0 |
| 5 Lutz 0 |
+------------------------------+
Alright back to the question itself. On the profile of a single user I'd like to show his ranking within the highscore-list.
Can this be done using a single query just showing that for example Tom (id=2) is ranked in place 3?
Thanks alot :-)
The idea is to ask, "how many players rank above #this_user":
select count(*) + 1 from
(
/* list of all users */
SELECT SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
) x
/* just count the ones with higher sum_points */
where sum_points > (select sum(points) from points where user_id = #this_user)
Edited to make result 1-based instead of 0-based
SELECT q.*,
#r := #r + 1 AS rank
FROM (
SELECT #r := 0
) vars,
(
SELECT u.*,
SUM(p.points) AS sum_points
FROM
user u
LEFT JOIN
points p
ON p.user_id = u.id
GROUP BY
u.id
ORDER BY
sum_points DESC
) q

Categories