MYSQL - Selecting multiple rows in a single row - mysql

I have a table in MYSQL named as permit_bills which contains columns as bill_no, alcohol_typ, origin, 2000ml, 1000ml, bill_date. Table is shown below:
+---------+--------------+---------+--------+--------+-----------+
| bill_no | alcohol_typ | origin | 2000ml | 1000ml | bill_date |
+---------+------------- + --------+--------+--------+-----------+
| 2001 | s | f | 2 | 1 |01-02-2017 |
| 2001 | m | w | 3 | 4 |01-02-2017 |
+---------+--------------+---------+--------+--------+-----------+
I want to select all rows from above table into a single row based on their bill_no and bill_date and want to display the columns of 2000ml and 1000ml as per their alcohol_typ and `origin.
My output table must be like this:
+---------+--------------+-------------+------------+------------+-----------+
| bill_no | s_f_2000ml | s_f_1000ml | m_w_2000ml | m_w_1000ml | bill_date |
+---------+------------- + ------------+------------+------------+-----------+
| 2001 | 2 | 1 | 3 | 4 |01-02-2017 |
+---------+--------------+-------------+------------+------------+-----------+

Try this (pivot) query -
SELECT
bill_no,
MAX(IF(alcohol_typ = 's' AND origin = 'f', `2000ml`, NULL)) AS s_f_2000ml,
MAX(IF(alcohol_typ = 's' AND origin = 'f', `1000ml`, NULL)) AS s_f_1000ml,
MAX(IF(alcohol_typ = 'm' AND origin = 'w', `2000ml`, NULL)) AS m_w_2000ml,
MAX(IF(alcohol_typ = 'm' AND origin = 'w', `1000ml`, NULL)) AS m_w_1000ml,
bill_date
FROM permit_bills
GROUP BY bill_no, bill_date

Are you sure your output table needs to look like that?
You may be able to use the GROUP_CONCAT function instead which is sometimes an amazingly useful tool. You will need to split or explode the values in your application, but it might be all you need.
https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_group-concat

Related

Select 2 matching columns on base of 3rd column

I want to match id's of column 1 from column 2.
_________________________________
| uid | profile_id | status |
|------|-------------|-----------
| 1 | 2 | checked |
| 2 | 1 | checked |
| 3 | 4 | unchecked|
| 4 | 1 | unchecked|
| 4 | 3 | checked |
| 1 | 4 | checked |
...
This is my table. I want to show the result of same values that match from id1 to id2 and status is checked. Following is the output:
__________________________
| uid | profile_id | status |
|------|-------------|-----------
| 1 | 2 | checked |
| 2 | 1 | checked |
...
Because id1 1 check the id2 2 and vice versa.
I done the following code.
SELECT
`aa`.`uid` AS `uid`,
`aa`.`profile_id` AS `profile_id`,
`aa`.`match_type` AS `match_type`
FROM
(
`matched_profiles` `aa`
left join `matched_profiles` `ab` on(
(`aa`.`uid` = `ab`.`profile_id`)
)
)
where
(
(`aa`.`uid` = `ab`.`profile_id`)
and (`ab`.`uid` = `aa`.`profile_id`)
);
but above code also show me the unchecked result.
Great question. Because of the structure of the data int this table, you can do this by joining the table to itself. You will have two sets of data, replicas of one another.
You want to make sure there is a two-way match between uid and profile_id.
You also want the status for both directions to be checked
SELECT
a.*
FROM matched_profiles a
inner join matched_profiles a2 on
a.uid = a2.profile_id
and a.profile_id = a2.uid
and a.status = 'checked'
and a2.status = 'checked';
You have a kind of explicit/inexplicit join going on. I reformatted to be completely explicit for clarity. You just need to add a filter to make sure all status values are checked.
Below is the SQL to build your test schema, which is nice to provide when asking these questions for SO users.
create table matched_profiles (
uid int,
profile_id int,
status varchar(18)
);
insert into matched_profiles
values
(1, 2, 'checked'),
(2, 1, 'checked'),
(3, 4, 'unchecked'),
(4, 1, 'unchecked'),
(4, 3, 'checked'),
(1, 4, 'checked');

Transposing rows into columns (MySQL)

So, lets say I have a table called "imports" that looks like this:
| id | importer_id | total_m | total_f |
|====|=============|=========|=========|
| 1 | 1 | 100 | 200 |
| 1 | 1 | 0 | 200 |
And I need the query to return it pivoted or transposed (rows to columns) in this way:
| total_m | sum(total_m) |
| total_f | sum(total_f) |
I can't think on a way to do this without using another table (maybe a temporary table?) and using unions, but there should be a better way to this anyway (maybe with CASE or IF?).
Thanks in advance.
select 'total_m', sum(total_m) from imports
union
select 'total_f', sum(total_f) from imports
http://sqlfiddle.com/#!9/fc1c0/2/0
You can "unpivot" by first expanding the number of rows, which is done below by cross joining a 2 row subquery. Then on each of those rows use relevant case expression conditions to align the former columns to the new rows ("conditional aggregates").
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE imports
(`id` int, `importer_id` int, `total_m` int, `total_f` int)
;
INSERT INTO imports
(`id`, `importer_id`, `total_m`, `total_f`)
VALUES
(1, 1, 100, 200),
(1, 1, 0, 200)
;
Query 1:
select
*
from (
select
i.importer_id
, concat('total_',cj.unpiv) total_type
, sum(case when cj.unpiv = 'm' then total_m
when cj.unpiv = 'f' then total_f else 0 end) as total
from imports i
cross join (select 'm' as unpiv union all select 'f') cj
group by
i.importer_id
, cj.unpiv
) d
Results:
| importer_id | total_type | total |
|-------------|------------|-------|
| 1 | total_f | 400 |
| 1 | total_m | 100 |

Divide columns values by total rows in impala

SELECT COUNT(DISTINCT cgi.sample_idSince Impala does not allow the SET operation, or subqueries in a select statement, I'm having a hard time figuring out how to divide column values by the total number of rows returned. My ultimate goal is to calculate minor allele frequency at each chr:start position.
My data is structured as follows:
| chr | start | stop | ref | allele1seq | allele2seq | sample_id |
| 6 | 66720709 | 66720710 | A | A | T | 101-46-3 |
| 7 | 66720809 | 66720810 | GG | GA | GG | 101-46-3 |
I'd like to do something similar to the query below:
WITH vars as
(SELECT cgi.chr, cgi.start, concat(cgi.chr, ':', CAST(cgi.start AS STRING)) as pos, cgi.ref, cgi.allele1seq, cgi.allele2seq,
CASE
WHEN (cgi.allele1seq = cgi.ref AND cgi.allele2seq <> cgi.ref) THEN '1'
WHEN (cgi.allele1seq <> cgi.ref AND cgi.allele2seq = cgi.ref) THEN '1'
WHEN (cgi.allele1seq = cgi.ref AND cgi.allele2seq = cgi.ref) THEN '2'
ELSE '0' END as ma_count
FROM comgen_variants as cgi)
SELECT vars.*, (CAST(vars.ma_count as INT)/
((SELECT COUNT(DISTINCT cgi.sample_id) from comgen_variants as cgi) * 2)) as maf
FROM vars
Where my desired output would like like:
| chr | start | ref | allele1seq | allele2seq | ma_count | maf |
| 6 | 66720709 | A | A | T | 1 | .05 |
| 7 | 66720809 | GG | GG | GG | 0 | 0 |
In addition to figuring out a way to divide by row count, I also need to group the results by chr and pos, and then count how many times each alternate allele (where allele1seq and allele2seq are not equal to ref) occurs instead of simply counting per row as I have above; but I haven't gotten that far due to the counting issue.
Thanks in advance for your help.
It looks like you could just calculate the total number of distinct sample_ids*2 in advance, and then use that for the subsequent query, since that value doesn't change per row. If the value did depend on the row, you might want to take a look at the analytic/window functions available to Impala.
But, since it doesn't look like you need to, you could do something like the following:
WITH total AS
(SELECT COUNT(DISTINCT sample_id) * 2 AS total FROM comgen_variants)
SELECT cgi.*,
(CASE
WHEN (cgi.allele1seq = cgi.ref AND cgi.allele2seq <> cgi.ref) THEN 1
WHEN (cgi.allele1seq <> cgi.ref AND cgi.allele2seq = cgi.ref) THEN 1
WHEN (cgi.allele1seq = cgi.ref AND cgi.allele2seq = cgi.ref) THEN 2
ELSE 0 END) / total.total AS maf
FROM comgen_variants AS cgi, total;
I'm not sure that this is what the minor allele frequency is, though; it seems like you'd want to choose the second most common allele frequency for each locus?

SELECT N rows before and after the row matching the condition?

The behaviour I want to replicate is like grep with -A and -B flags .
eg grep -A 2 -B 2 "hello" myfile.txt will give me all the lines which have "hello" in them, but also 2 lines before and 2 lines after it.
Lets assume this table schema :
+--------+-------------------------+
| id | message |
+--------+-------------------------+
| 1 | One tow three |
| 2 | No error in this |
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
| 8 | social game |
| 9 | stackoverflow |
|10 | stackexchange |
+------------+---------------------+
Now a query like :
Select * from theTable where message like '%hello%' will result in :
5 | hello world
How can I put another parameter "N" which selects N rows before, and N rows after the matched record i.e. for N = 2, the result should be :
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
For simplicity assume 'like %TERM%' matches only 1 row .
Here the result is supposed to be sorted on auto-increment id field.
Right, this works for me:
SELECT child.*
FROM stack as child,
(SELECT idstack FROM stack WHERE message LIKE '%hello%') as parent
WHERE child.idstack BETWEEN parent.idstack-2 AND parent.idstack+2;
Don't know if this is at all valid MySQL but how about
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id <= t.id
ORDER BY
ID DESC
LIMIT 3
UNION ALL
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id > t.id
ORDER BY
ID
LIMIT 2
Try this simple one (edited) -
CREATE TABLE messages(
id INT(11) DEFAULT NULL,
message VARCHAR(255) DEFAULT NULL
);
INSERT INTO messages VALUES
(1, 'One tow three'),
(2, 'No error in this'),
(3, 'My testing message'),
(4, 'php module test'),
(5, 'hello world'),
(6, 'team spirit'),
(7, 'puzzle game'),
(8, 'social game'),
(9, 'stackoverflow'),
(10, 'stackexchange');
SET #text = 'hello world';
SELECT id, message FROM (
SELECT m.*, #n1:=#n1 + 1 num, #n2:=IF(message = #text, #n1, #n2) pos
FROM messages m, (SELECT #n1:=0, #n2:=0) n ORDER BY m.id
) t
WHERE #n2 >= num - 2 AND #n2 <= num + 2;
+------+--------------------+
| id | message |
+------+--------------------+
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
+------+--------------------+
N value can be specified as user variable; currently it is - '2'.
This query works with row numbers, and this guarantees that the nearest records will be returned.
Try
Select * from theTable
Where id >=
(Select id - variableHere from theTable where message like '%hello%')
Order by id
Limit (variableHere * 2) + 1
(MS SQL Server only)
The most reliable way would be to use the row_number function that way it doesn't matter if there are gaps in the id. This will also work if there are multiple occurances of the search result and properly return two above and below each result.
WITH
srt AS (
SELECT ROW_NUMBER() OVER (ORDER BY id) AS int_row, [id]
FROM theTable
),
result AS (
SELECT int_row - 2 AS int_bottom, int_row + 2 AS int_top
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
WHERE ([message] like '%hello%')
)
SELECT theTable.[id], theTable.[message]
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
INNER JOIN result
ON srt.int_row >= result.int_bottom
AND srt.int_row <= result.int_top
ORDER BY srt.int_row
Adding an answer using date instead of an id.
The use-case here is an on-call rotation table with one record pr week.
Due to edits the id might be out of order for the purpose intended.
Any use-case having several records pr week, pr date or other will of course have to be mended.
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| startdate| datetime | NO | | NULL | |
| person | int(11) | YES | MUL | NULL | |
+----------+--------------+------+-----+---------+----------------+
The query:
SELECT child.*
FROM rota-table as child,
(SELECT startdate
FROM rota-table
WHERE YEARWEEK(startdate, 3) = YEARWEEK(now(), 3) ) as parent
WHERE
YEARWEEK(child.startdate, 3) >= YEARWEEK(NOW() - INTERVAL 25 WEEK, 3)
AND YEARWEEK(child.startdate, 3) <= YEARWEEK(NOW() + INTERVAL 25 WEEK, 3)

Generate unique username from first and last name?

I've got a bunch of users in my database and I want to reset all their usernames to the first letter of their first name, plus their full last name. As you can imagine, there are some dupes. In this scenario, I'd like to add a "2" or "3" or something to the end of the username. How would I write a query to generate a unique username like this?
UPDATE user
SET username=lower(concat(substring(first_name,1,1), last_name), UNIQUETHINGHERE)
CREATE TABLE bar LIKE foo;
INSERT INTO bar (id,user,first,last)
(SELECT f.id,CONCAT(SUBSTRING(f.first,1,1),f.last,
(SELECT COUNT(*) FROM foo f2
WHERE SUBSTRING(f2.first,1,1) = SUBSTRING(f.first,1,1)
AND f2.last = f.last AND f2.id <= f.id
)),f.first,f.last from foo f);
DROP TABLE foo;
RENAME TABLE bar TO foo;
This relies on a primary key id, so for each record inserted into bar, we only count duplicates found in foo with id less than bar.id.
Given foo:
select * from foo;
+----+------+--------+--------+
| id | user | first | last |
+----+------+--------+--------+
| 1 | aaa | Roger | Hill |
| 2 | bbb | Sally | Road |
| 3 | ccc | Fred | Mount |
| 4 | ddd | Darren | Meadow |
| 5 | eee | Sharon | Road |
+----+------+--------+--------+
The above INSERTs into bar, resulting in:
select * from bar;
+----+----------+--------+--------+
| id | user | first | last |
+----+----------+--------+--------+
| 1 | RHill1 | Roger | Hill |
| 2 | SRoad1 | Sally | Road |
| 3 | FMount1 | Fred | Mount |
| 4 | DMeadow1 | Darren | Meadow |
| 5 | SRoad2 | Sharon | Road |
+----+----------+--------+--------+
To remove the "1" from the end of user names,
INSERT INTO bar (id,user,first,last)
(SELECT f3.id,
CONCAT(
SUBSTRING(f3.first,1,1),
f3.last,
CASE f3.cnt WHEN 1 THEN '' ELSE f3.cnt END),
f3.first,
f3.last
FROM (
SELECT
f.id,
f.first,
f.last,
(
SELECT COUNT(*)
FROM foo f2
WHERE SUBSTRING(f2.first,1,1) = SUBSTRING(f.first,1,1)
AND f2.last = f.last AND f2.id <= f.id
) as cnt
FROM foo f) f3)
As a two-parter:
SELECT max(username)
FROM user
WHERE username LIKE concat(lower(concat(substring(first_name,1,1),lastname), '%')
to retrieve the "highest" username for that name combo. Extract the numeric suffix, increment it, then insert back into the database for your new user.
This is racy, of course. Two users with the same first/last names might stomp on each other's usernames, depending on how things work out. You'd definitely want to sprinkle some transaction/locking onto the queries to make sure you don't have any users conflicting.
Nevermind.... I just found the dupes:
select LOWER(CONCAT(SUBSTRING(first_name,1,1),last_name)) as new_login,count(* ) as cnt from wx_user group by new_login having count(* )>1;
And set those ones manually. Was only a handful.
Inspired in the answer of unutbu: there is no need to create an extra table neither several queries:
UPDATE USER a
LEFT JOIN (
SELECT USR_ID,
REPLACE(
CONCAT(
SUBSTRING(f.`USR_FIRSTNAME`,1,1),
f.`USR_LASTNAME`,
(
(SELECT IF(COUNT(*) > 1, COUNT(*), '')
FROM USER f2
WHERE SUBSTRING(f2.`USR_FIRSTNAME`,1,1) =
SUBSTRING(f.`USR_FIRSTNAME`,1,1)
AND f2.`USR_LASTNAME` = f.`USR_LASTNAME`
AND f2.`USR_ID` <= f.`USR_ID`)
)
),
' ',
'') as login
FROM USER f) b
ON a.USR_ID = b.USR_ID
SET a.USR_NICKNAME = b.login