How to perform stable sort over multiple columns? - sql-server

Imagine i have a data set that contains:
Date Id
-------------- ----
11/1/2017 null
11/4/2017 3
11/5/2017 null
11/12/2017 10
null 1
null 2
null 7
null 8
null 9
I want the rows ordered so that both columns are increasing.
Using a naïve ORDER BY Date, ID does not do it:
There is an ordering
There is an ordering that satisfies the results of my desired sort order:
the date column is always increasing
the id column value is always increasing
Or course, that's not a unique ordering:
Date Id
-------------- ---------------
null 1
11/1/2017 null
null 2
11/4/2017 3
null 7
null 8
null 9
11/5/2017 null
11/12/2017 10
A programming language could do it
I know i can accomplish this on the client side. In a functional functional programming language: use a stable sorting algorithm:
A stable sort is one which preserves the original order of the input
set, where the comparison algorithm does not distinguish between two
or more items.
Consider a sorting algorithm that sorts cards by rank, but not by
suit. The stable sort will guarantee that the original order of cards
having the same rank is preserved; the unstable sort will not.
Unfortunately i have
9.1 million rows
1.8 GB
of monotonically increasing rows to put in best possible chronological sort order. Obviously i'd prefer to do this on the server - which is well suited to handling large amounts of data.
How can i perform a stable-sort in SQL Server?
Example Data
Example SQL Fiddle
CREATE TABLE #SortDemo (Date datetime NULL, Id int NULL)
INSERT INTO #SortDemo (Date, Id)
VALUES
('20171101', null),
('20171104', 3),
('20171105', null),
('20171112', 10),
(null, 1),
(null, 2),
(null, 7),
(null, 8),
(null, 9)
SELECT * FROM #SortDemo
ORDER BY Date, Id

It is a solvable problem. You don't have to throw your hands up and say computers cannot be used to solve problems.
Start with the data, and add a new surrogate "Rank" column.
Date Id Rank
-------------- ---- ----
null 7 null
null 1 null
null 9 null
null 2 null
null 8 null
11/1/2017 null null
11/4/2017 3 null
11/5/2017 null null
11/12/2017 10 null
11/13/2017 null null
Then seed Rank to the Id:
UPDATE SortDemo SET Rank = Id
WHERE Id IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null null
11/4/2017 3 3
11/5/2017 null null
11/12/2017 10 10
11/13/2017 null null
Then for the remaining items items with a Date, we need to assign it to the "next" rank:
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null null <-- 3
11/4/2017 3 3
11/5/2017 null null <-- 10
11/12/2017 10 10
11/13/2017 null null <-- ?
with
UPDATE SortDemo SET Rank = (
SELECT MIN(Rank) FROM SortDemo s2
WHERE s2.Date >= SortDemo.Date)
WHERE SortDemo.Date IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null 3 <--
11/4/2017 3 3
11/5/2017 null 10 <--
11/12/2017 10 10
11/13/2017 null null <-- ?
There's also the edge case where there are no items "after" us; which we can fix by going backwards to one the highest previous rank:
UPDATE SortDemo SET Rank = (
SELECT MAX(Rank) FROM SortDemo s2
WHERE s2.Date <= SortDemo.Date)
WHERE SortDemo.Date IS NOT NULL
Date Id Rank
-------------- ---- ----
null 7 7
null 1 1
null 9 9
null 2 2
null 8 8
11/1/2017 null 3
11/4/2017 3 3
11/5/2017 null 10
11/12/2017 10 10
11/13/2017 null 10 <--10
And we're done
We can now sort by surrogate Rank, and break ties by sorting by Date:
SELECT * FROM SortDemo
ORDER BY Rank, Date
Date Id Rank
-------------- ---- ----
null 1 1
null 2 2
11/1/2017 null 3
11/4/2017 3 3
null 7 7
null 8 8
null 9 9
11/5/2017 null 10
11/12/2017 10 10
11/13/2017 null 10
Solution complete. Sql Fiddle
The answer is in escrow until Monday, so i can give people the opportunity to earn reputation for solving a unique problem.

Related

How to update 2 tables using T-SQL?

I'm just wondering why still getting not correct result (0 row(s) affected)
with my update SQL. Please help I just need to update the Table A from Table B data with Price, and Size. After executing the update script I get 0 rows(s) affected. Why?
Table A:
TableAId CountNo Class RoomNo Section Price Sale Size
4 1 NULL 9 B 24347000 NULL NULL
5 1 NULL 9 C 26881000 NULL NULL
12 1 NULL 8 B 24245000 NULL NULL
16 1 NULL 8 A 39038000 NULL NULL
3 1 NULL 8 C 26495370 NULL NULL
21 1 NULL 6 D 36423000 NULL NULL
14 1 NULL 6 C 27200000 NULL NULL
1 1 NULL 5 C 30483000 NULL NULL
2 1 NULL 5 D 41052330 NULL NULL
Table B:
TableBId CountNo Class RoomNo Section Transaction Sale Size
12 1 NULL 9 B NULL 24347000 23800
20 1 NULL 9 C NULL 26881000 22800
44 1 NULL 9 NULL NULL 40079000 23100
69 1 NULL 9 D NULL 37614000 22100
21 1 NULL 8 C NULL 26763000 22700
28 1 NULL 8 D NULL 37444000 22000
13 1 NULL 8 B NULL 24245000 23700
5 1 NULL 8 A NULL 39038000 22500
6 1 NULL 7 A NULL 39558000 22800
Updated table:
TableAId CountNo Class RoomNo Section Price Sale Size
4 1 NULL 9 B 24347000 24347000 23800
5 1 NULL 9 C 26881000 26881000 22800
12 1 NULL 8 B 24245000 24245000 23700
16 1 NULL 8 A 39038000 39038000 22500
3 1 NULL 8 C 26495370 26763000 22700
21 1 NULL 6 D NULL NULL NULL
14 1 NULL 6 C NULL NULL NULL
1 1 NULL 5 C NULL NULL NULL
2 1 NULL 5 D NULL NULL NULL
SQL statement:
UPDATE x
SET x.Sale = y.Sale,
x.Size = y.Size
FROM TableA x
JOIN TableB y ON x.CountNo = y.CountNo
AND x.Class = y.Class
AND x.RoomNo = y.RoomNo
AND x.Section = y.Section
(0 row(s) affected)
try this: you need to compare null values separately
UPDATE x
SET
x.Sale = y.Sale,
x.Size = y.Size
FROM TableA x
JOIN TableB y
ON
x.CountNo = y.CountNo AND
(x.Class = y.Class OR (x.Class IS NULL and y.Class IS NULL)) AND
x.RoomNo = y.RoomNo AND
x.Section = y.Section
I believe that is happening because of NULL
UPDATE x
SET
x.Sale = y.Sale,
x.Size = y.Size
FROM
TableA x
JOIN TableB y ON x.CountNo = y.CountNo
AND ISNULL(x.Class,'') = ISNULL(y.Class,'')
AND x.RoomNo = y.RoomNo
AND ISNULL(x.Section,'') = ISNULL(y.Section,'')
You need add ISNULL(, ) If column is nullable
UPDATE x
SET
x.Sale = y.Sale,
x.Size = y.Size
FROM TableA x
JOIN TableB y
ON
ISNULL(x.CountNo, 0) = ISNULL(y.CountNo, 0) AND
ISNULL(x.Class, '') = ISNULL(y.Class, '') AND
ISNULL(x.RoomNo, 0) = ISNULL(y.RoomNo, 0) AND
ISNULL(x.Section, '') = ISNULL(y.Section, 0)

SQL Server Multiple Counts in the same Query

I'm sure there is an easy way to do this but I've been struggling with this one...
Suppose I have an order table like so:
OrderId OrderStatus DriverId TripId
------- ----------- ------ ----
1 Available 5 2
2 Available 5 2
3 Available 5 2
4 Delivered 5 2
5 Delivered 5 3
6 Delivered 6 2
I want to group by each Driver and Trip with an extra column displaying the count of the OrderStatus when it is equal to 'Available'. So, for example
TotalOrderCountInTrip DriverId TripId AvailableOrdersCount
--------------------- -------- ------ --------------------
4 5 2 3
1 6 2 0
I've gotten this far but I can't figure out how to add the AvailableOrdersCount column:
select count(*) TotalOrderCountInTrip, dos.DriverId, dos.TripId
from DriverOrderSet dos (nolock)
group by
dos.DriverId,
dos.TripId
Add as a column:
sum(case when OrderStatus = 'Available' then 1 else 0 end)

SQL Server 2008 grouping based on 2 columns which contains null values

My question is in the table structure below. All items grouped by storeName and storeId then displays the total of each item for each store. But some of the items not sold in some stores therefore displays NULL.. Is that possible to
display egg&bacon instead of NULL for the first 8 records in itemName column and
display coke for instead of NULL the second 8 in itemName column and so on.
I do not know the how many item they have. So it is dynamic.
I want to do this because I have created a crosstab report with pentaho report designer. It works fine but it expects data in order to display it properly. So each item must have 8 records, one for each store. So I can group it by store and itemname. So the report displays - or 0 for the items not sold for those stores.
what I have
storeId storeName itemName total
1 storeA egg&bacon 75
2 storeB NULL NULL
3 storeC egg&bacon 30
4 storeD NULL NULL
5 storeE NULL NULL
6 storeF egg&bacon 50
7 storeG NULL NULL
8 storeH NULL NULL
1 storeA coke 105
2 storeB coke 90
3 storeC coke 60
4 storeD NULL NULL
5 storeE coke 20
6 storeF coke 80
7 storeG NULL NULL
8 storeH NULL NULL
what I want (If the table below is not realistic, I can create a separate column to group them but again how?)
storeId storeName itemName total
1 storeA egg&bacon 75
2 storeB egg&bacon 0
3 storeC egg&bacon 30
4 storeD egg&bacon 0
5 storeE egg&bacon 0
6 storeF egg&bacon 50
7 storeG egg&bacon 0
8 storeH egg&bacon 0
1 storeA coke 105
2 storeB coke 90
3 storeC coke 60
4 storeD NULL 0
5 storeE coke 20
6 storeF coke 80
7 storeG NULL 0
8 storeH NULL 0
This is the sample SQL statement
SELECT storeId , storeName, itemName, total FROM STORE
LEFT OUTER JOIN
(
SELECT storeId, storeName, itemId, total FROM REVENUE_CENTER as RVC
LEFT OUTER JOIN MENU_ITEM_TOTAL as MIT ON STORE.storeId = MIT.storeId
) as subQ ON STORE.storeId = subQ.storeId
LEFT OUTER JOIN MENU_ITEM as MI ON MI.itemId = subQ.itemId
thanks in advance
Oz.
If you want to know you have zero egg&bacon in a store you must at least set the item name to 'egg&bacon'
to change null to zero in the total column you can use ISNULL(itemName, 0)
since storeid and name are constant you should only keep storeid in the table, and group by it (better performance, less data space) and keep store name in another table. That will change your group by statements to be based only on store ID.
Consider not saving records for items shat are now null, null - this is something your code should handle.

Reassigning value using a CASE statement - T-SQL

here is a simple example of how my return data is structured:
id | cat id | value | reverse
1 | 1 | 5 | false
2 | 1 | 5 | false
3 | 2 | 2 | true
4 | 2 | 1 | false
5 | 1 | 3 | true
In the sql statement below, I am trying to get the count for each value type group by value column. If the reverse column is true, I need to reverse the scale. So the scale would be:
5 = 1
4 = 2
3 = 3
2 = 4
1 = 5
I tried the following SQL statement, but it seems not to be applying the reverse scale to the count, it is just ignoring the reverse flag all together:
select value, count(
case reverse
WHEN false THEN
CASE value
WHEN '5' THEN '1'
WHEN '4' THEN '2'
WHEN '3' THEN '3'
WHEN '2' THEN '4'
WHEN '1' THEN '5'
END
ELSE value
END
)
from table1
group by value
What I am doing wrong? How do I apply the reverse logic to count the reverse question value to the correct value?
So I may be misunderstanding; if so I apologize. But my understanding is that you're wanting to get the counts by an absolute value....so if I have five rows that had a value of 1 and five rows that had a value of 5 (reversed), I would want to show a count of ten for value of 1.
Assuming that's the case and that you have a string instead of an int (but that the values are all integers and not something more complicated:
;WITH reversed AS
(
SELECT id,
[cat id],
CASE WHEN [reverse] = 1 THEN 6 - CAST([value] AS INT)
ELSE CAST([value] AS INT) END AS [calcValue]
FROM table1
)
SELECT calcValue, COUNT(id) AS counts
FROM reversed
GROUP BY calcValue;
Part of the problem with the original calculation is that you're not doing anything with the original value before grouping it, you're just taking the count of the case statement. The COUNT() won't be different if the values within it are different, so long as the values aren't NULL.
Try this:
with data as (
select * from ( values
(1,1,5,'false'),
(2,1,5,'false'),
(3,2,2,'true'),
(4,2,1,'false'),
(5,1,3,'true')
) data(id,cat,value,reverse)
)
select
id,
cat,
value = case when reverse='false' then value else 6-value end
from data
returning this:
id cat value
----------- ----------- -----------
1 1 5
2 1 5
3 2 4
4 2 1
5 1 3

T-SQL Divide value across rows

I have the following columns and data in table:
PeriodID Days
1 NULL
2 NULL
3 NULL
4 NULL
5 NULL
Then I have days that should divide across the rows as follows:
If Days < 5 (for example 2) I will have:
PeriodID Days
1 NULL
2 NULL
3 NULL
4 1
5 1
If days >= 5 and days%5=0 (for example 5) I will have:
PeriodID Days
1 1
2 1
3 1
4 1
5 1
If days > 5 and days%5!=0 (for example 12) I will have:
PeriodID Days
1 3
2 3
3 2
4 2
5 2
I am able doing this with loops, and I hope for better solution using some smart technique or T-SQL function. Thanks in advance.
This should do it for you:
DECLARE #numDays int
SET #numDays = 12
UPDATE someTable
SET Days = CASE WHEN #numDays < 5
THEN CASE WHEN #numDays >= 6 - PeriodId THEN 1 ELSE NULL END
ELSE FLOOR((#numDays + 5 - PeriodId) / 5)
END

Resources