Update rows based on rownumber in SQL Server 2012 - sql

Ive been given some data in a spreadsheet which will soon be going into an automated import so i cannot do any manual entry on the spreadsheet. The data basically has the following columns. Trayid, trayname, itemdescription and rownumber. I didnt build these tables myself or i would of built it differently but i have to stick to the format which is already set.
The Data that is being imported will look at followed.
Trayid | Trayname | ItemDescription | RowNumber
1 Tray 1 Product 1 1
Product 2 2
Product 3 3
Product 4 4
2 Tray 2 Product 1 1
Product 2 2
Product 3 3
Product 4 4
Product 5 5
What i need to do is update the trayid and trayname for each of the other rows following row 1, so for example it will look like.
Trayid | Trayname | ItemDescription | RowNumber
1 Tray 1 Product 1 1
1 Tray 1 Product 2 2
1 Tray 1 Product 3 3
1 Tray 1 Product 4 4
2 Tray 2 Product 1 1
2 Tray 2 Product 2 2
2 Tray 2 Product 3 3
2 Tray 2 Product 4 4
2 Tray 2 Product 5 5
Im guessing i need to use a curser or something but im not sure, i think it can be done by going down the rownumbers and stopping when it see's rownumber 1 again and then carrying on with the next trayid and trayname.
Sorry if what i need doesnt make sense, it was awkward to explain.

SQL tables have no inherent ordering. So you cannot depend on that. But, there is something that you can do:
Define an identity column in the source table.
Create a view on the source table that excludes the identity.
Bulk insert into the view.
This will assign a sequential number to rows in the same order as the original data. Let's call this id. Then you can do your update by doing:
with toupdate (
select t.*,
max(TrayId) over (partition by grp) as new_TrayId,
max(TrayName) over (partition by grp) as new_TrayName
from (select t.*,
count(TrayId) over (order by id) as grp
from t
) t
)
update toupdate
set TrayId = new_TrayId,
TrayName = new_TrayName
where TrayId is null;
The idea is to define groups of rows corresponding to each tray. The simple idea is to count the number of non-NULL values before any given row -- everything in a group will then have the same grp value. Window functions then spread the actual value through all rows in the group (using max()), and these values are used for the update.

Related

How to I stop duplication on SQL join where I have order_ids and when people order more than 1 item (so multiple product_ids) to calculate discounts?

So my problem is my discount number is blowing up because an order has a discount for the entire order, but I am making a dataset where there are multiple lines for each order to represent each product in the order. Instead of the discount only applying once to the order, it adds the discount for every line.
what is happening
order_id
product_id
quantity
amount
discount
1
a
1
5
0
2
a
1
5
7
2
b
1
10
7
3
a
1
5
5
3
b
1
10
5
3
c
1
15
5
what i want
order_id
product_id
quantity
amount
discount
1
a
1
5
0
2
a
1
5
7
2
b
1
10
0
3
a
1
5
5
3
b
1
10
0
3
c
1
15
0
I just want the discount to be applied once per order, and my join is using order_id so that is why the discount is applying multiple times. I would attach my code, but it's a decent sized CTE
Figured it out. I did need to use a row_number() Over Partition by Order id, but I was also losing records if the order had more than 1 item. The solution was to use a CASE WHEN statement.
CASE WHEN ORDER_ROW_COUNT = 1 THEN DISCOUNT ELSE 0 END
this allowed me to keep the records without duplicating the discounts
You’re joining on a field that isn’t unique so the join is returning all the records for that order Id and therefore the discount is being applied to all the records for that order Id. You need some sort of differentiator field. Something that is unique in each orders data set.
Example:
Select *, row_number () over(partition by order_id order by order_id) as rownumber into #temp from table
This should give you something like in the picture.
rownumber table image
Then join on order_Id = order_Id and rownumber =1 and this would only update the first record for each order.

Update new foreign key column of existing table with ids from another table in SQL Server

I have an existing table to which I have added a new column which is supposed to hold the Id of a record in another (new) table.
Simplified structure is sort of like this:
Customer table
[CustomerId] [GroupId] [LicenceId] <-- new column
Licence table <-- new table
[LicenceId] [GroupId]
The Licence table has a certain number of licences per group than can be assigned to customers in that same group. There are multiple groups, and each group has a variable number of customers and licences.
So say there are 100 licences available for group 1 and there are 50 customers in group 1, so each can get a license. There are never more customers than there are licences.
Sample
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 NULL
2 1 NULL
3 1 NULL
4 1 NULL
5 2 NULL
6 2 NULL
7 2 NULL
8 3 NULL
9 3 NULL
Licence
[LicenceId] [GroupId]
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 2
12 3
13 3
14 3
15 3
16 3
17 3
Desired outcome
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 1
2 1 2
3 1 3
4 1 4
5 2 7
6 2 8
7 2 9
8 3 12
9 3 13
So now I have to do this one time update to give every customer a licence and I have no idea how to go about it.
I'm not allowed to use a cursor. I can't seem to do a MERGE UPDATE, because joining the Customer to the Licence table by GroupId will result in multiple hits.
How do I assign each customer the next available LicenceId within their group in one query?
Is this even possible?
You can use window functions:
with c as (
select c.*, row_number() over (partition by groupid order by newid()) as seqnum
from customers c
),
l as (
select l.*, row_number() over (partition by groupid order by newid()) as seqnum
from licenses c
)
update c
set c.licenceid = l.licenseid
from c join
l
on c.seqnum = l.seqnum and c.groupid = l.groupid;
This assigns the licenses randomly. That is really just for fun. The most efficient method is to use:
row_number() over (partition by groupid order by (select null)) as seqnum
SQL Server often avoids an additional sort operation in this case.
But you might want to order them by something else -- for instance by the ordering of the customer ids, or by some date column, or something else.
Gordon has put it very well in his answer.
Let me break it down into simpler steps for you.
Step 1. Use the ROW_NUMBER() function to assign a SeqNum to the Customers. Use PARTITION BY GroupId so that the number starts from 1 in every group. I would ORDER BY CustomerId
Step 2. Use the ROW_NUMBER() function to assign a SeqNum to the Licences. Use PARTITION BY GroupId so that the number starts from 1 in every group. ORDER BY LicenseId because your ask is to "assign each customer the next available LicenceId within their group".
Now use these 2 queries to update LicenseId in Customer table.

SQL to pick the next value

I have a table of values. Each value may have 1 or more entry, but only 1 should be active at any one time. The table has a primary INT ID
I need a method to make the 'current' value inactive and make the 'next' value the active value. If the current active value is the last active, instead make the first value active. Values with only 1 entry will always be active.
The sequence should work like below
Is anyone able to provide a way to achieve this?
You should not be showing runs in separate columns. Your data should put this information in separate rows. So your data should have a separate set of rows for each run:
id value run active
1 Apple 1 1
2 Apple 1 0
3 Apple 1 0
4 Banana 1 1
5 Banana 1 0
6 Cherry 1 1
1 Apple 2 0
2 Apple 2 1
3 Apple 2 0
4 Banana 2 0
5 Banana 2 1
6 Cherry 2 1
You can add the next run as:
with r as
select t.*, max(run) over () as max_run,
row_number() over (partition by run, value order by id) as seqnum,
lag(active) over (partition by run, value order by id) as prev_active
from runs
)
insert into runs (id, value, run, active)
select id, value, max_run + 1,
(case when prev_active = 1 then 1
when prev_active is null or seqnum = 1 then 1
else 0
end) as active
from r
where run = max_run;
Simply make a check, that is select id from the table is not max(id) of that table, then update the log to inactive and then update the id+1 to active.
And if select id from the table is max(ID) then simply update that row to inactive and update min(ID) to active.
build the query, itll be fun.

SQL Calculations With Multi-Group Affiliations

I'm attempting to have a function or view that is able to calculate and roll up various counts while being able to search on a many to many affiliation.
Here is an example data set:
Invoice Table:
InvoiceID LocationID StatusID
1 5 1
2 5 1
3 5 1
4 5 2
5 7 2
5 7 1
5 7 2
Group Table:
GroupID GroupName
1 Group 1
2 Group 2
GroupToLocation Table:
GroupToLocationID GroupID LocationID
1 1 5
2 2 5
3 2 7
I have gotten to the point where I could sum up the various statuses per location and get this:
LocationID Status1 Status2
5 3 1
7 1 2
Location 5 has 3 Invoices with a status of 1, and 1 invoice with a status of 2 while Location 7 has 1 status 1 and 2 status 2
There are two groups, and Location 5 is in both, while Location 7 is only in the second. I need to be able to set it up where I can append a where statement like this:
select * from vw_GroupCounts
where GroupName = 'Group 2'
or
select Invoice, SUM(*) from vw_GroupCounts
where GroupName = 'Group 2'
And that result in only getting Location 7. Whenever I do this, as I have to use left joins or something along those lines, the counts are duplicating for each group the the Location is affiliated with. I know I could do something along the lines of a subquery and pass in the GroupName into that, but the system I am working with uses a dynamic query builder that appends WHERE statements based on user input.
I don't mind using view, or functions, or any number of functions inside of functions, but I hope there is a way to do what I'm looking for.
Since locations 5 and 7 are in Group 2, if you search for group 2 in the where clause after joining all the tables, then you would get all records in this case, this isn't duplication, just the way the data is. A different join wouldn't change this, only changing the data. Let me know if I am misunderstanding something though.
Here is how you would join them to do that search.
Here it is with your first example of the location and status count.

Derby DB last x row average

I have the following table structure.
ITEM TOTAL
----------- -----------------
ID | TITLE ID |ITEMID|VALUE
1 A 1 2 6
2 B 2 1 4
3 C 3 3 3
4 D 4 3 8
5 E 5 1 2
6 F 6 5 4
7 4 5
8 2 8
9 2 7
10 1 3
11 2 2
12 3 6
I am using Apache Derby DB. I need to perform the average calculation in SQL. I need to show the list of item IDs and their average total of the last 3 records.
That is, for ITEM.ID 1, I will go to TOTAL table and select the last 3 records of the rows which are associated with the ITEMID 1. And take average of them. In Derby database, I am able to do this for a given item ID but I cannot make it without giving a specific ID. Let me show you what I've done it.
SELECT ITEM.ID, AVG(VALUE) FROM ITEM, TOTAL WHERE TOTAL.ITEMID = ITEM.ID GROUP BY ITEM.ID
This SQL gives the average of all items in a list. But this calculates for all values of the total tables. I need last 3 records only. So I changed the SQL to this:
SELECT AVG(VALUE) FROM (SELECT ROW_NUMBER() OVER() AS ROWNUM, TOTAL.* FROM TOTAL WHERE ITEMID = 1) AS TR WHERE ROWNUM > (SELECT COUNT(ID) FROM TOTAL WHERE ITEMID = 1) - 3
This works if I supply the item ID 1 or 2 etc. But I cannot do this for all items without giving an item ID.
I tried to do the same thing in ORACLE using partition and it worked. But derby does not support partitioning. There is WINDOW but I could not make use of it.
Oracle one
SELECT ITEMID, AVG(VALUE) FROM(SELECT ITEMID, VALUE, COUNT(*) OVER (PARTITION BY ITEMID) QTY, ROW_NUMBER() OVER (PARTITION BY ITEMID ORDER BY ID) IDX FROM TOTAL ORDER BY ITEMID, ID) WHERE IDX > QTY -3 GROUP BY ITEMID ORDER BY ITEMID
I need to use derby DB for its portability.
The desired output is this
RESULT
-----------------
ITEMID | AVERAGE
1 (9/3)
2 (17/3)
3 (17/3)
4 (5/1)
5 (4/1)
6 NULL
As you have noticed, Derby's support for the SQL 2003 "OLAP Operations" support is incomplete.
There was some initial work (see https://wiki.apache.org/db-derby/OLAPOperations), but that work was only partially completed.
I don't believe anyone is currently working on adding more functionality to Derby in this area.
So yes, Derby has a row_number function, but no, Derby does not (currently) have partition by.