I am stuck in this situation where I need to use Row Number and Column Number values from table's columns to derive the output mentioned below. I have tried everything - if/else, case when/then but not helping.
Any help/suggestions are really appreciated!
Here is a mocked up sample data present in db table -
+--------+--------+--------+----------+-------------+
| Record | ColNbr | RowNbr | ColTitle | CellContent |
+--------+--------+--------+----------+-------------+
| 1 | 1 | 1 | Unit | sqf |
| 1 | 1 | 2 | Unit | cm |
| 1 | 2 | 1 | Desc | roof |
| 1 | 2 | 2 | Desc | rod |
| 1 | 3 | 1 | Material | concrete |
| 1 | 3 | 2 | Material | steel |
| 1 | 4 | 1 | Quantity | 100 |
| 1 | 4 | 2 | Quantity | 12 |
| 1 | 1 | 1 | Unit | liter |
| 1 | 1 | 2 | Unit | ml |
| 1 | 2 | 1 | Desc | bowl |
| 1 | 2 | 2 | Desc | plate |
| 1 | 3 | 1 | Material | plastic |
| 1 | 3 | 2 | Material | glass |
| 1 | 4 | 1 | Quantity | 2 |
| 1 | 4 | 2 | Quantity | 250 |
+--------+--------+--------+----------+-------------+
Expected Output -
+--------+--------+--------+----------+-------------+
| Record | Unit | Desc | Material | Quantity |
+--------+--------+--------+----------+-------------+
| 1 | sqf | roof | concrete | 100 |
| 1 | cm | rod | steel | 12 |
| 2 | liter | bowl | plastic | 2 |
| 2 | ml | plate | glass | 250 |
+--------+--------+--------+----------+-------------+
If your actual data is like that, I suggest that you consider to separate the data to; for example, 4 different tables (unit,description,material & a table to store all that ids+quantity). The former 3 tables will store the prerequisite info that get minor updates throughout time and the last table will store all the quantity records. Let's say your tables will look something like this:
CREATE TABLE `Unit` (
unit_id INT,
unit_name VARCHAR(50));
+---------+-----------+
| unit_id | unit_name |
+---------+-----------+
| 1 | sqf |
| 2 | cm |
| 3 | liter |
| 4 | ml |
+---------+-----------+
CREATE TABLE `Description` (
desc_id INT,
desc_name VARCHAR(50));
+---------+-----------+
| desc_id | desc_name |
+---------+-----------+
| 1 | roof |
| 2 | rod |
| 3 | bowl |
| 4 | plate |
+---------+-----------+
CREATE TABLE `Material` (
mat_id INT,
mat_name VARCHAR(50));
+--------+----------+
| mat_id | mat_name |
+--------+----------+
| 1 | concrete |
| 2 | steel |
| 3 | plastic |
| 4 | glass |
+--------+----------+
CREATE TABLE `Records` (
unit_id INT,
desc_id INT,
mat_id INT,
quantity DECIMAL(14,4));
+---------+---------+--------+----------+
| unit_id | desc_id | mat_id | Quantity |
+---------+---------+--------+----------+
| 1 | 1 | 1 | 100 |
| 2 | 2 | 2 | 12 |
| 3 | 3 | 3 | 2 |
| 4 | 4 | 4 | 250 |
+---------+---------+--------+----------+
Then you insert the data accordingly.
Anyhow, for your existing data example, it could be done but there are some concern over whether the unit+desc+material+quantity matching are correct. The only way I can maybe at least think that it's correctly matched is by giving all of the query a similar ORDER BY clause. Hence, the following:
SELECT A.record,A.unit,B.Desc,C.Material,D.Quantity FROM
(SELECT #rn:=#rn+1 AS record,CASE WHEN coltitle='unit' THEN cellcontent END AS Unit
FROM yourtable, (SELECT #rn :=0 ) v
HAVING unit IS NOT NULL
ORDER BY colnbr) A LEFT JOIN
(SELECT #rn1:=#rn1+1 AS record,CASE WHEN coltitle='Desc' THEN cellcontent END AS `Desc`
FROM yourtable, (SELECT #rn1 :=0 ) v
HAVING `Desc` IS NOT NULL
ORDER BY colnbr) B ON a.record=b.record LEFT JOIN
(SELECT #rn2:=#rn2+1 AS record,CASE WHEN coltitle='material' THEN cellcontent END AS Material
FROM yourtable, (SELECT #rn2:=0 ) v
HAVING Material IS NOT NULL
ORDER BY colnbr) C ON a.record=c.record LEFT JOIN
(SELECT #rn3:=#rn3+1 AS record,CASE WHEN coltitle='Quantity' THEN cellcontent END AS Quantity
FROM yourtable, (SELECT #rn3:=0 ) v
HAVING Quantity IS NOT NULL
ORDER BY colnbr) D ON a.record=d.record;
The idea here is to make a sub-query based on COLTITLE then assign a numbering/ranking (#rn,#rn1,#rn2,#rn3) variable to each of the sub-query and join them up using LEFT JOIN. Now, this experiment works to exactly return the output that you need but its not a definite answer because there are some part that is questionable especially on matching the combination correctly. Hopefully, this will give you some idea.
Related
I have a table like this:
+---------------+--------------+------+-----+----------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+----------+
| id | smallint(6) | NO | PRI | NULL |
| Book | tinyint(4) | NO | | NULL |
| Chapter | smallint(6) | NO | | NULL |
| Paragraph | smallint(6) | NO | | NULL |
| Text | text | YES | | NULL |
| RevisionNum | mediumint(9) | NO | PRI | NULL |
+---------------+--------------+------+-----+----------+
mysql> select id,Book,Chapter,Paragraph,RevisionNum FROM MyTable ORDER BY id LIMIT 11;
+-----+------+---------+-----------+-------------+
| id | Book | Chapter | Paragraph | RevisionNum |
+-----+------+---------+-----------+-------------+
| 1 | 1 | 1 | 1 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 2 |
| 2 | 1 | 2 | 2 | 0 |
| 2 | 1 | 2 | 2 | 1 |
| 2 | 1 | 2 | 2 | 2 |
| 2 | 1 | 2 | 2 | 3 |
| 3 | 1 | 2 | 3 | 0 |
| 4 | 1 | 2 | 4 | 0 |
| 4 | 1 | 2 | 4 | 1 |
| 5 | 1 | 3 | 5 | 0 |
+-----+------+---------+-----------+-------------+
To find a book or chapter which has no unrevised paragraph,
I wish to query either the minimum value of the maximums of
all the distinct id's for that chapter or book, or else in
some fashion determine that no id remains unedited (with a
MAX(RevisionNum) of zero).
Most of my attempts to date have ended in errors like this one:
SELECT DISTINCT Book,RecordNum FROM MyTable
-> WHERE 0 < ALL (SELECT DISTINCT RecordNum,MAX(RevisionNum)
FROM MyTable
WHERE MAX(RevisionNum) > 0);
ERROR 1111 (HY000): Invalid use of group function
...And I wasn't using the "GROUP BY" function at all!
The following query produces results, but simply
gives ALL id's, and does not actually show a unique
set of Book records, as requested. How could this happen?
SELECT DISTINCT Book,id,MAX(RevisionNum) FROM MyTable GROUP BY id LIMIT 5;
+------+----+------------------+
| Book | id | MAX(RevisionNum) |
+------+----+------------------+
| 1 | 1 | 30 |
| 1 | 2 | 16 |
| 1 | 3 | 15 |
| 1 | 4 | 10 |
| 1 | 5 | 9 |
+------+----+------------------+
What would the correct query be to give results more like this:
+------+-----+-----------------------+
| Book | id | MIN(MAX(RevisionNum)) |
+------+-----+-----------------------+
| 1 | 5 | 3 |
| 2 | 17 | 1 |
| 3 | 33 | 2 |
| 4 | 147 | 0 |
| 5 | 225 | 2 |
+------+-----+-----------------------+
Are you looking for two levels of aggregation?
select id, book, min(max_revisionnum)
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum
from mytable
group by id, book, chapter, paragraph
) t
group by id, book;
EDIT:
Based on your comment, you can use:
select *
from (select id, book, chapter, paragraph, max(revisionnum) as max_revisionnum,
row_number() over (partition by book order by max(revisionnum) desc) as seqnum
from mytable
group by id, book, chapter, paragraph
) t
where seqnum = 1;
Here is a db<>fiddle.
In older versions of MariaDB, you can use a correlated subquery:
select t.*
from mytable t
where (id, book, chapter, paragraph, revisionnum) = (select t2.id, t2.book, t2.chapter, t2.paragraph, t2.revisionnum
from mytable t2
where t2.book = t.book
order by t2.revisionnum desc
limit 1
);
For this query, try adding an index on (book, revisionnum desc).
Update: I want to use dynamic sql to select question as column and put answer in row, like cursor or loop, is that possible?
I want the select result like this
+--------+---------------+--------------------------------------------------------------------------+
| userid | Living Status | This is another question get from row and it's longer than 64 characters |
+--------+---------------+--------------------------------------------------------------------------+
| 19 | married | q2_opt3 |
+--------+---------------+--------------------------------------------------------------------------+
And here is my query
select
userid,
min(if(question.ordering=1,o.name,NULL )) as 'Living Status',
min(if(question.ordering=2,o.name,NULL )) as 'This is another question get from row and it's longer than 64 characters'
from answer
inner join question on question.key_value = answer.key_value
inner join q_option o on question.id = o.question_id and o.value = answer.answer
where userid in (19)
GROUP BY id
The question table is like
+----+----------+---------------------------------------------------------------------------+--------------+
| id | ordering | question | key_value |
+----+----------+---------------------------------------------------------------------------+--------------+
| 1 | 1 | Living Status | livingStatus |
| 2 | 2 | This is another question get from row and it's longer than 64 characters | question_2 |
+----+----------+---------------------------------------------------------------------------+--------------+
The answer table is like
+----+--------+--------------+--------+
| id | answer | key_value | userid |
+----+--------+--------------+--------+
| 1 | 2 | livingStatus | 19 |
| 2 | 3 | question_2 | 19 |
+----+--------+--------------+--------+
The q_option table is like
+----+----------+-------------+-------+
| id | name | question_id | value |
+----+----------+-------------+-------+
| 1 | single | 1 | 1 |
| 2 | married | 1 | 2 |
| 3 | divorced | 1 | 3 |
| 4 | q2_opt1 | 2 | 1 |
| 5 | q2_opt2 | 2 | 2 |
| 6 | q2_opt3 | 2 | 3 |
+----+----------+-------------+-------+
For this question I have created a simple example that illustrates what I am asking.
Say I had a table called 'books'
+----+----------------------------+-----------+
| pk | title | author_id |
+----+----------------------------+-----------+
| 1 | The Lost Symbol | 1 |
| 2 | Follow Us Home | 2 |
| 3 | The Man in the High Castle | 3 |
+----+----------------------------+-----------+
(table a)
And another table called 'shops', that had a list of shops that sold each book:
+----+---------+-------------+-------+
| pk | book_id | shop_name | price |
+----+---------+-------------+-------+
| 1 | 1 | WHSmith | 5.00 |
| 2 | 1 | Waterstones | 7.00 |
| 3 | 1 | Amazon | 2.50 |
| 4 | 2 | WHSmith | 4.00 |
| 5 | 2 | Borders | 4.50 |
+----+---------+-------------+-------+
(table b)
If I do a simple select that grabs a book and all of the places it is sold using a join such as:
SELECT
books.*,
shops.shop_name,
shops.price
FROM
books
JOIN shops ON books.pk = shops.book_id
WHERE
book.book_name = "The Lost Symbol"
I would get results such as below:
+----+-----------------+-----------+-------------+-------+
| pk | title | author_id | shop_name | price |
+----+-----------------+-----------+-------------+-------+
| 1 | The Lost Symbol | 1 | WHSmith | 5.00 |
| 1 | The Lost Symbol | 1 | Waterstones | 7.00 |
| 1 | The Lost Symbol | 1 | Amazon | 2.50 |
+----+-----------------+-----------+-------------+-------+
(table c)
However, I would LIKE to receive results like this:
+----+-----------------+-----------+-------------+-------+
| pk | title | author_id | shop_name | price |
+----+-----------------+-----------+-------------+-------+
| 1 | The Lost Symbol | 1 | NULL | NULL |
| 1 | The Lost Symbol | 1 | WHSmith | 5.00 |
| 1 | The Lost Symbol | 1 | Waterstones | 7.00 |
| 1 | The Lost Symbol | 1 | Amazon | 2.50 |
+----+-----------------+-----------+-------------+-------+
(table d)
I.e. the first row is just the result of left outer join and the rest of the results are the the inner join.
An even more desired outcome is:
+------+-----------------+-----------+-------------+-------+
| pk | title | author_id | shop_name | price |
+------+-----------------+-----------+-------------+-------+
| 1 | The Lost Symbol | 1 | NULL | NULL |
| NULL | NULL | NULL | WHSmith | 5.00 |
| NULL | NULL | NULL | Waterstones | 7.00 |
| NULL | NULL | NULL | Amazon | 2.50 |
+------+-----------------+-----------+-------------+-------+
(table e)
Having shop_name and price concatenated and grouped in a single row seems not to work as it only does the first result from shops instead of all of them, also in my real world scenario, I have punctuation in the data so have to be careful with the separator.
So how would I get the result of table e?
You can use UNION ALL to build the required result set:
SELECT pk, title, author_id, NULL AS shop_name, NULL AS price
FROM books
WHERE books.title = "The Lost Symbol"
UNION ALL
SELECT NULL AS pk, NULL AS title, NULL AS author_id, shops.shop_name, shops.price
FROM books
JOIN shops ON books.pk = shops.book_id
WHERE books.title = "The Lost Symbol"
The first part of the union operation returns the first row of the result, i.e. the book title. The second part returns the rest of the rows, i.e.the shop names.
Demo here
I am trying to write a query to bring together data from three tables:
---------------------------------
| destinations |
---------------------------------
| id | city | status |
---------------------------------
| 1 | Milan | Open |
| 2 | Florence | Open |
| 3 | Venice | Open |
---------------------------------
---------------------------------
| trips |
---------------------------------
| id | from | to | train |
---------------------------------
| 1 | 1 | 2 | 2 |
| 2 | 1 | 2 | 3 |
| 3 | 2 | 1 | 2 |
| 4 | 2 | 3 | 2 |
| 5 | 1 | 3 | 1 |
| 6 | 3 | 1 | 1 |
---------------------------------
---------------------------------
| trains |
---------------------------------
| id | train |
---------------------------------
| 1 | T1 |
| 2 | ChooChoo |
| 3 | IC123 |
---------------------------------
The idea is that I would like to be able to show my user all the trips starting or ending in - say - Florence.
Something along these lines:
-----------------------------------------------------------------
| Query: all trains going to/from Florence |
-----------------------------------------------------------------
| trips.id | from.id | from (city) | to.id | to. city | train |
-----------------------------------------------------------------
| 1 | 1 | Milan | 2 | Florence | 2 |
| 2 | 1 | Milan | 2 | Florence | 3 |
| 3 | 2 | Florence | 1 | Milan | 2 |
| 4 | 2 | Florence | 3 | Venice | 2 |
-----------------------------------------------------------------
The problems that I'm facing are essentially two: the auto-referencing table of destinations (which I can easily solve with aliases) and the fact that I am trying to combine two sets of data from two different selects (which I thought of solving with temporary tables).
Now, it's all good if it wasn't that some columns have the same names. Because I want to preserve the "ids" in my temporary table (to be used with links) I can't create a temporary table with a wildcard (eg. SELECT * FROM), but I would have to spell out all the columns and write a monstrous query. It will work, but it won't be flexible, and if I'll add other columns in the future, it will be hell updating it!
Can any MySQL guru maybe suggest a better approach please?
Thanks and cheers from Australia.
G'day...
DROP TABLE IF EXISTS destinations;
CREATE TABLE destinations
(city_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,city VARCHAR(12) NOT NULL UNIQUE
,status VARCHAR(12) NOT NULL
);
INSERT INTO destinations VALUES
(1,'Milan','Open'),
(2,'Florence','Open'),
(3,'Venice','Open');
DROP TABLE IF EXISTS trips;
CREATE TABLE trips
(trip_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,from_city_id INT NOT NULL
,to_city_id INT NOT NULL
,train INT NOT NULL
,UNIQUE(from_city_id,to_city_id,train)
);
INSERT INTO trips VALUES
(1,1,2,2),
(2,1,2,3),
(3,2,1,2),
(4,2,3,2),
(5,1,3,1),
(6,3,1,1);
DROP TABLE IF EXISTS trains;
CREATE TABLE trains
(train_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,train VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO trains VALUES
(1,'T1'),
(2,'ChooChoo'),
(3,'IC123');
SELECT * FROM destinations;
+---------+----------+--------+
| city_id | city | status |
+---------+----------+--------+
| 1 | Milan | Open |
| 2 | Florence | Open |
| 3 | Venice | Open |
+---------+----------+--------+
SELECT * FROM trips;
+---------+--------------+------------+-------+
| trip_id | from_city_id | to_city_id | train |
+---------+--------------+------------+-------+
| 1 | 1 | 2 | 2 |
| 2 | 1 | 2 | 3 |
| 5 | 1 | 3 | 1 |
| 3 | 2 | 1 | 2 |
| 4 | 2 | 3 | 2 |
| 6 | 3 | 1 | 1 |
+---------+--------------+------------+-------+
SELECT * FROM trains;
+----------+----------+
| train_id | train |
+----------+----------+
| 2 | ChooChoo |
| 3 | IC123 |
| 1 | T1 |
+----------+----------+
SELECT t.trip_id
, t.from_city_id
, from_city.city
, t.to_city_id
, to_city.city
, t.train
FROM trips t
JOIN destinations from_city
ON from_city.city_id = t.from_city_id
JOIN destinations to_city
ON to_city.city_id = t.to_city_id
WHERE 'Florence' IN(from_city.city,to_city.city);
+---------+--------------+----------+------------+----------+-------+
| trip_id | from_city_id | city | to_city_id | city | train |
+---------+--------------+----------+------------+----------+-------+
| 3 | 2 | Florence | 1 | Milan | 2 |
| 4 | 2 | Florence | 3 | Venice | 2 |
| 1 | 1 | Milan | 2 | Florence | 2 |
| 2 | 1 | Milan | 2 | Florence | 3 |
+---------+--------------+----------+------------+----------+-------+
when you build a report, usually isn't a good idea to use SELECT *,
because when i modify a table, the report can shows wrong result.
usually i prefere to write the id field with id_content, ex. id_destination, id_to, id_train, ecc...
SELECT trips.id, f.id, f.city, t.id, t. city, trains.train
FROM trips trips
INNER JOIN destinations f
ON trips.from = f.id
INNER JOIN destinations t
ON trips.from = t.id
INNER JOIN trains trains
ON trips.train = trains.id
ORDER BY 1 ASC
I've got a table that looks like:
Table 1 ->
+----+--------+--------+
| id | name | author |
+----+--------+--------+
| 1 | First | Me |
| 2 | Second | You |
+----+--------+--------+
Table 2 ->
+-----+------------+-----------+------------+
| mid | table1_id | key | value |
+-----+------------+-----------+------------+
| 1 | 1 | desc | hello |
| 2 | 1 | begin_day | monday |
| 3 | 1 | end_day | tuesday |
| 4 | 2 | desc | goodbye |
| 5 | 2 | begin_day | wednesday |
| 6 | 2 | end_day | friday |
+-----+------------+-----------+------------+
The relationship here is that the id in table 1 corresponds to the table1_id in table 2.
The output that I'm trying to get is...
+----+---------+---------+-------------+-----------+-----------+
| id | name | author | desc | begin_day | end_day |
+----+---------+---------+-------------+-----------+-----------+
| 1 | First | Me | hello | monday | tuesday |
| 1 | Second | You | goodbye | wednesday | friday |
+----+---------+---------+-------------+-----------+-----------+
I've tried several different join statements -- all a variation of the below. I'm not that well versed in MySQL queries, however.
SELECT * FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Which produces...
+----+----------+----------+----------+------------+-----------+
| id | mid | name | author | key | value |
+----+----------+----------+----------+------------+-----------+
| 1 | 1 | First | Me | desc | hello |
| 1 | 2 | First | Me | begin_day | monday |
| 1 | 3 | First | Me | end_day | tuesday |
| 2 | 4 | Second | You | desc | goodbye |
| 2 | 5 | Second | You | begin_day | wednesday|
| 2 | 6 | Second | You | end_day | friday |
Obviously, iterating over this join statement produces 6 results, 1 for each row in table 2 that matches the id in table 1. How can I avoid this with a proper query statement?
Thank you in advance.
You can use a case statement if you know all of the columns you will be getting, as follows:
Select distinct table_1.*,
case when table_2.key='desc' then value end as desc,
case when table_2.key='begin_day' then value end as begin_day,
case when table_2.key='end_day' then value end as end_day
FROM table_1 LEFT JOIN table_2 on table_1.id = table_2.table1_id
Hope this helps!
SELECT
table_1.*,
MAX(IF(key='desc', value, NULL)) AS 'desc',
MAX(IF(key='begin_day', value, NULL)) AS begin_day,
MAX(IF(key='end_day', value, NULL)) AS end_day
FROM table_1
LEFT JOIN table_2 ON (id = table1_id)
GROUP BY id;