SQL sort equal values using case - sql

A similar question is already asked here. But current scenario is little bit complex than previous. In the example if same Itime then we can can sort by case but if Itime and result is same then how can I sort.
My question is, here the result ID: 3,5,6,1,2,7,8,4. Why it is 2,7,8 for fail case .
Why it is not 8,2,7?
If I want the expected result like: 3,5,1,6,8,2,7,4 how can I proceed?
Please run the below commands and help me to sort. Thanks in advance.
if object_id('tempdb.dbo.#temp321','U') is not null
drop table tempdb.dbo.#temp321
create table #temp321(id int, uname varchar(50), current_point int,
previous_point int, ITime datetime, Result varchar(10))
INSERT into #temp321 values('1','a','50','40','2012-11-12 13:12:28.103','pass')
INSERT into #temp321 values('2','b','15','10','2012-11-12 13:12:28.103','fail')
INSERT into #temp321 values('3','c','71','70','2012-11-12 12:58:30.000','pass')
INSERT into #temp321 values('4','d','34','30','2012-11-12 13:12:28.103','withdraw')
INSERT into #temp321 values('5','e','40','35','2012-11-12 12:58:41.360','withdraw')
INSERT into #temp321 values('6','f','65','60','2012-11-12 13:12:28.103','pass')
INSERT into #temp321 values('7','g','20','15','2012-11-12 13:12:28.103','fail')
INSERT into #temp321 values('8','h','10','7','2012-11-12 13:12:28.103','fail')
select
ID
from
#temp321
ORDER BY
ITime ASC,
CASE Result
WHEN 'pass' THEN 1
WHEN 'fail' THEN 2
WHEN 'withdrow' THEN 3
END
drop table #temp321
Current output ID: 3,5,6,1,2,7,8,4
Expected Output ID: 3,5,1,6,8,2,7,4

The current query will NOT deliver the same order every time.
For me your example delivers:
3, 5, 1, 6, 2, 7, 8, 4 (Note 1 and 6 being swapped)
1 and 6 are "equal" compared to their sort values taking into account for sorting. And if no sorting is specified (or equal sortings) the order within that bunch is - per definition - undefined. (depends on the order threads created the data)
Same applies for 2, 7, 8. You want the order 3, 5, 1, 6, 8, 2, 7, 4 - so you seem to "have" a logic how you expect it to be sorted? Then add that condition and you are done :)
(for your expected output adding current_point is what you want - but YOU have to know if you want to sort by that column)
SELECT *
FROM temp321
ORDER BY ITime ASC,
CASE Result
WHEN 'pass' THEN 1
WHEN 'fail' THEN 2
WHEN 'withdraw' THEN 3
END, current_point ASC

You can create subquery with as key value pair:
SELECT 'pass' value, 1 priority UNION ALL
SELECT 'fall' value, 2 priority UNION ALL
SELECT 'withdraw' value, 3 priority UNION ALL
Then do a join to a subquery on value column and order by priority, that will give you cleaner solution. You can even create temporary table and index by value if there are a lot of lookup values, to ensure speed is appropriate.

Related

SQL - TOP , IN ,WHERE , AND

I wrote a sentence like that :
"SELECT TOP 4 Words1, Words2, Type, ID FROM Table1 WHERE Unit IN (1, 2, 3 ) AND Type IN (4 )"
It works well for me
Then I tried something like that:
"SELECT TOP 4 Words1, Words2, Type, ID FROM Table1 WHERE Unit IN (1, 2, 3 ) AND Type IN (4 ) WHERE ID NOT IN ( SELECT TOP 4 ID FROM Table1 WHERE Unit IN (1, 2, 3 ) AND Type IN (4 ) )"
I try not to take the first 4 rows,
I couldn't write the syntax for that
any suggestions how to fix? thank you.
You miss the ORDER BY:
SELECT TOP 4
Words1,
Words2,
Type,
ID
FROM
Table1
WHERE
Unit IN (1, 2, 3)
AND
Type IN (4)
AND
ID NOT IN
(SELECT TOP 4 ID
FROM Table1
WHERE Unit IN (1, 2, 3) AND Type IN (4)
ORDER BY ID DESC)
ORDER BY
ID DESC
Assuming you are using SQL Server, use OFFSET/FETCH:
SELECT Words1, Words2, Type, ID
FROM Table1
WHERE Unit IN (1, 2, 3 ) AND Type IN (4 )
OFFSET 4 FETCH FIRST 4 ROWS ONLY;
You would use OFFSET 0 to start at the first row.
SELECT TOP 4 Words1, Words2, Type, ID
FROM Table1
WHERE Unit IN (1, 2, 3 )
AND Type IN (4 )
AND ID NOT IN (
SELECT TOP 4 ID
FROM Table1
WHERE Unit IN (1, 2, 3 ) AND Type IN (4 )
)
I think you just an error in using WHERE a second time instead of AND in your main clause. Although this will run without an error, you should include an ORDER BY when you use TOP. It makes what you're trying to do clearer. My guess is you're basing this on ID. You can get away with that in Access since it is probably the Primary Key, so it's used by default. It's also nice to understand what problem you're trying to solve. SQL statements don't always show intent.

Count length of consecutive duplicate values for each id

I have a table as shown in the screenshot (first two columns) and I need to create a column like the last one. I'm trying to calculate the length of each sequence of consecutive values for each id.
For this, the last column is required. I played around with
row_number() over (partition by id, value)
but did not have much success, since the circled number was (quite predictably) computed as 2 instead of 1.
Please help!
First of all, we need to have a way to defined how the rows are ordered. For example, in your sample data there is not way to be sure that 'first' row (1, 1) will be always displayed before the 'second' row (1,0).
That's why in my sample data I have added an identity column. In your real case, the details can be order by row ID, date column or something else, but you need to ensure the rows can be sorted via unique criteria.
So, the task is pretty simple:
calculate trigger switch - when value is changed
calculate groups
calculate rows
That's it. I have used common table expression and leave all columns in order to be easy for you to understand the logic. You are free to break this in separate statements and remove some of the columns.
DECLARE #DataSource TABLE
(
[RowID] INT IDENTITY(1, 1)
,[ID]INT
,[value] INT
);
INSERT INTO #DataSource ([ID], [value])
VALUES (1, 1)
,(1, 0)
,(1, 0)
,(1, 1)
,(1, 1)
,(1, 1)
--
,(2, 0)
,(2, 1)
,(2, 0)
,(2, 0);
WITH DataSourceWithSwitch AS
(
SELECT *
,IIF(LAG([value]) OVER (PARTITION BY [ID] ORDER BY [RowID]) = [value], 0, 1) AS [Switch]
FROM #DataSource
), DataSourceWithGroup AS
(
SELECT *
,SUM([Switch]) OVER (PARTITION BY [ID] ORDER BY [RowID]) AS [Group]
FROM DataSourceWithSwitch
)
SELECT *
,ROW_NUMBER() OVER (PARTITION BY [ID], [Group] ORDER BY [RowID]) AS [GroupRowID]
FROM DataSourceWithGroup
ORDER BY [RowID];
You want results that are dependent on actual data ordering in the data source. In SQL you operate on relations, sometimes on ordered set of relations rows. Your desired end result is not well-defined in terms of SQL, unless you introduce an additional column in your source table, over which your data is ordered (e.g. auto-increment or some timestamp column).
Note: this answers the original question and doesn't take into account additional timestamp column mentioned in the comment. I'm not updating my answer since there is already an accepted answer.
One way to solve it could be through a recursive CTE:
create table #tmp (i int identity,id int, value int, rn int);
insert into #tmp (id,value) VALUES
(1,1),(1,0),(1,0),(1,1),(1,1),(1,1),
(2,0),(2,1),(2,0),(2,0);
WITH numbered AS (
SELECT i,id,value, 1 seq FROM #tmp WHERE i=1 UNION ALL
SELECT a.i,a.id,a.value, CASE WHEN a.id=b.id AND a.value=b.value THEN b.seq+1 ELSE 1 END
FROM #tmp a INNER JOIN numbered b ON a.i=b.i+1
)
SELECT * FROM numbered -- OPTION (MAXRECURSION 1000)
This will return the following:
i id value seq
1 1 1 1
2 1 0 1
3 1 0 2
4 1 1 1
5 1 1 2
6 1 1 3
7 2 0 1
8 2 1 1
9 2 0 1
10 2 0 2
See my little demo here: https://rextester.com/ZZEIU93657
A prerequisite for the CTE to work is a sequenced table (e. g. a table with an identitycolumn in it) as a source. In my example I introduced the column i for this. As a starting point I need to find the first entry of the source table. In my case this was the entry with i=1.
For a longer source table you might run into a recursion-limit error as the default for MAXRECURSION is 100. In this case you should uncomment the OPTION setting behind my SELECT clause above. You can either set it to a higher value (like shown) or switch it off completely by setting it to 0.
IMHO, this is easier to do with cursor and loop.
may be there is a way to do the job with selfjoin
declare #t table (id int, val int)
insert into #t (id, val)
select 1 as id, 1 as val
union all select 1, 0
union all select 1, 0
union all select 1, 1
union all select 1, 1
union all select 1, 1
;with cte1 (id , val , num ) as
(
select id, val, row_number() over (ORDER BY (SELECT 1)) as num from #t
)
, cte2 (id, val, num, N) as
(
select id, val, num, 1 from cte1 where num = 1
union all
select t1.id, t1.val, t1.num,
case when t1.id=t2.id and t1.val=t2.val then t2.N + 1 else 1 end
from cte1 t1 inner join cte2 t2 on t1.num = t2.num + 1 where t1.num > 1
)
select * from cte2

SQL Server Sum a specific number of rows based on another column

Here are the important columns in my table
ItemId RowID CalculatedNum
1 1 3
1 2 0
1 3 5
1 4 25
1 5 0
1 6 8
1 7 14
1 8 2
.....
The rowID increments to 141 before the ItemID increments to 2. This cycle repeats for about 122 million rows.
I need to SUM the CalculatedNum field in groups of 6. So sum 1-6, then 7-12, etc. I know I end up with an odd number at the end. I can discard the last three rows (numbers 139, 140 and 141). I need it to start the SUM cycle again when I get to the next ItemID.
I know I need to group by the ItemID but I am having trouble trying to figure out how to get SQL to SUM just 6 CalculatedNum's at a time. Everything else I have come across SUMs based on a column where the values are the same.
I did find something on Microsoft's site that used the ROW_NUMBER function but I couldn't quite make sense of it. Please let me know if this question is not clear.
Thank you
You need to group by (RowId - 1) / 6 and ItemId. Like this:
drop table if exists dbo.Items;
create table dbo.Items (
ItemId int
, RowId int
, CalculatedNum int
);
insert into dbo.Items (ItemId, RowId, CalculatedNum)
values (1, 1, 3), (1, 2, 0), (1, 3, 5), (1, 4, 25)
, (1, 5, 0), (1, 6, 8), (1, 7, 14), (1, 8, 2);
select
tt.ItemId
, sum(tt.CalculatedNum) as CalcSum
from (
select
*
, (t.RowId - 1) / 6 as Grp
from dbo.Items t
) tt
group by tt.ItemId, tt.Grp
You could use integer division and group by.
SELECT ItemId, (RowId-1)/6 as Batch, sum(CalculatedNum)
FROM your_table GROUP BY ItemId, Batch
To discard incomplete batches:
SELECT ItemId, (RowId-1)/6 as Batch, sum(CalculatedNum), count(*) as Cnt
FROM your_table GROUP BY ItemId, Batch HAVING Cnt = 6
EDIT: Fix an off by one error.
To ensure you're querying 6 rows at a time you can try to use the modulo function : https://technet.microsoft.com/fr-fr/library/ms173482(v=sql.110).aspx
Hope this can help.
Thanks everyone. This was really helpful.
Here is what we ended up with.
SELECT ItemID, MIN(RowID) AS StartingRow, SUM(CalculatedNum)
FROM dbo.table
GROUP BY ItemID, (RowID - 1) / 6
ORDER BY ItemID, StartingRow
I am not sure why it did not like the integer division in the select statement but I checked the results against a sample of the data and the math is correct.

SQL Ordering records by "weight"

We have a system that processes records by a "priority" number in a table. We define the priority by the contents of the table, e.g.
UPDATE table
SET priority=3
WHERE processed IS NULL
UPDATE table
SET priority=2
WHERE balance>50
UPDATE table
SET priority=1
WHERE value='blah'
(please ignore the fact that there could be 'overlaps' between priorities :) )
This works fine - the table is processed in priority order, so all the rows where the column "value" is 'blah' are worked first.
I've been given the task of adding an option to order the records by a definable "weight". For example, we'd like 50% of the processing to be priority 1, 25% priority 2 and 25% priority 3. Therefore, from the above, in every 100 records 50 of them would be ones where "value" is 'blah", 25 of them would be where "balance" is greater than 50 etc.
I'm trying to figure out how to do this: some kind of weighted incrementing value for "priority" would seem to be the best way, but I can't get my head around how to code this. Can anyone help please?
EDIT: Apologies, should have said: this is running on MSSQL 2008
General idea is to collect tasks into buckets, divided on border of whole numbers:
select
task_id
from (
select
task_id,
((task_priority_order - 1) / task_priority_density) as task_processing_order
from (
select
t.task_id as task_id,
t.priority as task_priority,
row_number()
over (partition by t.priority order by t.priority) as task_priority_order,
case
when t.priority = 3 then 50
when t.priority = 2 then 25
when t.priority = 1 then 25
end as task_priority_density
from
table t
)
)
order by task_processing_order
In the diapason from 0.0 to 0.(9) we got 100 records constructed from first 50 records with priority 3, first 25 records with priority 2 and first 25 records with priority 1.
The next diapason from 1.0 to 1.(9) represents next bucket of records.
If no more tasks with some value of priority then remaining tasks will be placed in buckets in same ratio. E.g. if not enough tasks with priority 3 then remaining tasks will be arranged with ratio of 50/50.
task_id - some surrogate key for task identification.
P.S. Sorry, I can't test this query now, so any syntax correction very appreciated.
Update: Query syntax corrected according to comments.
Given test script provides the following output. If you would lay out some rules about what the end result should be, I'm willing to take another look at it.
Results
Priority Processed Balance Value
3 NULL NULL NULL
NULL 0 49 NULL
NULL 1 49 NULL
NULL 0 50 NULL
NULL 1 50 NULL
2 0 51 NULL
2 1 51 NULL
2 0 51 Notblah
1 1 51 blah
Test script
DECLARE #Table TABLE (Priority INTEGER, Processed BIT, Balance INTEGER, Value VARCHAR(32))
INSERT INTO #Table VALUES
(NULL, NULL, NULL, NULL)
, (NULL, 0, 49, NULL)
, (NULL, 1, 49, NULL)
, (NULL, 0, 50, NULL)
, (NULL, 1, 50, NULL)
, (NULL, 0, 51, NULL)
, (NULL, 1, 51, NULL)
, (NULL, 0, 51, 'Notblah')
, (NULL, 1, 51, 'blah')
UPDATE #table SET priority=3 WHERE processed IS NULL
UPDATE #table SET priority=2 WHERE balance > 50
UPDATE #table SET priority=1 WHERE value = 'blah'
SELECT *
FROM #table

In MYSQL, how can I select multiple rows and have them returned in the order I specified?

I know I can select multiple rows like this:
select * FROM table WHERE id in (1, 2, 3, 10, 100);
And I get the results returned in order: 1, 2, 3, 10, 100
But, what if I need to have the results returned in a specific order? When I try this:
select * FROM table WHERE id in (2, 100, 3, 1, 10);
I still get the results returned in the same order: 1, 2, 3, 10, 100
Is there a way to get the results returned in the exact order that I ask for?
(There are limitations due to the way the site is set up that won't allow me to ORDER BY using another field value)
the way you worded that I'm not sure if using ORDER BY is completely impossible or just ordering by some other field... so at the risk of submitting a useless answer, this is how you'd typically order your results in such a situation.
SELECT *
FROM table
WHERE id in (2, 100, 3, 1, 10)
ORDER BY FIELD (id, 2, 100, 3, 1, 10)
Unless you are able to do ORDER BY, there is no guaranteed way.
The sort you are getting is due to the way MySQL executes the query: it combines all range scans over the ranges defined by the IN list into a single range scan.
Usually, you force the order using one of these ways:
Create a temporary table with the value and the sorter, fill it with your values and order by the sorter:
CREATE TABLE t_values (value INT NOT NULL PRIMARY KEY, sorter INT NOT NULL)
INSERT
INTO t_values
VALUES
(2, 1),
(100, 1),
(3, 1),
(1, 1),
(10, 1);
SELECT m.*
FROM t_values v
JOIN mytable m
ON m.id = v.value
ORDER BY
sorter
Do the same with an in-place rowset:
SELECT m.*
FROM (
SELECT 2 AS value, 1 AS sorter
UNION ALL
SELECT 100 AS value, 2 AS sorter
UNION ALL
SELECT 3 AS value, 3 AS sorter
UNION ALL
SELECT 1 AS value, 4 AS sorter
UNION ALL
SELECT 10 AS value, 5 AS sorter
)
JOIN mytable m
ON m.id = v.value
ORDER BY
sorter
Use CASE clause:
SELECT *
FROM mytable m
WHERE id IN (1, 2, 3, 10, 100)
ORDER BY
CASE id
WHEN 2 THEN 1
WHEN 100 THEN 2
WHEN 3 THEN 3
WHEN 1 THEN 4
WHEN 10 THEN 5
END
You can impose an order, but only based on the value(s) of one or more columns.
To get the rows back in the order you specify in the example you would need to add a second column, called a "sortkey" whose values can be used to sort the rows in the desired sequence,
using the ORDER BY clause. In your example:
Value Sortkey
----- -------
1 4
2 1
3 3
10 5
100 2
select value FROM table where ... order by sortkey;