Order By with Case Statements Not Working as Expected - sql-server

I'm trying to sort a report in the order that the end user wants to work it.
First, it should be sorted by the following scenarios:
String_Field is populated, but Comment_Date is null
String_Field is not populated
Both String_Field and Comment_Date are populated
And then it should be sorted by the aging of Task_Date.
I've added the following to my ORDER BY:
(case when max(table1.comment_date) is null then 2 else 1 end) DESC,
(case when table1.string_field is null then 2 else 1 end) ASC,
max(table2.task_date) ASC
And then I added them to my SELECT also:
(case when max(table1.comment_date) is null then 2 else 1 end) as sort1
(case when table1.string_field is null then 2 else 1 end) as sort2
max(table2.task_date) as task_date
What I'm getting, and cannot figure out for the life of me why, is this (edited to show the high/low at each change):
I've tried casting, moving the aggregation around, and adding the values together, and I still end up with those 2s stuck in the middle of some invisible split in the 1s.
If it makes any difference, I come up with the values for string_field via a CTE Union Query, and comment_date is joined to string_field in a second CTE, before the second CTE (table1) is left joined to the table that contains task_date (table2). You can have a string_field without a comment_date, but never a comment_date without a string_field. There must be a task_date for the entry to appear in the report. The report is grouped by string_field.
I'm on SQL Server 2008 R2.
Thanks in advance for your help!
Edited to add Quasi-SQL:
WITH
old_new_string AS (
SELECT
tablea.old_string_field,
tablea.string_field,
'A' as match_type
FROM
tablea
WHERE
tablea.old_string_field is not null
UNION ALL
SELECT
B1.string_field AS old_string_field,
B2.string_field AS string_field,
'B1' as match_type
FROM
tableb B1
INNER JOIN tableb B2 ON B1.string_field <> B2.string_field AND B1.id1 = B2.id1
WHERE
B1.id1 is not null
UNION ALL
SELECT
B1.string_field AS old_string_field,
B2.string_field AS string_field,
'B2' as match_type
FROM
tableb B1
INNER JOIN tableb B2 ON B1.string_field <> B2.string_field AND B1.id1 = B2.id2
WHERE
B1.id1 is not null),
table1 AS (
SELECT TOP 100 PERCENT /* Enables order by to improve join performance */
old_new_string.old_string_field,
old_new_string.string_field,
old_new_string.match_type,
comment_date = (SELECT max(comment_table.comment_date) FROM comment_table WHERE old_new_string.string_field = comment_table.string_field and comment_table.comment_code = '001')
FROM
old_new_string
ORDER BY
old_new_string.old_string_field)
SELECT
tablez.string_field as old_string_field,
max(table2.task_date) as task_date,
min(cast(getdate() - table2.task_date as bigint)) as Aging
table1.string_field as new_string_field,
max(table1.match_type) as match_type,
max(table1.comment_date) as comment_date,
sort_order1 = (case when max(table1.comment_date) is null then 2 else 1 end),
sort_order2 = (case when table1.string_field is null then 2 else 1 end)
FROM
tablez
INNER JOIN table2 ON tablez.string_field = table2.string_field
LEFT JOIN table1 on tablez.string_field = table1.old_string_field
Where
table2.task_date IS NOT NULL
Group By
tablez.string_field, table1.new_string_field
ORDER BY
(case when max(table1.comment_date) is null then 2 else 1 end) DESC,
(case when table1.string_field is null then 2 else 1 end) ASC,
max(table2.task_date) ASC

ORDER BY
case when max(table1.comment_date) is null then 2 else 0 end
+ case when table1.string_field is null then 0 else 1 end DESC
or
ORDER BY CASE
WHEN max(table1.comment_date) is null and table1.string_field is not null
THEN 0
WHEN table1.string_field is null
THEN 1
WHEN max(table1.comment_date) is not null and table1.string_field is not null
THEN 2
ELSE 3 END

Related

Query for making multiple indefinite rows into one row with multiple columns

I have two tables
ID
ID2
1
1
1
2
2
3
3
4
3
5
And the second one
ID2
Code
Date1
1
A
01/01/2023
2
B
01/02/2023
3
C
01/03/2023
4
A
01/01/2023
5
D
01/15/2023
The second table has more columns that I need to include, but I'm only including two (Code and Date1) for the sake of brevity.
What I need is to unite everything based on the ID of the first table. So it would look something like
ID1
ID2-1
Code-1
Date1-1
ID2-2
Code-2
Date1-2
1
1
A
01/01/2023
2
B
01/02/2023
2
3
C
01/03/2023
NULL
NULL
NULL
3
4
A
01/01/2023
5
D
01/15/2023
In these examples one ID repeats up to two times in the second table, but the second table can have an indefinite amount of records tied to an ID from the first table. Meaning it might be Code-10, or Code-20, or maybe more or less. I need to do this in a pretty big query for a report I'm doing, so these are not the only fields that will be in the final result, but for this data specifically I only use two tables that have a very similar structure to the one I'm describing here. Any help will be appreciated.
To build on T N's code, this puppy builds a "dynamic" 30 column wide pivot.
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'
SELECT
A.ID
[COLUMNS]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID2) AS RowNum
FROM TableA
) A
JOIN TableB B
ON B.ID2 = A.ID2
GROUP BY A.ID
ORDER BY A.ID'
SELECT #SQL = REPLACE(#SQL, '[COLUMNS]', (
SELECT CONCAT(N'
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.ID2 END) AS [ID2-', x.sort, N']
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.Code END) AS [Code-', x.sort, N']
, MAX(CASE WHEN A.RowNum = ', x.sort, ' THEN B.Date END) AS [Date-', x.sort, N']')
FROM (
SELECT TOP 30 row_number() OVER(ORDER BY (SELECT NULL)) AS sort
FROM sys.objects so
) x
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'))
SELECT #SQL
EXEC(#SQL)
You can accomplish this by using a combination of a windowed ROW_NUMBER() number function and conditional aggregation.
First, ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) is used to assign sequences number to each row with the same ID. The results are then be joined with your second table and a GROUP BY ID is applied. Finally, conditional aggregation functions of the form MAX(CASE WHEN ... THEN .. END) can be used to select the proper value for each column, with specific row numbers assigned to specific columns.
Something like:
SELECT
A.ID,
MAX(CASE WHEN A.RowNum = 1 THEN B.ID2 END) AS [ID2-1],
MAX(CASE WHEN A.RowNum = 1 THEN B.Code END) AS [Code-1],
MAX(CASE WHEN A.RowNum = 1 THEN B.Date END) AS [Date-1],
MAX(CASE WHEN A.RowNum = 2 THEN B.ID2 END) AS [ID2-2],
MAX(CASE WHEN A.RowNum = 2 THEN B.Code END) AS [Code-2],
MAX(CASE WHEN A.RowNum = 2 THEN B.Date END) AS [Date-2]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID2) AS RowNum
FROM TableA
) A
JOIN TableB B
ON B.ID2 = A.ID2
GROUP BY A.ID
ORDER BY A.ID
Results:
ID
ID2-1
Code-1
Date-1
ID2-2
Code-2
Date-2
1
1
A
2023-01-01
2
B
2023-01-02
2
3
C
2023-01-03
null
null
null
3
4
A
2023-01-01
5
D
2023-01-15
See this db<>fiddle.
If you need to support an arbitrary number of records per ID instead of just two, you will then need to dive into the world of dynamic SQL.

Create new columns depending on the columns in a table

I have code in the this following example.
legacy_id phone_type phone_number
1 f 1234567890
1 b 1233854100
1 f 4110256565
2 f 0707070770
2 b 7895120044
I want the data to end up like the following.
legacy_id f_phone_number_1 b_phone_number_1 f_phone_number_2
1 1234567890 1233854100 4110256565
2 0707070770 7895120044
My initial approach works but I was hoping there is a more efficient what of doing this.
Select fill.legacy_id, max(fill.f_phone_number_1),max(fill.b_phone_number_1),max(fill.f_phone_number_2)
from
(
Select
a.legacy_id as legacy_id, a.phone_type as phone_type,
case
when a.phone_type = 'F' then a.phone_number and
dense_rank() over (partition by a.legacy_id, a.phone_type order by a.legacy_id, a.phone_type, a.phone_number) = 1
else null
end as f_phone_number_1,
case
when a.phone_type = 'F' then a.phone_number and
dense_rank() over (partition by a.legacy_id, a.phone_type order by a.legacy_id, a.phone_type, a.phone_number) = 2
else null
end as f_phone_number_2,
case
when a.phone_type = 'b' then a.phone_number and
dense_rank() over (partition by a.legacy_id, a.phone_type order by a.legacy_id, a.phone_type, a.phone_number) = 1
else null
end as b_phone_number_1
from table a
group by a.legacy_id, a.phone_type, a.phone_number
) fill
group by fill.legacy_id
Is there a more efficient way of approaching this?
If you don't need to go dynamic, a conditional aggregation should do the trick
Declare #YourTable table (legacy_id int,phone_type varchar(25),phone_number varchar(25))
Insert Into #YourTable values
(1,'f','1234567890'),
(1,'b','1233854100'),
(1,'f','4110256565'),
(2,'f','0707070770'),
(2,'b','7895120044')
Select legacy_id
,f_phone_number_1 = max(case when phone_type='f' and RowNr=1 Then phone_number else '' end)
,b_phone_number_1 = max(case when phone_type='b' and RowNr=1 Then phone_number else '' end)
,f_phone_number_2 = max(case when phone_type='f' and RowNr=2 Then phone_number else '' end)
,b_phone_number_2 = max(case when phone_type='b' and RowNr=2 Then phone_number else '' end)
From (
Select *
,RowNr=Row_Number() over (Partition By legacy_id,phone_type Order By (Select NULL) )
From #YourTable
) A
Group By legacy_id
Returns
legacy_id f_phone_number_1 b_phone_number_1 f_phone_number_2 b_phone_number_2
1 4110256565 1233854100 1234567890
2 0707070770 7895120044

2 results with 2 where in one select statement in sql-server

Let's say.. I have two statements
select min(Log_In_Time) from tbl where (event_ID=4)
select max(Log_Off_Time) from tbl where (event_ID=5)
How can I combine that 2 statement into one select statement which is resulted in 2 column like..
select min(Log_In_Time), max(Log_Off_Time) from tbl where ???????????????????
You can do this with a CASE statement:
Select
MIN (case when event_ID = 4 then Log_In_Time else null end) as MinTime,
MAX (case when event_ID = 5 then Log_Off_Time else null end) as MaxTime
from tbl
select min(Log_In_Time) from tbl where (event_ID=4)
union all
select max(Log_Off_Time) from tbl where (event_ID=5)

SQL Joins : in, out, shake it all about

I am performing the follwing sql to return a data where there is a match of both dob and address in tables1 & 2.
select table1.dob
, table1.address
, sum(case when person_status in ('A','B','C') then 1 else 0 end) as 'ABC_count'
, sum(case when person_status in ('D','E') then 1 else 0 end) as 'DE_Count'
, sum(case when person_status in ('F','G') then 1 else 0 end) as 'FG_Count'
from table1
inner join table2
on (table1.dob = table2.dob and table1.address = table2.address)
where table1.dob > #myDate
group by table1.dob, table1.address
order by table1.dob, table1.address
However I now want to return the data from table1 when there is no match in table2 and only that data, I thought simply changing inner join to left outer would perform what I required, it does not.
Thanks!
If there is no match in the join, the field from the second table are NULL, so you have to check for a NULL value in table2. Assuming dob is NOT NULL in table2, this should solve your problem:
select table1.dob
, table1.address
, sum(case when person_status in ('A','B','C') then 1 else 0 end) as 'ABC_count'
, sum(case when person_status in ('D','E') then 1 else 0 end) as 'DE_Count'
, sum(case when person_status in ('F','G') then 1 else 0 end) as 'FG_Count'
from table1
left outer join table2
on (table1.dob = table2.dob and table1.address = table2.address)
where table1.dob > #myDate and table2.dob is null
group by table1.dob, table1.address
order by table1.dob, table1.address
In this case thre's not a join, you should use NOT EXISTS function.
In my opinion LEFT JOIN is much more cleaner and you should go with that if there is no big difference between the performance of LEFT JOIN and NOT EXISTS. #JNK said "EXISTS and NOT EXISTS are ordinarily faster than joins or other operators like IN because they short circuit - the first time they get a hit they move on to the next entry", but my understanding is that NOT EXISTS and NOT IN are usually expensive as sql server has to go through all the records in the lookup table to make sure that the entry in fact does NOT EXIST, so i dont know how the short circuit would work
You could also use the EXCEPT keyword here.
select table1.dob
, table1.address
, sum(case when person_status in ('A','B','C') then 1 else 0 end) as 'ABC_count'
, sum(case when person_status in ('D','E') then 1 else 0 end) as 'DE_Count'
, sum(case when person_status in ('F','G') then 1 else 0 end) as 'FG_Count'
from table1
where table1.dob > #myDate
EXCEPT
select table1.dob
, table1.address
, sum(case when person_status in ('A','B','C') then 1 else 0 end) as 'ABC_count'
, sum(case when person_status in ('D','E') then 1 else 0 end) as 'DE_Count'
, sum(case when person_status in ('F','G') then 1 else 0 end) as 'FG_Count'
from table1
inner join table2
on (table1.dob = table2.dob and table1.address = table2.address)
where table1.dob > #myDate
That would get you all of the records in the first query that are not in the second query.

SQL Server - How to display master details data in columns

I have two tables, to be concise let’s call them TableA and TableB. This is the schema:
TableA
ID – int
Name varchar(50)
TableB
ID – int
TableA_Fk – int
Value varchar(50)
Each record in table A can have at most 9 records in table B. I want to be able to retrieve the data in a columnar form:
TableA-Name, TableB-Value1, … TableB-Value9
Is this possible using queries? Thanks!
You could do something like:
SELECT rank() OVER (ORDER BY tableA_FK) as rank, tableA_fk, value
INTO #temp
FROM TableB b
ORDER BY rank
SELECT a.Name,
CASE WHEN t.rank = 1 THEN t.Value ELSE NULL END AS TableB-Value1,
CASE WHEN t.rank = 2 THEN t.Value ELSE NULL END AS TableB-Value2,
CASE WHEN t.rank = 3 THEN t.Value ELSE NULL END AS TableB-Value3,
.... (etc.)
FROM TableA a
INNER JOIN #temp t ON a.Id = t.tableA_fk
You need Sql Server 2005 or up.
Sorry, but I don't have Sql Server (or the time) to test this well. Hope this gives you an idea and helps.
You will require a LEFT JOIN and a PIVOT table
This should do it, in addition to be DBRM independant.
SELECT A.Name
, SUM(CASE WHEN B.Value = 1 THEN 1 ELSE NULL END) AS B_Value_1
, SUM(CASE WHEN B.Value = 2 THEN 2 ELSE NULL END) AS B_Value_2
, SUM(CASE WHEN B.Value = 3 THEN 3 ELSE NULL END) AS B_Value_3
, SUM(CASE WHEN B.Value = 4 THEN 4 ELSE NULL END) AS B_Value_4
, SUM(CASE WHEN B.Value = 5 THEN 5 ELSE NULL END) AS B_Value_5
, SUM(CASE WHEN B.Value = 6 THEN 6 ELSE NULL END) AS B_Value_6
, SUM(CASE WHEN B.Value = 7 THEN 7 ELSE NULL END) AS B_Value_7
, SUM(CASE WHEN B.Value = 8 THEN 8 ELSE NULL END) AS B_Value_8
, SUM(CASE WHEN B.Value = 9 THEN 9 ELSE NULL END) AS B_Value_9
FROM A
INNER JOIN B ON B.TableA_FK = A.ID
GROUP BY A.Name
ORDER BY A.Name

Resources