T-SQL Query a matrix table for free position - sql

I'm trying to build a query for a matrix table which has a schema like this:
X | Y | Z | Disabled | OccupiedId |
--------------------------------------------
1 1 1 0 NULL
1 2 1 0 NULL
1 3 1 1 NULL
1 4 1 0 1
1 5 1 0 2
1 6 1 0 3
1 7 1 0 4
1 1 2 0 NULL
1 2 2 0 NULL
1 3 2 0 NULL
1 4 2 0 NULL
1 5 2 0 NULL
1 6 2 0 NULL
1 7 2 0 NULL
I want to group for X, Z and find the first available position on Y.
Available by all means is NOT Disabled and NOT Occupied.
In the example provided this query should return:
X | Z | FreeY
--------------------------------------------
1 1 2
1 2 7
The query should select the first free Y (or the last occupied Y) considering that each (X, Z) are filled starting from the end (MAX Y is constant)
I've tried different approach unsuccessfully :(
Any suggestions is highly appreciated!
Kind Regards,
D.

SQL fiddle
CREATE TABLE Coordinate
( X int, Y int,Z int, Disabled bit, OccupiedId int)
INSERT INTO Coordinate VALUES (1,1,1, 1, NULL)
INSERT INTO Coordinate VALUES (1,1,2, 0, NULL)
INSERT INTO Coordinate VALUES (1,1,3, 0, NULL)
INSERT INTO Coordinate VALUES (1,1,4, 0, NULL)
INSERT INTO Coordinate VALUES (1,2,1, 0, NULL)
INSERT INTO Coordinate VALUES (1,2,2, 0, NULL)
INSERT INTO Coordinate VALUES (1,2,3, 0, 123)
INSERT INTO Coordinate VALUES (1,2,4, 0, NULL)
INSERT INTO Coordinate VALUES (1,2,5, 1, NULL)
SELECT X, Z, MIN(Y) AS FirstFreePosition
FROM Coordinate
WHERE Disabled = 0 AND OccupiedId IS NULL
GROUP BY X, Z
OR -- if you need the unavailable combinations too, then something like this:
SELECT X, Z, MIN(CASE
WHEN Disabled = 1 OR OccupiedId IS NOT NULL
THEN 1000 --a big number
ELSE Y END) AS FirstFreePosition
FROM Coordinate
GROUP BY X, Z

For your edit (disabled=bit column), this query shows lastOccupiedID as well as firstFreeY
select x, z,
max(case when disabled=1 or occupiedid is not null
then Y else 0 end) lastOccupiedPosition,
maX(case when disabled=0 AND occupiedid is null
then Y else 0 end) firstFreeY
from matrix
group by x, z
order by x, z;
SQL Fiddle
MS SQL Server 2008 Schema Setup:
create table matrix(
X int , Y int , Z int , Disabled varchar(5) , OccupiedId int );
insert matrix values
(1 , 1 , 1 , 'True' , NULL ),
(1 , 1 , 2 , 'False' , NULL ),
(1 , 1 , 3 , 'False' , NULL ),
(1 , 1 , 4 , 'False' , NULL ),
(1 , 2 , 1 , 'False' , NULL ),
(1 , 2 , 2 , 'False' , NULL ),
(1 , 2 , 3 , 'False' , 123 ),
(1 , 2 , 4 , 'False' , NULL );
Query 1:
select x, z,
max(case when disabled='true' or occupiedid is not null
then Y else 0 end) lastOccupiedPosition
from matrix
group by x, z
order by x, z
Results:
| X | Z | LASTOCCUPIEDPOSITION |
--------------------------------
| 1 | 1 | 1 |
| 1 | 2 | 0 |
| 1 | 3 | 2 |
| 1 | 4 | 0 |

Related

SQL COUNT of zero values in multiple columns

I am calculating how many zeros appear in a series of columns based on a ID.
Example Table:
ID hour1 hour2 hour3
1 2 10 0
2 0 0 0
3 0 24 0
I think it would look something like this, but obviously it doesn't work
SELECT ID, COUNT(CASE WHEN(
FROM (VALUES (hour1) , (hour2) , (hour3))
AS VALUE (v)) AS ZERO_HOURS
Desired output:
ID ZERO_HOURS
1 1
2 3
3 2
One method is:
select t.id, h.num_zeros
from t cross apply
(select count(*) as num_zeros
from (values (hour1), (hour2), (hour3)) v(h)
where h = 0
) h;
Of course a case expression is not so hard either:
select t.id,
(case when hour1 = 0 then 1 else 0 end +
case when hour2 = 0 then 1 else 0 end
case when hour3 = 0 then 1 else 0 end
) as num_zeros
Or, if there are no negative or NULL values:
select t.id,
(1 - sign(hour1)) + (1 - sign(hour2)) + (1 - sign(hour3)) as num_zeros
Please try the following solution.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, hour1 INT, hour2 INT, hour3 INT);
INSERT INTO #tbl (hour1, hour2, hour3) VALUES
(2, 10, 0),
(0, 0 , 0),
(0, 24, 0);
-- DDL and sample data population, end
SELECT ID
, c.value('count(/root/*[./text()="0"])','INT') AS ZERO_HOURS
FROM #tbl
CROSS APPLY (
SELECT hour1, hour2, hour3
FOR XML PATH(''), TYPE, ROOT('root')) AS t(c);
Output
+----+------------+
| ID | ZERO_HOURS |
+----+------------+
| 1 | 1 |
| 2 | 3 |
| 3 | 2 |
+----+------------+

How to select a record from duplicate records based on a condition

I have a table X which has three columns SN , OI, FLAG .
Some sample values are
SN OI FLAG
1 a Y
1 a N
2 x N
3 d N
3 d Null
4 z Y
4 z null
5 k Y
5 k Y
5 k Y
6 l N
6 l N
I want the result on the below condition
If there are multiple values of same SN , i want the result on the condition that if FLAG has Y and N , then it should show Y , IF Flag has Null and N , it should show N , IF Flag has Y and Null, then is should show Y. SO in the above example this is what I should get .
SN FLAG
1 Y
2 N
3 N
4 Y
5 Y
6 N
You can group by sn and get the flag with conditional aggregation:
select sn,
case
when sum(case flag when 'Y' then 1 end) > 0 then 'Y'
when sum(case flag when 'N' then 1 end) > 0 then 'N'
end flag
from tablename
group by sn
order by sn
In your special case, this should also work:
select sn, max(flag) flag
from tablename
group by sn
order by sn
because 'Y' > 'N'.
See the demo.
Results:
> SN | FLAG
> -: | :---
> 1 | Y
> 2 | N
> 3 | N
> 4 | Y
> 5 | Y
> 6 | N
For your given rules, you can just use MAX():
select sn, max(flag) as flag
from t
group by sn;

SQL Server: Increment row value depending on previous row

I have a table with the columns id and value. I'd like to create a column that groups the id. If a row's current value equals 0 then a new group in ideal_group will be created.
Table:
id | value | ideal_group
1 1 1
2 1 1
3 1 1
4 0 2
5 1 2
6 0 3
7 0 4
I'm thinking the solution should be something like:
SET #n = 1;
SELECT id,
CASE
WHEN value = 0 THEN #n = #n + 1
ELSE #n END AS ideal_group
But I'd prefer not to use an counter variable. Is there another way to go about this?
Try the below code, I assumed, that values in value column are only 1s and 0s:
select id,
value,
sum(1 - value) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
More general solution (without mentioned assumption):
select id,
value,
sum(case value when 0 then 1 else 0 end) over (order by id rows between unbounded preceding and current row) + 1 [ideal_group]
from MY_TABLE
create table tbl (id int, value int);
insert into tbl values
(1, 1),
(2, 1),
(3, 1),
(4, 0),
(5, 1),
(6, 0),
(7, 0);
GO
7 rows affected
select id,
value,
1 + sum(iif(value = 0, 1, 0)) over
(order by id rows between unbounded preceding and current row) as ideal_group
from tbl
GO
id | value | ideal_group
-: | ----: | ----------:
1 | 1 | 1
2 | 1 | 1
3 | 1 | 1
4 | 0 | 2
5 | 1 | 2
6 | 0 | 3
7 | 0 | 4
dbfiddle here
If you reversed the 1 and 0 and it was only 1 or 0 this would be easier.
declare #T table (id int primary key, val int);
insert into #T values
(1, 1)
, (2, 1)
, (3, 1)
, (4, 0)
, (5, 1)
, (6, 0)
, (7, 0);
select t.id, t.val
, case when t.val = 0 then 1 else 0 end as trig
, sum(case when t.val = 0 then 1 else 0 end) over (order by t.id) + 1 as grp
from #T t
order by t.id;
id val trig grp
----------- ----------- ----------- -----------
1 1 0 1
2 1 0 1
3 1 0 1
4 0 1 2
5 1 0 2
6 0 1 3
7 0 1 4

SQL Server 2008 Cumulative Sum that resets value

I want to have the last column cumulative based on ROW_ID that resets every time it starts again with '1'.
Initially my table doesn't have the ROW_ID, this was created using partition so at least I can segregate my records.
It should add the Amt + CumulativeSum (except for the first record) all the way down and reset every time the Row_ID = 1.
I have tried several queries but it doesn't give me the desired result. I am trying to read answers from several forums but to no avail.
Can someone advise the best approach to do this?
For the sake of representation, I made the sample table as straightforward as possible.
ID ROW-ID Amt RunningTotal(Amt)
1 1 2 2
2 2 4 6
3 3 6 12
4 1 2 2
5 2 4 6
6 3 6 12
7 4 8 20
8 5 10 30
9 1 2 2
10 2 4 6
11 3 6 12
12 4 8 20
try this
declare #tb table(ID int, [ROW-ID] int, Amt money)
insert into #tb(ID, [ROW-ID], Amt) values
(1,1,2),
(2,2,4),
(3,3,6),
(4,1,2),
(5,2,4),
(7,4,8),
(8,5,10),
(9,1,2),
(10,2,4),
(11,3,6),
(12,4,8)
select *,sum(amt) over(partition by ([id]-[row-id]) order by id,[row-id]) AS cum from #tb
other version
select *,(select sum(amt) from #tb t where
(t.id-t.[row-id])=(t1.id-t1.[ROW-ID]) and (t.id<=t1.id) ) as cum
from #tb t1 order by t1.id,t1.[row-id]
Try this
SELECT distinct (T1.ID),
T1.ROW_ID,
T1.Amt,
CumulativeSum =
CASE
WHEN T1.RoW_ID=1 THEN T1.Amt
ELSE T1.Amt+ T2.Amt
END
FROM TestSum T1, TestSum T2
WHERE T1.ID = T2.ID+1
http://sqlfiddle.com/#!6/8b2a2/2
The idea is to create partitions from R column. First leave 1 if R = 1, else put 0. Then cumulative sum on that column. When you have partitions you can finally calculate cumulative sums on S column in those partitions:
--- --- ---
| 1 | | 1 | | 1 |
| 2 | | 0 | | 1 | --prev 1 + 0
| 3 | | 0 | | 1 | --prev 1 + 0
| 1 | | 1 | | 2 | --prev 1 + 1
| 2 | => | 0 | => | 2 | --prev 2 + 0
| 3 | | 0 | | 2 | --prev 2 + 0
| 4 | | 0 | | 2 | --prev 2 + 0
| 5 | | 0 | | 2 | --prev 2 + 0
| 1 | | 1 | | 3 | --prev 2 + 1
| 2 | | 0 | | 3 | --prev 3 + 0
--- --- ---
DECLARE #t TABLE ( ID INT, R INT, S INT )
INSERT INTO #t
VALUES ( 1, 1, 2 ),
( 2, 2, 4 ),
( 3, 3, 6 ),
( 4, 1, 2 ),
( 5, 2, 4 ),
( 6, 3, 6 ),
( 7, 4, 8 ),
( 8, 5, 10 ),
( 9, 1, 2 ),
( 10, 2, 4 ),
( 11, 3, 6 ),
( 12, 4, 8 );
For MSSQL 2008:
WITH cte1
AS ( SELECT ID ,
CASE WHEN R = 1 THEN 1
ELSE 0
END AS R ,
S
FROM #t
),
cte2
AS ( SELECT ID ,
( SELECT SUM(R)
FROM cte1 ci
WHERE ci.ID <= co.ID
) AS R ,
S
FROM cte1 co
)
SELECT * ,
( SELECT SUM(S)
FROM cte2 ci
WHERE ci.R = co.R
AND ci.ID <= co.ID
)
FROM cte2 co
For MSSQL 2012:
WITH cte
AS ( SELECT ID ,
SUM(CASE WHEN R = 1 THEN 1
ELSE 0
END) OVER ( ORDER BY ID ) AS R ,
S
FROM #t
)
SELECT * ,
SUM(s) OVER ( PARTITION BY R ORDER BY ID ) AS T
FROM cte
Output:
ID R S T
1 1 2 2
2 1 4 6
3 1 6 12
4 2 2 2
5 2 4 6
6 2 6 12
7 2 8 20
8 2 10 30
9 3 2 2
10 3 4 6
11 3 6 12
12 3 8 20
EDIT:
One more way. This looks way better by execution plan then first example:
SELECT * ,
CASE WHEN R = 1 THEN S
ELSE ( SELECT SUM(S)
FROM #t it
WHERE it.ID <= ot.ID
AND it.ID >= ( SELECT MAX(ID)
FROM #t iit
WHERE iit.ID < ot.ID
AND iit.R = 1
)
)
END
FROM #t ot

T-SQL Query a matrix table used as LIFO

Following my [question]: T-SQL Query a matrix table for free position
I've now trying to handle my matrix table as a LIFO. Each couple of (X,Z) represent a channel in which I can store an element. When I generate a location I'm now using the query provided in the above question and here below.
SELECT x, z, MAX(CASE WHEN disabled = 0 AND occupiedId IS NULL THEN Y ELSE 0 END) firstFreeY
FROM matrix
GROUP BY x, z
ORDER BY x, z;
This is working but it doesn't handle "holes". In fact It's possible that a Disabled flag is removed from the table or an element is manually deleted.
In case my Matrix table will look like this:
X Z Y Disabled OccupiedId
--------------------------------------------------
1 1 1 0 591
1 1 2 0 NULL
1 1 3 1 NULL
1 1 4 0 524
1 1 5 0 523
1 1 6 0 522
1 1 7 0 484
1 2 1 0 NULL
1 2 2 0 NULL
1 2 3 0 NULL
1 2 4 0 NULL
1 2 5 0 NULL
1 2 6 0 589
1 2 7 0 592
the result of the above query is:
X Z firstFreeY
------------------------
1 1 2
1 2 5
instead of:
X Y firstFreeY
------------------------
1 1 0
1 2 5
Any suggestions on how to achieve this?
This query looks for the largest Y that is smaller than all other occupied Y's:
select m1.X
, m1.Z
, max(
case
when m2.MinOccupiedY is null or m1.Y < m2.MinOccupiedY then m1.Y
else 0
end
) as FirstFreeY
from matrix m1
join (
select X
, Z
, min(
case
when disabled <> 0 or occupiedId is not null then Y
end
) as MinOccupiedY
from matrix
group by
X
, Z
) m2
on m1.X = m2.X
and m1.Z = m2.Z
group by
m1.X
, m1.Z
Live example at SQL Fiddle.
just to know if i understood what you were asking, is this working too?
select distinct
m1.x,m1.z, o.y
from
matrix m1
cross apply
(
select top 1 (case when m2.Disabled = 0 then m2.y else 0 end)
from matrix m2
where
m1.x = m2.x
and m1.z = m2.z
and m2.OccupiedId is null
order by m2.y desc
) o (y);