I have two tables PostalCodes (with one column with values from 00-00 to 99-999) and Customers (which has, beside all the customer's data, a postal code and ID of employee which is serving the customer).
So these two I am simply joining via postal code:
SELECT DISTINCT
KP.postal,
K.IDemp
FROM
PostalCodes KP
LEFT JOIN
[Customers] K ON K.postal = KP.postal
and I'm getting this:
| postal | IDemp |
-------- -------
| 00-000 | NULL |
| 00-001 | NULL |
| 00-001 | 12PH |
| 00-002 | NULL |
| 00-003 | NULL |
| 00-004 | NULL |
| 00-004 | 10PH |
| 00-005 | NULL |
| ... | ... |
So as you can see not all postal codes are used in the Customers table, but for my aim I need all postal codes assigned to some employee to created something like "area of service", so to do that I want to fill null values with last not null value to get something like this:
| postal | IDemp |
-------- -------
| 00-000 | NULL |
| 00-001 | 12PH |
| 00-002 | 12PH |
| 00-003 | 12PH |
| 00-004 | 10PH |
| 00-005 | 10PH |
| ... | ... |
I was trying to use LAG() function, but it was not working (or at least I don't know how use it properly)
LAG(K.IDemp) OVER (ORDER BY KP.postal)
I found few similar questions already, but could not come up how to use their answers to my case.
CodePudding user response:
SQL Server doesn't support the ignore nulls option on LAG (yet), but you can get around this by creating a binary value from the column you want to order by, and the column you want to retrieve and calling MAX which does ignore nulls. A full working solution would be:
IF OBJECT_ID(N'tempdb..#T', 'U') IS NOT NULL
DROP TABLE #T;
CREATE TABLE #T (Postal VARCHAR(6) NOT NULL, IDemp VARCHAR(4) NULL);
INSERT #T (Postal, IDemp)
VALUES
('00-000', NULL),
('00-001', '12PH'),
('00-002', NULL),
('00-003', NULL),
('00-004', '10PH'),
('00-005', NULL);
SELECT *,
LastNonNull = CONVERT(VARCHAR(6),
SUBSTRING(
MAX(CONVERT(BINARY(6), Postal) CONVERT(BINARY(4), IDemp))
OVER(ORDER BY Postal), 7,4))
FROM #T;
It might help explain if this is broken down a bit and we look at the results of this:
SELECT *,
BinaryValue = CONVERT(BINARY(6), Postal) CONVERT(BINARY(4), IDemp)
FROM #T
| Postal | IDemp | BinaryValue |
|---|---|---|
| 00-000 | NULL | NULL |
| 00-001 | 12PH | 0x30302D30303131325048 |
| 00-002 | NULL | NULL |
| 00-003 | NULL | NULL |
| 00-004 | 10PH | 0x30302D30303431305048 |
| 00-005 | NULL | NULL |
Since concatenating null value yields a null value, you only get a value where it is not null. You can then take advantage of binary sorting (left to right) and get the maximum value of this binary within a windowed function, that is the part: MAX(...) OVER(ORDER BY Postal).
This removes all the NULL values (since MAX ignores NULL) apart from the first row, since there is no previous non null value and gives data as follows:
| Postal | IDemp | MaxBinaryValue |
|---|---|---|
| 00-000 | NULL | NULL |
| 00-001 | 12PH | 0x30302D30303131325048 |
| 00-002 | NULL | 0x30302D30303131325048 |
| 00-003 | NULL | 0x30302D30303131325048 |
| 00-004 | 10PH | 0x30302D30303431305048 |
| 00-005 | NULL | 0x30302D30303431305048 |
It is then just a case of extracting the portion of the binary you are interested in (characters 7-10) and converting back to varchar using SUBSTRING and CONVERT
CodePudding user response:
A correlated sub-query might work:
SELECT DISTINCT
KP.postal,
(SELECT TOP 1 K.IDemp
FROM [Customers] K
WHERE K.postal <= KP.postal
AND K.IDemp Is Not Null
ORDER BY K.postal DESC) As IDemp
FROM
PostalCodes KP
