Subquery select with outer value for inner where clause - mysql

Users can have multiple records in the subscriptions table.
What I want to do is return their first name, last name, email, start date (of their first subscription, select start_date from subscriptions order by start_date asc limit 1, but I need it for that specific user)
// users
id
first_name
last_name
email
// subscriptions
id
email
start_date (TIMESTAMP)
end_date (TIMESTAMP)
status
I thought this would work, but it does not seem to:
select
distinct(users.email), status, first_name, last_name,
(select start_date from subscriptions where subscriptions.email = users.email order by start_date asc limit 1) as start_date
from
subscriptions sub
join
users u on sub.email = u.email
order by
sub.end_date desc
That returns the same start_date for everyone, since it's probably pulling the first one it matches.
SQL fiddle with the schema: http://sqlfiddle.com/#!9/245c05/5

This query:
select s.*
from subscriptions s
where s.start_date = (select min(start_date) from subscriptions where email = s.email)
returns the row for each user's first subscription.
Join it to users:
select u.*, t.status, t.start_date
from users u
left join (
select s.*
from subscriptions s
where s.start_date = (select min(start_date) from subscriptions where email = s.email)
) t on t.email = u.email
See the demo.
Results:
| id | email | first_name | last_name | status | start_date |
| --- | -------------- | ---------- | --------- | -------- | ------------------- |
| 1 | john#aol.com | John | Smith | active | 2018-02-12 23:34:02 |
| 2 | jim#aol.com | Jim | Smith | canceled | 2016-03-02 23:34:02 |
| 3 | jerry#aol.com | Jerry | Smith | active | 2017-12-12 23:34:02 |
| 4 | jackie#aol.com | Jackie | Smith | active | 2018-05-22 23:34:02 |

Related

Big Query: Join single latest row from second table

I have two tables. One is a list of Orders, and one is a list of Events.
For each Order, I want to join the single last Event that happened (using clicked_at) before the created_at of the Order.
I have tried numerous ways to get this to work and tried several other answers on Stack Overflow but I am struggling to return the correct data.
The sudo logic for the subquery in my mind is something like:
SELECT campaign, user_id, created_at
FROM `Events`
WHERE order.user_id = user_id AND clicked_at < order.created_at
ORDER created_at DESC
LIMIT 1
Please see the example data below:
# Orders
| order_id | user_id | created_at |
-----------------------------------
| 123 | abc | 2020-07-04 |
| 456 | abc | 2020-05-01 |
# Events
| campaign | keyword | user_id | clicked_at |
----------------------------------------------
| facebook | shoes | abc | 2020-07-03 |
| google | hair | abc | 2020-07-01 |
My desired result
# Orders with campaign attribution
| order_id | user_id | created_at | campaign | keyword |
---------------------------------------------------------
| 123 | abc | 2020-07-04 | facebook | shoes |
| 456 | abc | 2020-06-04 | null | null |
Thanks!
Alex
Below is for BigQuery Standard SQL
#standardSQL
SELECT a.*, campaign, keyword
FROM `project.dataset.orders` a
LEFT JOIN (
SELECT
ANY_VALUE(o).*,
ARRAY_AGG(STRUCT(campaign, keyword) ORDER BY clicked_at DESC LIMIT 1)[OFFSET(0)].*
FROM `project.dataset.orders` o
JOIN `project.dataset.events` e
ON o.user_id = e.user_id
AND clicked_at < created_at
GROUP BY FORMAT('%t', o)
)
USING(order_id)
if applied to sample data from our question - result is
Row order_id user_id created_at campaign keyword
1 123 abc 2020-07-04 facebook shoes
2 456 abc 2020-05-01 null null
with orders as (
select 123 as order_id, 'abc' as user_id, cast('2020-07-04' as date) as created_at union all
select 456, 'abc', '2020-05-01'
),
events as (
select 'facebook' as campaign, 'shoes' as keyword, 'abc' as user_id, cast('2020-07-03' as date) as clicked_at union all
select 'google', 'hair', 'abc', '2020-07-01'
),
logic as (
select
orders.order_id,
orders.user_id,
orders.created_at,
events.clicked_at,
events.campaign,
events.keyword,
row_number() over (partition by orders.order_id order by events.clicked_at desc) as rn
from orders
left join events
on orders.user_id = events.user_id and events.clicked_at < orders.created_at
)
select * except(rn)
from logic
where rn = 1

MySQL query to count user orders by type

I have this 3 tables: users, orders and order_item.
When a user can have a order, and the order item can be event only, membership only or both, and so 2 line will be written to the order_items
users tables
id | user_id | name | phone
---|-----------|-------|------
1 | 123456789 | Jon | 555-55555
2 | 123456780 | Alice | 555-6666
orders tables
id | user_id | user_uid | user_info
---|---------|-----------|----------
1 | 1 | 123456789 | bla
2 | 2 | 123456780 | foo
3 | 2 | 123456780 | foo
order_items table
id | order_id | order_type | price
--- | -------- | ---------- | ------
1 | 1 | membership | 70
2 | 1 | event | 200
3 | 2 | event | 300
4 | 3 | membership | 70
The relationship is like this,
order_items.order_id -> orders.id
orders.user_id -> users.id
orders.user_uid -> users.user_id
I'm looking for a query which will produce this type of output,
user_id | name | count_membership | count_events | total_orders
-------- | ------ | ------------------ | -------------- | --------------
123456789 | Jon | 1 | 1 | 1
123456780 | Alice | 1 | 1 | 2
I like to count the total orders a user have, and count how many of each of item he have. in the end I like to filter out all users where count_membership = 0
Thanks in advance,
You can get the expected result set by using some conditional aggregation
select u.user_id,
u.name,
sum(oi.membership_count) membership_count,
sum(oi.event_count) event_count,
count(o.id) total_orders
from users u
join orders o on u.id = o.user_id
join (
select order_id,
sum(order_type = 'membership') membership_count,
sum(order_type = 'event') event_count
from order_items
group by order_id
) oi on o.id = oi.order_id
group by u.user_id,u.name
Demo
Your query needs to join the three tables on their common fields, which would be user_id and order_id.
Since you said you don't want any results where membership is not an order type, your results example doesn't make sense and the grouping into single results that way using a single mysql query makes things complicated to put order_type which is a single field into two separate columns. So I adjusted slightly.
select users.user_id, users.name, order_type, count(order_id) as total_orders from orders, order_items, users where users.user_id = orders.user_id and orders.id = order_items.order_id and order_type = "membership" group by users.user_id, users.name
working output from mysql terminal:
mysql> select users.user_id, users.name, order_type, count(order_id) as total_orders from orders, order_items, users where users.user_id = orders.user_id and orders.id = order_items.order_id and order_type = "membership" group by users.user_id, users.name;
+---------+------+------------+--------------+
| user_id | name | order_type | total_orders |
+---------+------+------------+--------------+
| 123 | Jon | membership | 1 |
| 1234 | Pam | membership | 2 |
+---------+------+------------+--------------+

MySQL GroupBy with null/zero results

I'm currently writing a ticket system that has three tables
one for users:
users
+----+-----------+----------+
| ID | FirstName | LastName |
+----+-----------+----------+
| 1 | First | User |
| 2 | Second | User |
| 3 | Third | User |
| 4 | Fourth | User |
| 5 | Fifth | User |
+----+-----------+----------+
one for tickets:
ticket
+----+---------------+
| ID | TicketSubject |
+----+---------------+
| 1 | Ticket #1 |
| 2 | Ticket #2 |
| 3 | Ticket #3 |
| 4 | Ticket #4 |
+----+---------------+
and one to assign users to tickets to action (can be more than one user per ticket):
ticket_assigned
+----+----------+--------+
| ID | TicketID | UserID |
+----+----------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 3 | 5 |
| 5 | 3 | 3 |
+----+----------+--------+
I'm trying to create a summary to show each user, and how many tickets they have assigned to them, example:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fourth | 0 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Note that the last entry is "unassigned", this is the number of records in the ticket table that DONT appear in the ticket_assigned table (thus being, unassigned). Also further note that user "Fourth" is zero, in that that user has no records in the ticket_assigned table.
Here is the current MySQL query I am using:
SELECT
CASE
WHEN users.FirstName IS NULL
THEN 'Unassigned'
ELSE users.FirstName
END as 'UserName',
COUNT(*) as 'TicketCount'
FROM tickets
LEFT OUTER JOIN ticket_assigned ON tickets.ticket_id = ticket_assigned.ticket_id
LEFT OUTER JOIN users ON ticket_assigned.user_id = users.user_id
GROUP BY ticket_assigned.user_id
ORDER BY UserName;
Problem with this is that it's not showing any of the users that don't feature in the ticket_assigned table, I'm essentially getting this:
+------------+-------+
| Name | Count |
+------------+-------+
| First | 2 |
| Second | 1 |
| Third | 1 |
| Fifth | 1 |
| Unassigned | 2 |
+------------+-------+
Is anyone able to assist and tell me how I can modify my query to include users that have no records in the ticket_assigned table? Thanks in advance!
Use a LEFT JOIN with a subquery to aggregate tickets:
SELECT t1.FirstName,
COALESCE(t2.ticket_count, 0) AS num_tickets
FROM users t1
LEFT JOIN
(
SELECT UserID, COUNT(*) AS ticket_count
FROM ticket_assigned
GROUP BY UserID
) t2
ON t1.ID = t2.UserID
UNION ALL
SELECT 'Unassigned', COUNT(*)
FROM tickets t
WHERE NOT EXISTS (SELECT 1 FROM tickets_assigned ta
WHERE ta.ticketId = t.id)
In MySQL, I think you need a left join and union all:
select u.id, u.firstname, count(ta.userId) as num_tickets
from users u left join
tickets_assigned ta
on ta.userId = u.id
group by u.id, u.firstname
union all
select NULL, 'Unassigned', count(*)
from tickets t
where not exists (select 1
from tickets_assigned
where ta.ticketId = t.id
);
I included the u.id in the aggregations. I'm uncomfortable just aggregating (and reporting) by first name, because different people frequently have the same first name, even in a relatively small group.
SELECT
u2.Firstname, IFNULL(tmp.count, 0) AS count
FROM users u2
LEFT JOIN (
SELECT u.id, u.Firstname, COUNT(1) as count
FROM ticket_assigned ta
LEFT JOIN ticket t ON t.id = ta.ticketID
LEFT JOIN users u ON u.id = ta.userID
GROUP BY u.id
) tmp ON tmp.id = u2.id
UNION
SELECT
'Unassigned', count(1) AS count
FROM ticket
WHERE id NOT IN (SELECT ticketid FROM ticket_assigned)

Get last login time from SQL Server 2008 database

I need to display only last login time user data, data being - name, email, location and datetime.
Scenario is user registers him/her self for the first time and login datetime gets inserted in to eventonline.participant table...now for subsequent login of the same user only we save logintime into eventonline.logindatetime.
Below is the query I used, but it selects all users login data instead of last login datetime.
select
a.firstname as Name,
a.Email as Email,
a.Address1 as Location,
a.MobileNo as Contact,
a.datetime, a.ID
from
eventonline.participant a
where
a.eventid = '" + Convert.ToString(ConfigurationManager.AppSettings["event_id"]) + "'
union
select
a.firstname as Name,
a.Email as Email,
a.Address1 as Location,
a.MobileNo as Contact,
b.datetime, b.Rid
from
eventonline.participant a
join
eventonline.logindatetime b on a.Id = b.Rid
where
b.eventid = '" + Convert.ToString(ConfigurationManager.AppSettings["event_id"]) + "'
order by
datetime desc
Your Database : eventonline
, Table names : participant, logindatetime
Let say your participant table contains following data :-
registration_id | user_phone | user_email | first_name | last_name | created_timestamp
1 | 5545555454 | test1#test.com | tom | dave | 2016-03-05 21:33:55
2 | 5599992154 | test123#test.com | riya | bottom | 2016-03-05 15:33:55
3 | 5545876954 | test123#test.com |greg | george | 2016-03-09 12:32:55
And logindatetime table have following data :-
id | registration_id | ip_address | user_Agent | created_timestamp
1 | 2 | 10.2.3.101 | mozilla | 2016-03-05 15:54:11
2 | 3 | 10.0.5.99 | chrome | 2016-03-15 23:31:01
3 | 1 | 10.0.4.43 | safari | 2016-04-01 21:33:55
4 | 2 | 10.2.4.65 | mozilla | 2016-04-05 19:21:55
5 | 1 | 10.6.5.54 | chrome | 2016-04-11 19:12:15
Now if you want to retrieve the user data along with login data corresponding to the 5th row i.e the latest row in logindatetime time then below query will work.
select a.registration_id, a.user_phone, a.user_email , a.first_name, a.last_name, b.ip_address, b.user_Agent, b.created_timestamp from participant as a JOIN logindatetime as b where a.registration_id=b.registration_id order by b.created_timestamp desc limit 1;
Result
registration_id | user_phone | user_email | first_name | last_name ip_address | user_Agent | created_timestamp
1 | 5545555454 | test1#test.com | tom | dave | 10.6.5.54 | chrome | 2016-04-11 19:12:15
Here is how I would go about writing the query to do it.
I have used the rank over function in sql to grab only the last or first values in a partition of data I want to look at.
SELECT
*
FROM
(
select
-- Here is the magic that makes it work.
Rank() over (Partition BY a.Email order by a.datetime DESC) as Rank
a.firstname as Name,
a.Email as Email,
a.Address1 as Location,
a.MobileNo as Contact,
a.datetime,
a.ID
FROM
<YourFromCause>
)
-- naming the temp result set.
tmp
where
Rank = 1

Finding duplicates from two columns, but show all rows MySQL

I have a table like this
| user_id | company_id | employee_id |
|---------|------------|-------------|
| 1 | 2 | 123 |
| 2 | 2 | 123 |
| 3 | 5 | 432 |
| 4 | 5 | 432 |
| 5 | 7 | 432 |
I have a query that looks like this
SELECT COUNT(*) AS Repeated, employee_id, GROUP_CONCAT(user_id) as user_ids, GROUP_CONCAT(username)
FROM user_company
INNER JOIN user ON user.id = user_company.user_id
WHERE employee_id IS NOT NULL
AND user_company.deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING Repeated >1;
The results I am getting look like this
| Repeated | employee_id | user_ids |
|---------|--------------|------------|
| 2 | 123 | 2,3 |
| 2 | 432 | 7,8 |
I need results that look like this
| user_id |
|---------|
| 2 |
| 3 |
| 7 |
| 8 |
I realize my query is getting more, but that's just to make sure I'm getting the correct data. Now I need to get a single column result with each user_id in a new row for updating based on user_id in another query. I've tried this by only selecting the user_id but I only get two rows, I need all four rows of duplicates.
Any ideas on how to modify my query?
Here is the query to get all of your user_ids:
SELECT user_id
FROM user_company uc
INNER JOIN
(
SELECT employee_id, company_id
FROM user_company
WHERE employee_id IS NOT NULL
AND deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING COUNT(employee_id) >1
) AS `emps`
ON emps.employee_id = uc.`employee_id`
AND emps.company_id = uc.`company_id`;
This query below will generate the query you are looking for.
SELECT CONCAT('UPDATE user_company SET employee_id = null WHERE user_id IN (', GROUP_CONCAT(user_id SEPARATOR ', '),')') AS user_sql
FROM user_company uc
INNER JOIN
(SELECT employee_id, company_id
FROM user_company
WHERE employee_id IS NOT NULL
AND deleted_at IS NULL
GROUP BY employee_id, company_id
HAVING COUNT(employee_id) >1) AS `emps`
ON emps.employee_id = uc.`employee_id`
AND emps.company_id = uc.`company_id`;