Using CASE for a specific situation - How TO - sql

I'm trying to find the proper SQL for the following situation:
Supposed we have two tables:
TABLE A
ID int,
TEXT varchar(200)
TABLE B
ID int,
A_NO int,
B_NO int
Fields named "ID" on both tables can be join to link tables.
The following SQL:
SELECT
A.ID,
B.A_NO,
B.B_NO
FROM
A
LEFT JOIN
B
ON A.ID = B.ID
ORDER BY A.ID, B.A_NO, B.B_NO
gives the following results:
Now, the problem.
What is asked for is to have in the column B_NO a value = 1 for the MIN value of column A_NO and a value = 0 for all the others row with the same A_NO value.
The results below are expected:
Please note that, in this example, we can find two rows for each B_NO value but it is possible to have more than 2 rows.
I have tried to reproduce these results by using a CASE but with no success.
Thanks for you help in advance,
Bouzouki.

Try this using CTE and ROW_NUMBER(); (DEMO)
Please note: I have considered myT as your joined query of A and B tables for demo purpose. So replace myT with as yours A LEFT JOIN B ON A.ID = B.ID.
;with cte as (
select id, a_no, b_no,
row_number() over(partition by id,b_no order by a_no) rn
from myT
)
select id,a_no, case when rn=1 then b_no else 0 end b_no
from cte
order by a_no
--RESULTS FROM DEMO TABLE
| ID | A_NO | B_NO |
-------------------------
| 1031014 | 1 | 1 |
| 1031014 | 2 | 0 |
| 1031014 | 3 | 2 |
| 1031014 | 4 | 0 |
| 1031014 | 5 | 3 |
| 1031014 | 6 | 0 |
| 1031014 | 7 | 4 |
| 1031014 | 8 | 0 |
| 1031014 | 9 | 5 |
| 1031014 | 10 | 0 |

something like
select ID, a_no, b_no,
case when a_no = min_a_no then b_no else 0 end as new_b_no
from
a left join b on a.id = b.id left join
(Select ID, B_no, min(a_no) as min_a_no
from a left join b on a.id = b.id
group by id, b_no) m on a.id = m.id and b.b_no = m.b_no
ORDER BY A.ID, B.A_NO

Related

How to select multiple rows with only one tag row

Table A
RowID | A | B | C
-------+--------------+----------------+----
1 | google.com | search engine | 1
2 | google.com | |
3 | google.com | |
4 | google.com | |
6 | dropbox.com | consumer | 1
7 | dropbox.com | |
8 | dropbox.com | |
9 | dropbox.com | |
10 | dropbox.com | |
How to select row 1 to 4 where column c = 1?
Query below
select *
from tableA
where C = '1'
Despite there're better ways to collect data, if I understood well what you're trying to achieve, this should work on many situation:
DECLARE #t table (RowID int, A varchar(50), B varchar(50), C int)
DECLARE #MaxId int
INSERT INTO #t(RowID,A,B,C)
VALUES (1,'google.com','search engine',1),
(2,'google.com',NULL,NULL),
(3,'google.com',NULL,NULL),
(4,'google.com',NULL,NULL),
(6,'dropbox.com','consumer',1),
(7,'dropbox.com',NULL,NULL),
(8,'dropbox.com',NULL,NULL),
(9,'dropbox.com',NULL,NULL),
(10,'dropbox.com',NULL,NULL)
SELECT #MaxId = MAX(RowID)
FROM #t
SELECT T.RowID, T.A, I.B
FROM #t AS T
LEFT JOIN (SELECT RowID, B, ISNULL(LEAD(RowId) OVER (ORDER BY RowId) - 1,#MaxId) AS LastId
FROM #t
WHERE C = 1) AS I
ON T.RowID BETWEEN I.RowID AND I.LastId
WHERE I.B = 'search engine'
In this case the search criteria is on column B. I expect that real data has different values on column A for the same set of data (i.e. 'search engine'). For Example this solution works also on this dataset:
RowID | A | B | C
-------+------------------+----------------+----
1 | google.com | search engine | 1
2 | bing.com | |
3 | duckduckgo.com | |
4 | google.co.uk | |
6 | dropbox.com | consumer | 1
7 | facebook.com | |
8 | instagram.com | |
9 | onedrive.com | |
10 | wikipedia.com | |
Of course we're talking about sample, unreal data. Real data should be more complex, and I expect more columns, but it's just matter of adding them to the SELECT.
If column C is an integer then use
select *
from tableA
where C = 1
if is a string then be sure you have not hidden char
select *
from tableA
where trim(C) = '1'
row 1-4 do you want the o/p as of google.com then add one more condition to your where clause as
select *
from tableA
where C = '1' and A like
'%google.com%'
or if you want on basis of B only then
select *
from tableA
where C = '1' and B like
'%search engine%'
first you select row where column C value is 1
SELECT *
FROM tableA a
WHERE a.C = '1'
from there you can JOIN back to your table to get the rest of the rows based on A
SELECT a2.*
FROM tableA a
INNER JOIN tableA a2 ON a.A = a2.A
WHERE a.C = '1'
You can try this:
SELECT *
FROM TABLEA
WHERE A IN
(
SELECT A
FROM TABLEA
WHERE C = 1
)
Regards

T-SQL: Best way to replace NULL with most recent non-null value?

Assume I have this table:
+----+-------+
| id | value |
+----+-------+
| 1 | 5 |
| 2 | 4 |
| 3 | 1 |
| 4 | NULL |
| 5 | NULL |
| 6 | 14 |
| 7 | NULL |
| 8 | 0 |
| 9 | 3 |
| 10 | NULL |
+----+-------+
I want to write a query that will replace any NULL value with the last value in the table that was not null in that column.
I want this result:
+----+-------+
| id | value |
+----+-------+
| 1 | 5 |
| 2 | 4 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 14 |
| 7 | 14 |
| 8 | 0 |
| 9 | 3 |
| 10 | 3 |
+----+-------+
If no previous value existed, then NULL is OK. Ideally, this should be able to work even with an ORDER BY. So for example, if I ORDER BY [id] DESC:
+----+-------+
| id | value |
+----+-------+
| 10 | NULL |
| 9 | 3 |
| 8 | 0 |
| 7 | 0 |
| 6 | 14 |
| 5 | 14 |
| 4 | 14 |
| 3 | 1 |
| 2 | 4 |
| 1 | 5 |
+----+-------+
Or even better if I ORDER BY [value] DESC:
+----+-------+
| id | value |
+----+-------+
| 6 | 14 |
| 1 | 5 |
| 2 | 4 |
| 9 | 3 |
| 3 | 1 |
| 8 | 0 |
| 4 | 0 |
| 5 | 0 |
| 7 | 0 |
| 10 | 0 |
+----+-------+
I think this might involve some kind of analytic function - somehow partitioning over the value column - but I'm not sure where to look.
You can use a running sum to set groups and use max to fill in the null values.
select id,max(value) over(partition by grp) as value
from (select id,value,sum(case when value is not null then 1 else 0 end) over(order by id) as grp
from tbl
) t
Change the over() clause to order by value desc to get the second result in the question.
The best way has been covered by Itzik Ben-Gan here:The Last non NULL Puzzle
Below is a solution which for 10 million rows and completes around in 20 seconds on my system
SELECT
id,
value1,
CAST(
SUBSTRING(
MAX(CAST(id AS binary(4)) + CAST(value1 AS binary(4)))
OVER (ORDER BY id
ROWS UNBOUNDED PRECEDING),
5, 4)
AS int) AS lastval
FROM dbo.T1;
This solution assumes your id column is indexed
You can also try using correlated subquery
select id,
case when value is not null then value else
(select top 1 value from table
where id < t.id and value is not null order by id desc) end value
from table t
Result :
id value
1 5
2 4
3 1
4 1
5 1
6 14
7 14
8 0
9 3
10 3
If the NULLs are scattered I use a WHILE loop to fill them in
However if the NULLs are in longer consecutive strings there are faster ways to do it.
So here's one approach:
First find a record that we want to update. It has NULL in this record and no NULL in the prior record
SELECT C.VALUE, N.ID
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
Use that to update: (bit hazy on this syntax but you get the idea)
UPDATE N
SET VALUE = C.Value
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
.. now just keep doing it till you run out of rows
-- This is needed to set ##ROWCOUNT to non zero
SELECT 1;
WHILE ##ROWCOUNT <> 0
BEGIN
UPDATE N
SET VALUE = C.Value
FROM TABLE C
INNER JOIN TABLE N
ON C.ID + 1 = N.ID
WHERE C.VALUE IS NOT NULL
AND N.VALUE IS NULL;
END
The other way is to use a similiar query to get a range of id's to update. This works much faster if your NULLS are usually against consecutive id's
Here is the one simple approach using OUTER APPLY
CREATE TABLE #table(id INT, value INT)
INSERT INTO #table VALUES
(1,5),
(2,4),
(3,1),
(4,NULL),
(5,NULL),
(6,14),
(7,NULL),
(8,0),
(9,3),
(10,NULL)
SELECT t.id, ISNULL(t.value, t3.value) value
FROM #table t
OUTER APPLY(SELECT id FROM #table WHERE id = t.id AND VALUE IS NULL) t2
OUTER APPLY(SELECT TOP 1 value
FROM #table WHERE id <= t2.id AND VALUE IS NOT NULL ORDER BY id DESC) t3
OUTPUT:
id VALUE
---------
1 5
2 4
3 1
4 1
5 1
6 14
7 14
8 0
9 3
10 3
Using this sample data:
if object_id('tempdb..#t1') is not null drop table #t1;
create table #t1 (id int primary key, [value] int null);
insert #t1 values(1,5),(2,4),(3,1),(4,NULL),(5,NULL),(6,14),(7,NULL),(8,0),(9,3),(10,NULL);
I came up with:
with x(id, [value], grouper) as (
select *, row_number() over (order by id)-sum(iif([value] is null,1,0)) over (order by id)
from #t1)
select id, min([value]) over (partition by grouper)
from x;
I noticed, however, that Vamsi Prabhala beat me to it... My solution is identical to what he posted. (arghhhh!). So I thought I'd try a recursive solution. Here's a pretty efficient use of a recursive cte (provided that ID is indexed):
with sorted as (select *, seqid = row_number() over (order by id) from #t1),
firstRecord as (select top(1) * from #t1 order by id),
prev as
(
select t.id, t.[value], lastid = 1, lastvalue = null
from sorted t
where t.id = 1
union all
select t2.id, t2.[value], lastid+1, isnull(prev.[value],lastvalue)
from sorted t2
join prev on t2.id = prev.lastid+1
)
select id, [value]=isnull([value],lastvalue)--, *
from prev;
Normally I don't like recursive cte's (rCte for short) but in this case it offered an elegant solution and was faster than using the window aggregate function (sum over, min over...). Note the execution plans, the rcte on the bottom. The rCTE get's it done with two index seeks, one of which is for just one row. Unlike the window aggregate solution, the rcte does not require a sort. Running this with statistics io on; the rcte produces much less IO.
All this said, don't use either of these solutions, What the TheGameiswar posted will perform the best by far. His solution on a properly indexed id column would be lightening fast.
Following UPDATE statement can be used, please test it before use
update #table
set value = newvalue
from (
select
s.id, s.value,
(select top 1 t.value from #table t where t.id <= s.id and t.value is not null order by t.id desc) as newvalue
from #table S
) u
where #table.id = u.id and #table.value is null
stop worrying..here's the answer for you :)
SELECT *
INTO #TempIsNOtNull
FROM YourTable
WHERE value IS NOT NULL
SELECT *
INTO #TempIsNull
FROM YourTable
WHERE value IS NULL
UPDATE YourTable
SEt YourTable.value = UpdateDtls.value
FROM YourTable
JOIN (
SELECT OuterTab1.id,
#TempIsNOtNull.value
FROM #TempIsNull OuterTab1
CROSS JOIN #TempIsNOtNull
WHERE OuterTab1.id - #TempIsNOtNull.id > 0
AND (OuterTab1.id - #TempIsNOtNull.id) = ( SELECT TOP 1
OuterTab1.id - #TempIsNOtNull.id
FROM #TempIsNull InnerTab
CROSS JOIN #TempIsNOtNull
WHERE OuterTab1.id - #TempIsNOtNull.id > 0
AND OuterTab1.id = InnerTab.id
ORDER BY (OuterTab1.id - #TempIsNOtNull.id) ASC) ) AS UpdateDtls
ON (YourTable.id = UpdateDtls.id)

SQL to group by 2 IDs

So I have a table that's laid out like this:
table1:
ID | metric1 | metric2
A | 1 | 1
A | 1 | 1
B | 2 | 3
C | 3 | 2
And another table that may have an alternate ID an item may have (note that the new ID will also be in the table above). Example:
conversions:
old_ID | new_ID
A | C
So I'm trying to create a query that aggregates both on the new ID and the old-ID, but also preserves the old-ID if available. So basically the results I want look like this:
ID | potential_old_ID | metric1 | metric2
C | A | 5 | 4
B | NULL | 2 | 3
So far with my current strategy I've been able to get close with a query like this:
select
(CASE WHEN new_ID is null then ID else new_ID END) as ID,
(CASE WHEN new_ID is null then null else ID END) as potential_old_ID,
SUM(metric1),
SUM(metric2)
from table1
left join conversions on ID = old_ID
group by ID, new_ID
Which get's me close, but it still separates C and A in separate rows, which doesn't work for my use case:
ID | potential_old_ID | metric1 | metric2
C | A | 2 | 2
B | NULL | 2 | 3
C | NULL | 3 | 2
If I remove the new_ID from the group by I get an error on the query. Anyway I can get the results I'm looking for that I'm missing?
You need to make sure that the rows that have C already also have the same potential old ID as the ones that are A. Something like
SELECT ID, potential_old_ID, SUM(metric1), SUM(metric2)
FROM
( select
(CASE WHEN c1.new_ID is null then ID else c1.new_ID END) as ID,
COALESCE(c1.old_ID, c2.old_ID) as potential_old_ID,
metric1,
metric2
from table1
left join conversions c1 on ID = c1.old_ID
left join conversions c2 on ID = c2.new_ID
) AS data
GROUP BY ID, potential_old_ID
Hmmmm . . . I think this does what you want:
select coalesce(new_id, id) as id,
SUM(metric1),
SUM(metric2)
from table1 left join
conversions
on ID = old_ID
group by coalesce(new_id, id);

Showing the full list IDs when OUTER JOIN two tables

How to display not only the overlapped IDs from two tables (table A and table B) but also the unique IDs from two tables?
Here is the example code:
SELECT A.ID AS ID
FROM A
FULL OUTER JOIN B ON A.ID = B.ID
I think there's something I can do with the SELECT line but I don't know how.
coalesce() returns the first non-null value from a set of parameters. Is that what you are looking for?
rextester demo: http://rextester.com/QYAZV8300
create table a (id int)
insert into a values (1),(3),(5)
create table b (id int)
insert into b values (2),(3),(4)
select
a.id as A_Id
, b.id as B_Id
, coalesce(a.id,b.id) as Id
from a
full join b
on a.id = b.id
returns:
+------+------+----+
| A_Id | B_Id | Id |
+------+------+----+
| 1 | NULL | 1 |
| 3 | 3 | 3 |
| 5 | NULL | 5 |
| NULL | 2 | 2 |
| NULL | 4 | 4 |
+------+------+----+

Select non-unique id where no row meets criteria

Say I have this table, and I want to select the IDs where all D is < 4. In this case it would only select ID 1 because 2's D>4, and 3 has a D>4
+----+---+------+
| ID | D | U-ID |
+----+---+------+
| 1 | 1 | a |
+----+---+------+
| 1 | 2 | b |
+----+---+------+
| 2 | 5 | c |
+----+---+------+
| 3 | 5 | d |
+----+---+------+
| 3 | 2 | e |
+----+---+------+
| 3 | 3 | f |
+----+---+------+
I really don't even know where to start making a query for this, and my sql isn't good enough yet to know what to google, so I'm sorry if this has been asked before.
I would simply do:
select id
from table
group by id
having max(d) < 4;
If you happened to want all the original rows, I would use a window function:
select t.*
from (select t.*, max(d) over (partition by id) as maxd
from t
) t
where maxd < 4;
Here's one option using conditional aggregation:
select id
from yourtable
group by id
having count(case when d >= 4 then 1 end) = 0
SQL Fiddle Demo
If you need all the data from the corresponding rows/columns, you can either join back to the table using the above, or alternatively you could use not exists:
select *
from yourtable t
where not exists (
select 1
from yourtable t2
where t.id = t2.id and
t2.d >= 4
)
use this query.
select ID from yourtablename where D < 4;