how to give a space by joining 2 columns having repeated data - mysql

I have a table structure like this
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`userid` int(11) DEFAULT NULL,
`loan` int(11) DEFAULT NULL,
`name` varchar(90) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+----+--------+------+--------+
| id | userid | loan | name |
+----+--------+------+--------+
| 1 | 1 | 100 | x |
| 2 | 1 | 200 | X|
| 3 | 2 | 2000 | y|
| 4 | 3 | 1000 | z|
| 5 | 1 | 500 | a|
| 6 | 2 | 700 | b|
As you can see userid is repeating.For example userid 1 is having loan 100,200 and 500
My requirement is to get userid,loan and name and if the userid is repating then loan will be the sum of the repeating userid and name should be space
for example,In the above table userid 1 is repeating so sum is (100+200+500=800)
similarly for userid 2 the sum is 2700
The output i want should be like the below
+--------+-----------+----+
| userid | SUM(loan) |name|
+--------+-----------+----+
| 1 | 800 | |
| 2 | 2700 | |
| 3 | 1000 |z |
+--------+-----------+
I can do with userid and loan but I dont know how to put a space in name field if the userid is repeating
I tried like this
SELECT userid,SUM(loan) FROM
testforsum
GROUP BY userid
and the ouput I am getting is like this
+--------+-----------+
| userid | SUM(loan) |
+--------+-----------+
| 1 | 800 |
| 2 | 2700 |
| 3 | 1000 |
+--------+-----------+
I tried to create a sqlfiddle for it but I dont know why insertion is not happening.You can see the table here

SELECT userid, IF(COUNT(*) > 1, ' ', name) AS name, SUM(loan)
FROM testforsum GROUP BY userid;

select q.userid, q.s,
case when q.n > 1 then ' ' else q.name end
from
(SELECT userid, SUM(loan) s, count(loan) n, max(name) name
FROM testforsum GROUP BY userid) q;
Some explanation:
(...) q is a subquery which calculates for each userId sum of loans (NULLS are ignored), number of loans (NULLS are ignored).
max(name) it's a "fake" aggregate function, I used it to get any name for each userId (they are all the same for each userId so I can do that) because in ANSI SQL this query is wrong:
SELECT userid, IF(COUNT(*) > 1, ' ', name) AS name, SUM(loan)
FROM testforsum GROUP BY userid;
as all the expressions in the SELECT list should be either aggregate functions (for several rows return a single value) or expressions from the GROUP BY clause. As I know MySQL lets you break these rules, but I prefer to follow ANSI whenever it's possible.
So the subquery q results in a table with unique userIds with their names + sum and number of their loans.
Finally, the parent query filters the result of the q subquery using CASE.

Related

Selecting the most popular destination of my database on mysql

I've created 3 tables:
CREATE TABLE participe
(
IDadherent INTEGER,
IDsortie INTEGER,
CONSTRAINT pk2 PRIMARY KEY (IDadherent, IDsortie)
);
CREATE TABLE sortie
(
IDsortie INTEGER PRIMARY KEY,
jour DATE,
Latitude_sortie FLOAT,
Longitude_sortie FLOAT
);
CREATE TABLE adherent
(
IDadherent INTEGER PRIMARY KEY,
nom VARCHAR(30),
prenom VARCHAR(30)
);
TL;DR:
Table 1) Adherent (this is the people potentially going to a set of destinations with adherentID as a primary key)
Table 2) Sortie (this is the table with the potential destinations with destinationID as a primary key)
Table 3) Participe (this table links both primary keys: AdherentID and destinationID
If I select the content of the table "participe" I get something like this:
+----------+------------+
| IDsortie | IDadherent |
+----------+------------+
| 5 | 1 |
| 5 | 3 |
| 5 | 5 |
| 4 | 2 |
| 3 | 1 |
| 3 | 4 |
| 3 | 5 |
| 2 | 3 |
| 2 | 5 |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 0 | 6 |
+----------+------------+
I've attempted to order the above table and get a new table with the most popular destinations (IDsortie), expecting to get something like this:
+----------+----------------+
| IDsortie | Numeroadherent |
+----------+----------------+
| 5 | 3 |
| 4 | 1 |
| 3 | 3 |
| 2 | 2 |
| 1 | 3 |
| 0 | 1 |
+----------+----------------+
In order to achieve that glorious table I've used these queries (and failed miserably):
SELECT IDsortie, IDadherent
FROM participe
ORDER BY IDsortie DESC;
SELECT COUNT(IDsortie)
FROM participe
GROUP BY IDadherent
ORDER BY COUNT(IDadherent) ASC;
SELECT COUNT(IDsortie)
FROM
(SELECT COUNT(*) AS IDsortie
FROM participe
GROUP BY IDadherent) AS Results
One of the mysql mods has very kindly redirected me to other similar questions but I don't understand their answers, could someone please walk e through this (sorry for the inconvenience).
It looks like you want a single query:
SELECT IDsortie, COUNT(*) as Numeroadherent
FROM participe
GROUP BY IDsortie DESC
ORDER BY IDsortie DESC;
I'm not quite sure what your queries have to do with answering the question. Hence, it is unclear what your confusion is. The answer to your question is a simple GROUP BY query.
Try using the below query,
SELECT IDSortie
, COUNT(IDSortie) [Numeroadherent]
FROM Participe
GROUP
BY IDSortie
ORDER
BY IDSortie;

Update first occurrence of value in a time interval

I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.

Update table based on result of select on another table

I have two tables like this:
person:
id | name | sale | commission
1 | abc | 0 | 0
2 | xyz | 0 | 0
sale:
id | date | person_id | sale | commission
1 | 2016-05-01 | 1 | 10 | 1
2 | 2016-05-02 | 1 | 10 | 1
3 | 2016-05-03 | 1 | 10 | 1
4 | 2016-05-01 | 2 | 20 | 2
5 | 2016-05-02 | 2 | 20 | 2
6 | 2016-05-01 | 2 | 20 | 2
I want to update person table with single update query and change the table something like this:
person:
id | name | sale | commission
1 | abc | 30 | 3
2 | xyz | 60 | 6
I know I can sum sale like following but how to update following query result into person table directly.
SELECT person_id, SUM(sale), SUM(commission)
FROM sale
GROUP BY person_id;
As Strawberry said in the comments under your question, think long and hard before you save this information. It is denormalized, and it becomes stale. Rather, consider using it during report generation. Otherwise, well, as said, you may run into problems.
drop table if exists person;
create table person
( personId int auto_increment primary key,
name varchar(100) not null,
totSales decimal(9,2) not null,
totComm decimal(9,2)
);
insert person(name,totSales,totComm) values
('Joe',0,0),
('Sally',0,0);
-- just added persons 1 and 2 (auto_inc)
drop table if exists sale;
create table sale
( saleId int auto_increment primary key,
saleDate date not null,
personId int not null,
sale decimal(9,2) not null,
commission decimal(9,2) not null,
index(personId), -- facilitate a snappier "group by" later
foreign key (personId) references person(personId) -- Ref Integrity
);
insert sale(saleDate,personId,sale,commission) values
('2016-05-01',2,10,1),
('2016-05-01',1,40,4),
('2016-05-02',1,30,3),
('2016-05-07',2,10,1),
('2016-05-07',2,90,9);
-- the following dies on referential integrity, FK, error 1452 as expected
insert sale(saleDate,personId,sale,commission) values ('2016-05-01',4,10,1);
The update statement
update person p
join
( select personId,sum(sale) totSales, sum(commission) totComm
from sale
group by personId
) xDerived
on xDerived.personId=p.personId
set p.totSales=xDerived.totSales,p.totComm=xDerived.totComm;
The results
select * from person;
+----------+-------+----------+---------+
| personId | name | totSales | totComm |
+----------+-------+----------+---------+
| 1 | Joe | 70.00 | 7.00 |
| 2 | Sally | 110.00 | 11.00 |
+----------+-------+----------+---------+
2 rows in set (0.00 sec)
xDerived is merely an alias name. All derived tables need an alias name, whether or not you use the alias name explicitly.
UPDATE person
SET sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
);
works for me. See it in action at: http://ideone.com/F32oUU
EDIT for new version with additional aggregated column:
UPDATE person SET
sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
),
commission = (
SELECT SUM(s.commission) FROM sale s
WHERE s.person_id = person.id
);
http://ideone.com/yo1A9Y
This being said, I feel sure that a JOIN solution is better, and am hopeful another answerer will be able to post such a solution.

How to get GROUP_CONCAT only for rows with maximum value

Suppose, we have a table:
SELECT * FROM users_to_courses;
+---------+-----------+------------+---------+
| user_id | course_id | pass_date | file_id |
+---------+-----------+------------+---------+
| 1 | 1 | 2014-01-01 | 1 |
| 1 | 1 | 2014-01-01 | 2 |
| 1 | 1 | 2014-02-01 | 3 |
| 1 | 1 | 2014-02-01 | 4 |
+---------+-----------+------------+---------+
Schema:
CREATE TABLE `users_to_courses` (
`user_id` int(10) unsigned NOT NULL,
`course_id` int(10) unsigned NOT NULL,
`pass_date` date NOT NULL,
`file_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`, `course_id`, `pass_date`, `file_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
One user can pass a certain course multiple times, and every time he passes multiple certificates can be generated. user_id and course_id store the links to users and courses tables. file_id - to files table, where info about certificate files is stored.
In our example user #1 has passed course #1 twice and every time 2 certificates were issued: 4 records totally.
How can I get this data: for user_id=1 for every course get MAX(pass_date) and all the files, attached to this date. So far I could only get this:
SELECT
users_to_courses.course_id,
MAX(users_to_courses.pass_date) AS max_passed_date,
GROUP_CONCAT(users_to_courses.file_id SEPARATOR ',') AS files
FROM
users_to_courses
WHERE
users_to_courses.user_id=1
GROUP BY
users_to_courses.course_id;
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 1,2,3,4 |
+-----------+-----------------+---------+
I need this:
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 3,4 |
+-----------+-----------------+---------+
I think, this requires a compound GROUP BY.
fiddle
Try the below query it first gets max date for all the records and then we can join only those record in the outer query. You can use the same query for more than one user by adding group by utc.user_id
SELECT
utc.course_id,
mdt.maxDate AS max_passed_date,
GROUP_CONCAT(utc.file_id SEPARATOR ',') AS files
FROM
users_to_courses utc
join
(SELECT MAX(pass_date) AS maxDate, course_id cId, user_id uId
FROM users_to_courses GROUP BY user_id, course_id) AS mdt
ON
mdt.uId = utc.user_id
AND
mdt.cId = utc.course_id
AND
mdt.maxDate = utc.pass_date
WHERE
utc.user_id=1
GROUP BY
utc.course_id;

query to fetch records and their rank in the DB

I have a table that holds usernames and results.
When a user insert his results to the DB, I want to execute a query that will return
the top X results ( with their rank in the db) and will also get that user result
and his rank in the DB.
the result should be like this:
1 playername 4500
2 otherplayer 4100
3 anotherone 3900
...
134 current player 140
I have tried a query with union, but then I didnt get the current player rank.
ideas anyone?
The DB is MYSQL.
10x alot and have agreat weekend :)
EDIT
This is what I have tried:
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
WHERE result_date >= NOW() - INTERVAL 1 DAY
LIMIT 10)
union
(select substr(first_name,1,10) as first_name, result
FROM top_scores ts
where first_name='XXX' and result=3030);
SET X = 0;
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
LIMIT 10;
Re your comment:
How about this:
SET X = 0;
SELECT ranked.*
FROM (
SELECT #X:=#X+1 AS rank, username, result
FROM myTable
ORDER BY result DESC
) AS ranked
WHERE ranked.rank <= 10 OR username = 'current';
Based on what I am reading here:
Your table structure is:
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| name | varchar(50) | YES | | NULL | |
| result | int(11) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
Table Data looks like:
+---------+--------+
| name | result |
+---------+--------+
| Player1 | 4500 |
| Player2 | 4100 |
| Player3 | 3900 |
| Player4 | 3800 |
| Player5 | 3700 |
| Player6 | 3600 |
| Player7 | 3500 |
| Player8 | 3400 |
+---------+--------+
You want a result set to look like this:
+------+---------+--------+
| rank | name | result |
+------+---------+--------+
| 1 | Player1 | 4500 |
| 2 | Player2 | 4100 |
| 3 | Player3 | 3900 |
| 4 | Player4 | 3800 |
| 5 | Player5 | 3700 |
| 6 | Player6 | 3600 |
| 7 | Player7 | 3500 |
| 8 | Player8 | 3400 |
+------+---------+--------+
SQL:
set #rank = 0;
select
top_scores.*
from
(select ranks.* from (select #rank:=#rank+1 AS rank, name, result from ranks) ranks) top_scores
where
top_scores.rank <= 5
or (top_scores.result = 3400 and name ='Player8');
That will do what you want it to do
assuming your table has the following columns:
playername
score
calculated_rank
your query should look something like:
select calculated_rank,playername, score
from tablename
order by calculated_rank limit 5
I assume you have PRIMARY KEY on this table. If you don't, just create one. My table structure (because you didn't supply your own) is like this:
id INTEGER
result INTEGER
first_name VARCHAR
SQL query should be like that:
SELECT #i := #i+1 AS position, first_name, result FROM top_scores, (SELECT #i := 0) t ORDER BY result DESC LIMIT 10 UNION
SELECT (SELECT COUNT(id) FROM top_scores t2 WHERE t2.result > t1.result AND t2.id > t1.id) AS position, first_name, result FROM top_scores t1 WHERE id = LAST_INSERT_ID();
I added additional condition into subquery ("AND t2.id > t1.id") to prevent multiple people with same result having same position.
EDIT: If you have some login system, it would be better to save userid with result and get current user result using it.