Custom sorting using two different columns sql - mysql

In my table, I have these two columns called year and season that i'd like to sort by. Some example of their values might be
----------------------------
| id | etc | year | season |
| 0 | ... | 2016 | FALL |
| 1 | ... | 2015 | SPRING |
| 2 | ... | 2015 | FALL |
| 3 | ... | 2016 | SPRING |
----------------------------
How would I go about performing a select where I get the results as such?
| 1 | ... | 2015 | SPRING |
| 2 | ... | 2015 | FALL |
| 3 | ... | 2016 | SPRING |
| 0 | ... | 2016 | FALL |
The easy part would be ORDER BY table.year ASC, but how do I manage the seasons now? Thanks for any tips!

You can do this:
SELECT *
FROM yourtable
ORDER BY year, CASE WHEN season = 'spring' THEN 0 ELSE 1 END;
If you want to do the same for the other two seasons, you can do the same using CASE, but it will be much easier and more readable to use a table something like this:
SELECT t1.*
FROM yourtable AS t1
INNER JOIN
(
SELECT 'spring' AS season, 0 AS sortorder
UNION
SELECT 'Fall' AS season, 1 AS sortorder
UNION
SELECT 'Winter' AS season, 2 AS sortorder
UNION
SELECT 'summer' AS season, 3 AS sortorder
) AS t2
ORDER BY t1.year, t2.season;

If you want to order by all four seasons, starting with Spring, extend your CASE statement:
ORDER BY CASE season
WHEN 'spring' then 1
WHEN 'summer' then 2
WHEN 'fall' then 3
WHEN 'autumn' then 3
WHEN 'winter then 4
ELSE 0 -- Default if an incorrect value is entered. Could be 5
END
Alternately, to handle all possible cases, you might want to build a table with the season name and a sort order. Say, for example, some of your data was in german. You could have a table - SeasonSort - with the fields SeasonName and SortOrder. Then add data:
CREATE TABLE SeasonSort (SeasonName nvarchar(32), SortOrder tinyint)
INSERT INTO SeasonSort (SeasonName, SortOrder)
VALUES
('spring', 1),
('frühling', 1),
('fruhling', 1), -- Anglicized version of German name
('summer', 2),
('sommer', 2),
('fall', 3),
('autumn', 3),
('herbst', 3),
('winter', 4) -- same in English and German
Then your query would become:
SELECT t.*
FROM MyTable t
LEFT JOIN seasonSort ss
ON t.season = ss.SeasonName
ORDER BY t.Year,
isnull(ss.SortOrder, 0)

Related

Can I get the full rows when using group by multiple columns?

If the date, item, and category are the same in the table,
I'd like to treat it as the same row and return n rows out of them(ex: if n is 3, then limit 0, 3).
------------------------------------------
id | date | item | category | ...
------------------------------------------
101 | 20220201| pencil | stationery | ... <---
------------------------------------------ | treat as same result
105 | 20220201| pencil | stationery | ... <---
------------------------------------------
120 | 20220214| desk | furniture | ...
------------------------------------------
125 | 20220219| tongs | utensil | ... <---
------------------------------------------ | treat as same
129 | 20220219| tongs | utensil | ... <---
------------------------------------------
130 | 20220222| tongs | utensil | ...
expected results (if n is 3)
-----------------------------------------------
id | date | item | category | ... rank
-----------------------------------------------
101 | 20220201| pencil | stationery | ... 1
-----------------------------------------------
105 | 20220201| pencil | stationery | ... 1
-----------------------------------------------
120 | 20220214| desk | furniture | ... 2
-----------------------------------------------
125 | 20220219| tongs | utensil | ... 3
-----------------------------------------------
129 | 20220219| tongs | utensil | ... 3
The problem is that I have to bring the values of each group as well.
If I have only one column to group by, I can compare id value with origin table, but I don't know what to do with multiple columns.
Is there any way to solve this problem?
For reference, I used a user variable to compare it with previous values,
I couldn't use it because the duration was slow.
SELECT
*,
IF(#prev_date=date and #prev_item=item and #prev_category=category,#ranking, #ranking:=#ranking+1) AS sameRow,
#prev_item:=item,
#prev_date:= date,
#prev_category:=category,
#ranking
FROM ( SELECT ...
I'm using Mysql 8.0 version and id value is not a continuous number because I have to order by before group by.
if I understand correctly, you can try to use dense_rank window function and set order by with your expected columns
if date column can represent the order number I would put it first.
SELECT *
FROM (
SELECT *,dense_rank() OVER(ORDER BY date, item, category) rnk
FROM T
) t1
SQLFIDDLE
Window functions come in very handy in this situation. But for those of us still using MySQL 5.7, where functions such as row_number don't exist, we have to either resort to using a user variable and resetting the value every time before the main statement, or defining the user variable directly in the statement.
method 1
set #row_id=0; -- remember to reset the row_id to 0 every time before the main query below
select id,date,item,category,rank from testtb join
(
select date,item,category, (#row_id:=#row_id+1) as rank
from
(select date,item,category from testtb group by date,item,category) t1
) t2
using(date,item,category);
method 2
select id,date,item,category,rank from testtb join
(
select date,item,category, (#row_id:=#row_id+1) as rank
from
(select date,item,category from testtb group by date,item,category) t1, (select #row_id := 0) as n
) t2
using(date,item,category);

How to generate a survey curve with SQL query

I have a table with 2 columns WorkItem and LiveDays. For example
| WorkItem | LiveDays |
| A | 8 |
| B | 2 |
| C | 5 |
....
I would like to generate a survey data of the work item. Each item is normalized as starting from day 1 and ending to LiveDays, and value of nth day is how many workitems is still live (in process). For example
| Days | Counter | Comments |
| 1 | 3 | (A, B, C)|
| 2 | 3 | (A, B, C)|
| 3 | 2 | (A, C) |
| 4 | 2 | (A, C) |
| 5 | 2 | (A, C) |
| 6 | 1 | (A) |
| 7 | 1 | (A) |
| 8 | 1 | (A) |
Is it possible to use SQL query instead of inserting data into a new table with transaction?
Thanks
To show it can be done (it does after all answer your original question) here's your example reproduced using Db2 Developer-C 11.1 on dbfiddle.uk: (You did say there were several databases you could use, and this will no doubt serve to illustrate that different databases do things in different ways!). Note: additional MySQL solution further down.
CREATE TABLE surveydata AS (
WITH t1(workitem, livedays)
AS (VALUES ('A', 8), ('B', 2), ('C', 5)),
numbers(seq)
AS (VALUES (1)
UNION ALL
SELECT seq + 1
FROM numbers
WHERE seq < (SELECT MAX(livedays)
FROM t1)),
xdata(workitem, ndays)
AS (SELECT workitem,
seq
FROM t1,
numbers
WHERE seq <= livedays)
SELECT ndays AS "Days",
COUNT(*) AS "Counter",
'(' || LISTAGG(workitem, ', ') WITHIN GROUP (ORDER BY workitem) || ')' AS "Comments"
FROM xdata
GROUP BY ndays
) WITH DATA;
with the result of SELECT * FROM surveydata as below ==>
UPDATE: With a bit more fiddling, I've managed to get a solution using MySQL 8.0 as well:
WITH recursive t1(workitem, livedays)
AS (SELECT 'A', 8
UNION ALL SELECT 'B', 2
UNION ALL SELECT 'C', 5 ),
numbers(seq)
AS (SELECT 1 AS seq
UNION ALL
SELECT seq + 1
FROM numbers
WHERE seq < (SELECT MAX(livedays)
FROM t1)),
xdata(workitem, ndays)
AS (SELECT workitem,
seq
FROM t1,
numbers
WHERE seq <= livedays)
SELECT ndays AS "Days",
COUNT(*) AS "Counter" ,
CONCAT('(', GROUP_CONCAT(workitem ORDER BY workitem SEPARATOR ', '), ')') AS "Comments"
FROM xdata
GROUP BY ndays;

How to write a multiple query solution as a single query in MySQL

My task is to find all those subjects, by their id, that have (at least one, but) the fewest lowest passing grades in the database (the grade being the grade 6). I've managed to write the solution with three queries, however my task is to write it as a single query in MySQL. Thank you in advance.
-- 1. single query "solution"
SELECT subject_id FROM (SELECT subject_id, COUNT(*) AS six_count
FROM exams WHERE grade = 6
GROUP BY subject_id) AS sixes
WHERE subject_id = (SELECT MIN(six_count) FROM sixes);
-- 2. multiple queries solution
CREATE TABLE sixes AS (SELECT subject_id, COUNT(*) AS six_count
FROM exams WHERE grade = 6
GROUP BY subject_id);
SELECT subject_id FROM sixes
WHERE subject_id = (SELECT MIN(six_count) FROM sixes);
DROP TABLE sixes;
EDIT:
Exams table example:
| subject_id | student_id | exam_year | exam_mark | grade | exam_date |
| 1 | 20100022| 2011 | 'apr' | 10 | 2011-04-11 |
| 2 | 20100055| 2011 | 'oct' | 6 | 2011-10-04 |
| 3 | 20110030| 2011 | 'jan1' | 7 | 2011-01-26 |
| 5 | 20110055| 2011 | 'jan2' | 6 | 2011-02-13 |
| 5 | 20110001| 2011 | 'jun1' | 8 | 2011-06-23 |
This should do the trick. The sub query selects the first lowest number of sixes. The main query selects all subjects with that number. The trick is in ORDER BY count(*) LIMIT 1, which makes the sub query return the record with the lowest count.
SELECT
subject_id,
count(*) as six_count
FROM exams
WHERE grade = 6
GROUP BY subject_id
HAVING count(*) =
( SELECT count(*)
FROM exams
WHERE grade = 6
GROUP BY subject_id
ORDER BY count(*)
LIMIT 1
)
This pattern should do the trick. Generalized names.
SELECT subjectID
FROM TEST_DATA
WHERE grade = 6
GROUP
BY SubjectID
HAVING COUNT(1) =
( SELECT count(1) AS minCount
FROM TEST_DATA
WHERE grade = 6
GROUP
BY subjectID
ORDER
BY minCount
LIMIT 1
);

returning rows satisfying multiple conditions for a column from a mysql table

I have mysql table with hospitals and treatments(associated with sub treatments) that they provide. I need to make mysql query on the table which returns hospitals providing all treatment/sub_treatment given in a list. For example:
From table below I need hospitals providing all treatments in list: (tretament_id, sub_treatment_id) = (1-1, 1-2). So result must be hospitals with id 1 and 8.
hospital_id | treatment_id | sub_treatment_id
-------------------------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 1 | 3
_________________________________________________
4 | 1 | 1
4 | 2 | 1
_________________________________________________
8 | 1 | 1
8 | 1 | 2
_________________________________________________
7 | 2 | 1
I tried WHERE IN but it works like OR so returns hospital 4 which satisfies only (1,1). How can I write an sql query like WHERE IN but which works like AND?
Try this:
SELECT hospital_id
FROM mytable
WHERE (treatment_id, sub_treatment_id) IN ((1, 1), (1, 2))
GROUP BY hospital_id
HAVING COUNT(CASE
WHEN (treatment_id, sub_treatment_id) IN ((1, 1), (1, 2))
THEN 1
END) = 2
Demo here
You can do this using group by and having:
select hospital_id
from t
where treatment_id = 1 and sub_treatment_id in (1, 2)
group by hospital_id
having count(*) = 2;
Note: This assumes that there are no duplicates in the table. That is easy enough to fix using count(distinct), but probably not necessary.
Here is a solution using GROUP_CONCAT and JOIN:
select distinct t.hospital_id
from hospitals h and treatments t ON h.id = t.hospital_id
having GROUP_CONCAT(CONCAT(t.treatment_id, '-', t.sub_treatment_id)
ORDER BY t.treatment_id, t.sub_treatment_id)
= '1-1,1-2';

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)