Using cross join with multiple variable in cte - sql

I have some order numbers and want to check if any number has been skipped. I'll use left table method which is:
select * from
#CreatedCrossTable (which has all possibilities)
Left Join #MainTableWhichHaveRealSerialNo mt
where
mt is null
Order no structure is: "CodeType.Year.Month.SerialNo". For instance: "DP.21.07.001","DP.21.07.002".. or not DP, but "FB.21.07.001", "FB.21.07.002" etc.
I want to create a cross table schema for determine the skipped SerilNo values (CreatedCrossTable
above):
(Serial number is reset every month)
CodeType | Year | Month | SerialNo
DP 21 1 1
DP 21 1 2
DP 21 1 3
DP 21 1 4
...
(All SerialNos must increase max serial number of the original table's SerialNo (MainTableWhichHaveRealSerialNo) Also codeType,year and month values must match)
DP 21 2 1
DP 21 2 2
...
FB 21 1 1
FB 21 1 2
...
FB 21 1 1
FB 21 1 2
FB 21 1 3
...
Each Codes' and Month's serial number have a different Maximum Number
for creating CrossTable. I've written that code:
;WITH cteSerialNo AS
(
SELECT 1 AS ORDERNO
UNION ALL
SELECT (ORDERNO+1) AS ORDERNO FROM cteSerialNo WHERE ORDERNO < MAX_ORDER_NO
)
,cteMonthYear AS
(
SELECT CAST('2021.01.01' AS DATE) AS Dt
UNION ALL
SELECT DATEADD(MONTH , 1, Dt) AS Dt
FROM cteMonthYear
WHERE DATEADD (MONTH, 1, Dt) < GETDATE()
)
SELECT
*
FROM
(
SELECT
CODES.CODETYPE,
YEAR(Dts.Dt) AS 'YEAR',
MONTH(Dts.Dt) AS 'MONTH'
FROM
##KK_TBL_CODETYPES AS CODES
CROSS JOIN cteMonthYear AS Dts
) AS CROSSTABLE
CROSS JOIN cteSerialNo AS cSN
How can i enter (MAX_ORDER_NO) for each variable in this code?

Assuming that the max SerialNo value is based on the existing values in the SerialNo column, you would want to just find all possible combinations up to that SerialNo value and then remove those that have a match in the source data:
-- Define test data
declare #t table(CodeType varchar(2),[Year] int,[Month] int,SerialNo int);
insert into #t values
('DP',21,1,1)
,('DP',21,1,2)
,('DP',21,1,3)
--,('DP',21,1,4) -- Missing so should be in Output
,('DP',21,1,5)
,('DP',21,2,1)
,('DP',21,2,2)
,('FB',21,1,1)
,('FB',21,1,2)
,('FB',21,2,1)
,('FB',21,2,2)
--,('FB',21,2,3) -- Missing so should be in Output
,('FB',21,2,4)
;
with m as -- Get Max SerialNo for each grouping
(
select CodeType
,[Year]
,[Month]
,max(SerialNo) as MaxSerialNo
from #t
group by CodeType
,[Year]
,[Month]
)
,t as -- Create a table with 10 rows in
(
select t
from(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as t(t)
)
,n as -- Self join those 10 rows 5 times to generate a possible 10*10*10*10*10 = 100,000 incrementing numbers using row_number
(
select top(select max(MaxSerialNo) from m) row_number() over (order by (select null)) as n
from t,t t2,t t3,t t4,t t5
)
-- Join from the numbers table to the source data to generate all possible SerialNo values up to the Max
select m.CodeType
,m.[Year]
,m.[Month]
,n.n as SerialNo
from n
left join m
on n.n <= m.MaxSerialNo
except -- Then exclude any that are in the source data
select CodeType
,[Year]
,[Month]
,SerialNo
from #t
order by CodeType
,[Year]
,[Month]
,SerialNo
Output
CodeType
Year
Month
SerialNo
DP
21
1
4
FB
21
2
3

Related

How to write query for this using between in SQL Server 2008?

How to write query for this result?
I need output of EXP_POINT value in between with find middle value of this PKT_NO wise. Means that near by value find.
SELECT *
FROM TABLE_MAIN T1
CROSS APPLY
(
SELECT * FROM TABLE_SUB xT2
WHERE T2.PKT_NO = xT2.PKT_NO
AND xT2.EXP_POINT BETWEEN T1.EXP_POINT-0.100 AND T1.EXP_POINT + 0.100
)EX
I tired this query but I am not getting proper result.
My solution assumes that there can be any number of rows in TABLE_SUB. I have assumed that when you specify the middle row, that you mean that the rows are sorted by EXT_POINT, if this is not what you need you will have to amend the ORDER BY clause of the PARTITION statement in the RowOrderPerPkt CTE.
If there are an odd number of rows then it takes the middle row per PKT.
If there are an even number of rows then it takes the middle two rows per PKT and takes the average of them.
So here it is:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE TABLE_MAIN (PKT INT,EXT DECIMAL(10,3))
INSERT INTO TABLE_MAIN (PKT,EXT)
VALUES (1101,0.508), (1102,1.998), (1103,0.423)
CREATE TABLE TABLE_SUB (PKT INT,EXT DECIMAL(10,3))
INSERT INTO TABLE_SUB (PKT,EXT)
VALUES (1101,0.504), (1101,0.505), (1101,0.510)
,(1102,1.990), (1102,1.995), (1102,2.005)
,(1103,0.504), (1103,0.505), (1103,0.510), (1103,1.990)
Query 1:
;WITH RowOrderPerPkt
AS
(
SELECT PKT, EXT, ROW_NUMBER() OVER (PARTITION BY PKT ORDER BY EXT) AS RN
FROM TABLE_SUB
),
NumRowsPerPkt
AS
(
SELECT PKT, COUNT(*) AS MaxRows
FROM TABLE_SUB
GROUP BY PKT
)
-- TABLE_SUB with an odd number of rows per PKT
-- Simply take the middle row
-- i.e. MaxRows / 2 + 1
SELECT T1.PKT, T1.EXT, ROPP.EXT
FROM TABLE_MAIN T1
INNER JOIN RowOrderPerPkt ROPP
ON ROPP.PKT = T1.PKT
INNER JOIN NumRowsPerPkt NRPP
ON NRPP.PKT = ROPP.PKT
WHERE NRPP.MaxRows % 2 = 1 AND
ROPP.RN = NRPP.MaxRows / 2 + 1
UNION
-- TABLE_SUB with an even number of rows per PKT
-- Simply take the middle 2 rows and find the average
-- i.e. get the rows MaxRows / 2 and MaxRows / 2 + 1
SELECT T1.PKT, T1.EXT, AVG(ROPP.EXT)
FROM TABLE_MAIN T1
INNER JOIN RowOrderPerPkt ROPP
ON ROPP.PKT = T1.PKT
INNER JOIN NumRowsPerPkt NRPP
ON NRPP.PKT = ROPP.PKT
WHERE NRPP.MaxRows % 2 = 0
AND (ROPP.RN = NRPP.MaxRows / 2 OR ROPP.RN = NRPP.MaxRows /2 + 1)
GROUP BY T1.PKT, T1.Ext
Results:
| PKT | EXT | EXT |
|------|-------|--------|
| 1101 | 0.508 | 0.505 |
| 1102 | 1.998 | 1.995 |
| 1103 | 0.423 | 0.5075 |
declare #t table (PKT INT,EXT DECIMAL(10,3))
insert into #t (PKT,EXT)values (1101,0.508),(1102,1.998)
declare #tt table (PKT INT,EXT DECIMAL(10,3))
insert into #tt (PKT,EXT)values (1101,0.504),(1101,0.505),(1101,0.510),(1102,1.990),(1102,1.995),(1102,2.005)
select * from #t T
CROSS APPLY (select ROW_NUMBER()OVER(PARTITION BY PKT ORDER BY EXT )RN, PKT,EXT FROm #tt )TT
WHERE T.PKT = TT.PKT AND TT.RN = 2
If you want only one value, then you need to use TOP, preferably with ORDER BY. This should do what you want:
SELECT *
FROM TABLE_MAIN T1 CROSS APPLY
(SELECT TOP 1 *
FROM TABLE_SUB T2
WHERE T1.PKT_NO = T2.PKT_NO AND
T2.EXP_POINT BETWEEN T1.EXP_POINT-0.100 AND T1.EXP_POINT + 0.100
ORDER BY ABS(T2.EXP_POINT - T1.EXP_POINT)
) EX;

SQL Group By Certain Amounts

I have an order table which has the customer id and order amount. I want to join these orders but joined orders cannot exceed a certain amount. An example below:
Let's say the maximum amount is 33 pallets and I have a table like this:
Order ID Client ID Amount
1 100001 10
2 100001 22
3 100001 13
4 100001 33
5 100001 1
6 100001 5
7 100001 6
The result should be:
Order ID Client ID Amount Joined ID Joined Amount
1 100001 10 100001A 32
2 100001 22 100001A 32
3 100001 13 100001B 13
4 100001 33 100001C 33
5 100001 1 100001D 12
6 100001 5 100001D 12
7 100001 6 100001D 12
Here, if we can also come up with a way to ad orders numbered 5,6,7 to joined order 10001B it would be great. But even this solution will be enough.
I have a few ideas on how to solve this but I couldn't really come up with a working solution. I'll be handling around 2000 Order Ids like this, so also I don't want this to be a slow operation. I'm using SQL Server 2014
you can find proposed solution (sql definition) with help of recursive CTE here: http://sqlfiddle.com/#!6/285c16/45
basicaly CTE iterates ordered list (by clientID, orderID) and evaluate if summed amount is not over 33.
i have added next clientID to mock data, to test correct subcount criteria evaluation.
here is query to obtain results:
-- prepare numbering for iteration
with orders_nr
as
(
select row_number() over(order by clientID, id) as [nr],
o.*
from orders o
)
,
-- prepare sum totals
re
as
(
select id, amount, amount as amount_total ,o.[nr] as nr,
clientID
from orders_nr o
where o.[nr]=1
UNION ALL
select o.id, o.amount,
CASE WHEN o.clientID <> r.clientID then o.amount
ELSE o.amount+ r.amount_total END,
o.[nr] as nr, o.clientID
from orders_nr o join re r
on (o.[nr]=r.[nr]+1)
)
,
-- iterate total - evaluate current criteria (<=33)
re2 as
(
select re.id, re.amount, re.amount_total,
re.[nr] as [group], re.[nr], re.clientID
from re
where re.[nr]=1
UNION ALL
select r.id, r.amount,
CASE WHEN r.amount+re2.amount_total >33
OR r.clientID<>re2.clientID
then r.amount ELSE re2.amount_total+r.amount END
as amount_total,
CASE WHEN r.amount+re2.amount_total >33
OR r.clientID<>re2.clientID THEN
r.[nr] ELSE re2.[group] END as [group], r.[nr], r.clientID
from re r join re2
on (r.[nr]=re2.[nr]+1 )
)
, group_total
AS
(
select [group], clientID, max(amount_total) as total
FROM re2
group by [group], clientID
),
result
as
(
select
r.id, r.clientID, r.amount,
cast(r.clientid as varchar(20))
+'-'+char(64+cast(
dense_rank()
over( partition by r.clientID
order by r.[clientID], r.[group])
as varchar(3))) as joinedID
, gt.total as joinedAmount
from re2 as r join group_total gt
on (r.clientID=gt.clientID AND r.[group]=gt.[group])
)
select * from result
Not certain if I'm understanding the question correctly, but you might try
select [Client ID], [Joined ID], sum([Amount]) as Total_Amount
from [table_name]
group by [Client ID], [Joined ID]
having sum([Amount]) <= 33
It leaves off the Order ID, but since it looks to be unique, you can't use it in a group by.
Edit was to add the having clause in the query to say we can't have something that adds up to more than 33.
I tried to solve with simple selects and without using an explicit cursor but it was a little hard in that way.
I've solved it and got exactly what you wanted with:
a TempTable, a cursor, a counter for checking the sum of sequent amounts, CHAR() function to generate letters; I calculated the values and inserted into temp table finally updated the temp table, following is what I tried and the DEMO IS HERE.
create table #tbl_name
(OrderID int,
ClientID int,
Amount int,
joinedId varchar(15) ,
joinedAmount int)
insert #tbl_name(OrderID,ClientID,Amount)
select OrderID,ClientID,Amount from tbl_name
declare cr cursor for
select orderId,
clientId,
amount
from tbl_name
order by OrderId
declare #summedAmount int,
#orderId int,
#clientId int,
#amount int,
#counter int
set #summedAmount=0
set #counter=65
open cr
fetch from cr into #orderId,#clientId,#amount
while (##fetch_status=0)
begin
if (#amount + #summedAmount < 33)
begin
set #summedAmount=#summedAmount+#amount
update #tbl_name
set joinedId=cast(#ClientId as varchar(10))+char(#counter),
joinedAmount=#summedAmount
where orderId=#orderId
end
else if (#amount + #summedAmount >33)
begin
set #counter=#counter+1
set #summedAmount=#amount
update #tbl_name
set joinedId=cast(#ClientId as varchar(10))+char(#counter),
joinedAmount=#Amount
where orderId=#orderId
end
fetch from cr into #orderId,#clientId,#amount
end
close cr
deallocate cr
go
with CTE as
(
select JoinedId, max(joinedAmount) mx
from #tbl_name
group by JoinedId
)
update #tbl_name
set joinedAmount = CTE.mx
from #tbl_name
join CTE on #tbl_name.JoinedId=CTE.JoinedId
select * from #tbl_name
drop table #tbl_name

How do I aggregate numbers from a string column in SQL

I am dealing with a poorly designed database column which has values like this
ID cid Score
1 1 3 out of 3
2 1 1 out of 5
3 2 3 out of 6
4 3 7 out of 10
I want the aggregate sum and percentage of Score column grouped on cid like this
cid sum percentage
1 4 out of 8 50
2 3 out of 6 50
3 7 out of 10 70
How do I do this?
You can try this way :
select
t.cid
, cast(sum(s.a) as varchar(5)) +
' out of ' +
cast(sum(s.b) as varchar(5)) as sum
, ((cast(sum(s.a) as decimal))/sum(s.b))*100 as percentage
from MyTable t
inner join
(select
id
, cast(substring(score,0,2) as Int) a
, cast(substring(score,charindex('out of', score)+7,len(score)) as int) b
from MyTable
) s on s.id = t.id
group by t.cid
[SQLFiddle Demo]
Redesign the table, but on-the-fly as a CTE. Here's a solution that's not as short as you could make it, but that takes advantage of the handy SQL Server function PARSENAME. You may need to tweak the percentage calculation if you want to truncate rather than round, or if you want it to be a decimal value, not an int.
In this or most any solution, you have to count on the column values for Score to be in the very specific format you show. If you have the slightest doubt, you should run some other checks so you don't miss or misinterpret anything.
with
P(ID, cid, Score2Parse) as (
select
ID,
cid,
replace(Score,space(1),'.')
from scores
),
S(ID,cid,pts,tot) as (
select
ID,
cid,
cast(parsename(Score2Parse,4) as int),
cast(parsename(Score2Parse,1) as int)
from P
)
select
cid, cast(round(100e0*sum(pts)/sum(tot),0) as int) as percentage
from S
group by cid;

Build table with previous months (cumulative)

I'm a bit lost with the following problem that I need to solve with an SQL query, no plsql. The idea is to build a cumulative column to calculate all previous months. The input table looks like
Month
1
2
3
..
24
I need build the following table :
Month Cum_Month
1 1
2 1
2 2
3 1
3 2
3 3
..
24 1
...
24 23
All this in SQL Server 2008, thanks in advance
You can do it like this:
DECLARE #tbl TABLE ([Month] INT)
INSERT #tbl VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24)
SELECT Month
, ROW_NUMBER() OVER (PARTITION BY Month ORDER BY Month) num
FROM #tbl a
JOIN
(
SELECT *
FROM master..spt_values
WHERE type = 'P'
)
b ON b.number < a.Month
master..spt_values is used to generate numbers, after numbers are generated result of the subquery is joined on the #tbl to get the number of rows that corresponds to the month. After that ROW_NUMBER is used to create appropriate ordinal numbers for each month.
Here's a pretty cool trick not using any tables:
SELECT N.Number as Month, N2.Number as Cum_Month
FROM
(SELECT Number FROM master..spt_values WHERE Number BETWEEN 1 AND 24 AND Type = 'P') N
JOIN (SELECT Number FROM master..spt_values WHERE Number BETWEEN 1 AND 24 AND Type = 'P') N2 ON N.Number >= N2.Number
ORDER BY N.Number, N2.Number
And the Fiddle.
And if you really don't want the last 24 24 (why not), just change the second query to between 1 and 23).

SQL gaps in dates

I am trying to find gaps in the a table based on a state code the tables look like this.
StateTable:
StateID (PK) | Code
--------------------
1 | AK
2 | AL
3 | AR
StateModel Table:
StateModelID | StateID | EfftiveDate | ExpirationDate
-------------------------------------------------------------------------
1 | 1 | 2012-06-28 00:00:00.000| 2012-08-02 23:59:59.000
2 | 1 | 2012-08-03 00:00:00.000| 2050-12-31 23:59:59.000
3 | 1 | 2055-01-01 00:00:00.000| 2075-12-31 23:59:59.000
The query I am using is the following:
Declare #gapMessage varchar(250)
SET #gapMessage = ''
select
#gapMessage = #gapMessage +
(Select StateTable.Code FROM StateTable where t1.StateID = StateTable.StateID)
+ ' Row ' +CAST(t1.StateModelID as varchar(6))+' has a gap with '+
CAST(t2.StateModelID as varchar(6))+ CHAR(10)
from StateModel t1
inner join StateModel t2
on
t1.StateID = t2.StateID
and DATEADD(ss, 1,t1.ExpirationDate) < t2.EffectiveDate
and t1.EffectiveDate < t2.EffectiveDate
if(#gapMessage != '')
begin
Print 'States with a gap problem'
PRINT #gapMessage
end
else
begin
PRINT 'No States with a gap problem'
end
But with the above table example I get the following output:
States with a gap problem
AK Row 1 has a gap with 3
AK Row 2 has a gap with 3
Is there anyway to restructure my query so that the gap between 1 and 3 does not display because there is not a gap between 1 and 2?
I am using MS sql server 2008
Thanks
WITH
sequenced AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY StateID ORDER BY EfftiveDate) AS SequenceID,
*
FROM
StateModel
)
SELECT
*
FROM
sequenced AS a
INNER JOIN
sequenced AS b
ON a.StateID = b.StateID
AND a.SequenceID = b.SequenceID - 1
WHERE
a.ExpirationDate < DATEADD(second, -1, b.EfftiveDate)
To make this as effective as possible, also add an index on (StateID, EfftiveDate)
I wanted to just give credit to MatBailie, but don't have the points to do it yet, so I thought I would help out anyone else looking for a similar solution that may want to take it a step further like I needed to. I have changed my application of his code (which involves member enrollment) to the same language as the example here.
In my case, I needed these things:
I have two similar tables that I need to develop into one total table. In this example, let's make the tables like this: SomeStates + OtherStates = UpdatedTable. These are UNIONED in the AS clause.
I didn't want to remove any rows due to gaps, but I wanted to flag them on the StateID level. This is added as an additional column 'StateID_GapFlag'.
I also wanted to add a column to hold the oldest or MIN(EffectiveDate). This would be used in later calculations of SUM(period) to get a total duration, excluding gaps. This is the column 'MIN_EffectiveDate'.
;WITH sequenced
( SequenceID
,EffectiveDate
,ExpirationDate)
AS
(select
ROW_NUMBER() OVER (PARTITION BY StateID ORDER by EffectiveDate) as SequenceID,
* from (select EffectiveDate, ExpirationDate from SomeStates
UNION ALL
(select EffectiveDate, ExpirationDate from OtherStates)
) StateModel
where
EffectiveDate > 'filter'
)
Select DISTINCT
IJ1.[MIN_EffectiveDate]
,coalesce(IJ2.GapFlag,'') as [MemberEnrollmentGapFlag]
,EffectiveDate
,ExpirationDate
into UpdatedTable
from sequenced seq
inner join
(select StateID, min(EffectiveDate) as 'MIN_EffectiveDate'
from sequenced
group by StateID
) IJ1
on seq.member# = IJ1.member
left join
(select a.member#, 'GAP' as 'StateID_GapFlag'
from sequenced a
inner join
sequenced b
on a.StateID = b.StateID
and a.SequenceID = (b.sequenceID - 1)
where a.ExpirationDate < DATEADD(day, -1, b.EffectiveDate)
) LJ2
on seq.StateID = LJ2.StateID
You could use ROW_NUMBER to provide an ordering of stateModel's for each state, then check that the second difference for consecutive rows doesn't exceed 1. Something like:
;WITH Models (StateModelID, StateID, Effective, Expiration, RowOrder) AS (
SELECT StateModelID, StateID, EffectiveDate, ExpirationDate,
ROW_NUMBER() OVER (PARTITION BY StateID, ORDER BY EffectiveDate)
FROM StateModel
)
SELECT F.StateModelId, S.StateModelId
FROM Models F
CROSS APPLY (
SELECT M.StateModelId
FROM Models M
WHERE M.RowOrder = F.RowOrder + 1
AND M.StateId = F.StateId
AND DATEDIFF(SECOND, F.Expiration, M.Effective) > 1
) S
This will get you the state model IDs of the rows with gaps, which you can format how you wish.