SQL Server : sorting the values based on ID column - sql-server-2008

The input and the desired output is shown below
Input
id Msg res time
--------------------
1 a1 8 11:14:00
1 qq 8 11:15:00
1 d1 8 11:16:00
1 e1 8 11:17:00
1 f1 8 11:18:00
1 h 4 11:19:00
1 i 4 11:20:00
1 m35 4 11:21:00
1 n 4 11:22:00
1 o 4 11:23:00
1 p0 4 11:23:00
2 a1 4 11:24:00
2 p0 4 11:25:00
2 qq 4 11:26:00
2 c 4 11:27:00
2 h 4 11:28:00
2 o 4 11:29:00
3 c 4 11:30:00
3 qq 4 11:31:00
3 e1 4 11:32:00
Desired output:
id Msg res time
---------------------
1 a1 8 11:14:00
1 d1 8 11:16:00
1 e1 8 11:17:00
1 f1 8 11:18:00
1 h 4 11:19:00
1 i 4 11:20:00
1 p0 4 11:24:00
1 qq 8 11:15:00
1 m35 4 11:21:00
1 n 4 11:23:00
1 o 4 11:22:00
2 a1 4 11:24:00
2 c 4 11:27:00
2 h 4 11:28:00
2 p0 4 11:25:00
2 qq 4 11:26:00
2 o 4 11:29:00
3 c 4 11:30:00
3 e1 4 11:32:00
3 qq 4 11:31:00
The code is below
CREATE TABLE k (id int, Msg varchar(1000), result int, time time);
INSERT INTO k VALUES (‘1’, ‘a1’, ‘8’, ‘11:14:00’)
INSERT INTO k VALUES (‘1’, ‘qq’, ‘8’, ‘11:15:00’)
INSERT INTO k VALUES (‘1’, ‘d1’, ‘8’, ‘11:16:00’)
INSERT INTO k VALUES (‘1’, ‘e1’, ‘8’, ‘11:17:00’)
INSERT INTO k VALUES (‘1’, ‘f1’, ‘8’, ‘11:18:00’)
INSERT INTO k VALUES (‘1’, ‘h’, ‘4’, ‘11:19:00’)
INSERT INTO k VALUES (‘1’, ‘i’, ‘4’, ‘11:20:00’)
INSERT INTO k VALUES (‘1’, ‘m35’, ‘4’, ‘11:21:00’)
INSERT INTO k VALUES (‘1’, ‘n’, ‘4’, ‘11:22:00’)
INSERT INTO k VALUES (‘1’, ‘o’, ‘4’, ‘11:23:00’)
INSERT INTO k VALUES (‘1’, ‘p0’, ‘4’, ‘11:23:00’)
INSERT INTO k VALUES (‘2’, ‘a1’, ‘4’, ‘11:24:00’)
INSERT INTO k VALUES (‘2’, ‘p0’, ‘4’, ‘11:25:00’)
INSERT INTO k VALUES (‘2’, ‘qq’, ‘4’, ‘11:26:00’)
INSERT INTO k VALUES (‘2’, ‘c’, ‘4’, ‘11:27:00’)
INSERT INTO k VALUES (‘2’, ‘h’, ‘4’, ‘11:28:00’)
INSERT INTO k VALUES (‘2’, ‘o’, ‘4’, ‘11:29:00’)
INSERT INTO k VALUES (‘3’, ‘c’, ‘4’, ‘11:30:00’)
INSERT INTO k VALUES (‘3’, ‘qq’, ‘4’, ‘11:31:00’)
INSERT INTO k VALUES (‘3’, ‘e1’, ‘4’, ‘11:32:00’)
select *
from k
order by
case
when Msg = ‘a1’ or Msg = ‘b1’ or Msg = ‘b0’ or Msg = ‘c’ or Msg = ‘d1’ or Msg = ‘e1’ or Msg = ‘f1’ or Msg = ‘g’ or Msg = ‘h’ or Msg = ‘i’ then 1
when Msg = ‘p0’ then 2
when Msg = ‘p1’ then 3
when Msg = ‘qq’ then 4
when Msg = ‘i’ then 5
when Msg = ‘j’ then 6
when Msg = ‘k0’ then 7
when Msg = ‘k1’ then 8
when Msg = ‘l’ then 9
when Msg = ‘l1’ then 10
else 11
end, Msg
But this code is not returning the desired output. In case if there is only one set of values in the ‘id’ column then it sorts the ‘Msg’ column accordingly as per the condition given in the code. But in the sample input there are 3 different sets of values in ‘id’ column (1,2 and 3). So when I execute the above code the sorting is done for the whole data. It is obvious because I did not give any conditions for grouping by ‘id’ column. Is there any solution to achieve the desired result? (Also I tried to fetch each line in the input table by using cursors but even there I was not able to put the conditions properly in the loop). Any help would be useful.

This is what you are looking for:
select * from k order by id, Msg
Basically, you already have the value of 'id' column and you want to order the result by two columns, first the Id and then the Msg; meaning the rows that have the same value in id, will be sorted by the value of Msg.

Related

Is the query acting like it should? [duplicate]

Here is a simplified version of my table:
It contains the columns
employee_id column2 column3 x y
and they are all NUMERIC
I am having trouble writing a complicated MySQL query. I want to get all the employeeid and column3 values for all employees that have the same value in column2 as another employee and have DIFFERENT values for x and y PAIR from every other employee. For example, if the following 4 rows were in table:
2 100 123.456 5 7
1 234 123.456 5 7
3 100 456.789 5 10
4 100 123.456 5 7
The rows 2 100 123.456 5 7 and 3 100 456.789 5 10 should be obtained because they have different employee ids (2 vs 3), the same value for column2 (100 and 100), and different x, y pair: (Employee 2 has x = 5 and y = 7, which is distinct from x = 5 and y = 10).
How can I compare the documents of table with other documents within itself?
SELECT e1.*
FROM employee e1
JOIN employee e2
ON e1.employee_id <> e2.employee_id
AND e1.column2 = e2.column2
AND e1.X <> e2.X
AND e1.Y <> e2.Y
But I guess your last condition should be
AND (e1.X <> e2.X OR e1.Y <> e2.Y)

Joining a table with itself vs using Group By MySQL

Here is a simplified version of my table:
It contains the columns
employee_id column2 column3 x y
and they are all NUMERIC
I am having trouble writing a complicated MySQL query. I want to get all the employeeid and column3 values for all employees that have the same value in column2 as another employee and have DIFFERENT values for x and y PAIR from every other employee. For example, if the following 4 rows were in table:
2 100 123.456 5 7
1 234 123.456 5 7
3 100 456.789 5 10
4 100 123.456 5 7
The rows 2 100 123.456 5 7 and 3 100 456.789 5 10 should be obtained because they have different employee ids (2 vs 3), the same value for column2 (100 and 100), and different x, y pair: (Employee 2 has x = 5 and y = 7, which is distinct from x = 5 and y = 10).
How can I compare the documents of table with other documents within itself?
SELECT e1.*
FROM employee e1
JOIN employee e2
ON e1.employee_id <> e2.employee_id
AND e1.column2 = e2.column2
AND e1.X <> e2.X
AND e1.Y <> e2.Y
But I guess your last condition should be
AND (e1.X <> e2.X OR e1.Y <> e2.Y)

Put all rows from one table to another

how I can put all the rows from table A to table B witch all table have the same columns names
ex :
table A :
x y z
1 2 3
4 5 6
7 8 9
table B:
x y z
10 11 12
table C should be like this :
x y z
1 2 3
4 5 6
7 8 9
10 11 12
PS : I am using a query , I do'nt want to insert in a real table
If you only want to select data from A and B, the UNION works:
SELECT x, y, z FROM A
UNION SELECT x, y, z FROM B
If you want to insert data from A to B (which contradicts your P.S. but corresponds to your first sentence) then this is your solution:
INSERT into B SELECT * from A

Improvement of VBA code in Access to do something like a pivot table

I have a table of data in MS Access 2007. There are 6 fields per record, thousands of records. I want to make a sort of pivot table like object. That is, if any two rows happens to be the same in the first 4 fields, then they will end up grouped together into one row. The column headers in this pivot table will be the values from the 5th field, and the value in the pivot table will be the 6th field, a dollar amount. Think of the 5th field as letters A, B, C, D, E, F, G. So, the table I start with might have a row with A in the 5th field and $3.48 in the 6th field. Another row may match in the first 4 fields, have B in the 5th field and $8.59 in the 6th field. Another may match in the first 4 fields, have E in the 5th field and $45.20 in the 6th field. I want all these rows to be turned into one row (in a new table) that starts with the first 4 fields where they match, then lists $3.48, $8.59, $0.00, $0.00, $45.20, $0.00, $0.00, corresponding to column headers A, B, C, D, E, F, G (since no records contained C, D, F, G, their corresponding values are $0.00), and then ends with one more field that totals up the money in that row.
Currently, I have some VBA code that does this, written by someone else a few years ago. It is extremely slow and I am hoping for a better way. I asked a previous question (but not very clearly so I was advised to create a new question), where I was asking if there was a better way to do this in VBA. My question asked about reading and writing large amounts of data all at once in Access through VBA, which I know is a good practice in Excel. That is, I was hoping to take my original table and just assign the entire thing to an array all at once (as in Excel, instead of cell by cell), then work with that array in VBA and create some new array and then write that entire array all at once to a new table (instead of record by record, field by field). From the answers in that question, it seems like that is not really a possibility in Access, but my best bet might be to use some sort of query. I tried the Query Wizard and found the Cross Tab query which is close to what I describe above. But, there appears to be a max of 3 fields used in the Row Heading, whereas here I have 4. And, instead of putting $0.00 when a value is not specified (like C, D, F, G in my example above), it just leaves a blank.
Update (in response to Remou's comment to give sample data): Here is some sample data.
ID a b c d e f
7 1 2 3 5 A 5
8 1 2 3 5 B 10
9 1 2 3 5 C 15
10 1 2 3 5 D 20
11 1 2 3 5 E 25
12 1 2 4 4 A 16
13 1 2 4 4 B 26
14 1 3 3 7 D 11
15 1 3 3 7 B 11
The result should be:
a b c d an bn cn dn en Total
1 2 3 5 5 10 15 20 25 75
1 2 4 4 16 26 0 0 0 42
1 3 3 7 0 11 0 11 0 22
But, when I copy and paste the SQL given by Remou, the only output I get is
a b c d an bn cn dn en
1 2 3 5 5 10 15 20 25
This is, I think, what you want, but it would be better to consider database design, because this is a spreadsheet-like solution.
SELECT t0.a,
t0.b,
t0.c,
t0.d,
Iif(Isnull([a1]), 0, [a1]) AS an,
Iif(Isnull([b1]), 0, [b1]) AS bn,
Iif(Isnull([c1]), 0, [c1]) AS cn,
Iif(Isnull([d1]), 0, [d1]) AS dn,
Iif(Isnull([e1]), 0, [e1]) AS en
FROM (((((SELECT DISTINCT t.a,
t.b,
t.c,
t.d
FROM table3 t) AS t0
LEFT JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS a1
FROM table3 t
WHERE t.e = "A") AS a0
ON ( t0.d = a0.d )
AND ( t0.c = a0.c )
AND ( t0.b = a0.b )
AND ( t0.a = a0.a ))
LEFT JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS b1
FROM table3 t
WHERE t.e = "B") AS b0
ON ( t0.d = b0.d )
AND ( t0.c = b0.c )
AND ( t0.b = b0.b )
AND ( t0.a = b0.a ))
LEFT JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS c1
FROM table3 t
WHERE t.e = "C") AS c0
ON ( t0.d = c0.d )
AND ( t0.c = c0.c )
AND ( t0.b = c0.b )
AND ( t0.a = c0.a ))
LEFT JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS d1
FROM table3 t
WHERE t.e = "D") AS d0
ON ( t0.d = d0.d )
AND ( t0.c = d0.c )
AND ( t0.b = d0.b )
AND ( t0.a = d0.a ))
LEFT JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS e1
FROM table3 t
WHERE t.e = "E") AS e0
ON ( t0.d = e0.d )
AND ( t0.c = e0.c )
AND ( t0.b = e0.b )
AND ( t0.a = e0.a );
Table3
ID a b c d e f
1 1 2 3 4 a €10.00
2 1 2 3 4 b €10.00
3 1 2 3 4 c €10.00
4 1 2 3 4 d €10.00
5 1 2 3 4 e €10.00
6 1 2 3 5 a €10.00
7 1 2 3 5 b
8 1 2 3 5 c €10.00
9 1 2 3 5 d €10.00
10 1 2 3 5 e €10.00
Result
There are two rows, because there are only two different sets in the first four columns.
a b c d an bn cn dn en
1 2 3 4 €10.00 €10.00 €10.00 €10.00 €10.00
1 2 3 5 €10.00 €0.00 €10.00 €10.00 €10.00
The way the sql above is supposed to work, is that it selects each of the four definition columns and the currency column from the table where the sort column has a particular sort letter and labels the currency column with the sort letter, each of these sub queries are then assembled, however, you can take a sub query and look at the results. The last one is the part between the parentheses:
INNER JOIN (SELECT t.a,
t.b,
t.c,
t.d,
t.f AS e1
FROM table3 t
WHERE t.e = "E") AS e0

Get the column order of a query

I have a table with two columns [id, value] both numeric.
In this example:
[ id, value ]
[ 1, 6 ]
[ 2, 4 ]
[ 3, 10 ]
[ 4, 2 ]
[ 5, 7 ]
[ 6, 3 ]
For a given id I'd like to retrieve the top 3 id's (those with highest value), their top position and if the given id is not in the top 3, also get its position, id and value:
Example 1: ask_id = 5 Return:
[ position, id, value ]
[ 1, 3, 10 ]
[ 2, 5, 7 ]
[ 3, 1, 6 ]
Example 2: ask_id = 4. Return:
[ position, id, value ]
[ 1, 3, 10 ]
[ 2, 5, 7 ]
[ 3, 1, 6 ]
[ 6, 4, 2 ]
So the important points are:
How to get for the position column?
How to get the additional row if possible (anyway there's no problem if I need two queries)?
select t2.pos, t1.id, t1.value
from test as t1
inner join
(select id, value, #pos:=if(#pos is null, 0, #pos)+1 as pos
from test order by value desc) as t2
on t1.id=t2.id
where t2.pos<=3 or t2.id={$ask_id}
order by t2.pos;
Basically, the idea is like this:
Rank the rows by value.
Retrieve rows where at least one of the following is true:
position BETWEEN 1 AND 3
id = #given_id
These posts give examples of how you could substitute ranking functions (at least the most fundamental of them, ROW_NUMBER()) in MySQL:
ROW_NUMBER() in MySQL
MSSQL Row_Number() over(order by) in MySql
This method should be used with caution, though, as this article explains.
That said, one possible implementation of the above steps might look like this:
SET #pos = 0;
SELECT
position,
id,
value
FROM (
SELECT
id,
value,
#pos := #pos + 1 AS position
FROM atable
ORDER BY value DESC
) s
WHERE position BETWEEN 1 AND 3
OR id = #given_id
ORDER BY position
Tested in MySQL
to retrieve the top 3 id's (those with highest value) with position in ascending order.
set #num = 0;
SELECT #num := #num + 1 as position_sequence,id,value FROM tablename
ORDER BY value desc
limit 3;
I've not (yet) tested the selected answer in MySQL on the interesting cases where there are ties in the top three places, but I have tested this code in Informix on those cases, and it produces the answer I think should be produced.
Assuming that the table is called leader_board:
CREATE TABLE leader_board(id INTEGER NOT NULL PRIMARY KEY, value INTEGER NOT NULL);
INSERT INTO leader_board(id, value) VALUES(1, 6);
INSERT INTO leader_board(id, value) VALUES(2, 4);
INSERT INTO leader_board(id, value) VALUES(3, 10);
INSERT INTO leader_board(id, value) VALUES(4, 2);
INSERT INTO leader_board(id, value) VALUES(5, 7);
INSERT INTO leader_board(id, value) VALUES(6, 3);
This query works on the data shown, assuming that the special ID is 4:
SELECT b.position - c.tied + 1 AS standing, a.id, a.value
FROM leader_board AS a
JOIN (SELECT COUNT(*) AS position, d.id
FROM leader_board AS d
JOIN leader_board AS e ON (d.value <= e.value)
GROUP BY d.id
) AS b
ON a.id = b.id
JOIN (SELECT COUNT(*) AS tied, f.id
FROM leader_board AS f
JOIN leader_board AS g ON (f.value = g.value)
GROUP BY f.id
) AS c
ON a.id = c.id
WHERE (a.id = 4 OR (b.position - c.tied + 1) <= 3) -- Special ID = 4; Top N = 3
ORDER BY position, a.id;
Output on original data:
standing id value
1 3 10
2 5 7
3 1 6
6 4 2
Explanation
The two sub-queries are closely related, but they produce different answers. At one time, I used two temporary tables to hold those results. In particular, the first sub-query (AS b) produces a position, but when there are ties, the position is the lowest rather than the highest of the tied positions. That is, given:
ID Value
1 10
2 7
3 7
4 7
The outputs will be:
Position ID
1 1
4 2
4 3
4 4
However, we would like to count them as:
Position ID
1 1
2 2
2 3
2 4
So, the corrected position is the original position minus the number of tied values (3 for ID ∈ { 2, 3, 4 }, 1 for ID 1) plus 1. The second sub-query returns the number of tied values for each ID. There might be a neater way to do that calculation, but I'm not sure what it is at the moment.
Special cases
However, the code should demonstrate that it handles the cases where:
There are 2 or more ID values with the same top value.
There are 2 or more ID values with the same second highest top score (but the top one is unique).
There are 2 or more ID values with the same third highest top score (but the top two are unique).
To save rewriting the query each time, I converted it into an Informix-style stored procedure which take both the Special ID and the Top N (defaulting to 3) values that should be displayed and made them into parameters of the procedure. (Yes, the notation in the RETURNING clause is weird.)
CREATE PROCEDURE leader_board_standings(extra_id INTEGER, top_n INTEGER DEFAULT 3)
RETURNING INTEGER AS standing, INTEGER AS id, INTEGER AS value;
DEFINE standing, id, value INTEGER;
FOREACH SELECT b.position - c.tied + 1 AS standing, a.id, a.value
INTO standing, id, value
FROM leader_board AS a
JOIN (SELECT COUNT(*) AS position, d.id
FROM leader_board AS d
JOIN leader_board AS e ON (d.value <= e.value)
GROUP BY d.id
) AS b
ON a.id = b.id
JOIN (SELECT COUNT(*) AS tied, f.id
FROM leader_board AS f
JOIN leader_board AS g ON (f.value = g.value)
GROUP BY f.id
) AS c
ON a.id = c.id
WHERE (a.id = extra_id OR (b.position - c.tied + 1) <= top_n)
ORDER BY position, a.id
RETURN standing, id, value WITH RESUME;
END FOREACH;
END PROCEDURE;
This can be invoked to produce the same result as before:
EXECUTE PROCEDURE leader_board_standings(4);
To illustrate the various cases outlined above, add and remove extra rows:
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
6 4 2
INSERT INTO leader_board(id, value) VALUES(10, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
3 5 7
7 4 2
INSERT INTO leader_board(id, value) VALUES(11, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
1 11 10
8 4 2
INSERT INTO leader_board(id, value) VALUES(12, 10);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
1 10 10
1 11 10
1 12 10
9 4 2
DELETE FROM leader_board WHERE id IN (10, 11, 12);
EXECUTE PROCEDURE leader_board_standings(6, 4); -- Special ID 6; Top 4
1 3 10
2 5 7
3 1 6
4 2 4
5 6 3
INSERT INTO leader_board(id, value) VALUES(7, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
7 4 2
INSERT INTO leader_board(id, value) VALUES(13, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
2 13 7
8 4 2
INSERT INTO leader_board(id, value) VALUES(14, 7);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
2 7 7
2 13 7
2 14 7
9 4 2
DELETE FROM leader_board WHERE id IN(7, 13, 14);
INSERT INTO leader_board(id, value) VALUES(8, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
7 4 2
INSERT INTO leader_board(id, value) VALUES(9, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
3 9 6
8 4 2
INSERT INTO leader_board(id, value) VALUES(15, 6);
EXECUTE PROCEDURE leader_board_standings(4);
1 3 10
2 5 7
3 1 6
3 8 6
3 9 6
3 15 6
9 4 2
EXECUTE PROCEDURE leader_board_standings(3); -- Special ID 3 appears in top 3
1 3 10
2 5 7
3 1 6
That all looks correct to me.