SQL - Append a count with a where has to query results - mysql

I have a Laravel booking app but am currently doing some manual reporting for a client.
I have a SQL query I run in SequelPro:
SELECT t.name
, t.email
, t.trial_ends_at
,
FROM teams t
ORDER
BY t.trial_ends_at DESC
However, what I now wish to do is add another field to each row that shows the client count for that team.
The relationships for clients of a team is:
users can have many bookings,
bookings belong to a user,
bookings have a team_id field
What I wish to do is append the count of users where they have made at least 1 booking of that team id.
In Laravel's eloquent I would do:
return User::whereHas('bookings', function($q) {
$q->where('team_id', THE ID)
})->count();

Join to a subquery which finds the counts:
SELECT
t.name,
t.email,
t.trial_ends_at,
COALESCE(b.cnt, 0) AS client_cnt
FROM teams t
LEFT JOIN
(
SELECT team_id, COUNT(*) AS cnt
FROM bookings
GROUP BY team_id
) b
ON t.id = b.team_id -- this assumes that id joins to team_id
ORDER BY
t.trial_ends_at DESC;
You had the following requirement:
What I wish to do is append the count of users where they have made at least 1 booking of that team id.
It seems to me that a user would only appear in a record in the bookings table if there was a reservation associated with that record. In other words, I don't think we need to do any extra check for this requirement, since if a user does appear, then by default he already appeared once.

If your first query produce $teams then you could do something like-
foreach($teams as $team){
$userCount = User::whereHas('bookings', function($q) use($team){
$q->where('team_id', $team->id);
})->count();
if($userCount>0){
$team->userCount = $userCount;
}
}
This will append userCount if you have more then single booking in the $teams collections, otherwise not.

Related

MYSQL count listings for each user where listing published in past

I have a MYSQL query that I am having difficulties getting to do what I want.
I have a users table (userstbl) containing all my user records, and a listings table (listings) contains all listings posted by each user. I am trying to select the name and address of each user and provide a count of listings for each user which was listed between a certain date range, but only count adverts for unique category_id's which is working fine.
The issue is that I only want to count listings that have been published. I have another table which is identical to my listings table called "listings_log" and contains a record for every change made to every listing record. If one of the records in "listings_log" for the listing has a "listings_log.published=1" than the listing was published. Each record in the "listings_log" table has a "listing_id" which is the same as in the "listings" table.
This is the query I have now :
SELECT
userstbl.userid,
userstbl.fullname,
userstbl.fulladdress,
COUNT(DISTINCT(
CASE WHEN listings.ad_type = 1
AND DATE(listings.date_listed) BETWEEN '2018-01-01' AND '2018-04-01'
THEN listings.category_id
END )
) AS Listings_Count_2018,
DATE_FORMAT(userstbl.reg_date, "%d/%m/%Y") AS RegisteredDate
FROM
users
LEFT JOIN listings ON listings.userid = userstbl.user_id
GROUP BY userstbl.userid
This counts the number of unique listings records between the correct dates for each user.
But I somehow only need to count listings records, where there is a corresponding listings_log record for that listing with published set to "1". The "listings_log" table and "listings" table both have a common listing_id column, but the listings_log table can have multiple records for each listing showing every change to each listing.
So I want to also join on the listings_log.listing_id = listings.listing_id and at least one of the "listings_log" records for that "listing_id" has listings_log.published = "1".
As you did not provide sample tables and a minimal reproducible example, a lot of this is guesswork. I am assuming for each user you want the total number of listing records. I built up the SQL with subqueries that are meant to be read "from the inside out."
select u.userid, u.fullname, u.fulladdress, sq.count from usertbl u join (
select u.userid, sum(c.count) as count from usertbl u join (
select count(*) as count, l.userid, l.listing_id from listings l join (
select distinct listing_id from listings_log where listings_log.published = "1"
) ll on l.listing_id = ll.listing_id
and l.ad_type = 1
and date(l.date_listed) between '2018-01-01' and '2018-04-01'
group by l.userid, l.listing_id
) c on u.userid = c.userid
group by u.userid
) sq on u.userid = sq.userid
;
See DB Fiddle

Use SELECT through three table

I tried to write a query, but unfortunately I didn't succeed.
I want to know how many packages delivered over a given period by a person.
So I want to know how many packages were delivered by John (user_id = 1) between 01-02-18 and 28-02-18. John drives another car (another plate_id) every day.
(orders_drivers.user_id, plates.plate_name, orders.delivery_date, orders.package_amount)
I have 3 table:
orders with plate_id delivery_date package_amount
plates with plate_id plate_name
orders_drivers with plate_id plate_date user_id
I tried some solutions but didn't get the expected result. Thanks!
Try using JOINS as shown below:
SELECT SUM(o.package_amount)
FROM orders o INNER JOIN orders_drivers od
ON o.plate_id=od.plate_id
WHERE od.user_id=<the_user_id>;
See MySQL Join Made Easy for insight.
You can also use a subquery:
SELECT SUM(o.package_amount)
FROM orders o
WHERE EXISTS (SELECT 1
FROM orders_drivers od
WHERE user_id=<user_id> AND o.plate_id=od.plate_id);
SELECT sum(orders.package_amount) AS amount
FROM orders
LEFT JOIN plates ON orders.plate_id = orders_drivers.plate_id
LEFT JOIN orders_driver ON orders.plate_id = orders_drivers.plate_id
WHERE orders.delivery_date > date1 AND orders.delivery_date < date2 AND orders_driver.user_id = userid
GROUP BY orders_drivers.user_id
But seriously, you need to ask questions that makes more sense.
sum is a function to add all values that has been grouped by GROUP BY.
LEFT JOIN connects all tables by id = id. Any other join can do this in this case, as all ids are unique (at least I hope).
WHERE, where you give the dates and user.
And GROUP BY userid, so if there are more records of the same id, they are returned as one (and summed by their pack amount.)
With the AS, your result is returned under the name 'amount',
If you want the total of packageamount by user in a period, you can use this query:
UPDATE: add a where clause on user_id, to retrieve John related data
SELECT od.user_id
, p.plate_name
, SUM(o.package_amount) AS TotalPackageAmount
FROM orders_drivers od
JOIN plates p
ON o.plate_id = od.plate_id
JOIN orders o
ON o.plate_id = od.plate_id
WHERE o.delivery_date BETWEEN convert(datetime,01/02/2018,103) AND convert(datetime,28/02/2018,103)
AND od.user_id = 1
GROUP BY od.user_id
, p.plate_name
It groups rows on user_id and plate_name, filter a period of delivery_date(s) and then calculate the sum of packageamount for the group

List of all products with user's saved products

I'm having an issue trying to figure out a query that will allow me to show a list of all of my product as well as showing whether or not a user has saved any given product.
I have 3 tables involved in the query (users, product_user, product).
I am determing whether or not a user has saved a product by joining the three tables and checking if user_id is null or not with the following query:
SELECT products.*, users.id as 'user_id' FROM products
LEFT JOIN product_user ON products.id = product_user.product_id
LEFT JOIN users ON product_user.user_id = users.id AND users.id =1;
However this returns duplicate rows when the user has saved a product (user_id null version and user_id = 1 version). A distinct statement won't work because the rows aren't distinct in this case. What is best practices to ensure that I only get back distinct products? I need to get back the entire list of products, whether or not the user has saved it.
This is being queried in mysql.
I think this does what you want:
select p.*,
(select pu.user_id
from product_user pu
where pu.product_id = p.id and pu.user_id = 1
limit 1
) as user_id
from products p;
This will return only one row per product. The row will have the user_id -- only once and it has to match whatever you pass in.

Complex SQL query over four tables does not fetch wanted result

Imagine the following scenario: Employees of a company can give votes to an arbitrary question (integer value).
I have a complex request where I want to fetch five information:
Name of the company
Average vote value per company
Number of employees
Number of votes
Participation (no of votes/no of employees)
The SQL query shall only fetch votes of companies, that the current user is employed at.
Therefore I am accessing four different tables, following you see an excerpt of the table declarations:
User
- id
Company
- id
- name
Employment
- user_id (FK User.id)
- company_id (FK Company.id)
Vote
- company_name
- vote_value
- timestamp
User and Company are related by an Employment (n:m relation, but needs to be extra table). The table Vote shall not be connected by PK/FK-relation, but they can be related to a company by their company name (Company.name = Vote.company_name).
I managed to fetch all information except for the number of employees correctly by the following SQL query:
SELECT
c.name AS company,
AVG(v.vote_value) AS value,
COUNT(e.user_id) AS employees,
COUNT(f.face) AS votes,
(COUNT(e.user_id) / COUNT(v.vote_value)) AS participation
FROM Company c
JOIN Employment e ON e.company_id = c.id
JOIN User u ON u.id = e.user_id
JOIN Vote v
ON v.company_name = c.name
AND YEAR(v.timestamp) = :year
AND MONTH(v.timestamp) = :month
AND DAY(v.timestamp) = :day
WHERE u.id = :u_id
GROUP BY v.company_name, e.company_id
But instead of fetching the correct number of employees, the employee field is always equal the number of votes. (And therefore the participation value is also wrong.)
Is there any way to perform this in one query without subqueries1? What do I have to change so that the query fetches the correct number of employees?
1 I am using Doctrine2 and try to avoid subqueries as Doctrine does not support them. I just did not want to pull this into a Doctrine discussion. That's I why I broke this topic down to SQL level.
If you want to fetch the number of employees then the issue is that you are filtering by only 1 employee:
WHERE u.id = :u_id
Secondly, bear in mind that if you want to count the amount of employees and you have gotten into the vote grouping level, then of course you will have the amount of rows equal to the amount of votes. So you will have to distinct count as #Przem... mentioned:
COUNT(DISTINCT e.user_id) AS employees,
That way you will uniquely count the employees for the company (getting rid of the repeated employee ids for all the votes the employee has).
As you mentioned in a comment:
It returns the 1 as employee count
This is because of the where condition forcing to 1 employee with many votes. The distinct will only count the unique 1 employee filtered by the where clause and that is why you get only 1. However, that is the correct result (based on your filter condition).
Adding subqueries in the select clause will also get you to the right result but at the expense of performance.
Try this--it calculates the votes as one subquery and the employees as another subquery.
SELECT c.name,
ce.employee_count,
cv.vote_count,
cv.vote_count / ce.employee_count,
cv.vote_value
FROM
(select company, count(*) AS 'employee_count'
FROM employment GROUP BY company) ce
INNER JOIN company c
ON c.id = ce.company
INNER JOIN
(select company, AVG(vote_value) AS 'vote_value', count(*) as 'vote_count'
FROM vote v GROUP BY company) cv
ON c.id = cv.company
Well I think with a query defined like that you should add the DISTINCT keyword while counting the number of employees:
SELECT
c.name AS company,
AVG(v.vote_value) AS value,
COUNT(DISTINCT e.user_id) AS employees,
COUNT(f.face) AS votes,
(COUNT(DISTINCT e.user_id) / COUNT(v.vote_value)) AS participation
FROM Company c
JOIN Employment e ON e.company_id = c.id
JOIN User u ON u.id = e.user_id
JOIN Vote v
ON v.company_name = c.name
AND YEAR(v.timestamp) = :year
AND MONTH(v.timestamp) = :month
AND DAY(v.timestamp) = :day
GROUP BY v.company_name, e.company_id;
Not sure if it is possible in MySQL, though.
Edit: as #Mosty Mostacho pointed out, the condition on u.id was the problem, and without it and with addition of DISTINCT keyword, the query returns correct results and I edited the above query.

MySQL query summing records for users

I'm using MySQL with phpmyadmin -- which I only started to use today. If y'all can help me with this query you will create some major happiness:
My objective: to identify which users of status "userEnabled" have >3 records from the goals table associated with them.
userID is a field that relates the tables.
TABLE NAMES: users, goals
I think this would be the beginning of the query:
SELECT * FROM `users` WHERE `userEnabled`=1
Please let me know any details needed.
You need to join record from goals where the userid matches. Filter on the enabled flag, then count the results. Something like:
select * from users
INNER JOIN goals ON users.userID = goals.userID
WHERE user.userEnabled = 1
GROUP BY user.userID
HAVING count(user.userID) > 3