How to make query faster - sql

Here is my code:
SELECT m.id, (SELECT count(r.id)
FROM reports as r
WHERE r.date BETWEEN NOW()-INTERVAL '7 days' AND NOW()
and r.decision = 'ACCEPTED'
and r.customer_id in (SELECT c.id
FROM customers as c
WHERE c.manager_id = m.id)) as count
FROM managers as m;
Where tables are managers, customers and reports. I want to SELECT, for every manager, number of reports all of his customers have made (with selected properties). Problem with this code is that it takes too much time and somehow cannot find a better way of building this query. Do you have any idea?

I think , a standard inner join among those three table with indexes defined on reports.date, reports.customer_id and customers.manager_id columns as mentioned on the comment( moreover considering managers.id and customer.id columns as expected to be primary keys which already have unique indexes on them ) will suffice for performance point of view :
SELECT m.id, count(r.id)
FROM customers c
JOIN managers m ON r.customer_id = c.id
JOIN reports r ON m.id = c.manager_id
WHERE r.date BETWEEN NOW() - INTERVAL '7 days' AND NOW()
AND r.decision = 'ACCEPTED'
GROUP BY m.id

Related

How to select objects if not exist between a period in Sqlite

I like to select those users who haven't filled out a form in the last 7 days but I'm stuck. The background: I am working on an app that lists the users who have filled out the form but I wrote it in another query that works fine. Now I need to select just those users who haven't filled the form out in the last 7 days.
The query I wrote selects all the users because everyone has objects that outside the period.
How can I select just those users who haven't filled out the form in the given period but not to include all users. As you can see on the picture the user with id 1 appears two times with Yes and No.
Tha query I wrote:
SELECT DISTINCT auth_user.id,
CASE WHEN felmeres.date BETWEEN date("now", "-7 day") AND date('now')
THEN 'Yes'
ELSE 'No'
END AS period
FROM felmeres
LEFT JOIN profile ON profile.user_id = felmeres.user_name_id
ORDER BY felmeres.date DESC
You could use a join aggregation approach:
SELECT p.user_id
FROM profile p
INNER JOIN felmeres f
ON f.user_name_id = p.user_id
GROUP BY p.user_id
HAVING SUM(f.date BETWEEN date('now', '-7 day') AND date('now')) = 0;
If profile contains the users' data then it should be the left table in the LEFT join and the condition for the dates should be placed in the ON clause, so that you filter out the matching users:
SELECT p.*
FROM profile p LEFT JOIN felmeres f
ON f.user_name_id = p.user_id AND f.date BETWEEN date(CURRENT_DATE, '-7 day') AND CURRENT_DATE
WHERE f.user_name_id IS NULL;
Or, with NOT EXISTS:
SELECT p.*
FROM profile p
WHERE NOT EXISTS (
SELECT 1
FROM felmeres f
WHERE f.user_name_id = p.user_id AND f.date BETWEEN date(CURRENT_DATE, '-7 day') AND CURRENT_DATE
);

Finding days when users haven't created any entries

I've 2 tables: users and time_entries, time entries has a foreign key to the users table. Users may create time entries with some time amount in it. I want to write a query which could return summarized amounts of time in arbitrary dates range grouped by user and date - it's easy but I need to include also days when nobody entered any time_entry. I've tried to create an additional table called calendar with dates and left join time_entries to it but I couldn't retrieve a list of users that haven't entered any time_entry. Here is my query:
SELECT te.date, SUM(te.amount), user_name
FROM calendar c
LEFT JOIN time_entries te on c.date = te.date
RIGHT JOIN asp_net_users anu on te.user_id = anu.id
GROUP BY user_name, te.date
If you just want the days no user made any entry. you can use NOT EXISTS and a correlated subquery.
SELECT c.date
FROM calendar c
WHERE NOT EXISTS (SELECT *
FROM time_entries te
WHERE te.date = c.date);
If you want all users along with the days they haven't made any entry cross join the users and the days and then also use a NOT EXISTS.
SELECT anu.user_name,
c.date
FROM asp_net_users anu
CROSS JOIN calendar c
WHERE NOT EXISTS (SELECT *
FROM time_entries te
WHERE te.user_id = anu.id
AND te.date = c.date);
Thanks to sticky bit examples I was able to write the following query which solves my problem:
SELECT c.date, a.id, COALESCE(sum(te.amount), 0)
FROM asp_net_users a
CROSS JOIN (SELECT *
FROM calendar
WHERE date BETWEEN '2019-10-01 00:00:00'::timestamp AND '2019-10-31 00:00:00'::timestamp) c
LEFT JOIN time_entries te on a.id = te.user_id AND c.date = te.date
WHERE a.department_guid = '95b7538d-3830-48d7-ba06-ad7c51a57191'
GROUP BY c.date, a.id
ORDER BY c.date

Write SQL query that returns results for each sorted value in main column

I'm working on a project related with my college and I have the following tables:
user - table with information about registered students
payment - table with information about payments
lesson - table with list of lessons.
Possible statuses of lessons:
CONFIRMED - lesson happened successfully
SCHEDULED - for future
lessons
CANCELED - for lessons that were canceled
Each lesson appears in table only one time. Lesson status is being updated.
I need to write a SQL query that returns for each country:
number of registered users
% of users, who made their first payment in 3 days after registration
% of users, who made their first payment in 3 days after registration and had 2 confirmed
lessons in 7 days after registration
But aside from using DISTINCT to sort the table with non repeated elements, I'm stuck with the subqueries required to filter the information using each country as reference.
Perhaps you need to adapt this to your SQL dialect (mainly adding intervals on timestamps). I guess the rest should work (not tested anyways).
WITH pay3day AS (
SELECT u.id
FROM user u
INNER JOIN payment p ON p.user_id = u.id
WHERE p.datetime <= u.date_joined + INTERVAL '3 days'
GROUP BY u.id
), less2in7 AS (
SELECT u.id
FROM user u
INNER JOIN lesson l ON l.user_id = u.id
WHERE l.status = 'CONFIRMED'
AND l.datetime <= u.date_joined + INTERVAL '7 days'
GROUP BY u.id
HAVING COUNT(*) > 0
)
SELECT
COUNT(*) AS a,
(COUNT(p.id) * 100.0) / COUNT(*) AS b,
(COUNT(p.id + l.id) * 100.0) / COUNT(*) AS c
FROM user u
LEFT JOIN pay3day p ON p.id = u.id
LEFT JOIN less2in7 l ON l.id = u.id
GROUP BY u.country_code

Joining two SQL tables with different column names in Metabase

I am using Metabase to ask questions on my DB. I am trying to join two tables where the same info (user ID) has two different names. The code I wrote is as follows:
SELECT
game_states.game_module AS game, count(*)
FROM
game_states gs LEFT JOIN users u ON gs.user_id = u.id;
WHERE
games_states.state = 'after_hands'
AND
user.last_joined_stamp > now() - interval '30 days'
GROUP BY
1
ORDER BY
2 DESC
I keep getting the following error:
ERROR: invalid reference to FROM-clause entry for table "game_states" Hint: Perhaps you meant to reference the table alias "gs". Position: 127
Once you define a table alias, use it!
SELECT gs.game_module AS game, count(*)
-------^
FROM game_states gs LEFT JOIN
users u
ON gs.user_id = u.id;
WHERE gs.state = 'after_hands' AND
------^
u.last_joined_stamp > now() - interval '30 days'
------^
GROUP BY 1
ORDER BY 2 DESC;
Incidentally, you probably intend:
SELECT gs.game_module AS game, count(u.id)
FROM game_states gs LEFT JOIN
users u
ON gs.user_id = u.id AND
u.last_joined_stamp > now() - interval '30 days'
WHERE gs.state = 'after_hands'
GROUP BY 1
ORDER BY 2 DESC;
You are using LEFT JOIN, so presumably want to include all matching games -- even those with no matching users. If so, don't filter on u in the where clause and count the matches, so you can get 0.

sqlite select query

I am working in Android with SQLite.
My db has 2 tables:
Table 1: cars
_id(int,pri-key)
reg(text)
type(text)
Table 2: jobs
_id(int,foreign-key)
date(date)
I need a sqlite statment which will get me all cars which have NOT had a job in the past 3 weeks. Iam actually porting the app from c#, and the statment I use for this (in MySQL) is
SELECT c.id, c.reg, c.type FROM buses c WHERE NOT EXISTS (SELECT NULL FROM jobs j WHERE j.id = c.id AND j.date >= CURRENT_DATE - INTERVAL 21 DAY
But the SqliteDataBase Object I am working with in android takes a different format, How would I run this query?
Thanks in advance.
I would try something like this:
SELECT * from cars A LEFT OUTER JOIN jobs B on A._id = B._id WHERE B._id IS NULL OR B.date < date('now', '-21 days');
The LEFT OUTER JOIN, ensures all values from the cars table are shown in the output (including the ones that don't match the join criteria - i.e. ones that don't have an entry in the jobs table). The WHERE criteria, filters for either, ones that don't have an entry in the jobs table (B._id IS NULL) or ones that are more than 21 days old B.date < date('now', '-21 days')
Of course I am assuming, there will be only 1 entry on the Jobs table for each car. If there will be more, you probably want to use MAX to get the latest date.
WORKING SOLUTION: SELECT * from cars A LEFT OUTER JOIN jobs B on A._id = B._id GROUP BY A._id HAVING B._id IS NULL OR MAX(B.date) < date('now', '-21 days');