How can get null column after UNPIVOT? - sql

I have got the following query:
WITH data AS(
SELECT * FROM partstat WHERE id=4
)
SELECT id, AVG(Value) AS Average
FROM (
SELECT id,
AVG(column_1) as column_1,
AVG(column_2) as column_2,
AVG(column_3) as column_3
FROM data
GROUP BY id
) as pvt
UNPIVOT (Value FOR V IN (column_1,column_2,column_3)) AS u
GROUP BY id
if column_1,column_2 and column_3 (or one of this columns) have values then i get result as the following:
id, Average
4, 5.12631578947368
if column_1,column_2 and column_3 have NULL values then the query does not return any rows as the following:
id, Average
my question is how can i get as the following result if columns contents NULL values?
id, Average
4, NULL

Have you tried using COALESCE or ISNULL?
e.g.
ISNULL(AVG(column_1), 0) as column_1,
This does mean that you will get 0 as the result instead of 'NULL' though - do you need null when they are all NULL?
Edit:
Also, is there any need for an unpivot? Since you are specifying all 3 columns, why not just do:
SELECT BankID, (column_1 + column_2 + column_3) / 3 FROM partstat
WHERE bankid = 4
This gives you the same results but with the NULL
Of course this is assuming you have 1 row per bankid
Edit:
UNPIVOT isn't supposed to be used like this as far as I can see - I'd unpivot first then try the AVG... let me have a go...
Edit:
Ah I take that back, it is just a problem with NULLs - other posts suggest ISNULL or COALESCE to eliminate the nulls, you could use a placeholder value like -1 which could work e.g.
SELECT bankid, AVG(CASE WHEN value = -1 THEN NULL ELSE value END) AS Average
FROM (
SELECT bankid,
isnull(AVG(column_1), -1) as column_1 ,
AVG(Column_2) as column_2 ,
Avg(column_3) as column_3
FROM data
group by bankid
) as pvt
UNPIVOT (Value FOR o in (column_1, column_2, column_3)) as u
GROUP BY bankid
You need to ensure this will work though as if you have a value in column2/3 then column_1 will no longer = -1. It might be worth doing a case to see if they are all NULL in which case replacing the 1st null with -1

Here is an example without UNPIVOT:
DECLARE #partstat TABLE (id INT, column_1 DECIMAL(18, 2), column_2 DECIMAL(18, 2), column_3 DECIMAL(18, 2))
INSERT #partstat VALUES
(5, 12.3, 1, 2)
,(5, 2, 5, 5)
,(5, 2, 2, 2)
,(4, 2, 2, 2)
,(4, 4, 4, 4)
,(4, 21, NULL, NULL)
,(6, 1, NULL, NULL)
,(6, 1, NULL, NULL)
,(7, NULL, NULL, NULL)
,(7, NULL, NULL, NULL)
,(7, NULL, NULL, NULL)
,(7, NULL, NULL, NULL)
,(7, NULL, NULL, NULL)
;WITH data AS(
SELECT * FROM #partstat
)
SELECT
pvt.id,
(ISNULL(pvt.column_1, 0) + ISNULL(pvt.column_2, 0) + ISNULL(pvt.column_3, 0))/
NULLIF(
CASE WHEN pvt.column_1 IS NULL THEN 0 ELSE 1 END +
CASE WHEN pvt.column_2 IS NULL THEN 0 ELSE 1 END +
CASE WHEN pvt.column_3 IS NULL THEN 0 ELSE 1 END
, 0)
AS Average
FROM (
SELECT id,
AVG(column_1) as column_1,
AVG(column_2) as column_2,
AVG(column_3) as column_3
FROM data
GROUP BY id
) as pvt

Related

sql SERVER - distinct selection based on priority columns

hello I would like to find a solution to solve my problem in a single request if possible.
For the moment I take all the records then I go through the lines one by one to eliminate what I don't want.
I have 2 tables : first table with links
the second with the prefered label for the url
the second table must be consulted keeping only the row with maximum priority
priority rules are
the current user then
the user group and finally
everyone.
if the hidden column is true, exclude any reference to the url
here is the expected result.
Unfortunately, I don't see any other solution than to multiply the conditions on several selects and unions.
if you have a idea to solve my problem, thank you in advance for your help
It appears as though you can rely on pref_id for the preference ordering, correct? If so, you could try:
SELECT *
FROM table2
INNER JOIN table1 ON table2.url_id = table1.url_id
QUALIFY ROW_NUMBER() OVER (
PARTITION BY table1.url
ORDER BY pref_id ASC
) = 1
This will partition by the url and then provide only the one with lowest pref_id.
I didn't test this SQL as I wasn't sure which RDBMS you're running on, but I used Rasgo to translate the SQL.
maybe of interest in this tricky query:
select so.*, table1.url from
(select distinct t.url_id,
(select pref_id from table2 s where s.url_id = t.url_id order by "user" is null, "group" is null limit 1) pref_id
from table2 t
where not exists(select 1 from table2 s where s.hide and s.url_id = t.url_id)
) ids
join table2 so on so.pref_id = ids.pref_id
join table1 ON table1.url_id = ids.url_id
order by so.url_id;
here is my solution but i think there is better to do.
in the condition's select, I built a column which gives a level note according to the priorities
DECLARE #CUR_USER VARCHAR(10) = 'ROBERT'
DECLARE #CUR_GROUP VARCHAR(10) = 'DEV'
DECLARE #TABLE1 TABLE (
URL_ID INT
,URLNAME VARCHAR(100)
);
DECLARE #TABLE2 TABLE (
PREF_ID INT
,URL_ID INT
,FAVORITE_LABEL VARCHAR(100)
,USER_GROUP VARCHAR(10)
,USER_CODE VARCHAR(10)
,HIDE_URL DECIMAL(1, 0) DEFAULT 0
);
INSERT INTO #TABLE1
VALUES
(1, 'https://stackoverflow.com/')
,(2, 'https://www.microsoft.com/')
,(3, 'https://www.apple.com/')
,(4, 'https://www.wikipedia.org/')
;
INSERT INTO #TABLE2
VALUES
(1000, 1, 'find everything', NULL, 'ROBERT', 0)
,(1001, 1, 'a question ? find the answer here', 'DEV', NULL, 0)
,(1002, 1, 'StackOverFlow', NULL, NULL, 0)
,(1003, 2, 'Windows', 'DEV', NULL, 0)
,(1004, 2, 'Microsoft', NULL, NULL, 0)
,(1005, 3, 'Apple', NULL, NULL, 0)
,(1006, 4, 'Free encyclopedia', NULL, 'ROBERT', 1)
,(1007, 4, 'Wikipedia', NULL, NULL, 0)
,(1008, 1, 'StackOverFlow FOR MAT', 'MAT', NULL, 0)
,(1009, 2, 'Microsoft FOR MAT', 'MAT', NULL, 0)
,(1010, 3, 'Apple', 'MAT', NULL, 1)
,(1011, 4, 'Wikipedia FOR MAT', 'MAT', NULL, 0)
,(1012, 1, 'StackOverFlow', NULL, 'JEAN', 1)
,(1013, 2, 'Microsoft ', NULL, 'JEAN', 0)
,(1014, 3, 'Apple', NULL, 'JEAN', 0)
,(1015, 4, 'great encyclopedia', NULL, 'JEAN', 0)
;
SELECT t2.* ,t1.URLName
FROM #TABLE1 t1
INNER JOIN #TABLE2 t2 ON t1.URL_ID = t2.URL_ID
WHERE EXISTS (
SELECT 1
FROM (
SELECT TOP (1) test.PREF_ID
,CASE
-- if I do not comment this case: jean from the MAT group will not see apple
-- WHEN Hide_Url = 1
-- THEN 3
WHEN USER_code IS NOT NULL
THEN 2
WHEN USER_GROUP IS NOT NULL
THEN 1
ELSE 0
END AS ROW_LEVEL
FROM #TABLE2 test
WHERE (
(
test.USER_GROUP IS NULL
AND test.user_group IS NULL
AND test.USER_code IS NULL
)
OR (test.USER_GROUP = #CUR_GROUP)
OR (test.USER_code = #CUR_USER)
)
AND t2.URL_ID = test.URL_ID
ORDER BY ROW_LEVEL DESC
) test
WHERE test.PREF_ID = t2.PREF_ID
AND Hide_Url = 0
)
Simply use an ORDER BY clause that puts the preferred row first. You can use this in the window function ROW_NUMBER and work with this or use a lateral top(1) join with CROSS APPLY.
select *
from urls
cross apply
(
select top(1) *
from labels
where labels.url_id = urls.url_id
where [Group] is not null or [user] is not null or hide is not null
order by
case when [Group] is null then 2 else 1 end,
case when [user] is null then 2 else 1 end,
case when hide is null then 2 else 1 end
) top_labels
order by urls.url_id;

Select rows after the current row which are null and combine them to rows

I have written the following SQL code to display the data as rows, where the row after data is having null values except on the description column.
DECLARE #StudentData TABLE
(
RowID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
RemarksDate NVARCHAR(20),
StudentName NVARCHAR(1000),
Description NVARCHAR(MAX),
TotStudents NVARCHAR(100)
)
INSERT INTO #StudentData(RemarksDate, StudentName, Description, TotStudents)
VALUES('2/1/2021', NULL, 'Poor In English', '14'),
(NULL, NULL, '1 ABC', NULL),
(NULL, NULL, '1 XYZ', NULL),
(NULL, NULL, '1 MNO', NULL),
(NULL, NULL, '1 IGH', NULL),
(NULL, NULL, '10 KKK', NULL),
('2/1/2021', NULL, 'Poor In Maths', '5'),
(NULL, NULL, '5 PQR', NULL),
('2/8/2021', NULL, 'Poor In Social', '1'),
(NULL, NULL, '1 RST', NULL)
This results in the output as follows:
I have written the following query to group and display rows:
SELECT t1.RemarksDate, LTRIM(RIGHT(t2.Description, LEN(t2.Description) - PATINDEX('%[0-9][^0- 9]%', t2.Description ))) StudentName, t1.Description
,LEFT(t2.Description, PATINDEX('%[0-9][^0-9]%', t2.Description ))
FROM (
SELECT *, RowID + TotStudents MaxVal
FROM #StudentData
WHERE RemarksDate is NOT NULL
) t1
JOIN (
SELECT *
FROM #StudentData
WHERE RemarksDate is NULL
) t2 ON t2.RowId BETWEEN t1.RowID and t1.MaxVal
The data is displayed as follows
Expected output is as follows
2/1/2021 ABC Poor In English 1
2/1/2021 XYZ Poor In English 1
2/1/2021 MNO Poor In English 1
2/1/2021 IGH Poor In English 1
2/1/2021 KKK Poor In English 10
2/1/2021 PQR Poor In Maths 5
2/8/2021 RST Poor In Social 1
This is a type of gaps-and-islands problem. There are many solutions, I will give you one that only requires a single scan of the base table.
We have a header row and child rows, and we need to apply the header row values to the child rows.
We can solve this by defining the start point of each group, then taking windowed header values for each group and finally filtering out the header rows
WITH Groupings AS (
SELECT *,
GroupId = MAX(CASE WHEN Description LIKE 'Poor%' THEN RowID END)
OVER (ORDER BY RowID ROWS UNBOUNDED PRECEDING)
FROM #StudentData s
),
GroupValues AS (
SELECT
RemarksDate = MAX(CASE WHEN Description LIKE 'Poor%' THEN RemarksDate END)
OVER (PARTITION BY GroupId),
DescriptionHeader = MAX(CASE WHEN Description LIKE 'Poor%' THEN Description END)
OVER (PARTITION BY GroupId),
Space = CHARINDEX(' ', Description),
Description
FROM Groupings
)
SELECT
RemarksDate,
DescriptionHeader,
StudentName = SUBSTRING(Description, Space + 1, LEN(Description)),
SomeNumber = LEFT(Description, Space - 1)
FROM GroupValues
WHERE Description NOT LIKE 'Poor%';
db<>fiddle
Except the fact that the table design is pretty awful, I would suggest the following approach:
WITH cteRemarks AS(
SELECT *, LEAD(RowId) OVER (ORDER BY RowID) AS RowIdNxt
FROM #StudentData
WHERE TotStudents IS NOT NULL
)
SELECT r.RemarksDate
,RIGHT(t.Description, LEN(t.Description)-CHARINDEX(' ', t.Description)) AS StudentsName
,r.Description AS Description
,LEFT(t.Description, CHARINDEX(' ', t.Description)-1) AS Val
FROM cteRemarks r
LEFT JOIN #StudentData t ON t.TotStudents IS NULL
AND t.RowID > r.RowID
AND t.RowID < ISNULL(r.RowIDNxt, 99999999)

Convert multiple bit columns into one field

I have the following table that contains multiple bit columns, and what I'm wanting to do is convert it for display purposes with a hard-coded label based on the bit column values. This is shown in the highlighted yellow column name expectedresult.
Test Data
CREATE TABLE [dbo].[testbit](
[StaffId] [int] NULL,
[type1] [bit] NOT NULL,
[type2] [bit] NULL,
[type3] [bit] NULL,
[type4] [bit] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[testbit] ([StaffId], [type1], [type2], [type3], [type4]) VALUES (1, 1, 0, 0, 1)
GO
INSERT [dbo].[testbit] ([StaffId], [type1], [type2], [type3], [type4]) VALUES (2, 0, 1, 0, 0)
GO
INSERT [dbo].[testbit] ([StaffId], [type1], [type2], [type3], [type4]) VALUES (3, 1, 1, 1, 1)
GO
Use a CASE WHEN <expr> or CASE <expr> WHEN <value> expression, then concatenate in an outer-query with CONCAT_WS.
A CASE expression evaluates to NULL when an ELSE case is omitted, so these expressions are equivalent:
CASE x WHEN 1 THEN 'foo' END
CASE x WHEN 1 THEN 'foo' ELSE NULL END
CASE WHEN x = 1 THEN 'foo' END
CASE WHEN x = 1 THEN 'foo' ELSE NULL END
The CONCAT_WS function is variadic and concatenates non-NULL strings with the separator string (the first argument).
CONCAT_WS( ', ', 'a', 'b', NULL, NULL, 'c' ) == 'a, b, c'
Here's how I'd do it:
SELECT
StaffId,
CONCAT_WS( ', ', Type1Text, Type2Text, Type3Text, Type4Text ) AS TypeNames
FROM
(
SELECT
StaffId,
CASE type1 WHEN 1 THEN 'Type1Name' END AS Type1Text,
CASE type2 WHEN 1 THEN 'Type2Name' END AS Type2Text,
CASE type2 WHEN 1 THEN 'Type3Name' END AS Type3Text,
CASE type3 WHEN 1 THEN 'Type4Name' END AS Type4Text
FROM
...
) AS iq
The outer query can be elided if you don't mind making it slightly harder to read:
SELECT
StaffId,
CONCAT_WS( ', ',
CASE type1 WHEN 1 THEN 'Type1Name' END AS Type1Text,
CASE type2 WHEN 1 THEN 'Type2Name' END AS Type2Text,
CASE type2 WHEN 1 THEN 'Type3Name' END AS Type3Text,
CASE type3 WHEN 1 THEN 'Type4Name' END AS Type4Text
) AS TypeNames
FROM
...

How can I only pull back the latest result in SQL?

I'm struggling to find out how to only return the latest iteration of the claimtid in the result set. I'm using this query:
SELECT
claimid, paiddate
CASE
WHEN actid = '119' THEN 'Channel Exception'
WHEN actid = '127' THEN 'Rejected'
WHEN actid = '128' THEN 'Accepted'
WHEN actid = '130' THEN 'Adjustment Complete'
WHEN actid = '133' THEN 'Channel Ready'
END AS [Status]
FROM
Encounter
WHERE
claimtid LIKE '173225AR0%' OR claimtid LIKE '197565GL0%' OR
claimtid LIKE '293215QW0%' OR claimtid LIKE
ORDER BY
claimtid
This query returns the following result:
|claimtid |paiddt |Status |
-+----------+----------+-------------------+
1|173225AR00|2017-03-01|Adjustment Complete|
2|173225AR01|2017-04-11|Accepted |
3|197565GL00|2017-03-17|Accepted |
4|197565GL01|2017-03-19|Adjustment Complete|
5|197565GL02|2017-04-01|Rejected |
6|293215QW00|2017-04-19|Adjustment Complete|
7|293215QW01|2017-04-23|Accepted |
I'm not sure what I can add to my query so that the results will only bring back lines 2, 5, and 7. My actual query contains produces more rows in the result.
This is only an example, but is accurate to the situation. I'll need to pull back more than 3 rows, but it needs to be the latest iteration.
Each additional iteration makes the last number in the claimtid go up by one. I won't know how many iterations there are of each claimtid.
Try this, but I am not sure of the performance penalty for using a string for claimtid as in your case:
SELECT claimid, paiddate
FROM
Encounter
WHERE claimtid IN (
SELECT MAX(paiddt), claimtid
FROM Encounter
GROUP BY SUBSTRING (claimtid, 6, 8)
) t
Assuming you can count on the paiddate field to know the most recent record.
If you can't separate the two fields of the claim ID into separate columns of the data table itself, you can pull them apart in a query, to allow you to use the max() aggregate to find the largest value of the second field.
select
claim,
paiddate,
CASE
WHEN actid = '119' THEN 'Channel Exception'
WHEN actid = '127' THEN 'Rejected'
WHEN actid = '128' THEN 'Accepted'
WHEN actid = '130' THEN 'Adjustment Complete'
WHEN actid = '133' THEN 'Channel Ready'
END AS [Status]
from
(
select
left(claimid, 6) as claim,
max(right(claimid,4)) as seq,
from
Encounter
group by
left(claimid, 6)
) as ms
inner join Encounter as e
on e.claimid = ms.claim + ms.seq;
This should do the trick...
IF OBJECT_ID('tempdb..#ClaimData', 'U') IS NOT NULL
DROP TABLE #ClaimData;
CREATE TABLE #ClaimData (
RN INT NOT NULL IDENTITY(1,1),
claimtid CHAR(10) NOT NULL,
paiddt DATE NOT NULL,
[Status] VARCHAR(20) NOT NULL
);
INSERT #ClaimData (claimtid, paiddt, Status) VALUES
('173225AR00', '2017-03-01', 'Adjustment Complete'),
('173225AR01', '2017-04-11', 'Accepted'),
('197565GL00', '2017-03-17', 'Accepted'),
('197565GL01', '2017-03-19', 'Adjustment Complete'),
('197565GL02', '2017-04-01', 'Rejected'),
('293215QW00', '2017-04-19', 'Adjustment Complete'),
('293215QW01', '2017-04-23', 'Accepted');
--SELECT * FROM #ClaimData cd;
--=========================================================
SELECT TOP 1 WITH TIES
cd.RN, cd.claimtid, cd.paiddt, cd.Status
FROM
#ClaimData cd
CROSS APPLY ( VALUES (SUBSTRING(cd.claimtid, 1, 6), SUBSTRING(cd.claimtid, 7, 4)) ) sc (Claim_1, Claim_2)
ORDER BY
ROW_NUMBER() OVER (PARTITION BY sc.Claim_1 ORDER BY sc.Claim_2 DESC);
Results...
RN claimtid paiddt Status
----------- ---------- ---------- --------------------
2 173225AR01 2017-04-11 Accepted
5 197565GL02 2017-04-01 Rejected
7 293215QW01 2017-04-23 Accepted
Edit...
A slightly better performing solution that produces the same results...
SELECT
RN = CAST(SUBSTRING(MAX(bv.BinaryValue), 39, 4) AS INT),
claimtid = CAST(SUBSTRING(MAX(bv.BinaryValue), 1, 10) AS CHAR(10)),
paiddt = CAST(SUBSTRING(MAX(bv.BinaryValue), 11, 8) AS DATE),
Status = CAST(SUBSTRING(MAX(bv.BinaryValue), 19, 20) AS VARCHAR(20))
FROM
#ClaimData cd
CROSS APPLY ( VALUES (CAST(cd.claimtid AS BINARY(10)) + CAST(cd.paiddt AS BINARY(8)) + CAST(cd.Status AS BINARY(20)) + CAST(cd.RN AS BINARY(4))) ) bv (BinaryValue)
GROUP BY
SUBSTRING(cd.claimtid, 1, 6);
Try this, assuming you can't have a early claimtid with a later paiddt:
IF OBJECT_ID('tempdb..#ClaimData') IS NOT NULL
DROP TABLE #ClaimData
CREATE TABLE #ClaimData (
ID INT NOT NULL IDENTITY(1,1)
, claimtid CHAR(10) NOT NULL
, paiddt DATE NOT NULL
, [Status] VARCHAR(20) NOT NULL
)
INSERT #ClaimData (claimtid, paiddt, Status) VALUES
('173225AR00', '2017-03-01', 'Adjustment Complete')
,('173225AR01', '2017-04-11', 'Accepted')
,('197565GL00', '2017-03-17', 'Accepted')
,('197565GL01', '2017-03-19', 'Adjustment Complete')
,('197565GL02', '2017-04-01', 'Rejected')
,('293215QW00', '2017-04-19', 'Adjustment Complete')
,('293215QW01', '2017-04-23', 'Accepted')
SELECT
ID
, x.claimtid
, x.paiddt
, x.Status
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY LEFT(Claimtid, LEN(Claimtid) - 2) ORDER BY paiddt DESC) RN
, *
FROM #ClaimData
) x
WHERE x.RN = 1
Otherwise change RN to ROW_NUMBER() OVER (PARTITION BY LEFT(Claimtid, LEN(Claimtid) - 2) ORDER BY RIGHT(Claimtid, 2) DESC) RN

How to get AVG in CASE with condition?

I have a table with integer values.
They could be negative, 0, positive and NULL.
I need treat NULL as 0, calculate average for a given date and if average value is less than 0 then put 0 there.
My query is the following:
select
Id,
ValueDate,
case
when avg(isnull(Value, 0)) > 0 then avg(isnull(Value, 0))
else 0
end AvgValue
from SomeTable
where ValueDate = #givenDate
group by Id, ValueDate
How to avoid double aggregate function definition in case statement (aggregate statement could be much more complex)?
I think the greatest function could help you:
select
Id,
ValueDate,
greatest(avg(isnull(Value, 0)),0) AvgValue
from SomeTable
where ValueDate = #givenDate
group by Id, ValueDate
This is a solution without creating implementation of any not build-in functions. I know your example will be more complex but this is just an idea:
CREATE TABLE DataSource
(
[ID] TINYINT
,[Value] INT
)
INSERT INTO DataSource ([ID], [Value])
VALUES (1, 2)
,(1, 0)
,(1, NULL)
,(1, 98)
,(1, NULL)
,(2, -4)
,(2, 0)
,(2, 0)
,(2, NULL)
SELECT [ID]
,MAX([Value])
FROM
(
SELECT [ID]
,AVG(COALESCE([Value],0))
FROM DataSource
GROUP BY [ID]
UNION ALL
SELECT DISTINCT [ID]
,0
FROM DataSource
) Data([ID],[Value])
GROUP BY [ID]
Here is the fiddle - http://sqlfiddle.com/#!6/3d223/14