I have a database which store records like:
+----+---------------------+-------------+-----------------+
| id | user_name | status| date |
+----+---------------------+-------------+-----------------+
| 1 | A | Paid| 2016-10-11|
| 2 | B | Not Paid| 2016-10-12|
| 3 | C | Paid| 2016-10-12|
| 4 | A | Not Paid| 2016-10-13|
+----+---------------------+-------------+-----------------+
I wish to obtain the results like:
+----+---------------------+-------------+-----------------+-----------------+
| id | user_name | 2016-10-11| 2016-10-12 | 2016-10-13 |
+----+---------------------+-------------+-----------------+-----------------+
| 1 | A | Paid| NA| Not Paid|
| 2 | B | NA| Not Paid| NA|
| 3 | C | NA| Paid| Na|
+----+---------------------+-------------+-----------------+-----------------+
How can I query it to obtain the results like this?
PS: Poor English
FYI: I'm using mySQL as as DBMS and here is the create script:
CREATE TABLE `moneygame`.`pay_table`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(50),
`status` VARCHAR(50),
`p_date` DATE,
PRIMARY KEY (`id`)
);
If don't have a fixed number of dates then I wouldn't recommend doing what you are trying to do. Anyways, here's the solution to the problem.
create table p as select * from
(select 1 id, 'A' user_name, 'Paid' status, '2016-10-11' date union
select 2 id, 'B' user_name, 'Not Paid' status, '2016-10-12' date union
select 3 id, 'C' user_name, 'Paid' status, '2016-10-12' date union
select 4 id, 'A' user_name, 'Not Paid' status, '2016-10-13' date) t;
When dates are represented as columns
select user_name, ifnull(max(a),'NA') `2016-10-11`,
ifnull(max(b),'NA') `2016-10-12`, ifnull(max(c),'NA') `2016-10-13`
from (select user_name,
case when date = '2016-10-11' then status else null end a,
case when date = '2016-10-12' then status else null end b,
case when date = '2016-10-13' then status else null end c
from p group by user_name, date) s group by user_name;
When user names are represented as columns
Doing it this way should be optimal if you have a fixed number of users and an moving range of dates.
select date, ifnull(max(a),'NA') A,
ifnull(max(b),'NA') B, ifnull(max(c),'NA') C from
(select date,
case when user_name = 'A' then status else null end a,
case when user_name = 'B' then status else null end b,
case when user_name = 'C' then status else null end c
from p group by date, user_name) x group by date;
By the way if you still need to do dynamic columns, you should read this:
How to automate pivot tables in MySQL
MySQL pivot into dynamic number of columns
You can query like this
select user_name, max([2016-10-11]) as [2016-10-11], max([2016-10-12]) [2016-10-12],max([2016-10-13]) [2016-10-13] from #yourtable
pivot
(max(status) for date in ([2016-10-11],[2016-10-12],[2016-10-13])) p
group by user_name
Related
I am using MySQL 5.6 and I have a table structure like below
| user_id | email_1 | email_2 | email_3 |
| 1 | abc#test.com | | |
| 2 | xyz#test.com | | joe#test.com |
| 3 | | test#test.com | bob#joh.com |
| 4 | | | x#y.com |
I want to fetch the first n email addresses from this table.
For example, if I want to fetch the first 5 then only the first 3 rows should return.
This makes certain assumptions about the uniqueness of data, that might not be true...
SELECT DISTINCT x.* FROM my_table x
JOIN
(SELECT user_id, 1 email_id,email_1 email FROM my_table WHERE email_1 IS NOT NULL
UNION ALL
SELECT user_id, 2 email_id,email_2 email FROM my_table WHERE email_2 IS NOT NULL
UNION ALL
SELECT user_id, 3 email_id,email_3 email FROM my_table WHERE email_3 IS NOT NULL
ORDER BY user_id, email_id LIMIT 5
) y
ON y.user_id = x.user_id
AND CASE WHEN y.email_id = 1 THEN y.email = x.email_1
WHEN y.email_id = 2 THEN y.email = x.email_2
WHEN y.email_id = 3 THEN y.email = x.email_3
END;
You want to return as many rows as necessary to get five emails. So you need a running total of the email count.
select user_id, email_1, email_2, email_3
from
(
select
user_id, email_1, email_2, email_3,
coalesce(
sum((email_1 is not null) + (email_2 is not null) + (email_3 is not null))
over (order by user_id rows between unbounded preceding and 1 preceding)
, 0) as cnt_prev
from mytable
) counted
where cnt_prev < 5 -- take the row if the previous row has not reached the count of 5
order by user_id;
You need a current MySQL version for SUM OVER to work.
The counting of the emails uses a MySQL feature: true equals 1 and false equals 0 in MySQL. Thus (email_1 is not null) + (email_2 is not null) + (email_3 is not null) counts the emails in the row.
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ac415e71733699547196ae01cb1caf13
I have a MySql table of users order and it has columns such as:
user_id | timestamp | is_order_Parent | Status |
1 | 10-02-2020 | N | C |
2 | 11-02-2010 | Y | D |
3 | 11-02-2020 | N | C |
1 | 12-02-2010 | N | C |
1 | 15-02-2020 | N | C |
2 | 15-02-2010 | N | C |
I want to count number of new custmer per day defined as: a customer who orders non-parent order and his order status is C AND WHEN COUNTING A USER ONCE IN A DAY WE DONT COUNT HIM FOR OTHER DAYS
An ideal resulted table will be:
Timestamp: Day | Distinct values of User ID
10-02-2020 | 1
11-02-2010 | 1
12-02-2010 | 0 <--- already counted user_id = 1 above, so no need to count it here
15-02-2010 | 1
table name is cscart_orders
If you are running MySQL 8.0, you can do this with window functions an aggregation:
select timestamp, sum(timestamp = timestamp0) new_users
from (
select
t.*,
min(case when is_order_parent = 'N' and status = 'C' then timestamp end) over(partition by user_id) timestamp0
from mytable t
) t
group by timestamp
The window min() computes the timestamp when each user became a "new user". Then, the outer query aggregates by date, and counts how many new users were found on that date.
A nice thing about this approach is that it does not require enumerating the dates separately.
You can use two levels of aggregation:
select first_timestamp, count(*)
from (select t.user_id, min(timestamp) as first_timestamp
from t
where is_order_parent = 'N' and status = 'C'
group by t.user_id
) t
group by first_timestamp;
I have a table in following structure:
id performance_id employee_comments manager_comments
1 23 NULL Well done.
2 46 NULL Improve Speed
3 46 Trying to improve NULL
4 46 NULL Put more effort
In above structure, at a time, only one comments exist, either employee_comments or manager_comments, not both simultaneously.
I want to fetch last non-empty employee comments and manager_comments in a single row.
I need mysql query to display output as:
performance_id employee_comments manager_comments
23 NULL Well done.
46 Trying to improve Put more effort
You can get the largest id for each of the employee comments and manager comments, per performance_id this way:
SELECT performance_id,
MAX(CASE WHEN employee_comments IS NOT NULL THEN id END) AS emp_id,
MAX(CASE WHEN manager_comments IS NOT NULL THEN id END) AS mgr_id
FROM MyTable
GROUP BY performance_id
The above only returns one row per performance_id, with two id numbers. These are the "last" entries, as determined by greater id numbers.
Output:
+----------------+--------+--------+
| performance_id | emp_id | mgr_id |
+----------------+--------+--------+
| 23 | NULL | 1 |
| 46 | 3 | 4 |
+----------------+--------+--------+
With that result, you can join it back to the table to retrieve other columns:
SELECT t.performance_id,
t.employee_comments,
t.manager_comments
FROM (
SELECT performance_id,
MAX(CASE WHEN employee_comments IS NOT NULL THEN id END) AS emp_id,
MAX(CASE WHEN manager_comments IS NOT NULL THEN id END) AS mgr_id
FROM MyTable
GROUP BY performance_id
) AS x
JOIN MyTable AS t ON t.id IN (emp_id, mgr_id)
Output:
+----------------+-------------------+------------------+
| performance_id | employee_comments | manager_comments |
+----------------+-------------------+------------------+
| 23 | NULL | Well done. |
| 46 | Trying to improve | NULL |
| 46 | NULL | Put more effort |
+----------------+-------------------+------------------+
The above return up to two rows per performance_id. You can use another GROUP BY to force them onto one row:
SELECT t.performance_id,
MAX(t.employee_comments) AS employee_comments,
MAX(t.manager_comments) AS manager_comments
FROM (
SELECT performance_id,
MAX(CASE WHEN employee_comments IS NOT NULL THEN id END) AS emp_id,
MAX(CASE WHEN manager_comments IS NOT NULL THEN id END) AS mgr_id
FROM MyTable
GROUP BY performance_id
) AS x
JOIN MyTable AS t ON t.id IN (emp_id, mgr_id)
GROUP BY performance_id
Output:
+----------------+-------------------+------------------+
| performance_id | employee_comments | manager_comments |
+----------------+-------------------+------------------+
| 23 | NULL | Well done. |
| 46 | Trying to improve | Put more effort |
+----------------+-------------------+------------------+
try this query
SELECT t.id,t.performance_id,t2.employee_comments,t3.manager_comments FROM test1 t join test1 t2 on t2.employee_comments IS NOT NULL and t.manager_comments IS NULL
join test1 t3 on t3.manager_comments IS NOT NULL and t.id < t3.id
group by t.performance_id
This can be done by a nested query in MySQL. We will have to do a union and group by performance_id
SELECT performance_id ,MAX(employee_comments)
employee_comments,MAX(manager_comments) manager_comments
FROM(
SELECT performance_id,employee_comments,manager_comments FROM MyTable
WHERE id IN (SELECT * FROM
(SELECT id FROM MyTable WHERE manager_comments IS NOT NULL ORDER BY id DESC)AS t)
UNION
SELECT performance_id,employee_comments,manager_comments FROM MyTable
WHERE id IN (SELECT * FROM
(SELECT id FROM MyTable WHERE employee_comments IS NOT NULL ORDER BY id DESC) AS p)) g GROUP BY performance_id;
The above query should work for fetching the last non-empty employee comments and manager_comments
I have tried and tested it...
I have a fairly big table (10,000+ records) that looks more or less like this:
| id | name | contract_no | status |
|----|-------|-------------|--------|
| 1 | name1 | 1022 | A |
| 2 | name2 | 1856 | B |
| 3 | name3 | 1322 | C |
| 4 | name4 | 1322 | C |
| 5 | name5 | 1322 | D |
contract_no is a foreign key which of course can appear in several records and each record will have a status of either A, B, C, D or E.
What I want is to get a list of all the contract numbers, where ALL the records referencing that contract are in status C, D, E, or a mix of those, but if any of the records are in status A or B, omit that contract number.
Is it possible to do this using a SQL query? Or should I better export the data and try to run this analysis using another language like Python or R?
Post aggregate filtering should do the trick
SELECT contract_no FROM t
GROUP BY contract_no
HAVING SUM(status='A')=0
AND SUM(status='B')=0
You can use group by with having to get such contract numbers.
select contract_number
from yourtable
group by contract_number
having count(distinct case when status in ('C','D','E') then status end) >= 1
and count(case when status = 'A' then 1 end) = 0
and count(case when status = 'B' then 1 end) = 0
Not that elegant as the other two answers, but more expressive:
SELECT DISTINCT contract_no
FROM the_table t1
WHERE NOT EXISTS (
SELECT *
FROM the_table t2
WHERE t2.contract_no = t1.contract_no
AND t2.status IN ('A', 'B')
)
Or
SELECT DISTINCT contract_no
FROM the_table
WHERE contract_no NOT IN (
SELECT contract_no
FROM the_table
AND status IN ('A', 'B')
)
I have a MySQL table containing login logs. Each entry contains user email, IP address, timestamp and the login result (0 fail, 1 success).
+------------+------------------+------------+--------+
| ip | email | datetime | result |
+------------+------------------+------------+--------+
| 2130706433 | user1#domain.com | 1426498362 | 0 |
| 2130706433 | user1#domain.com | 1426498363 | 1 |
| 2130706433 | user1#domain.com | 1426498364 | 0 |
| 1134706444 | user2#domain.com | 1426498365 | 0 |
+------------+------------------+------------+--------+
My goal is to create a unique query to extract the count of failed logins from a given timestamp, and the timestamp of the last login for user1#domain.com. In this case I would like to obtain (suppose for simplicity that all entries are after the required timestamp)
+--------+------------+
| count | datetime |
+--------+------------+
| 3 | 1426498364 |
+--------+------------+
Until now, I've created two separated queries to extract results separately
SELECT COUNT(result) as count FROM (SELECT result FROM accesslogs WHERE datetime>1426498360 AND result=0) as subt
SELECT MAX(datetime) as datetime FROM accesslogs WHERE email=`user1#domain.com`
Now I'm tring to combine them to get results with a single query. I was wondering about using JOIN statement, but I don't know a column where queries may join. What can I do?
You can use conditional aggregation for this, something as
select
sum(
case
when result=0 and datetime>1426498360
then 1 else 0 end
) as `count`,
max(
case
when email = 'user1#domain.com' then datetime end
) as datetime
from accesslogs ;
Here is an example
mysql> select * from test ;
+------------+------------------+------------+--------+
| ip | email | datetime | result |
+------------+------------------+------------+--------+
| 2130706433 | user1#domain.com | 1426498362 | 0 |
| 2130706433 | user1#domain.com | 1426498363 | 1 |
| 2130706433 | user1#domain.com | 1426498364 | 0 |
| 1134706444 | user2#domain.com | 1426498365 | 0 |
+------------+------------------+------------+--------+
4 rows in set (0.00 sec)
mysql> select
-> sum(
-> case
-> when result=0 and datetime>1426498360
-> then 1 else 0 end
-> ) as `count`,
-> max(
-> case
-> when email = 'user1#domain.com' then datetime end
-> ) as datetime
-> from test ;
+-------+------------+
| count | datetime |
+-------+------------+
| 3 | 1426498364 |
+-------+------------+
1 row in set (0.00 sec)
Try
select * from (selete statement a) a join (selete statement b) b
You could use UNION ALL but both queries must have same columns.
I would suggest add text column to each query descibing type of value in other column like:
SELECT COUNT(result) as count, 'count' AS TYPE FROM (SELECT result FROM accesslogs WHERE datetime>1426498360 AND result=0) as subt
UNION ALL
SELECT MAX(datetime) as datetime, 'max' AS TYPE FROM accesslogs WHERE email='user1#domain.com'
EDIT: Sorry but I realised that this is more what you are trying to do (this is t-sql code but should work)
SELECT a.email, a.count, b.datetime FROM (
SELECT COUNT(result) as count, email FROM (SELECT result, email FROM
accesslogs AND result=0) as subt
GROUP BY email) a
JOIN (
SELECT MAX(datetime) as datetime , email FROM accesslogs GROUP BY email) b
on a.email = b.email
WHERE a.email='user1#domain.com'
AND a.datetime >'2012-12-17'
Take a look at MySQLs GROUP BY and HAVING. See if this works for you:
Any email:
SELECT email, COUNT(resultSet) as loginAttempts,
COUNT(resultSet) - SUM(resultSet) as failedAttempts
FROM accesslogs
GROUP BY email;
Specific email:
SELECT COUNT(result) as count, MAX(logDate) FROM accesslogs
GROUP BY email
HAVING email='user1#example.com';