How to retreive stack from Database - database

I have this table called 'Stack'.
+---------------+-------+-------------+
| Stack_Counter | value | Stack_Depth |
+---------------+-------+-------------+
| 1 | 3 | 1 |
| 2 | 0 | 2 |
| 3 | 0 | 1 |
| 4 | | 0 |
| 5 | 3 | 1 |
| 6 | 3 | 2 |
| 7 | 1 | 3 |
| 8 | 2 | 2 |
| 9 | 4 | 1 |
| 10 | 2 | 2 |
| 11 | 0 | 3 |
| 12 | 0 | 2 |
| 13 | 0 | 1 |
| 14 | 2 | 2 |
| 15 | 2 | 3 |
| 16 | 1 | 4 |
| 17 | 1 | 3 |
| 18 | 2 | 2 |
| 19 | 1 | 3 |
| 20 | 0 | 4 |
+---------------+-------+-------------+
I want to find out the stack array in Stack_Counter '20'.
So the correct answer should be
+---------------+-------+-------------+
| Stack_Counter | value | Stack_Depth |
+---------------+-------+-------------+
| 13 | 0 | 1 |
| 18 | 2 | 2 |
| 19 | 1 | 3 |
| 20 | 0 | 4 |
+---------------+-------+-------------+
Basically , this is to find out consecutive rows in selected Stack_Depth.
Is there any way to acheive it?

... and here's a generic all-SQL solution:
SELECT Stack_Counter, value, Stack_Depth
FROM
(SELECT *, RANK() OVER (
PARTITION BY Stack_Depth
ORDER BY Stack_Counter DESC) rank
FROM stack)
WHERE rank=1 AND Stack_Depth > 0;

An SQL-only solution may be more trouble than it's worth (either because of its complexity or its tediousness), but here is one that works with SQLite:
SELECT * FROM (SELECT * FROM stack WHERE Stack_Depth=1 ORDER BY Stack_Counter DESC LIMIT 1)
UNION ALL
SELECT * FROM (SELECT * FROM stack WHERE Stack_Depth=2 ORDER BY Stack_Counter DESC LIMIT 1)
UNION ALL
SELECT * FROM (SELECT * FROM stack WHERE Stack_Depth=3 ORDER BY Stack_Counter DESC LIMIT 1)
UNION ALL
SELECT * FROM (SELECT * FROM stack WHERE Stack_Depth=4 ORDER BY Stack_Counter DESC LIMIT 1);

The window functions I mentioned in comments proved to be more difficult than I thought... for myself at the time. Peak's answer is an elegant solution using the rank() window function, just what I had originally intended. In the mean time, sqlite also supports recursive CTE (Common Table Expressions; WITH statement):
WITH RECURSIVE
latest (id, level) AS (
VALUES (20, (SELECT Stack_Depth FROM stack WHERE Stack_Counter = 20))
UNION ALL
SELECT (SELECT max(Stack_Counter)
FROM stack
WHERE Stack_Depth = level - 1
AND Stack_Counter <= 20),
level - 1
FROM latest
WHERE level - 1 > 0
)
SELECT stack.*
FROM stack INNER JOIN latest
ON stack.Stack_Counter = latest.id
ORDER BY stack.Stack_Counter
There are three places that I had to insert the desired stack level, but these could all be replaced with a named SQL parameter if you're calling this from a prepared statement in the host language.
And if you're not interested in choosing a particular Stack_Counter value, rather just want the result from the entire table, then replace the VALUES clause with a SELECT like
WITH RECURSIVE
latest (id, level) AS (
SELECT * FROM (SELECT Stack_Counter, Stack_Depth FROM stack ORDER BY Stack_Counter DESC LIMIT 1)
UNION ALL
SELECT (SELECT max(Stack_Counter)
FROM stack
WHERE Stack_Depth = level - 1),
level - 1
FROM latest
WHERE level - 1 > 0
)
SELECT stack.*
FROM stack INNER JOIN latest
ON stack.Stack_Counter = latest.id
ORDER BY stack.Stack_Counter

Related

SQL Server: Flag only First duplicate row

I want to flag only the first duplicate ID-VL combination in the dataset shown below. Column FirstOccurence is what I want the end result to be.
ID VL FirstOccurence
1 a 1
1 b 1
2 a 1
2 a 0
3 a 1
3 a 0
4 a 1
4 a 0
5 a 1
5 b 1
5 a 0
There is currently not a unique index available in the original table.
Is there any way to do this with for instance the LAG-functionality? I cannot find any examples online that result in the flagging of duplicates. Any suggestions are much appreciated!
Kind regards,
Igor
One method is with ROW_NUMBER() along with a CASE expression:
SELECT
ID
,VL
,CASE ROW_NUMBER() OVER(PARTITION BY ID, VL ORDER BY ID, VL) WHEN 1 THEN 1 ELSE 0 END AS FirstOccurance
FROM dbo.example
ORDER BY
ID
,VL
,FirstOccurance;
Results:
+----+----+----------------+
| ID | VL | FirstOccurance |
+----+----+----------------+
| 1 | a | 1 |
| 1 | b | 1 |
| 2 | a | 0 |
| 2 | a | 1 |
| 3 | a | 0 |
| 3 | a | 1 |
| 4 | a | 0 |
| 4 | a | 1 |
| 5 | a | 0 |
| 5 | a | 1 |
| 5 | b | 1 |
+----+----+----------------+
Note that this result order differs from your end result. If there are one or more columns present in the table that provide the same ordering as the results in you question, specify that in the ORDER BY clause instead.

Creating a conditonal ROW_NUMBER() Partition clause based on previous row value

I have a table that looks like this:
+----------------+--------+
| EvidenceNumber | ID |
+----------------+--------+
| 001 | 8 |
| 001.A | 8 |
| 001.A.01 | 8 |
| 001.A.02 | 8 |
| 001.B | 8 |
| 001.C | 8 |
| 001.D | 8 |
| 001.E | 8 |
| 001.F | 8 |
| 001.G | 8 |
| 001.G.01 | 8 |
+----------------+--------+
If 001 were a bag, inside of it was 001.A, 001.B, and so on through to 001.G
In the output above, 001.A was another bag, and that bag contained 001.A.01 and 001.A.02. The same thing can be seen with 001.G.01.
Every entry in this table is either a bag or an item. I am only interested in counting the amount of items per ID.
Since 001.A.01 and 001.A.02 is the last we see of the "001.A's" we know A.01 and A.02 were items.
Since we see 001.B only once, that was an item as well.
001.G was a bag, but 001.G.01 was an item.
The above output is showing 8 items and 3 bags.
I feel like Row_number and the Partition clause is the perfect tool for the job, but I can't find a way to partition based on a clause that uses a previous row's value.
Maybe something like that isn't even necessary here, but I pictured it like:
{001} -- variable
{001}.A -- variable seen again, obviously 001 was a bag. Create new variable {001.A} and move on.
{001.A}.01 -- same thing.
{001.A.01} -- Unique variable. This is a final step. This is a bag and should be Row number 1.
Obviously, the below code is just making "ItemNum" 1 for each item since there are not duplicates.
SELECT
ROW_NUMBER() OVER(Partition BY EvidenceNumber ORDER BY EvidenceNumber) AS ItemNum,
EvidenceNumber,
ID
FROM EVIDENCE
WHERE ID = '18'
ORDER BY EvidenceNumber
+---------+----------------+--------+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+--------+
| 1 | 001 | 8 |
| 1 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 1 | 001.A.02 | 8 |
| 1 | 001.B | 8 |
| 1 | 001.C | 8 |
| 1 | 001.D | 8 |
| 1 | 001.E | 8 |
| 1 | 001.F | 8 |
| 1 | 001.G | 8 |
| 1 | 001.G.01 | 8 |
+---------+----------------+--------+
Ideally, it would partition on the items only, so in this case:
+---------+----------------+----+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+----+
| 0 | 001 | 8 |
| 0 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 2 | 001.A.02 | 8 |
| 3 | 001.B | 8 |
| 4 | 001.C | 8 |
| 5 | 001.D | 8 |
| 6 | 001.E | 8 |
| 7 | 001.F | 8 |
| 0 | 001.G | 8 |
| 8 | 001.G.01 | 8 |
+---------+----------------+----+
I don't think window functions alone are the best approach. Instead:
select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t ;
Then sum these up using another subquery:
select t.*,
sum(is_item) over (partition by caseid order by EvidenceNumber) as item_counter
from (select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t
) t;
trick with Lead and Row_Number:
DECLARE #Table TABLE (
EvidenceNumber varchar(64),
Id int
)
INSERT INTO #Table VALUES
('001',8),
('001.A',8),
('001.A.01',8),
('001.A.02',8),
('001.B',8),
('001.C',8),
('001.D',8),
('001.E',8),
('001.F',8),
('001.G',8),
('001.G.01',8);
WITH CTE AS (
SELECT
[IsBag] = PATINDEX(EvidenceNumber+'%',
IsNull(LEAD(EvidenceNumber) OVER (ORDER BY EvidenceNumber),0)
),
[EvidenceNumber],
[Id]
FROM
#Table
)
SELECT
[NumItem] = IIF(IsBag = 0,ROW_NUMBER() OVER (PARTITION BY [ISBag] order by [IsBag]),0),
[EvidenceNumber],
[Id]
FROM
CTE
ORDER BY EvidenceNumber

Getting Top 10 based on column value

I have a code that output a long list of the sum of count of work orders per name and sorts it by total, name and count:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency])
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY Name) as Total from cte
order by Total desc, Name, [CountItem] desc
but I only want to get the top 10 Names with the highest total like the one below:
+-------+-------------------------------+-------+-------+
| Name | Emergency | Count | Total |
+-------+-------------------------------+-------+-------+
| PLB | No | 7 | 15 |
| PLB | No Hot Water | 4 | 15 |
| PLB | Resident Locked Out | 2 | 15 |
| PLB | Overflowing Tub | 1 | 15 |
| PLB | No Heat | 1 | 15 |
| GG | Broken Lock - Exterior | 6 | 6 |
| BOA | Broken Lock - Exterior | 2 | 4 |
| BOA | Garage Door not working | 1 | 4 |
| BOA | Resident Locked Out | 1 | 4 |
| 15777 | Smoke Alarm not working | 3 | 3 |
| FP | No air conditioning | 2 | 3 |
| FP | Flood | 1 | 3 |
| KB | No electrical power | 2 | 3 |
| KB | No | 1 | 3 |
| MEM | Noise Complaint | 3 | 3 |
| ANG | Parking Issue | 2 | 2 |
| ALL | Smoke Alarm not working | 2 | 2 |
| AAS | No air conditioning | 1 | 2 |
| AAS | Toilet - Clogged (1 Bathroom) | 1 | 2 |
+-------+-------------------------------+-------+-------+
Note: I'm not after unique values. As you can see from the example above it gets the top 10 names from a very long table.
What I want to happen is assign a row id for each name so all PLB above will have a row id of 1, GG = 2, BOA = 3, ...
So on my final select I will only add the where clause where row id <= 10. I already tried ROW_NUMBER() OVER(PARTITION BY Name ORDER BY Name) but it's assigning 1 to every unique Name it encounters.
You may try this:
;with cte as (
SELECT [Name],
[Emergency],
count([Emergency]) as [CountItem]
FROM tableA
GROUP BY [Name], [Emergency]),
ct as (
select Name,[Emergency],[Count],SUM([CountItem]) OVER(PARTITION BY PropertyName) as Total from cte
),
ctname as (
select dense_rank() over ( order by total, name ) as RankName, Name,[Emergency],[Count], total from ct )
select * from ctname where rankname < 11

What's an efficient way to count "previous" rows in SQL?

Hard to phrase the title for this one.
I have a table of data which contains a row per invoice. For example:
| Invoice ID | Customer Key | Date | Value | Something |
| ---------- | ------------ | ---------- | ------| --------- |
| 1 | A | 08/02/2019 | 100 | 1 |
| 2 | B | 07/02/2019 | 14 | 0 |
| 3 | A | 06/02/2019 | 234 | 1 |
| 4 | A | 05/02/2019 | 74 | 1 |
| 5 | B | 04/02/2019 | 11 | 1 |
| 6 | A | 03/02/2019 | 12 | 0 |
I need to add another column that counts the number of previous rows per CustomerKey, but only if "Something" is equal to 1, so that it returns this:
| Invoice ID | Customer Key | Date | Value | Something | Count |
| ---------- | ------------ | ---------- | ------| --------- | ----- |
| 1 | A | 08/02/2019 | 100 | 1 | 2 |
| 2 | B | 07/02/2019 | 14 | 0 | 1 |
| 3 | A | 06/02/2019 | 234 | 1 | 1 |
| 4 | A | 05/02/2019 | 74 | 1 | 0 |
| 5 | B | 04/02/2019 | 11 | 1 | 0 |
| 6 | A | 03/02/2019 | 12 | 0 | 0 |
I know I can do this using either a CTE like this...
(
select
count(*)
from table
where
[Customer Key] = t.[Customer Key]
and [Date] < t.[Date]
and Something = 1
)
But I have a lot of data and that's pretty slow. I know I can also use cross apply to achieve the same thing, but as far as I can tell that's not any better performing than just using a CTE.
So; is there a more efficient means of achieving this, or do I just suck it up?
EDIT: I originally posted this without the requirement that only rows where Something = 1 are counted. Mea culpa - I asked it in a hurry. Unfortunately I think that this means I can't use row_number() over (partition by [Customer Key])
Assuming you're using SQL Server 2012+ you can use Window Functions:
COUNT(CASE WHEN Something = 1 THEN CustomerKey END) OVER (PARTITION BY CustomerKey ORDER BY [Date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -1 AS [Count]
Old answer before new required logic:
COUNT(CustomerKey) OVER (PARTITION BY CustomerKey ORDER BY [Date]
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) -1 AS [Count]
If you're not using 2012 an alternative is to use ROW_NUMBER
ROW_NUMBER() OVER (PARTITION BY CustomerKey ORDER BY [Date]) - 1 AS Count

Need to convert rows into columns with sort [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I am trying to convert rows into columns but include a sort of the data.
Sample Data
For instance:
+-----+------+------+
| CId | Cat1 | cat2 |
+-----+------+------+
| 1 | 10 | 6 |
| 1 | 230 | 100 |
| 2 | 1222 | 30 |
| 3 | 2 | 50 |
| 4 | 33 | 21 |
| 1 | 33 | 13 |
+-----+------+------+
Expected output
+-----+------+-----+-----+-----+-----+-----+
| CId | Rw1 | Rw2 | Rw3 | Rw4 | Rw5 | Rw6 |
+-----+------+-----+-----+-----+-----+-----+
| 1 | 10 | 33 | 230 | 6 | 13 | 100 |
| 2 | 1222 | 30 | 0 | 0 | 0 | 0 |
| 3 | 2 | 50 | 0 | 0 | 0 | 0 |
| 4 | 33 | 21 | 0 | 0 | 0 | 0 |
+-----+------+-----+-----+-----+-----+-----+
See how CID: 1 sorted all values for Cat1 after that's done, need to sort cat2 and everything should be in one row.
Please let me know how to do it.
You can get the result by unpivoting and pivoting the data, but you will also want to use row_number() to keep the data in the sequence that you want.
First step would be to query your current data and apply a row_number() to get a value for each row, partitioned by the cid and ordered by cat1 and cat2:
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
See Demo. Once you have the data, then you will unpivot the multiple columns cat1 and cat2 into a single column with multiple rows. You can use the UNPIVOT function or you can use CROSS APPLY to convert the data:
select cid, value
, 'rw'+cast(row_number() over(partition by cid order by col, seq) as varchar(10)) rw
from
(
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
) d
cross apply
(
select 1, cat1 union all
select 2, cat2
) c (col, value)
See Demo. When you unpivot the data, you will apply the row_number() a second time, this will be used to create your new columns names. When applied this time, you will partition the data by the cid and order it by your columns cat1/cat2 (I used 1/2) as well as the sequence you original created. This new row number will create all of the new column headers and it will keep the data in the order that you want to display it in.
Finally you will apply the PIVOT function:
select cid,
coalesce(rw1, 0) rw1,
coalesce(rw2, 0) rw2,
coalesce(rw3, 0) rw3,
coalesce(rw4, 0) rw4,
coalesce(rw5, 0) rw5,
coalesce(rw6, 0) rw6
from
(
select cid, value
, 'rw'+cast(row_number() over(partition by cid order by col, seq) as varchar(10)) rw
from
(
select cid, cat1, cat2,
row_number() over(partition by cid order by cat1, cat2) seq
from yourtable
) d
cross apply
(
select 1, cat1 union all
select 2, cat2
) c (col, value)
) src
pivot
(
max(value)
for rw in (rw1, rw2, rw3, rw4, rw5, rw6)
) piv;
See SQL Fiddle with Demo. This gives a final result:
| CID | RW1 | RW2 | RW3 | RW4 | RW5 | RW6 |
|-----|------|-----|-----|-----|-----|-----|
| 1 | 10 | 33 | 230 | 6 | 13 | 100 |
| 2 | 1222 | 30 | 0 | 0 | 0 | 0 |
| 3 | 2 | 50 | 0 | 0 | 0 | 0 |
| 4 | 33 | 21 | 0 | 0 | 0 | 0 |

Resources