SQL query for parent child with no relationship - sql

I need help regarding a query that I want to create. For instance, let's say I have this table:
Description Level Is_Active
----------------------------------------------------
(1)Metallic industry products 1 1
(2)+ Various metal products 2 1
(3)++ Other metal products 3 1
(1)Rubber and plastic products 1 1
(2)+ Rubber products 2 1
(2)+ Other rubber products 2 1
(3)++ Other product types 3 1
where level specifies the relationship. The records in the table are set to reproduce a tree structure. What I'm trying to do is a query which selects all the parents with children from this table that are active. If, for instance, the Is_Active column for Metallic industry products is set to 0, I don't want to display it and it's children (arious metal products and other metal products).
The same for Various metal products, if it's not active, don't display it and it's children. I tried joining with the same table or using the WITH function but sadly I can't find a solution.
A more concrete example is this. Metallic industry products becomes inactive. Then the select result should be:
Description Level Is_Active
----------------------------------------------------
(1)Rubber and plastic products 1 1
(2)+ Rubber products 2 1
(2)+ Other rubber products 2 1
(3)++ Other product types 3 1
Or let's say the child of Metallic industry products becomes inactive. The result set should be like this:
Description Level Is_Active
----------------------------------------------------
(1)Metallic industry products 1 1
(1)Rubber and plastic products 1 1
(2)+ Rubber products 2 1
(2)+ Other rubber products 2 1
(3)++ Other product types 3 1

OK, I've had a go and it isn't pretty, but see what you think. I've used WHILE loops rather than cursors to minimise the code:
create table #ordered_products
(id int identity(1,1),
grouping int default 0,
Description varchar(128),
level int,
is_active bit);
insert into #ordered_products (Description, level, is_active)
select description, level, is_active from products;
-- All rows now have an order
declare #grouping int = 0;
declare #currentid int;
declare #found bit = 1;
declare #level int;
declare #base int;
set #currentid = (select min(id) from #ordered_products);
-- Go through all the rows setting a new group each time we hit level 1
while (#found = 1)
begin
set #level = (select level from #ordered_products where id = #currentid);
if (#level = 1)
begin
set #grouping = #grouping + 1
end
update #ordered_products set grouping = #grouping where id = #currentid
set #currentid = #currentid + 1;
set #found = (select 1 from #ordered_products where id = #currentid);
end
-- For each group, set the children to be inactive if the parent is
declare #maxgroup int = (select MAX(GROUPING) from #ordered_products);
declare #currentgroup int = 1;
while (#currentgroup <= #maxgroup)
begin
begin
set #base = (select MIN(id) from #ordered_products where is_active = 0 and grouping = #currentgroup);
if (#base > 0)
begin
update #ordered_products set is_active = 0 where grouping = #currentgroup and id > #base;
end
end
set #currentgroup = #currentgroup + 1;
end
-- Output
select * from #ordered_products where is_active = 1;
drop table #ordered_products;

Related

Create equal sized, random buckets, with no repetition to the row

Having some difficulty in a scheduling task.
Background: I have 100 members, 10 different sessions, and 10 different activities.
Rules:
Each member must do each activity only once.
Each activity must have the same number of members in each session.
The members must be with (at least mostly) different people in each session.
Each activity must be run in each session with 10 people per activity.
The expected outcome would be something like this:
Person ID
Session ID
Activity ID
1
S1
A
2
S1
B
3
S1
C
1
S2
B
2
S2
C
3
S2
A
In the above example, each activity in each session has only 1 participant, I have to lock that activity in that session out at 10 members.
I have tried a few different solutions in excel / SQL, but not able to meet all 3 rules. The hardest being keeping each activity/session slot to 10 people.
The closest solution I've had is the following.. its not pretty though:
SET STATISTICS TIME, io OFF
-- Create list of applicants
IF OBJECT_ID('process.Numbers') IS NOT NULL DROP TABLE process.Numbers
CREATE TABLE Numbers (ApplicantID INT, SessionID INT, GroupID INT)
DECLARE #i INT,
#Session INT,
#Group INT;
SELECT #i = 1;
SET NOCOUNT ON
WHILE #i <= 100
BEGIN
INSERT INTO Numbers (ApplicantID, SessionID) VALUES (#i, 1);
SELECT #i = #i + 1;
END;
-- Duplicate ApplicantID list for each different session
SELECT #Session = 1
WHILE #Session <= 10
BEGIN
IF #Session > 1
BEGIN
INSERT INTO
Numbers (ApplicantID, SessionID)
SELECT ApplicantID, #Session FROM Numbers WHERE SessionID = 1
END
-- SELECT RANDOM TOP 10 AND SET AS GROUP ID
SELECT #Group = 1
WHILE #Group <= 10
BEGIN
WITH dups_check AS ( SELECT ApplicantID,
GroupID,
COUNT(*) AS vol
FROM Numbers
GROUP BY ApplicantID,
GroupID),
cte AS ( SELECT TOP 10 *
FROM Numbers
WHERE numbers.GroupID IS NULL
AND SessionID = #Session
AND NOT EXISTS (SELECT 1
FROM dups_check
WHERE Numbers.ApplicantID = dups_check.ApplicantID
AND dups_check.GroupID = #Group)
ORDER BY newid())
UPDATE cte SET GroupID = #Group
SELECT #Group = #Group + 1
END
SELECT #Session = #Session + 1
END
SELECT * FROM Numbers
SET NOCOUNT OFF
This code starts to fall over regularly in the higher session numbers when it tries to set an activity that the individual has already done.
Thanks!
I tried using your code to Generate ApplicantID and SessionID rows and modified the last part to generate GroupID column using Ranking functions.
Below is the output of what I have tried:
SET STATISTICS TIME, io OFF
-- Create list of applicants
IF OBJECT_ID('dbo.Numbers') IS NOT NULL DROP TABLE dbo.Numbers
CREATE TABLE dbo.Numbers (ApplicantID INT, SessionID INT, GroupID INT)
DECLARE #i INT,
#Session INT,
#Group INT;
SELECT #i = 1;
SET NOCOUNT ON
WHILE #i <= 100
BEGIN
INSERT INTO Numbers (ApplicantID, SessionID) VALUES (#i, 1);
SELECT #i = #i + 1;
END;
-- Duplicate ApplicantID list for each different session
SELECT #Session = 1
WHILE #Session <= 10
BEGIN
IF #Session > 1
BEGIN
INSERT INTO
Numbers (ApplicantID, SessionID)
SELECT ApplicantID, #Session FROM Numbers WHERE SessionID = 1
END
SELECT #Session = #Session + 1
END
SET NOCOUNT OFF
drop table if exists #temp;
select ApplicantID, SessionID, row_number() OVER(PARTITION BY applicantID ORDER BY applicantID) AS grp_row into #temp
from Numbers
update a
set a.GroupID = b.grp_row
from Numbers a
join #temp b on a.ApplicantID = b. ApplicantID and a.SessionID = b.SessionID
where a.GroupID is null
Each member must do each activity only once.
There are 100 applicants, and as an example, I am showing applicants 1 & 100. Here Each applicant is having each groupID only once.
Each activity must have the same number of members in each session.
There are 10 GroupID's and the number of applicants for each GroupID is the same (100).
The members must be with (at least mostly) different people in each session.
There are 100 applicants but I am taking the top 10 as an example. Here each sessionID has different applicants.

How to get records that between m records back and n records forward from a reference row - Non-sequential data

My scenario is as follows:
I have a reference record, say, ProductId = 1
The records each have a non-unique ItemTypeId
I would like to fetch records that exists between the following points
START POINT being 2 records BACKWARDS of type ItemTypeId = 1, from record of ProductId =1
END POINT being 3 records FORWARDS of type ItemTypeId = 1, from record of ProductId = 1
The query should get ALL data between the two points, inclusively
Here's a picture that illustrates this better than my words:
How would I structure my query to do this?
Any better way to do it without temp tables?
Thank-you!
Note that for this to work at all, you need that record ID to be an actual column in the table. Rows have no inherent order in a table.
With that in place, you can use LAG and LEAD to get what you want:
CREATE TABLE #t
(
RecordId INT IDENTITY(1,1),
ProductId INT,
ItemType INT
);
INSERT INTO #t(ProductId, ItemType)
VALUES
(5,1),(3,1),(7,3),(6,1),(2,7),
(1,1),(7,3),(8,1),(10,3),(9,5),
(11,1),(19,1),(17,4),(13,3);
WITH c1 AS
(
SELECT ProductId,
RecordId,
LAG(RecordId,2) OVER (ORDER BY RecordId) AS Back2,
LEAD(RecordId,3) OVER (ORDER BY RecordId) AS Forward3
FROM #t
WHERE ItemType = (SELECT ItemType FROM #t WHERE ProductId = 1)
),c2 AS
(
SELECT c1.Back2, c1.Forward3 FROM c1
WHERE c1.ProductId = 1
)
SELECT #t.*
FROM #t
INNER JOIN c2 ON #t.RecordId BETWEEN c2.Back2 AND c2.Forward3;
If you wanna do without using temp tables as you ask, the following solution work.
But it is not very nice i agree.
Well this is what i done :
CREATE DATABASE TEST;
USE TEST
CREATE TABLE PRODUCT
(
ProductId INT,
ItemType INT
)
INSERT INTO PRODUCT
VALUES
(5,1),
(3,1),
(7,3),
(6,1),
(2,7),
(1,1),
(7,3),
(8,1),
(10,3),
(9,5),
(11,1),
(19,1),
(17,4),
(13,3)
DECLARE product_cursor CURSOR FOR
SELECT * FROM PRODUCT;
OPEN product_cursor
DECLARE
#ProductId INT,
#ItemId INT,
#END_FETCH INT,
#countFrom INT,
#countTo INT
DECLARE #TableResult TABLE
(
RProductId INT,
RItemId INT
)
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
SET #END_FETCH = 0
SET #countFrom = 0
SET #countTo = 0
WHILE ##FETCH_STATUS = 0 AND #END_FETCH = 0
BEGIN
IF #ItemId = 1 AND (#countFrom = 0 AND #countTo = 0)
BEGIN
SET #countFrom = 3
SET #countTo = 3
END
ELSE
BEGIN
IF #countFrom > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countFrom -= 1
--SELECT 'CountFrom : ', #countFrom
END
END
ELSE
BEGIN
IF #countTo > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countTo -= 1
--SELECT 'CountTO : ', #countTo
END
END
ELSE
BEGIN
SET #END_FETCH = 1
END
END
END
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
END
CLOSE product_cursor
DEALLOCATE product_cursor
SELECT * FROM #TableResult
And this is the result i got :
RProductId RItemId
3 1
7 3
6 1
2 7
1 1
7 3
8 1
10 3
9 5
11 1
19 1
But i prefer the solution of #James Casey.
By the way, why won't you use temp table ?

SQL Query to retrieve the last records till the quantity purchased reaches the total quantity in stock

I have a table that have the ItemCode and Quantity in stock and another table that contains the purchases.
I want a query to get the Quantity in stock (ex. Qty = 5) and to take the purchase table to get the purchase invoices by descending order and take the Item Prices.
The Query has to keep retrieving records from the Purchase table according to the Quantity till we reach sum of Quantity in stock = 5.
ex.
**Purchase No ItemCode Qty Cost Price**
2 123 2 100
3 123 10 105
6 123 2 100
8 123 1 90
9 123 2 120
---------------------------------------------
**ItemCode Qty in Stock**
123 5
--------------------------------------------
In this example I want the query to retrieve for me the last 3 invoices (9,8 and 6) because the Qty (2+1+2 = 5)
Is there any suggestion .
Thank you in advance
This script should do the job.
/* SQL SCRIPT BEGIN */
create table #tmp (PurchaseNo int, ItemCode int, Qty int)
insert into #tmp (PurchaseNo, ItemCode, Qty)
select
p1.PurchaseNo, p1.ItemCode, sum(t.Qty) as Qty
from
Purchases p1
join
(
select
p2.PurchaseNo,
p2.ItemCode, p2.Qty
from
Purchases p2
) t on p1.PurchaseNo <= t.PurchaseNo and p1.ItemCode = t.ItemCode
group by p1.PurchaseNo, p1.ItemCode
order by p1.ItemCode, sum(t.Qty) asc
select * From #tmp
where
ItemCode = 123
and
Qty < 5
union
select top 1 * From #tmp
where
ItemCode = 123
and
Qty >= 5
order by PurchaseNo desc
drop table #tmp
/* SQL SCRIPT END */
Hi This can be the solution :
Here I have Used Result Table which will store the result.
I have used three tables Purchage(PurchageNo,ItemCode,Qty) , Stock(ItemCode,QtyInStock) and result(PurchageNo).
Full Workable Code is Here:
DECLARE #ItemCode int;
DECLARE #AvailableQty int;
SET #ItemCode = 123 ;
SET #AvailableQty = (select QtyInStock from Stock where ItemCode = #ItemCode);
SELECT
RowNum = ROW_NUMBER() OVER(ORDER BY PurchageNo),*
INTO #PurchageTemp
FROM Purchage
DECLARE #MaxRownum INT;
SET #MaxRownum = (select COUNT(*)from #PurchageTemp);
DECLARE #Iter INT;
SET #Iter = 1;
DECLARE #QtySum int=0;
DECLARE #QtySumTemp int=0;
DECLARE #CurrentItem int;
WHILE (#Iter <= #MaxRownum and #QtySum <= #AvailableQty)
BEGIN
set #QtySumTemp=#QtySum;
set #QtySumTemp = #QtySumTemp + (SELECT Qty FROM #PurchageTemp WHERE RowNum = #Iter and ItemCode=#ItemCode);
IF #QtySumTemp <= #AvailableQty
BEGIN
set #QtySum=#QtySumTemp;
set #CurrentItem= (SELECT PurchageNo FROM #PurchageTemp WHERE RowNum = #Iter and ItemCode=#ItemCode);
insert into [Result] values (#CurrentItem);
END
SET #Iter = #Iter + 1
END
DROP TABLE #PurchageTemp

Loop for each row

I have two tables with FOREIGN KEY([Table_ID])
Columns
ID Table_ID ActiveFlag
1 1 0
2 2 1
3 1 1
4 3 0
Sys_Tables
Table_ID Name
1 Request
2 Plan
3 Contecst
I'm writing a stored procedure that returns any column for each table.
Example Output for values ​​above
--first output table
ID Table_ID ActiveFlag
1 1 0
3 1 1
--second output table
ID Table_ID ActiveFlag
2 2 1
--third output table
ID Table_ID ActiveFlag
4 3 0
My idea is this
Select c.*
from Ccolumns c
inner join Sys_tables t
on t.Table_ID = c.Table_ID and t.Table_ID = #Parameter
My problem, i do't know how to make a loop for each row. I need the best way. Example i can use following loop:
DECLARE #i int = 0
DECLARE #count int;
select #count = count(t.Table_ID)
from Sys_tables t
WHILE #i < #count BEGIN
SET #i = #i + 1
--DO ABOVE SELECT
END
But this is not entirely correct. Example my Sys_tables such data may be
Table_ID Name
1 Request
102 Plan
1001 Contecst
Do You have any idea?
There are couple ways you can achieve that: loops and cursors, but first of all you need to know that it's a bad idea: either are very slow, anyway, here's some kind of loop sample:
declare #row_ids table (
id INT IDENTITY (1, 1),
rid INT
);
insert into #row_ids (rid) select someIdField from SomeTable
declare #cnt INT = ##ROWCOUNT
declare #currentRow INT = 1
WHILE (#currentRow <= #cnt)
BEGIN
SELECT rid FROM #row_ids WHERE id = #currentRow
SET #currentRow = #currentRow + 1
END
I guess you're using SQL Server, right?
Then, you can use a CURSOR as here: How to write a cursor inside a stored procedure in SQL Server 2008

How to calculate with sql queries or views

I have a table
id name parentid
----------------
1 a 0
2 b 1
3 c 2
4 d 1
Now I want to calculate level with
if direct parent id count = 6 then level1,
if have 6 level1 count then level2,
if have 6 level2 count then level3, and so on
I am using SQL Server 2005 Express
You have to use sql recursion query may be this help you http://msdn.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
This should do the job: Simply jump to the parent until you reach the top and count the amount of iterations.
create function dbo.CalcLevel (#ID int)
returns int
as
begin
declare #level int=0
while #ID != 0 begin
select #ID=parentID from MyTable where ID=#ID
set #level = #level + 1
if (#level = 1000) set #ID = 0 -- compensate endless loop
end
return #level
end