How to Query this in mysql - mysql

I have question about querying. I need to sum total of admin per account that active / logged less than 15 days from now. The problem is one account can have many admin and on admin table have "last login" field with date.
So we want to get is all admin that not logged for less 15 days from now.. so if one of the admin logged in 15 days from now will not count...
Example:
Account Table
id | Account name
------------------
1 | Tiger company
-------------------
2 | Bear Company
Admin Table
id | Account ID | admin name | last login
-------------------------------------------
1 | 1 | Billy Tiger | 09-01-2018
2 | 1 | Shirley | 09-22-2018
3 | 2 | John Bear | 09-06-2018
4 | 2 | Kyle Bear | 09-08-2018
So based on above data if today 09-26-2018 then I need to get the total number per account and sum it that the admin per site is not or never login below 15 days from 09-26-2018 which is below 09-11-2018, so if one of the admin is logged will be not counted.
So from the example above.. what I want to get the total sum is "1" so basically per account.. hy "1" because the tiger company admin...t he "shirley" has been logged on 09-22-2018 so it's active... so not counted it and the Bear company none of the admin logged after 09-11-2018 so it count as 1...
I hope it example explains it well.. sorry for being all confusing.. is it possible to do that in one query? and like select sum(id) as 'total' ....

use sub-query , you want to filter those account who's any id login last 15 days so 1st i find those id who login with in last 15 days and filtered then count according to accountid
CREATE TABLE `Account` (
`id` int(11) PRIMARY KEY NOT NULL,
`Accountname` varchar(25) NOT NULL
);
CREATE TABLE `Admin` (
`id` int(11) PRIMARY KEY NOT NULL,
`Account_ID` int(11) NOT NULL,
`Admin_Name` varchar(25) NOT NULL,
`Last_Login` date NOT NULL
);
INSERT INTO Account
VALUES (1, 'Tiger Company'), (2, 'Bear Company');
INSERT INTO `Admin`
VALUES (1, 1, 'Billy Tiger', '2018-09-01'),
(2, 1, 'Shirley', '2018-09-22'),
(3, 2, 'John Bear', '2018-09-06'),
(4, 2, 'Kyle Bear','2018-09-08' );
select t1.Account_ID
,a1.Accountname,
count(distinct t1.Account_ID) as total from
(
select a.* from Admin a
left join
(
select distinct Account_ID from Admin
where Last_Login>=DATE_SUB(CURDATE(), INTERVAL 15 DAY)
) t on a.Account_ID=t.Account_ID
where t.Account_ID is null
) t1 join Account a1 on t1.Account_ID =a1.id group by t1.Account_ID,a1.AccountName
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=baa13620bccbf6a70f6e0fc7d6e8d199
Account_ID Accountname total
2 Bear Company 1

Easiest and probably the most efficient way at scale would be to use a JOIN between the two tables:
SELECT `account`.`id`, `account`.`Account name`, COUNT(1) as 'Active Admins'
FROM `admin`
JOIN `account` ON `account`.`id` = `admin`.`Account_ID`
WHERE `admin`.`Last_Login` >= DATE_ADD(NOW(), INTERVAL -15 DAY)
GROUP BY `account`.`id`
Result:
| id | Account name | Active Admins |
|----|---------------|---------------|
| 1 | Tiger Company | 1 |
SQL Fiddle

Related

SQL Query to find the number of entries for all tournaments in 2012?

My schema is:
CREATE TABLE entry (
MemberID int NOT NULL,
TourID int NOT NULL,
Year int NOT NULL,
PRIMARY KEY (MemberID,TourID,Year)
);
INSERT INTO entry VALUES (118,24,2011),(228,24,2012),(258,24,2011),(286,24,2010),
(286,24,2011),(286,24,2012),(415,24,2012),(228,25,2012),(239,25,2012),(415,25,2010),
(228,36,2012),(415,36,2011),(415,36,2012),(235,38,2010),(235,38,2012),(258,38,2011),
(415,38,2010),(415,38,2012),(235,40,2011),(235,40,2012),(239,40,2010),(415,40,2010),
(415,40,2011),(415,40,2012);
CREATE TABLE tournament (
TourID int NOT NULL,
TourName varchar(20) DEFAULT NULL,
TourType varchar(20) DEFAULT NULL,
Active char(1) DEFAULT NULL,
PRIMARY KEY (TourID)
);
INSERT INTO tournament VALUES (24,'London','Social','Y'),(25,'Leeds','Social','Y'),
(36,'Bath','Open','Y'),(38,'Liverpool','Open','N'),(40,'Birminigham','Open','Y');
I want to get tournament id and number of entries for all tournaments in 2012?
So far I have tried this below code:
SELECT tourid, COUNT(year) FROM entry WHERE year = 2012 GROUP BY tourid;
| tourid | count |
______________________
| 24 | 3 |
| 25 | 2 |
| 36 | 2 |
| 38 | 2 |
| 40 | 2 |
______________________
and getting the above value... I am not sure whether it is right or not. Would someone please help me.
Thank you
For your condition with showing tourname, try this:
SELECT entry.tourid, tournament.tourname, COUNT(year)
FROM entry
JOIN tournament ON entry.tourid=tournament.tourid
WHERE year = 2012
GROUP BY entry.tourid, tournament.tourname;
Use JOIN on identical column. Here its TourID.
You can make it a bit better (shorter/easier to write) by assigning aliases to the tables.
SELECT A.tourid, B.tourname,
COUNT(*) -- columns in SELECT also can assign alias like "COUNT(*) AS Cn" etc.
FROM entry AS A -- you can either assign table alias "entry AS A" or simply "entry A"
JOIN tournament AS B
ON A.tourid=B.tourid
WHERE A.year = 2012
GROUP BY A.tourid, B.tourname;
Here is a demo

How to get one extra record for LEFT JOIN to represent a record not include on the left joined table

I have a database with two tables one table (shops) has an admin user column and the other a user with less privileges. I plan to LEFT JOIN the table of the user with less privileges. When I retrieve the data, the records for the admin user must be on a separate row and must have NULL values for the left joined table followed by records of users with less privileges (records of the left joined table) if any. I am using MySQL.
I have looked into the UNION commands but I don't think it can help. Please see the results bellow of what I need.
Thank you.
SELECT *
FROM shops LEFT JOIN users USING(shop_id)
WHERE shop_id = 1 AND (admin_id = 1 OR user_id = 1);
+---------+----------+---------+
| shop_id | admin_id | user_id |
+---------+----------+---------+
| 1 | 1 | NULL | <-- Need this one extra record
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 1 | 1 | 3 |
+---------+----------+---------+
Here is an example structure of the databases and some sample data:
CREATE SCHEMA test DEFAULT CHARACTER SET utf8 ;
USE test;
CREATE TABLE admin(
admin_id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY(admin_id)
);
CREATE TABLE shops(
shop_id INT NOT NULL AUTO_INCREMENT,
admin_id INT NOT NULL,
PRIMARY KEY(shop_id),
CONSTRAINT fk_shop_admin FOREIGN KEY(admin_id) REFERENCES admin (admin_id)
);
CREATE TABLE users(
user_id INT NOT NULL AUTO_INCREMENT,
shop_id INT NOT NULL,
CONSTRAINT fk_user_shop FOREIGN KEY(shop_id) REFERENCES admin (shop_id)
);
-- Sample data
INSERT INTO admin() VALUES ();
INSERT INTO shops(admin_id) VALUES (1);
INSERT INTO users(shop_id) VALUES (1),(1),(1);
I think you need union all:
select s.shop_id, s.admin_id, null as user_id
from shops s
where s.shop_id = 1
union all
select s.shop_id, s.admin_id, u.user_id
from shops s join
users u
on s.shop_id = u.shop_id
where shop_id = 1;
Put your where condition in On clause
SELECT *
FROM shops LEFT JOIN users on shops.shop_id=users.shop_id and (admin_id = 1 OR user_id = 1)
WHERE shops.shop_id = 1

JOIN query in MySQL produces wrong result

I have two tables complaints and complaints_reply in my MySQl database. Users can add complaints which are stored in complaints the complaints reply are stored in complaints_reply table. I am trying to JOIN both these table contents on a specific condition. Before I mention what I am trying to get and the problem I faced, I will explain the structure of these two tables first.
NB: The person who adds complaints is complaint owner & person who adds a complaint reply is complaint replier. Complaint owner can also add replies. So he can either be the complaint owner or the complaint replier. The two tables have a one-to-many relationship. A complaint can have more than one complaint reply. member_id in complaint table represents complaint owner & mem_id in complaints_reply represent complaint replier
DESIRED OUTPUT:
Join the two tables and fetch values and show the complaint and complaint’s reply as a single result set. But the condition is kinda tricky. The last added complaint reply from the complaints_reply table should be fetched for the complaint in complaints table in such a way that the complaint owner should not be the complaint replier. I use posted_date & posted_time from complaints_reply table to fetch the last added complaint reply for a complaint & that complaint replier has to be shown in the result set.
So, from the sample data the tables contain now, the output that I should get is:
+------+---------+----------+-------------+-------------------+
| id | title |member_id |last_replier |last_posted_dt |
+------+---------+----------+-------------+-------------------+
| 1 | x | 1000 |2002 | 2015-05-2610:11:17|
| 2 | y | 1001 |1000 | 2015-05-2710:06:16|
+------+---------+----------+-------------+-------------------+
But what I got is:
+------+---------+----------+-------------+-------------------+
| id | title |member_id |last_replier |last_posted_dt |
+------+---------+----------+-------------+-------------------+
| 1 | x | 1000 |1001 | 2015-05-2610:11:17|
| 2 | y | 1001 |2000 | 2015-05-2710:06:16|
+------+---------+----------+-------------+-------------------+
The date is correct, but the returned complaint replier last_replier is wrong.
This is my query.
SELECT com.id,
com.title,
com.member_id,
last_comp_reply.last_replier,
last_comp_reply.last_posted_dt
FROM complaints com
LEFT JOIN
(SELECT c.id AS complaint_id,
c.member_id AS parent_mem_id,
cr.mem_id AS last_replier,
max(cr.posted_dt) AS last_posted_dt
FROM
(SELECT cr.complaint_id,cr.mem_id,c.id,c.member_id,(CONCAT(cr.posted_date,cr.posted_time)) AS posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id,
cr.mem_id,
posted_dt)cr,
complaints c
WHERE cr.complaint_id=c.id
GROUP BY cr.complaint_id,
c.id,
c.member_id) AS last_comp_reply ON com.id=last_comp_reply.complaint_id
Table structure for table complaints
CREATE TABLE IF NOT EXISTS `complaints` (
`id` int(11) NOT NULL,
`title` varchar(500) NOT NULL,
`member_id` int(11) NOT NULL,
`posted_date` date NOT NULL,
`posted_time` time NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
Indexes for table complaints
ALTER TABLE `complaints`
ADD PRIMARY KEY (`id`);
AUTO_INCREMENT for table complaints
ALTER TABLE `complaints`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3;
Dumping data for table complaints
INSERT INTO `complaints` (`id`, `title`, `member_id`, `posted_date`, `posted_time`) VALUES
(1, 'x', 1000, '2015-05-05', '02:06:15'),
(2, 'y', 1001, '2015-05-14', '02:08:10');
Table structure for table complaints_reply
CREATE TABLE IF NOT EXISTS `complaints_reply` (
`id` int(11) NOT NULL,
`complaint_id` int(11) NOT NULL,
`comments` text NOT NULL,
`mem_id` int(11) NOT NULL,
`posted_date` date NOT NULL,
`posted_time` time NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=10 ;
Indexes for table complaints_reply
ALTER TABLE `complaints_reply`
ADD PRIMARY KEY (`id`);
AUTO_INCREMENT for table complaints_reply
ALTER TABLE `complaints_reply`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=10;
Dumping data for table complaints_reply
INSERT INTO `complaints_reply` (`id`, `complaint_id`, `comments`, `mem_id`, `posted_date`, `posted_time`) VALUES
(1, 1, 'reply1', 2000, '2015-05-08', '02:07:08'),
(2, 1, 'reply2', 2001, '2015-05-06', '06:05:08'),
(3, 1, 'reply3', 1000, '2015-05-14', '02:12:13'),
(4, 2, 'hola', 1000, '2015-05-27', '10:06:16'),
(5, 2, 'hello', 2000, '2015-05-04', '03:09:09'),
(6, 2, 'gracias', 1001, '2015-05-31', '06:12:18'),
(7, 1, 'reply4', 1001, '2015-01-04', '04:08:12'),
(8, 2, 'puta', 1001, '2015-06-13', '06:12:18'),
(9, 1, 'reply5', 1000, '2015-06-01', '04:08:12'),
(10, 1, 'reply next', 2002, '2015-05-26', '10:11:17');
P.S.
To give an idea about what my query is all about, I'll explain the sub query that is used to combine the tables & give result based on the condition: complaint owner should not be the complaint replier is:
SELECT cr.complaint_id,
cr.mem_id,
c.id,
c.member_id,
(CONCAT(cr.posted_date,cr.posted_time)) AS posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id,
cr.mem_id,
posted_dt
And the result for this is:
+--------------+---------+----------+-------------+-------------------+
| complaint_id | mem_id | id |member_id | posted_dt |
+--------------+---------+------- +-------------+-------------------+
| 1 | 1001 | 1 |1000 | 2015-01-0404:08:12|
| 1 | 2000 | 1 |1000 | 2015-05-0802:07:08|
| 1 | 2001 | 1 |1000 | 2015-05-0606:05:08|
| 1 | 2002 | 1 |1000 | 2015-05-2610:11:17|
| 2 | 1000 | 2 |1001 | 2015-05-2710:06:16|
| 2 | 2000 | 2 |1001 | 2015-05-0403:09:09|
+--------------+---------+----------+-------------+-------------------+
member_id here represents complaint owner and mem_id represents complaint replier
The inner query gives the result based on the condition, then everything after this goes haywire. I don't know where I made mistake. The complaint replies added by complaint owner is not fetched in this table. So far so good. Is there any alternative way to get the result from here?
This query gives the result.
SELECT com.id AS complaint_id,
com.member_id AS parent_mem_id,
crep.mem_id AS last_replier,
crl.last_posted_dt
FROM complaints com
LEFT JOIN complaints_reply crep ON com.id=crep.complaint_id
JOIN
(SELECT cr.complaint_id,
max(CONCAT(cr.posted_date,'_',cr.posted_time)) AS last_posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id)crl ON CONCAT(crep.posted_date,'_',crep.posted_time)=crl.last_posted_dt
AND crep.complaint_id=crl.complaint_id

How do I get a left join with a group by clause to return all the rows?

I am trying to write a query to determine how much of my inventory is committed at a given time, i.e. current, next month, etc.
A simplified example:
I have an inventory table of items. I have an offer table that specifies the customer, when the offer starts, and when the offer expires. I have a third table that associates the two.
create table inventory
(id int not null auto_increment , name varchar(32) not null, primary key(id));
create table offer
(id int not null auto_increment , customer_name varchar(32) not null, starts_at datetime not null, expires_at datetime, primary key (id));
create table items
(id int not null auto_increment, inventory_id int not null, offer_id int not null, primary key (id),
CONSTRAINT fk_item__offer FOREIGN KEY (offer_id) REFERENCES offer(id),
CONSTRAINT fk_item__inventory FOREIGN KEY (inventory_id) REFERENCES inventory(id));
create some inventory
insert into inventory(name)
values ('item 1'), ('item 2'),('item 3');
create two offers for this month
insert into offer(customer_name, starts_at)
values ('customer 1', DATE_FORMAT(NOW(), '%Y-%m-01')), ('customer 2', DATE_FORMAT(NOW(), '%Y-%m-01'));
and one for next month
insert into offer(customer_name, starts_at)
values ('customer 3', DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 1 MONTH), '%Y-%m-01'));
Now add some items to each offer
insert into items(inventory_id, offer_id)
values (1,1), (2,1), (2,2), (3,3);
What I want is a query that will show me all the inventory and the count of the committed inventory for this month. Inventory would be considered committed if the starts_at is less than or equal to now, and the offer has not expired (expires_at is null or expires_at is in the future)
The results I would expect would look like this:
+----+--------+---------------------+
| id | name | committed_inventory |
+----+--------+---------------------+
| 1 | item 1 | 1 |
| 2 | item 2 | 2 |
| 3 | item 3 | 0 |
+----+--------+---------------------+
3 rows in set (0.00 sec)
The query that I felt should work is:
SELECT inventory.id
, inventory.name
, count(items.id) as committed_inventory
FROM inventory
LEFT JOIN items
ON items.inventory_id = inventory.id
LEFT JOIN offer
ON offer.id = items.offer_id
WHERE (offer.starts_at IS NULL OR offer.starts_at <= NOW())
AND (offer.expires_at IS NULL OR offer.expires_at > NOW())
GROUP BY inventory.id, inventory.name;
However, the results from this query does not include the third item. What I get is this:
+----+--------+---------------------+
| id | name | committed_inventory |
+----+--------+---------------------+
| 1 | item 1 | 1 |
| 2 | item 2 | 2 |
+----+--------+---------------------+
2 rows in set (0.00 sec)
I cannot figure out how to get the third inventory item to show. Since inventory is the driving table in the outer joins, I thought that it should always show.
The problem is the where clause. Try this:
SELECT inventory.id
, inventory.name
, count(offers.id) as committed_inventory
FROM inventory
LEFT JOIN items
ON items.inventory_id = inventory.id
LEFT JOIN offer
ON offer.id = items.offer_id and
(offer.starts_at <= NOW() or
offer.expires_at > NOW()
)
GROUP BY inventory.id, inventory.name;
The problem is that you get a matching offer, but it isn't currently valid. So, the where clause fails because the offer dates are not NULL (there is a match) and the date comparison fails because the offer is not current ly.
For item 3 the starts_at from offer table is set to March, 01 2014 which is greater than NOW so (offer.starts_at IS NULL OR offer.starts_at <= NOW()) condition will skip the item 3 record
See fiddle demo

Create View with rank column in limited amount of data

So I have an MySQL table structured like this:
CREATE TABLE `spenttime` {
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) NOT NULL,
`serverid` int(11) NOT NULL,
`time` int(11) NOT NULL,
`day` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `dbid_sid_day` (`userid`,`serverid`,`day`)
}
Where I'm storing time spent on my game servers every day for each registered player. time is the amount of time spent, in seconds, day is an unix timestamp of each day (beginning of the day). I want to create an View on my database that will show for each user time spent on server every week, but with an column displaying rank of that time, independent for each server on each week. For example data (for clarify i will use date format Y-M-D instead of unix timestamp for day column on this example):
INSERT INTO `spenttime` (`userid`, `serverid`, `time`, `day`) VALUES
(1, 1, 200, '2013-04-01'),
(1, 1, 150, '2013-04-02'),
(2, 1, 100, '2013-04-02'),
(3, 1, 500, '2013-04-04'),
(2, 2, 400, '2013-04-04'),
(1, 1, 300, '2013-04-08'),
(3, 1, 200, '2013-04-08');
For that data in viev named spenttime_week should appear:
+--------+----------+--------+------------+------+
| userid | serverid | time | yearweek | rank |
+--------+----------+--------+------------+------+
| 1 | 1 | 350 | '2013-W14' | 2 |
| 2 | 1 | 100 | '2013-W14' | 3 |
| 3 | 1 | 500 | '2013-W14' | 1 |
| 2 | 2 | 400 | '2013-W14' | 1 |
| 1 | 1 | 300 | '2013-W15' | 1 |
| 3 | 1 | 200 | '2013-W15' | 2 |
+--------+----------+--------+------------+------+
I know how to generate view wihout rank, i have only troubles with rank column...
How can I make that happen?
//edit
Additionaly, this column MUST appear in viev, I cannot generate It in select from that view, because app where I will use it don't allow that...
First you need to create a first VIEW that sums the spent time for every user on the same week:
CREATE VIEW total_spent_time AS
SELECT userid,
serverid,
sum(time) AS total_time,
yearweek(day, 3) as week
FROM spenttime
GROUP BY userid, serverid, week;
then you can create your view as this:
CREATE VIEW spenttime_week AS
SELECT
s1.userid,
s1.serverid,
s1.total_time,
s1.week,
count(s2.total_time)+1 AS rank
FROM
total_spent_time s1 LEFT JOIN total_spent_time s2
ON s1.serverid=s2.serverid
AND s1.userid!=s2.userid
AND s1.week = s2.week
AND s1.total_time<=s2.total_time
GROUP BY
s1.userid,
s1.serverid,
s1.total_time,
s1.week
ORDER BY
s1.week, s1.serverid, s1.userid
Please see a fiddle here.
Lots of ways you could get the yearweek column, a quick lazy solution to that for clarity (because I doubt you're struggling with that). But here's how you can get the rank.
Use a self join to get dataset including rows with higher time value than current row, then count the rows with higher value:
This is much easier in MSSQL, which is where I live 99% of the time, and where you can just use the RANK() function. I hadn't realised until today there wasn't an equivalent in mysql. Fun to work out how to get the same result without MS's helping hand.
Prep stuff for context:
CREATE TABLE spenttime (userid int, serverid int, [time] int, [day] DATETIME)
CREATE TABLE weeklookup (weekname VARCHAR(10), weekstart DATETIME, weekend DATETIME)
INSERT INTO spenttime (userid, serverid, [time], [day]) VALUES
(1, 1, 200, '2013-apr-01'),
(1, 1, 150, '2013-apr-02'),
(2, 1, 100, '2013-apr-02'),
(3, 1, 500, '2013-apr-04'),
(2, 2, 400, '2013-apr-04'),
(1, 1, 300, '2013-apr-08'),
(3, 1, 200, '2013-apr-08');
INSERT INTO weeklookup(weekname, weekstart, weekend) VALUES
('2013-w14', '01/apr/2013', '08/apr/2013'),
('2013-w15', '08/apr/2013', '15/apr/2013')
GO
CREATE VIEW weekgroup AS
SELECT a.userid ,
a.serverid ,
a.[time] ,
w1.weekname
FROM spenttime a
INNER JOIN weeklookup w1 ON [day] >= w1.weekstart
AND [day] < w1.weekend
GO
Select statement for the view:
SELECT wv1.userid ,
wv1.serverid ,
wv1.[time] ,
wv1.weekname AS yearweek ,
COUNT(wv2.[time]) + 1 AS rank
FROM weekgroup wv1
LEFT JOIN weekgroup wv2 ON wv1.[time] < wv2.[time]
AND wv1.weekname = wv2.weekname
AND wv1.serverid = wv2.serverid
GROUP BY wv1.userid ,
wv1.serverid ,
wv1.[time] ,
wv1.weekname
ORDER BY wv1.weekname ,
wv1.[time] DESC
If you want to store the rank, you would use an insert trigger. The insert trigger would calculate the rank, as something like:
select count(*)
from spenttime_week w
where w.yearweek = new.yearweek and time >= new.time
However, I would not recommend this, because you then have to create an update trigger as well, and modify rank values that are already inserted.
Instead, access the table using SQL like:
select w.*,
(select count(*) from spenttime_week w2 where w2.yearweek = w.yearweek and w2.time >= w.time
) as rank
from spenttime_week w
This SQL may vary, depending on how you want to handle ties in the data. For performance reasons, you should have an index on at least yearweek, and probably on yearweek, time.