Order rows in a sequence and fill gaps for missing rows - tsql

I got a problem regarding missing rows in a table that is giving me a headache.
As base data, I have the following table:
declare #table table
(
id1 int,
id2 int,
ch char(1) not null,
val int
)
insert into #table values (1112, 121, 'A', 12)
insert into #table values (1351, 121, 'A', 13)
insert into #table values (1411, 121, 'B', 81)
insert into #table values (1312, 7, 'C', 107)
insert into #table values (1401, 2, 'A', 107)
insert into #table values (1454, 2, 'D', 107)
insert into #table values (1257, 6, 'A', 1)
insert into #table values (1269, 6, 'B', 12)
insert into #table values (1335, 6, 'C', 12)
insert into #table values (1341, 6, 'D', 5)
insert into #table values (1380, 6, 'A', 3)
The output should be ordered by id2 and follow a fixed sequence of ch, which should repeat until next id2 begins.
Sequence:
'A'
'B'
'C'
'D'
If the sequence or the pattern is interrupted, it should fill the missing rows with null, so that i get this result table:
id1 id2 ch val
----------------------------
1112 121 'A' 12
NULL 121 'B' NULL
NULL 121 'C' NULL
NULL 121 'D' NULL
1351 121 'A' 13
1411 121 'B' 81
NULL 121 'C' NULL
NULL 121 'D' NULL
NULL 7 'A' NULL
NULL 7 'B' NULL
1312 7 'C' 107
NULL 7 'D' NULL
1401 2 'A' 107
NULL 2 'B' NULL
NULL 2 'C' NULL
1454 2 'D' 107
and so on...
What I'm looking for is a way to do this without iterations.
I hope someone can help!
Thanks in advance!

A solution might be this:
declare #table table ( id1 int, id2 int, ch char(1) not null, val int )
insert into #table values (1112, 121, 'A', 12)
,(1351, 121, 'A', 13),(1411, 121, 'B', 81),(1312, 7, 'C', 107),(1401, 2, 'A', 107)
,(1454, 2, 'D', 107),(1257, 6, 'A', 1),(1269, 6, 'B', 12),(1335, 6, 'C', 12)
,(1341, 6, 'D', 5),(1380, 6, 'A', 3)
;with foo as
(select
*
,row_number() over (partition by id2 order by id1) rwn
,ascii(isnull(lag(ch,1) over (partition by id2 order by id1),'A'))-ascii('A') prev
,count(*) over (partition by id2,ch) nr
,ascii(ch)-ascii('A') cur
from #table
)
,bar as
(
select
*,case when cur<=prev and rwn>1 then 4 else 0 end + cur-prev step
from foo
)
,foobar as
(
select *,sum(step) over (partition by id2 order by id1 rows unbounded preceding) rownum
from bar
)
,iterations as
(
select id2,max(nr) nr from foo
group by id2
)
,blanks as
(
select
id2,ch chnr,char(ch+ascii('A') )ch,ROW_NUMBER() over (partition by id2 order by c.nr,ch)-1 rownum,c.nr
from iterations a
inner join (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) c(nr)
on c.nr<=a.nr
cross join (values (0),(1),(2),(3)) b(ch)
)
select
b.id1,a.id2,a.ch,b.val
from blanks a
left join foobar b
on a.id2=b.id2 and a.rownum=b.rownum
order by a.id2,a.rownum
I first make the query "foo" which looks at the row number and gets the previous value for ch for each id2.
"bar" then finds how many missing values there are between the rows. For instance If the previous was an A and the current is a c then there are 2. If the previous was an A and the current is an A, then there are 4!
"foobar" then adds the steps, thus numbering the original rows, where they should be in the final output.
"iterations" counts the number of times the "ABCD" rows should appear.
"BLANKS" then is all the final rows, that is for each id2, it outputs all the "ABCD" rows that should be in the final output, and numbers them in rownum
Finally I left join "foobar" with "BLANKS" on id2 and rownum. Thus we get the correct number of rows, and the places where there are values in the original is output.

If you can manage to add an extra column in your table, that defines which [id2] are part from the same sequence you can try this:
declare #table table
(
id1 int,
id2 int,
ch char(1) not null,
val int,
category int -- extra column
)
insert into #table values (1112, 121, 'A', 12, 1)
insert into #table values (1351, 121, 'A', 13, 2)
insert into #table values (1411, 121, 'B', 81, 2)
insert into #table values (1312, 7, 'C', 107, 3)
insert into #table values (1401, 2, 'A', 107, 4)
insert into #table values (1454, 2, 'D', 107, 4)
insert into #table values (1257, 6, 'A', 1, 5)
insert into #table values (1269, 6, 'B', 12, 5)
insert into #table values (1335, 6, 'C', 12, 5)
insert into #table values (1341, 6, 'D', 5, 5)
insert into #table values (1380, 6, 'A', 3, 5)
DECLARE #sequence table (seq varchar(1))
INSERT INTO #sequence values ('A'), ('B'), ('C'), ('D')
SELECT b.id1, a.id2, a.seq, b.val, a.category
INTO #T1
FROM (
SELECT *
FROM #table
CROSS JOIN #sequence
) A
LEFT JOIN (
SELECT * FROM #table
) B
ON 1=1
AND a.id1 = b.id1
AND a.id2 = b.id2
AND a.seq = b.ch
AND a.val = b.val
;WITH rem_duplicates AS (
SELECT *, dup = ROW_NUMBER() OVER (PARTITION by id2, seq, category ORDER BY id1 DESC)
FROM #T1
) DELETE FROM rem_duplicates WHERE dup > 1
SELECT * FROM #T1 ORDER BY id2 DESC, category ASC, seq ASC
DROP TABLE #T1

I'm little confused by your output, try this:
Update
DECLARE #table TABLE
(
row INT IDENTITY(1, 1) ,
id1 INT ,
id2 INT ,
ch CHAR(1) NOT NULL ,
val INT
);
DECLARE #Sequence TABLE ( ch3 CHAR(1) NOT NULL );
INSERT INTO #Sequence
VALUES ( 'A' );
INSERT INTO #Sequence
VALUES ( 'B' );
INSERT INTO #Sequence
VALUES ( 'C' );
INSERT INTO #Sequence
VALUES ( 'D' );
INSERT INTO #table
VALUES ( 1112, 121, 'A', 12 );
INSERT INTO #table
VALUES ( 1351, 121, 'A', 13 );
INSERT INTO #table
VALUES ( 1411, 121, 'B', 81 );
INSERT INTO #table
VALUES ( 1312, 7, 'C', 107 );
INSERT INTO #table
VALUES ( 1401, 2, 'A', 107 );
INSERT INTO #table
VALUES ( 1454, 2, 'D', 107 );
INSERT INTO #table
VALUES ( 1257, 6, 'A', 1 );
INSERT INTO #table
VALUES ( 1269, 6, 'B', 12 );
INSERT INTO #table
VALUES ( 1335, 6, 'C', 12 );
INSERT INTO #table
VALUES ( 1341, 6, 'D', 5 );
INSERT INTO #table
VALUES ( 1380, 6, 'A', 3 );
SELECT r.id1 ,
fin.id2 ,
ch3 ,
r.val
FROM ( SELECT *
FROM ( SELECT CASE WHEN r.chd - l.chd = 1 THEN 0
ELSE 1
END [gap in sq] ,
l.*
FROM ( SELECT id2 ,
ASCII(ch) chd ,
ch ,
val ,
id1 ,
row
FROM #table
) AS l
LEFT JOIN ( SELECT id2 ,
ASCII(ch) chd ,
row
FROM #table
) AS r ON l.row = r.row - 1
) AS temp ,
#Sequence s
WHERE temp.[gap in sq] = 1
OR ( temp.[gap in sq] = 0
AND s.ch3 = temp.ch
)
) AS fin
LEFT JOIN #table r ON r.id2 = fin.id2
AND r.id1 = fin.id1
AND r.ch = fin.ch3

Related

Raws into Strings in Columns - only if unique

In another post I have asked how to improve the query below which currently returns:
Now I have another question. How to modificate the code to have final cluster string only with ErrorCodes which are unique in entire string so for line 1 returns only one B, C, A (skip second C and second A).
Regards,
Arek
DECLARE #table1 TABLE
(
[Case] INT,
ErrorCode CHAR(1),
[Date] varchar(20)
);
INSERT INTO #table1
VALUES
(1, 'A', '2018-01-25'),
(1, 'B', '2018-01-15'),
(1, 'C', '2018-01-15'),
(1, 'A', '2018-01-15'),
(1, 'C', '2018-01-15'),
(1, 'A', '2018-01-15'),
(2, 'D', '2018-01-26'),
(2, 'A', '2018-01-26'),
(2, 'D', '2018-01-25'),
(2, 'C', '2018-01-24'),
(2, 'C', '2018-01-24');
SELECT *
FROM #table1;
SELECT tabel2.[Case],
tabel2.[Date],
STUFF(
(
SELECT ', ' + ErrorCode
FROM #table1 t1
WHERE t1.[Case] = tabel2.[Case]
AND t1.[Date] = tabel2.[Date]
FOR XML PATH('')
),
1,
1,
''
) AS [ErrorCode]
FROM
(SELECT DISTINCT [Case], [Date] FROM #table1) AS tabel2
ORDER BY tabel2.[Case],
tabel2.[Date];
If I get this correctly, it should be enough to add a DISTINCT to the sub-query returning the CSV:
STUFF(
(
SELECT ** DISTINCT ** ', ' + ErrorCode --remove **
FROM #table1 t1
WHERE t1.[Case] = tabel2.[Case]
AND t1.[Date] = tabel2.[Date]
FOR XML PATH('')
),

List ranges and the total count based on condition

Table Schema
CREATE TABLE [dbo].[TblMaster](
[SID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[VID] [int] NOT NULL,
[CreatedDate] [datetime] default (getdate()) NOT NULL,
[CharToAdd] [varchar](10) NULL,
[Start] [int] NOT NULL,
[End] [int] NOT NULL
)
GO
CREATE TABLE [dbo].[TblDetails](
[DetailsID] [int] IDENTITY(1,1) NOT NULL Primary Key,
[SID] [int] NOT NULL,
[Sno] [int] NOT NULL,
[ConcatenatedText] [varchar](20) NOT NULL,
[isIssued] [bit] default (0) NOT NULL,
[isUsed] [bit] default (0) NOT NULL
)
GO
Sample Data:
Insert into dbo.TblMaster Values (1,default, 'CA', 1, 5)
Insert into dbo.TblMaster Values (1,default, 'PA', 1, 5)
GO
Insert into dbo.TblDetails values(1, 1, 'CA1', 0,0)
Insert into dbo.TblDetails values(1, 2, 'CA2', 0,0)
Insert into dbo.TblDetails values(1, 3, 'CA3', 0,0)
Insert into dbo.TblDetails values(1, 4, 'CA4', 1,0)
Insert into dbo.TblDetails values(1, 5, 'CA5', 0,0)
Insert into dbo.TblDetails values(2, 1, 'PA1', 0,0)
Insert into dbo.TblDetails values(2, 2, 'PA2', 0,0)
Insert into dbo.TblDetails values(2, 3, 'PA3', 1,0)
Insert into dbo.TblDetails values(2, 4, 'PA4', 0,0)
Insert into dbo.TblDetails values(2, 5, 'PA5', 0,0)
Insert into dbo.TblDetails values(3, 1, '1', 0,0)
Insert into dbo.TblDetails values(3, 2, '2', 1,0)
Insert into dbo.TblDetails values(3, 3, '3', 1,0)
Insert into dbo.TblDetails values(3, 4, '4', 0,0)
Insert into dbo.TblDetails values(3, 5, '5', 0,0)
GO
Expected Output:
Query I have built as of now:
Declare #VID INT = 1
;WITH Tmp as
(
SELECT
TM.CharToAdd as Prefix,
sno,
sno - ROW_NUMBER() OVER(ORDER BY sno) as grp
FROM dbo.TblDetails TD
LEFT JOIN dbo.TblMaster TM on TM.[SID] = TD.[SID]
WHERE isIssued = 0 and isUsed = 0
AND TM.VID = #VID
)
SELECT Prefix,
MIN(sno) as RangeStart,
MAX(sno) as RangeEnd,
COUNT(*) as [Count]
FROM Tmp
GROUP BY grp, Prefix
In the TblDetails table want to find the range of available values and its total counts from all records whose bit columns are 0. If bit column is 1 then it means it is already used so I am trying to skip it and list rest as available records. Doubtful whether am I explaining the problem statement well so have provided the sample data and expected output for better understanding. I did try doing some recursive function but the result isn't matching the expected output. So looking for help to resolve this.
You were very close...
CODE
Declare #VID INT = 1
;with cte as(
select
m.CHarToAdd,
d.sno,
d.sno - ROW_NUMBER() OVER(partition by m.CharToAdd ORDER BY sno) as grp
from
TblMaster m
inner join
TblDetails d on
d.sid = m.sid
where
d.isIssued = 0 and d.isUsed = 0 and m.vid = #VID)
select
CharToAdd,
min(sno) as Start,
max(sno) as [End],
(max(sno) - min(sno) + 1) as [Count]
from cte
group by
CHarToAdd, grp
order by
CHarToAdd
RESULTS
CharToAdd Start End Count
CA 1 3 3
CA 5 5 1
PA 1 2 2
PA 4 5 2

WHERE field1 IN (NULL)

I want to retrieve data from the table based on a couple of columns, some with data, other with NULL. In the first code example below, the procedure is fine - all the rows are returned. In the second example no rows are returned - because NULL=NULL return FALSE.
The third example is more or less what I have in mind, when the column has NULL values, then this clause has to be "ignored" and only the data in the first two columns are used to return the rows based on these two columns.
SELECT * FROM XYZ
WHERE col1 IN ('a', 'b')
AND col2 IN ('c', 'd')
AND col3 IN ('e', 'f')
SELECT * FROM XYZ
WHERE col1 IN ('a', 'b')
AND col2 IN ('c', 'd')
AND col3 IN (NULL) --???
SELECT * FROM XYZ
WHERE col1 IN ('a', 'b')
AND col2 IN ('c', 'd')
--AND col3 IN (NULL) --IGNORED
Currently I use a dynamic SQL statement, but is very slow.
SET #EventCodeList =
(SELECT REPLACE(tEventCode, ' ', '') FROM tblActivityPerCC_Config WHERE tCostCenter = #CostCenter)
SET #EventCodeStr =
CASE WHEN #EventCodeList IS NULL THEN ' logs.tEventCode LIKE (''%'') '
ELSE ' logs.tEventCode IN (SELECT qValue FROM nsEMV.dbo.fncReturnCommaDelimitedStringAsTable(#EventCodeList))'
END
SET #SQLString = N'
SELECT
ccg.Field1,
logs.Field2
FROM dbo.tblEMV_Logsheet AS logs
INNER JOIN dbo.tblLookup_EMVEquipment AS ccg ON logs.tEquipmentKey = ccg.tEquipmentKey
WHERE tDate BETWEEN ''' + CONVERT(varchar(30), #BMonth) + ''' AND ''' + CONVERT(varchar(30), #EMonth) + '''
AND logs.tAreaCode IN ('XYZ', 'ABC')
AND ' + #EventCodeStr + ' --THE FOLLOWING COLUMNS MAY HAVE NULL VALUES
AND ' + #SourceStr + '
AND ' + #DestinationStr'
Any help would be appreciated.
IN RESPONSE TO Jayvee's SUGGESTION BELOW:
This does not do what was intended or I do something wrong!
CREATE TABLE #temp
(
value varchar(10),
value2 varchar(10)
)
INSERT INTO #temp (value, value2) SELECT '5', '3'
INSERT INTO #temp (value, value2) SELECT NULL, '2'
INSERT INTO #temp (value, value2) SELECT '4', NULL
INSERT INTO #temp (value, value2) SELECT '6', '2'
INSERT INTO #temp (value, value2) SELECT '6', NULL
INSERT INTO #temp (value, value2) SELECT '6', '4'
INSERT INTO #temp (value, value2) SELECT NULL, '1'
INSERT INTO #temp (value, value2) SELECT NULL, '4'
SELECT value as [value],value2 as [value2] FROM #temp
WHERE ISNULL(value,'') IN ('4')
AND ISNULL(value2,'') IN ('4')
DROP TABLE #temp
Try IS NULL
SELECT * FROM XYZ
WHERE col1 IN ('a', 'b')
AND col2 IN ('c', 'd')
AND col3 IS NULL -- Instead of IN (NULL)
for each column test it against your parameter and include the IS NULL test, combine these with parentheses, like this:
SELECT
*
FROM XYZ
WHERE (col1 IN ('a', 'b') OR col1 IS NULL)
AND (col2 IN ('c', 'd') OR col2 IS NULL)
AND (col3 IN (NULL) OR col3 IS NULL)
here col3 IN (NULL) is used to demonstrate a parameter that was not provided
something like this should work:
SELECT * FROM XYZ
WHERE ISNULL(col1,'') IN ('a', 'b','')
AND ISNULL(col2,'') IN ('c', 'd','')
AND ISNULL(col3,'') IN ('')

Transpose records from 2 "one to many" tables into one record

Basically, I would like to get a recordset similar to this:
CustomerID, CustomerName, OrderNumbers
1 John Smith 112, 113, 114, 115
2 James Smith 116, 117, 118
Currently I am using an Sql Server UDF to concatenate order #s.
Are there more efficient solutions ?
1) There are allot of solutions.
2) Example (SQL2005+):
DECLARE #Customer TABLE (
CustomerID INT PRIMARY KEY,
Name NVARCHAR(50) NOT NULL
);
INSERT #Customer VALUES (1, 'Microsoft');
INSERT #Customer VALUES (2, 'Macrosoft');
INSERT #Customer VALUES (3, 'Appl3');
DECLARE #Order TABLE (
OrderID INT PRIMARY KEY,
CustomerID INT NOT NULL -- FK
);
INSERT #Order VALUES (1, 1);
INSERT #Order VALUES (2, 2);
INSERT #Order VALUES (3, 2);
INSERT #Order VALUES (4, 1);
INSERT #Order VALUES (5, 2);
SELECT c.CustomerID, c.Name, oa.OrderNumbers
FROM #Customer AS c
/*CROSS or */OUTER APPLY (
SELECT STUFF((
SELECT ',' + CONVERT(VARCHAR(11), o.OrderID) FROM #Order AS o
WHERE o.CustomerID = c.CustomerID
-- ORDER BY o.OrderID -- Uncomment of order nums must be sorted
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)'), 1, 1, '') AS OrderNumbers
) oa;
Output:
CustomerID Name OrderNumbers
----------- --------- ------------
1 Microsoft 1,4
2 Macrosoft 2,3,5
3 Appl3 NULL

T-SQL Column values count

Say I have a table A and it has 5 columns (Column1, Column2.. Column5), the values in each column is one char size and stored only
as alphabetic as follows
ID Column1 Column2 Column3 Column4 Column5
1 A C D A B
2 A D A B A
3 B K Q C Q
4 A K E E B
5 F K F F S
I need a count of each different value stored in column1 to column5, I want the following information
Column1 has A's count=3, B's count=1, F's count=1
Column2 has C's count=1, D's count=1, K's count=3
and so on
What is the correct way and format to return these values?
Thanks
You could try:
SELECT 'Col1' As 'Col', Column1 as 'Value', COUNT(*) as 'Ct'
FROM MyTable
GROUP BY Column1
UNION ALL
SELECT 'Col2', Column2, COUNT(*)
FROM MyTable
GROUP BY Column2
...
You will need to write an additional SELECT to UNION for each column you want to aggregate, but it will return the data you are after.
Can you just execute an individual query for each needed column using a GROUP BY?
SELECT Column1, COUNT(Column1) FROM TableName
GROUP BY Column1
You can use unpivot in a derived table (or CTE) and group by column name and value in the outer query.
Try this:
declare #T table
(
ID int,
Column1 char(1),
Column2 char(1),
Column3 char(1),
Column4 char(1),
Column5 char(1)
)
insert into #T values
(1, 'A', 'C', 'D', 'A', 'B'),
(2, 'A', 'D', 'A', 'B', 'A'),
(3, 'B', 'K', 'Q', 'C', 'Q'),
(4, 'A', 'K', 'E', 'E', 'B'),
(5, 'F', 'K', 'F', 'F', 'S')
;with C as
(
select ID, Col, Val
from (
select ID, Column1, Column2, Column3, Column4, Column5
from #T
) as T
unpivot (Val for Col in (Column1, Column2, Column3, Column4, Column5)) as U
)
select Col, Val, count(*) as ValCount
from C
group by Col, Val
order by Col
The union approach is going to server you best. The individual query for each union would look something like this:
Select Distinct Column1, COUNT(Column1)OVER(PARTITION BY Column1) Col1Count, 'Column1' ColumnName
From ColTable
Union All
...