SQL Server - Repeat Characters after nth row - sql

How to repeat characters after every nth Row. For example, I want result like this:
1 1234-A
2 32423-B
3 324234-C
4 afsd-D
5 32432-A
6 32423-B
7 3dsfa33-C
8 sdfw3rf-D
This A,B,C and D will be repeated though out result set. NO other characters.

Try this:
SELECT YourID
,YourDescr + '-' + CHAR((ROW_NUMBER() OVER (ORDER BY YourID) - 1) % 4 + 65) AS YourDescr
FROM YourTable
It doesn't rely on the value of the ID column.

Generate a row number for every four rows and use case statement to append the required characters. Try this.
CREATE TABLE #str(id INT,string VARCHAR(50))
INSERT #str
VALUES (1,'1234' ),(2,'32423' ),(3,'324234' ),
(4,'afsd' ),(5,'32432' ),(6,'32423' ),
(7,'3dsfa33' ),(8,'sdfw3rf' )
SELECT id,
CASE
WHEN rn = 1 THEN string + '-A'
WHEN rn = 2 THEN string + '-B'
WHEN rn = 3 THEN string + '-C'
WHEN rn = 4 THEN string + '-D'
END AS String
FROM (SELECT ( ( id - 1 )%4 ) + 1 rn,*
FROM #str)a
Result :
+----+-----------+
| id | String |
+----+-----------+
| 1 | 1234-A |
| 2 | 32423-B |
| 3 | 324234-C |
| 4 | afsd-D |
| 5 | 32432-A |
| 6 | 32423-B |
| 7 | 3dsfa33-C|
| 8 | sdfw3rf-D|
+----+-----------+

Related

Update column values sequentially where the new value is based on updated values in a 'previous' row

Consider the following table:
select id, val, newval from test1;
id | val | newval
----+-----+--------
1 | 1 | 0
2 | 2 | 0
3 | 5 | 0
4 | 9 | 0
5 | 10 | 0
I am looking for an update query that can sequentially update the values in the newval column where the updated value is the difference between val and val.prev (the value of val in the previous row) plus the UDATED value of newval from the previous row. The result would be:
select id, val, newval from test1;
id | val | newval
----+-----+--------
1 | 1 | 0 = set to zero since result would be NULL (no previous row exists)
2 | 2 | 1 = 2 - 1 + 0
3 | 5 | 4 = 5 - 2 + 1
4 | 9 | 8 = 9 - 5 + 4
5 | 10 | 9 = 10 - 9 + 8
^ - uses updated value of newval
I came close to a solution with the following:
WITH tt AS (
SELECT id, COALESCE(val - lag(val) OVER w + lag(newval) OVER w,0) as nv
FROM test1
WINDOW w AS (ORDER BY id)
)
UPDATE test1 SET newval = tt.nv FROM tt
WHERE test1.id = tt.id;
Which gives the following result:
select id, val, newval from test1;
id | val | newval
----+-----+--------
1 | 1 | 0
2 | 2 | 1 = 2 - 1 + 0
3 | 5 | 3 = 5 - 2 + 0
4 | 9 | 4 = 9 - 5 + 0
5 | 10 | 1 = 10 - 9 + 0
^ - uses old value of newval
but this solution does not use the updated values of newval. It uses the old values.
I know I can write a function to loop through the table one row at a time but from what I have read this method would be inefficient and normally discouraged. My actual application is more complicated and involves large tables so efficiency is important.
I am pretty sure that your logic reduces to:
select id, val, (val - 1) as newval
from t;
Why? You are taking a cumulative sum of the difference between val and val in the previous row. The cumulative difference ends up being the most recent value minus the first value.
The first value is 1. The above hardcodes that value. It would be easy enough to adjust the logic for the first value:
select id, val,
(val - first_value(val) over (order by id) as newval
from t;

T-SQL If condition inside select

I have a condition where there are multiple scenarios and I need to check each scenario. I need to concatenate the result if two or more cases matches.
What I thought was , using CASE statement but i am unable to concatenate multiple scenarios using CASE statement.
So what I am doing now is , using if statement . Below is the query i did:
declare #result varchar(200)
set #result=''
;with cte as
(
select * from table 1
)
if(column 1=5) set #result=#result+'case1'
if(column 2=6) set #result=#result+'case2'
if(column 3=7) set #result=#result+'case3'
if(column 4=10) set #result=#result+'case4'
select *,#result from cte
So here, i need to use Select statement right after CTE but i cannot use IF/ELSE statement in select statement . But also i cannot use the CASE Statement with variable concatenate.
So the result should be like :
id | column 1 | column 2| column 3| column 4| Result
-----------------------------------------------------
3 | 5 | 6 | 6 | 9 | case1;`case2
4 | 4 | 7 | 7 | 10 | case2
5 | 5 | 6 | 6 | 10 | case1;`case2; case4
6 | 5 | 6 | 7 | 10 | case2;case2;case3;case4
7 | 4 | 5 | 6 | 3 | No Result
Anyone could help me to complete this ?
You can concatenate multiple CASEs in a single SELECT:
SELECT
CASE WHEN 1=1 THEN 'Case1' ELSE '' END
+ CASE WHEN 2=2 THEN 'Case2' ELSE '' END
+ CASE ...
EDIT based on comments: If, as you say, your broken code using IF really would solve your issue, then there is no issue, since the if statements you used don't need to be in the SELECT at all. You could simply do this:
declare #result varchar(200)
set #result=''
if(1=1) set #result=#result+'case1'
if(2=2) set #result=#result+'case2'
;with cte as
(
select * from table 1
)
select *,#result from cte
EDIT based on the update to original question:
So my first solution is correct. The additional details in your question allow me to make it a little more clear. By the way you can't use a variable at all for this:
;with cte as
(
select * ,
CASE WHEN column1=5 THEN 'case1' ELSE '' END +
CASE WHEN column2=6 THEN 'case2' ELSE '' END +
CASE WHEN column3=7 THEN 'case3' ELSE '' END +
CASE WHEN column4=10 THEN 'case4' ELSE '' END AS result
from table 1
)
select *,
CASE WHEN result='' THEN 'No Result Found' ELSE result END as result
from cte
Note that if you need to separate the values with semi-colons (as shown in your question) you can put a semi-colon before each value (example ';case3'), and in the final SELECT, use STUFF() to remove the first semi-colon.
;WITH cte AS
(
SELECT * FROM table1
)
SELECT *,(
CASE
WHEN Column1 <> 5 AND Column2 <> 6 AND Column3 <> 7 AND Column4 <> 10 THEN 'No Result'
ELSE
CASE WHEN Column1 = 5 THEN 'Case 1;' ELSE '' END +
CASE WHEN Column2 = 6 THEN 'Case 2;' ELSE '' END +
CASE WHEN Column3 = 7 THEN 'Case 3;' ELSE '' END +
CASE WHEN Column4 = 10 THEN 'Case 4;' ELSE '' END
END
)Result
FROM cte
| id | Column1 | Column2 | Column3 | Column4 | Result |
|----|---------|---------|---------|---------|------------------------------|
| 3 | 5 | 6 | 6 | 9 | Case 1;Case 2; |
| 4 | 4 | 7 | 7 | 10 | Case 3;Case 4; |
| 5 | 5 | 6 | 6 | 10 | Case 1;Case 2;Case 4; |
| 6 | 5 | 6 | 7 | 10 | Case 1;Case 2;Case 3;Case 4; |
| 7 | 4 | 5 | 6 | 9 | No Result |
Check SQL Fiddle

Make single record to multiple records in sql server

I have a records look like below
From two rows, I want to split ShiftPattern values and create multiple records and StartWeek will be created sequentially.
Final Query:
Split ShiftPattern Column and Create multiple records
Increase StartWeek like as 20, 21 to rotation.
Output result
This is what you need. Tested in fiddle.
SQLFiddle Demo
select q.locationid,q.employeeid,
case
when (lag(employeeid,1,null) over (partition by employeeid order by weekshiftpatternid)) is null
then startweek
else startweek + 1
end as rotation ,
q.weekshiftpatternid,
q.shiftyear
from
(
select locationid,employeeid, left(d, charindex(',', d + ',')-1) as weekshiftpatternid ,
startweek,shiftyear
from (
select *, substring(shiftpattern, number, 200) as d from MyTable locationid left join
(select distinct number from master.dbo.spt_values where number between 1 and 200) col2
on substring(',' + shiftpattern, number, 1) = ','
) t
) q
Output
+------------+------------+----------+--------------------+-----------+
| locationid | employeeid | rotation | weekshiftpatternid | shiftyear |
+------------+------------+----------+--------------------+-----------+
| 1 | 10000064 | 20 | 1006 | 2016 |
| 1 | 10000064 | 21 | 1008 | 2016 |
| 1 | 10000065 | 20 | 1006 | 2016 |
| 1 | 10000065 | 21 | 1008 | 2016 |
+------------+------------+----------+--------------------+-----------+
Similar:
In my test table my ID is your EmployeeID or however you want to work it.
SELECT
*,
LEFT(shiftBits, CHARINDEX(',', shiftBits + ',')-1) newShiftPattern,
StartWeek+ROW_NUMBER() OVER(PARTITION BY ID ORDER BY shiftBits ) as newStartWeek
FROM
(
SELECT
SUBSTRING(shiftPattern, number, LEN(shiftPattern)) AS shiftBits,
test2.*
FROM
test2,master.dbo.spt_values
WHERE
TYPE='P' AND number<LEN(shiftPattern)
AND SUBSTRING(',' + shiftPattern, number, 1) = ','
) AS x

If the difference between two sequences is bigger than 30, deduct bigger sequence

I'm having a hard time trying to make a query that gets a lot of numbers, a sequence of numbers, and if the difference between two of them is bigger than 30, then the sequence resets from this number. So, I have the following table, which has another column other than the number one, which should be maintained intact:
+----+--------+--------+
| Id | Number | Status |
+----+--------+--------+
| 1 | 1 | OK |
| 2 | 1 | Failed |
| 3 | 2 | Failed |
| 4 | 3 | OK |
| 5 | 4 | OK |
| 6 | 36 | Failed |
| 7 | 39 | OK |
| 8 | 47 | OK |
| 9 | 80 | Failed |
| 10 | 110 | Failed |
| 11 | 111 | OK |
| 12 | 150 | Failed |
| 13 | 165 | OK |
+----+--------+--------+
It should turn it into this one:
+----+--------+--------+
| Id | Number | Status |
+----+--------+--------+
| 1 | 1 | OK |
| 2 | 1 | Failed |
| 3 | 2 | Failed |
| 4 | 3 | OK |
| 5 | 4 | OK |
| 6 | 1 | Failed |
| 7 | 4 | OK |
| 8 | 12 | OK |
| 9 | 1 | Failed |
| 10 | 1 | Failed |
| 11 | 2 | OK |
| 12 | 1 | Failed |
| 13 | 16 | OK |
+----+--------+--------+
Thanks for your attention, I will be available to clear any doubt regarding my problem! :)
EDIT: Sample of this table here: http://sqlfiddle.com/#!6/ded5af
With this test case:
declare #data table (id int identity, Number int, Status varchar(20));
insert #data(number, status) values
( 1,'OK')
,( 1,'Failed')
,( 2,'Failed')
,( 3,'OK')
,( 4,'OK')
,( 4,'OK') -- to be deleted, ensures IDs are not sequential
,(36,'Failed') -- to be deleted, ensures IDs are not sequential
,(36,'Failed')
,(39,'OK')
,(47,'OK')
,(80,'Failed')
,(110,'Failed')
,(111,'OK')
,(150,'Failed')
,(165,'OK')
;
delete #data where id between 6 and 7;
This SQL:
with renumbered as (
select rn = row_number() over (order by id), data.*
from #data data
),
paired as (
select
this.*,
startNewGroup = case when this.number - prev.number >= 30
or prev.id is null then 1 else 0 end
from renumbered this
left join renumbered prev on prev.rn = this.rn -1
),
groups as (
select Id,Number, GroupNo = Number from paired where startNewGroup = 1
)
select
Id
,Number = 1 + Number - (
select top 1 GroupNo
from groups where groups.id <= paired.id
order by GroupNo desc)
,status
from paired
;
yields as desired:
Id Number status
----------- ----------- --------------------
1 1 OK
2 1 Failed
3 2 Failed
4 3 OK
5 4 OK
8 1 Failed
9 4 OK
10 12 OK
11 1 Failed
12 1 Failed
13 2 OK
14 1 Failed
15 16 OK
Update: using the new LAG() function allows somewhat simpler SQL without a self-join early on:
with renumbered as (
select
data.*
,gap = number - lag(number, 1) over (order by number)
from #data data
),
paired as (
select
*,
startNewGroup = case when gap >= 30 or gap is null then 1 else 0 end
from renumbered
),
groups as (
select Id,Number, GroupNo = Number from paired where startNewGroup = 1
)
select
Id
,Number = 1 + Number - ( select top 1 GroupNo
from groups
where groups.id <= paired.id
order by GroupNo desc
)
,status
from paired
;
I don't deserve answer but I think this is even shorter
with gapped as
( select id, number, gap = number - lag(number, 1) over (order by id)
from #data data
),
select Id, status
ReNumber = Number + 1 - isnull( (select top 1 gapped.Number
from gapped
where gapped.id <= data.id
and gap >= 30
order by gapped.id desc), 1)
from #data data;
This is simply Pieter Geerkens's answer slightly simplified. I removed some intermediate results and columns:
with renumbered as (
select data.*, gap = number - lag(number, 1) over (order by number)
from #data data
),
paired as (
select *
from renumbered
where gap >= 30 or gap is null
)
select Id, Number = 1 + Number - (select top 1 Number
from paired
where paired.id <= renumbered.id
order by Number desc)
, status
from renumbered;
It should have been a comment, but it's too long for that and wouldn't be understandable.
You might need to make another cte before this and use row_number instead of ID to join the recursive cte, if your ID's are not in sequential order
WITH cte AS
( SELECT
Id, [Number], [Status],
0 AS Diff,
[Number] AS [NewNumber]
FROM
Table1
WHERE Id = 1
UNION ALL
SELECT
t1.Id, t1.[Number], t1.[Status],
CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN t1.Number - 1 ELSE Diff END,
CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN 1 ELSE t1.[Number] - Diff END
FROM Table1 t1
JOIN cte ON cte.Id + 1 = t1.Id
)
SELECT Id, [NewNumber], [Status]
FROM cte
SQL Fiddle
Here is another SQL Fiddle with an example of what you would do if the ID is not sequential..
SQL Fiddle 2
In case sql fiddle stops working
--Order table to make sure there is a sequence to follow
WITH OrderedSequence AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY Id) RnId,
Id,
[Number],
[Status]
FROM
Sequence
),
RecursiveCte AS
( SELECT
Id, [Number], [Status],
0 AS Diff,
[Number] AS [NewNumber],
RnId
FROM
OrderedSequence
WHERE Id = 1
UNION ALL
SELECT
t1.Id, t1.[Number], t1.[Status],
CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN t1.Number - 1 ELSE Diff END,
CASE WHEN t1.[Number] - cte.[Number] >= 30 THEN 1 ELSE t1.[Number] - Diff END,
t1.RnId
FROM OrderedSequence t1
JOIN RecursiveCte cte ON cte.RnId + 1 = t1.RnId
)
SELECT Id, [NewNumber], [Status]
FROM RecursiveCte
I tried to optimize the queries here, since it took 1h20m to process my data. I had it down to 30s after some further research.
WITH AuxTable AS
( SELECT
id,
number,
status,
relevantId = CASE WHEN
number = 1 OR
((number - LAG(number, 1) OVER (ORDER BY id)) > 29)
THEN id
ELSE NULL
END,
deduct = CASE WHEN
((number - LAG(number, 1) OVER (ORDER BY id)) > 29)
THEN number - 1
ELSE 0
END
FROM #data data
)
,AuxTable2 AS
(
SELECT
id,
number,
status,
AT.deduct,
MAX(AT.relevantId) OVER (ORDER BY AT.id ROWS UNBOUNDED PRECEDING ) AS lastRelevantId
FROM AuxTable AT
)
SELECT
id,
number,
status,
number - MAX(deduct) OVER(PARTITION BY lastRelevantId ORDER BY id ROWS UNBOUNDED PRECEDING ) AS ReNumber,
FROM AuxTable2
I think this runs faster, but it's not shorter.

Opposite of SELECT TOP

Transact-SQL has a handy SELECT TOP 4 [whatever] FROM.........
I want to make a SELECT query returning the last "n" entries from a table instead of the first ones.
This is the query I would use to return the first four items entered at the table, using SELECT TOP:
sql = "SELECT TOP 4 [news_title], [news_date_added], [news_short_description],
[news_ID] FROM [Web_Xtr_News] WHERE ([news_type] = 2 OR [news_type] = 3) AND
[news_language] = '" + Language + "' ORDER BY [news_ID] ASC"
I need to return the last four.
Change the order of the table from ASC to DESC.
It's exactly this: http://www.sqlfiddle.com/#!3/6c813/1
with bottom as(
select top 4 *
from tbl
order by n desc
)
select *
from bottom
order by n
Data source:
| N |
|----|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
Output:
| N |
|----|
| 7 |
| 8 |
| 9 |
| 10 |
Continue to use TOP, and reverse the order:
SELECT TOP 4 [news_title],
[news_date_added],
[news_short_description],
[news_ID]
FROM [Web_Xtr_News]
WHERE ([news_type] = 2
OR [news_type] = 3)
AND [news_language] = #Language
ORDER BY [news_ID] DESC
(It was rewritten to use parameters of course. Your original is vulnerable to SQL injection.)
You can reverse the ordering by using DESC instead of ASC at the end of your query.
A quick way to select batches from a list is to create a unique identifier like a (ROW ID) as (1, 2, 3, etc.) and create a nested query.
Select *
from
(select
row_number() as id
,column1
,column2
from
table1
)as D
where D.id (<>=! between)