Can you help me to build a SQL query?
For example i have this table table :
FK1 | FK2
1 | 1
1 | 2
1 | 3
1 | 4
1 | 1 (duplicated ID)
2 | 1
2 | 2
2 | 3
2 | 5
And i would like to get all FK1 groups with distinct FK2 rows count, like that :
FK1 | COUNT(FK2)
1 | 4 // (Distinct ID : 1, 2, 3, 4)
1 | 1 // (Distinct ID : 1)
2 | 4 // (Distinct ID : 1, 2, 3, 5)
Is it possible in SQL? Maybe with nested queries, i don't know...
Thank you for yours answers (i hope)
Phil
Hmmm . . . I think this might do what you want:
select fk1, rn, count(*)
from (select t.*,
(#rn := if(#fk2 = fk2, #rn + 1,
if(#fk2 := fk2, 1, 1)
)
) as rn
from t cross join
(select #fk2 := -1, #rn := 0) params
order by fk1, fk2
) t
group by fk1, rn;
At least, this returns the result set that you have specified.
Related
If I have a table with the following columns and values, ordered by parent_id:
id parent_id line_no
-- --------- -------
1 2
2 2
3 2
4 3
5 4
6 4
And I want to populate line_no with a sequential number that starts over at 1 every time the value of parent_id changes:
id parent_id line_no
-- --------- -------
1 2 1
2 2 2
3 2 3
4 3 1
5 4 1
6 4 2
What would the query or sproc look like?
NOTE: I should point out that I only need to do this once. There's a new function in my PHP code that automatically creates the line_no every time a new record is added. I just need to update the records that already exist.
Most versions of MySQL do not support row_number(). So, you can do this using variables. But you have to be very careful. MySQL does not guarantee the order of evaluation of variables in the select, so a variable should not be assigned an referenced in different expressions.
So:
select t.*,
(#rn := if(#p = parent_id, #rn + 1,
if(#p := parent_id, 1, 1)
)
) as line_no
from (select t.* from t order by id) t cross join
(select #p := 0, #rn := 0) params;
The subquery to sort the table may not be necessary. Somewhere around version 5.7, this became necessary when using variables.
EDIT:
Updating with variables is fun. In this case, I would just use subqueries with the above:
update t join
(select t.*,
(#rn := if(#p = parent_id, #rn + 1,
if(#p := parent_id, 1, 1)
)
) as new_line_no
from (select t.* from t order by id) t cross join
(select #p := 0, #rn := 0) params
) tt
on t.id = tt.id
set t.line_no = tt.new_line_no;
Or, a little more old school...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,parent_id INT NOT NULL
);
INSERT INTO my_table VALUES
(1, 2),
(2 , 2),
(3 , 2),
(4 , 3),
(5 , 4),
(6 , 4);
SELECT x.*
, CASE WHEN #prev = parent_id THEN #i := #i+1 ELSE #i := 1 END i
, #prev := parent_id prev
FROM my_table x
, (SELECT #prev:=null,#i:=0) vars
ORDER
BY parent_id,id;
+----+-----------+------+------+
| id | parent_id | i | prev |
+----+-----------+------+------+
| 1 | 2 | 1 | 2 |
| 2 | 2 | 2 | 2 |
| 3 | 2 | 3 | 2 |
| 4 | 3 | 1 | 3 |
| 5 | 4 | 1 | 4 |
| 6 | 4 | 2 | 4 |
+----+-----------+------+------+
You can use subquery if the row_number() doesn't help :
select t.*,
(select count(*)
from table t1
where t1.parent_id = t.parent_id and t1.id <= t.id
) as line_no
from table t;
Imagine I have a database table called Log and it seems like this:
+----+---------+---------+
| id | item_id | message |
+----+---------+---------+
| 1 | 1 | A |
+----+---------+---------+
| 2 | 1 | B |
+----+---------+---------+
| 3 | 1 | C |
+----+---------+---------+
| 4 | 2 | A |
+----+---------+---------+
| 5 | 2 | C |
+----+---------+---------+
| 6 | 2 | B |
+----+---------+---------+
| 7 | 3 | B |
+----+---------+---------+
| 8 | 3 | A |
+----+---------+---------+
| 9 | 3 | C |
+----+---------+---------+
If I query
select * from log where item_id = 1 order by id;
I will receive the rows 1, 2 and 3. The same that if I query
select * from log where item_id = 1 order by message;
But if I make the same with item 2, orders will be different. Ordering by id, the order of rows will be 4, 5 and 6 but ordering by message will be 4, 6 and 5.
So here is my question, is it possible to make a query to know which item_ids have different orders comparing the two queries?
In this example the result would be items 2 and 3.
If I understand correctly, you can use group_concat():
select l.item_id,
group_concat(l.id order by l.id) as id_ordering,
group_concat(l.id order by l.message) as message_ordering
from log l
group by l.item_id;
You can get the ones that are different using a having clause:
select l.item_id,
group_concat(l.id order by l.id) as id_ordering,
group_concat(l.id order by l.message) as message_ordering
from log l
group by l.item_id
having id_ordering <> message_ordering;
You can use variables in order to solve this as a gaps and islands problem.
The following query:
SELECT id, item_id, message, id - seq
FROM (
SELECT id, item_id, message,
#seq := IF(#i = item_id, #seq + 1,
IF(#i := item_id, 1, 1)) AS seq
FROM log
CROSS JOIN (SELECT #i := 0, #seq := 0) AS v
ORDER BY item_id, message) AS t
ORDER BY id;
generates this output:
id, item_id, message, id - seq
--------------------------------
1, 1, A, 0
2, 1, B, 0
3, 1, C, 0
4, 2, A, 3
5, 2, C, 2
6, 2, B, 4
7, 3, B, 5
8, 3, A, 7
9, 3, C, 6
So id-seq can be used in order to detect order mismatches between id and message:
SELECT item_id
FROM (
SELECT id, item_id, message,
#seq := IF(#i = item_id, #seq + 1,
IF(#i := item_id, 1, 1)) AS seq
FROM log
CROSS JOIN (SELECT #i := 0, #seq := 0) AS v
ORDER BY item_id, message) AS t
GROUP BY item_id
HAVING COUNT(DISTINCT id-seq) > 1
ORDER BY id;
Demo here
I have a table with products.
The table has a companyId field.
Let's describe it like this:
id --- companyId
1 | 2
2 | 3
3 | 4
4 | 2
5 | 3
6 | 1
7 | 4
I want to select all the records ordered by companyId but with the company id looping, as so:
id --- companyId
6 | 1
1 | 2
2 | 3
3 | 4
4 | 2
5 | 3
7 | 4
How can I achieve it?
You can achieve this using MySQL user defined variables
SELECT
t.id,
t.companyId
FROM
(
SELECT
*,
IF(#sameCompany = companyId , #rn := #rn + 1,
IF(#sameCompany := companyId, #rn := 1,#rn := 1)
) AS rn
FROM companytable
CROSS JOIN (SELECT #sameCompany := -1, #rn := 1) AS var
ORDER BY companyId
) AS t
ORDER BY t.rn , t.companyId
See Demo
Explanation:
First sort the data according to companyId so that the same company ids stick together.
Now take a walk along this sorted result and assign a sequentially increasing row number every time you see the same companyId otherwise assign 1 as row number.
Now name this sorted result (with row number) t.
Finally sort these data (t) according to ascending row number and ascending companyId.
The current table looks something like this:
[id | section | order | thing]
[1 | fruits | 0 | apple]
[2 | fruits | 0 | banana]
[3 | fruits | 0 | avocado]
[4 | veggies | 0 | tomato]
[5 | veggies | 0 | potato]
[6 | veggies | 0 | spinach]
I'm wondering how to make the table look more like this:
[id | section | order | thing]
[1 | fruits | 1 | apple]
[2 | fruits | 2 | banana]
[3 | fruits | 3 | avocado]
[4 | veggies | 1 | tomato]
[5 | veggies | 2 | potato]
[6 | veggies | 3 | spinach]
"order" column updated to a sequential number, starting at 1, based on "section" column and "id" column.
You can do this with an update by using a join. The second table to the join calculates the ordering, which is then used for the update:
update t join
(select t.*, #rn := if(#prev = t.section, #rn + 1, 1) as rn
from t cross join (select #rn := 0, #prev := '') const
) tsum
on t.id = tsum.id
set t.ordering = tsum.rn
You don't want to do this as an UPDATE, as that will be really slow.
Instead, do this on INSERT. Here's a simple one-line INSERT that will grab the next order number and inserts a record called 'kiwi' in the section 'fruits'.
INSERT INTO `table_name` (`section`, `order`, `thing`)
SELECT 'fruits', MAX(`order`) + 1, 'kiwi'
FROM `table_name`
WHERE `section` = `fruits`
EDIT: You could also do this using an insert trigger, e.g.:
DELIMITER $$
CREATE TRIGGER `trigger_name`
BEFORE INSERT ON `table_name`
FOR EACH ROW
BEGIN
SET NEW.`order` = (SELECT MAX(`order`) + 1 FROM `table_name` WHERE `section` = NEW.`section`);
END$$
DELIMITER ;
Then you could just insert your records as usual, and they will auto-update the order value.
INSERT INTO `table_name` (`section`, `thing`)
VALUES ('fruits', 'kiwi')
Rather than storing the ordering, you could derive it:
SELECT t.id
,t.section
,#row_num := IF (#prev_section = t.section, #row_num+1, 1) AS ordering
,t.thing
,#prev_section := t.section
FROM myTable t
,(SELECT #row_num := 1) x
,(SELECT #prev_value := '') y
ORDER BY t.section, t.id
Note that order is a keyword and is therefore not the greatest for a column name. You could quote the column name or give it a different name...
I have a table 'student_marks' with two columns 'student_id' and 'mark':
student_id | marks
-------------------
1 | 5
2 | 2
3 | 5
4 | 1
5 | 2
I need to compute the rank corresponding to the marks. The expected output for the above table is:
student_id | marks | rank
-------------------------
1 | 5 | 1
2 | 2 | 3
3 | 5 | 1
4 | 1 | 5
5 | 2 | 3
Since the two students with students_id 1 and 3 has highest mark 5, they are placed in rank 1. For students with marks 2, the rank is 3 as there are two students who has more marks then these guys.
How do we write queries to compute the ranks as shown above?
This should work although it's heavy on variables.
SELECT student_id, mark, rank FROM (
SELECT t.*,
#rownum := #rownum + 1 AS realRank,
#oldRank := IF(mark = #previous,#oldRank,#rownum) AS rank,
#previous := mark
FROM student_marks t,
(SELECT #rownum := 0) r,
(SELECT #previous := 100) g,
(SELECT #oldRank := 0) h
ORDER BY mark DESC
) as t
ORDER BY student_id;
Look at this fiddle: http://sqlfiddle.com/#!2/2c7e5/32/0