Creating duplicating rank over multiple columns - sql

I have data as below where for one customer ID there are several orders (KEY) which is the primary key. I have also have a activity flag as below (either 0 or 1).
CUST_ID KEY FLAG
1 1 1
1 2 1
1 3 1
1 4 0
1 5 0
1 6 1
1 7 1
1 8 0
1 9 0
Now I want to create ranks as below based on the FLAG. The idea is to give same Rank as preceding row if the FLAG is same as preceding row. The Rank increments if the current value is different from preceding value.
CUST_ID KEY FLAG RN
1 1 1 1
1 2 1 1
1 3 1 1
1 4 0 2
1 5 0 2
1 6 1 3
1 7 1 3
1 8 0 4
1 9 0 4
I'm new to SQL, so please let me know if I need to reframe my question.

Use LAG() window function to get each row's previous flag and then use SUM() window function to create the rankings:
SELECT CUST_ID, KEY, FLAG,
SUM(CASE WHEN FLAG <> prev_FLAG THEN 1 END) OVER (PARTITION BY CUST_ID ORDER BY KEY) RN
FROM (
SELECT *, LAG(FLAG, 1, FLAG - 1) OVER (PARTITION BY CUST_ID ORDER BY KEY) prev_FLAG
FROM tablename
) t;
See the demo.
The code could be simplified, depending on the specific database that you use.

Related

Identify a FK which has the highest value from a list of values in its source table

I have following tables.
Part
id
name
1
Part 1
2
Part 2
3
Part 3
Operation
id
name
part_id
order
1
Op 1
1
10
2
Op 2
1
20
3
Op 3
1
30
4
Op 1
2
10
5
Op 2
2
20
6
Op 1
3
10
Lot
id
part_id
Operation_id
10
1
2
11
2
5
12
3
6
I am selecting the results from Lot table and I want to select a column last_Op which is based on the order value of the operation_id. If value of order for the operation_id is the highest for the respective part_id, return 1 else return 0
SELECT
id,
part_id,
operation_id,
last_Op
FROM Lot
expected result set based on the tables above.
id
part_id
operation_id
last_op
10
1
2
0
11
2
5
1
12
3
6
1
In above example, first row returns last_op = 0 because operation_id = 2 is associated with part_id = 1 and it has the highest order = 30. Since operation_id for this part is not pointing towards the highest order value, 0 is returned.
The other two rows return 1 because operation_id 5 and 6 are associated with part_id 2 and 3 respectively and they are pointing towards the highest 'order' value.
If value of order for the operation_id is the highest for the respective part_id, return 1 else return 0
This sounds like window functions will help:
select l.*,
(case when o.order = o.max_order then 1 else 0 end) as last_op
from lot l left join
(select o.*,
max(o.order) over (partition by o.part_id) as max_order
from operations o
) o
on l.operation_id = o.id;
Note: order is a very poor name for a column because it is a SQL keyword.

Resetting a Count in SQL

I have data that looks like this:
ID num_of_days
1 0
2 0
2 8
2 9
2 10
2 15
3 10
3 20
I want to add another column that increments in value only if the num_of_days column is divisible by 5 or the ID number increases so my end result would look like this:
ID num_of_days row_num
1 0 1
2 0 2
2 8 2
2 9 2
2 10 3
2 15 4
3 10 5
3 20 6
Any suggestions?
Edit #1:
num_of_days represents the number of days since the customer last saw a doctor between 1 visit and the next.
A customer can see a doctor 1 time or they can see a doctor multiple times.
If it's the first time visiting, the num_of_days = 0.
SQL tables represent unordered sets. Based on your question, I'll assume that the combination of id/num_of_days provides the ordering.
You can use a cumulative sum . . . with lag():
select t.*,
sum(case when prev_id = id and num_of_days % 5 <> 0
then 0 else 1
end) over (order by id, num_of_days)
from (select t.*,
lag(id) over (order by id, num_of_days) as prev_id
from t
) t;
Here is a db<>fiddle.
If you have a different ordering column, then just use that in the order by clauses.

Order a column by pair of values

I have 2 simple columns:
one column is simply an ID column and the other column is some sort of boolean type column.
ID V
0 1
1 0
2 0
3 1
4 1
5 0
I want to order the rows by pair of values (0,1) of column V
(while still keeping the ID as minimal as possible):
ID V
1 0
0 1
2 0
3 1
5 0
4 1
How do I do that ?
Seems you want get rows for 0 and 1 in turn:
select id, v
from tab
order by
row_number()
over (partition by v
order by id)
,v

Select Query to Get Unique Cells in Two Columns

I have an SQL Server database, that logs weather device sensor data.
The table looks like this:
Id DeviceId SensorId Value
1 1 1 42
2 1 1 3
3 1 2 30
4 2 2 0
5 2 1 1
6 3 1 26
7 3 1 23
8 3 2 1
In return the query should return the following:
Id DeviceId SensorId Value
2 1 1 3
3 1 2 30
4 2 2 0
5 2 1 1
7 3 1 23
8 3 2 1
For each device the sensor should be unique. i.e. Values in Columns DeviceId and SensorId should be unique (row-wise).
Apologies if I'm not clear enough.
If you don't want to sum Value as your desired result suggest, so you just want to take an "arbitrary" row of each "DeviceId + SensorId"-group:
WITH CTE AS
(
SELECT Id, DeviceId, SensorId, Value,
RN = ROW_NUMBER() OVER (PARTITION BY DeviceId, SensorId ORDER BY ID DESC)
FROM dbo.TableName
)
SELECT Id, DeviceId, SensorId, Value
FROM CTE
WHERE RN = 1
ORDER BY ID
This returns the row with the highest ID per group. You need to change ORDER BY ID DESC if you want a different result. Demo: http://sqlfiddle.com/#!6/8e31b/2/0 (your result)

SQL: Need to create two unique records for each single record

The simple question is how can you take a set of records with a PK and create exactly two records for each source with a slightly altered key for the duplicate? In other words, I take 4000 records and produce 8000 records where 4000 are identical and the other 4000 have a slightly altered key. I cannot do a union because this is essentially two selects (long story).
The rest gets complicated, but maybe necessary to provide examples.
This is my original set (it contains over 4000 records)
dateGroup areaGroup itemID editionID
1 1 1 1
1 1 1 2
1 2 1 1
1 2 2 1
2 1 1 1
2 1 1 2
2 2 1 1
2 2 1 2
For each record I need to create a duplicate record ganging the areaGroups together under '0', then create a comma separated list of original areaGroups as a separate field. (The "why" is some dumb programmer (me) made a mistake about 15 years ago.) I can renumber the editionIDs as necessary, but the original and duplicate record must have the same editionID (thus why a union wouldn't work). The PK remains the same as above (all fields)
dateGroup areaGroup itemID editionID aGroups
1 0 1 1 1
1 0 1 2 1
1 0 1 1 2 // Duplicate (EditionID)
1 0 2 1 2
2 0 1 1 1
2 0 1 2 1
2 0 1 1 2 // Duplicate (EditionID)
2 0 1 2 2
The end result would renumber the editionID as needed to make the record unique.
dateGroup areaGroup itemID editionID aGroups (EditionID is what is altered)
1 0 1 1 1
1 0 1 2 1
1 0 1 2 2 1 changed to 2 (one more than row 1)
1 0 2 1 2
2 0 1 1 1
2 0 1 2 1
2 0 1 2 2 1 changed to 2 (one more than row 1)
2 0 1 2 2
1 1 1 1
1 1 1 2
1 2 1 2 1 changed to 2 (editionID) to match
1 2 2 1
2 1 1 1
2 1 1 2
2 2 1 2 1 changed to 2 to match above
2 2 1 2
I know you could calculate the editionID like a row rank like so:
select row_number() over (
partition by dateGroup, itemID
order by dateGroup, itemID) as editionID
So all I need is to know how to duplicate the records from a single set
do a cross join on a derived table:
( select 1 as aGroups union all select 2 )
I'd create a temporary table with duplicates and their count.
Then I'd filter the original table to have only unique rows, and insert another row for each row in the temporary table, incrementing their editionID.
In MySQL, I'd use user #variables; not sure about MS SQL.
Did you try UNION ALL instead of just UNION
UDPATE perhaps I misunderstood the problem and I thought you were having a problem with the union loosing the duplicates.
If the problem is that you want to do a row_number over a union why don't you do somthing like
select row_number() over (
partition by dateGroup, itemID
order by dateGroup, itemID) as editionID
FROM
(
SELECT
dateGroup, itemID
FROM TableA
UNION ALL
SELECT
dateGroup, itemID
FROM TableB
) Data