Multiple Inserts knowing the quantity - sql

Let's supposed I Have a table with products like this
product | number
----------------------
aaaa | 2
bbbb | 3
cccc | 1
dddd | 4
It's a little more complex, but the idea is the same.
I need to return something like this
aaaa0001
aaaa0002
bbbb0003
bbbb0004
bbbb0005
cccc0006
dddd0007
dddd0008
dddd0009
dddd0010
I mean, each product the number of times specified in the column, with some kind of identificator at the end.
How can I do this ?
I thought of table variable, with identity on the Id
DECLARE #Codigos TABLE (ID INT IDENTITY(1,1), Barra Varchar(50) NOT NULL)
and after the inserts, do
select rtrim(ltrim(barra)) + right('0000' + rtrim(ltrim(cast(id as varchar(10)))),4) from #Codigos
but This mean that I'll have to insert for each products the number of times in the product variable, like
#Codigos
id | Barra
----------------
1 | aaaa
2 | aaaa
and I'm facing problems with that. How can I do that ? With a cursor ? That's what I thought, but I want to know if there's a better option.
Maximum will be 300 rows in original table.

This should do it:
;WITH CTE AS
(
SELECT product, Number, 1 RN
FROM YourTable
UNION ALL
SELECT product, Number, RN + 1
FROM CTE
WHERE RN + 1 <= Number
)
SELECT product + RIGHT('000' + CAST(RN AS VARCHAR(3)),3) YourColumn
FROM CTE
ORDER BY product, Number
OPTION(MAXRECURSION 0)
Here is a sqlfiddle for you to try.

Related

SQL sort by closest string match in multiple columns

I have a search parameter that I am trying to search on multiple columns using the "Like % InputParam %" pattern matching which gives me the following result. ie - Matching OrderID and Ref no to the input parameter.
Consider I have the following table -
OrderId | Name | Ref No |
12345 | XYZ | 120545 |
1205 | ABC | 451003 |
00120505 | CDE | 000174 |
Here OrderID, Ref no are strings and the input query = '1205'. I want the result to be sorted from the most matched to the least matched.
Where most matched is the most accurate match like 1205 = 1205 here
and Least matched is a substring like 00120505 = 1205.
Output -
OrderId | Name | Ref no |
1205 | ABC | 451003 |
12345 | XYZ | 120545 |
00120505 | CDE | 000174 |
You can do it by defining a computed column e.g. MATCH_SCORE with a value that depends on comparisons between OrderId and the value you are looking for; and then use ORDER BY MATCH_SCORE.
Working example:
DECLARE #tbl TABLE (OrderId VARCHAR(10), Name VARCHAR(10), RefNo VARCHAR(10))
INSERT INTO #tbl VALUES ('12345','XYZ','120545'), ('1205','ABC','451003'), ('00120505','CDE','000174')
DECLARE #v VARCHAR(10) = '1205'
;WITH data AS
(SELECT *,
CASE WHEN OrderId = #v THEN 1
WHEN OrderId LIKE #v + '%' THEN 2
WHEN OrderId LIKE '%' + #v + '%' THEN 3
ELSE 4
END AS MATCH_SCORE
FROM #tbl
)
SELECT * FROM data ORDER BY MATCH_SCORE
Output:
OrderId
Name
RefNo
MATCH_SCORE
1205
ABC
451003
1
00120505
CDE
000174
3
12345
XYZ
120545
4
I used a Common Table Expression to construct data, which defines a temporary named result set that is used by the SELECT that follows it.
You need to define what you mean by the distance between two strings. I'll use #peter-b 's definition in the example below. Once you know how to measure how close a string is to the search parameter, you can transpose the columns to rows with cross apply (LATERAL is the standard name), and use the min distance to order the rows.
with t (orderid, refno) as (
select '12345','120545'
union all
select '1205','451003'
)
select t.orderid, t.refno
, min(case when u.s = '1205' then 1
when u.s like '1205'+'%' then 2
when u.s like '%' + u.s + '%' then 3
else 4
end) as distance
from t
cross apply (
select t.orderid
union all
select t.refno
) as u (s)
group by t.orderid, t.refno
order by 3
;
orderid refno distance
1205 451003 1
12345 120545 2
Fiddle

Generate random numbers in a specific range without duplicate values

SELECT CEILING (RAND(CAST(NEWID() AS varbinary)) *275) AS RandomNumber
This creates random numbers. However, it spits out duplicates
Generate a numbers table with the range of your desire. In my case, I do it via recursive cte. Then order the numbers table using the newid function.
with numbers as (
select 0 as val union all
select val + 1 from numbers where val < 275
)
select ord = row_number() over(order by ap.nid),
val
into #rands
from numbers n
cross apply (select nid = newid()) ap
order by ord
option (maxrecursion 1000);
One run of the code above results in a table of 276 values that begins and ends as follows:
| ord | val |
+-----+-----+
| 1 | 102 |
| 2 | 4 |
| 3 | 127 |
| ... | ... |
| 276 | 194 |
Non duplicating ordering of random numbers.
You can select from it a variety of ways, but one way could be:
-- initiate these to begin with
declare #ord int = 1;
declare #val int;
declare #rand int;
-- do this on every incremental need for a random number
select #val = val,
#ord = #ord + 1
from #rands
where ord = #ord;
print #val;
In the comments to my other answer, you write:
The table I'm working with has an ID , Name , and I want to generate a 3rd column that assigns a unique random number between 1-275 (as there are 275 rows) with no duplicates.
In the future, please include details like this in your original question. With this information, we can help you out better.
First, let's make a sample of your problem. We'll simplify it to just 5 rows, not 275:
create table #data (
id int,
name varchar(10)
);
insert #data values
(101, 'Amanda'),
(102, 'Beatrice'),
(103, 'Courtney'),
(104, 'Denise'),
(105, 'Elvia');
Let's now add the third column you want:
alter table #data add rando int;
Finally, let's update the table by creating a subquery that orders the rows randomly using row_number(), and applying the output the the column we just created:
update reordered
set rando = rowNum
from (
select *,
rowNum = row_number() over(order by newid())
from #data
) reordered;
Here's the result I get, but of course it will be different every time it is run:
select *
from #data
| id | name | rando |
+-----+----------+-------+
| 101 | Amanda | 3 |
| 102 | Beatrice | 1 |
| 103 | Courtney | 4 |
| 104 | Denise | 5 |
| 105 | Elvia | 2 |

Extract the maximum value from the last number in a hierarchyID?

I have a column with hierarchy IDs converted to strings in SQL Server. I need to add new hierarcyIDs for the new lines, but first I have to find the last child of the current ID. The hierarchyIDs are look like these:
/1/1/1/6/1/
/1/1/1/6/7/
/1/1/1/6/3/
/1/1/1/6/13/
/1/1/1/6/4/
As you can see, the maximum number is not equal with the count of the lines, so I can not use count()+1 unfortunately.
What I need to extract from this list is:
13
I only have experience in PL SQL, where it was easy to do this with regexp functions, but I can not find the solution in SQL Server.
You can use some STRING operation as below to get your desired output-
DEMO HERE
WITH your_table(your_column)
AS
(
SELECT '/1/1/1/6/1/' UNION ALL
SELECT '/1/1/1/6/7/' UNION ALL
SELECT '/1/1/1/6/3/' UNION ALL
SELECT '/1/1/1/6/13/' UNION ALL
SELECT '/1/1/1/6/4/'
)
SELECT
MAX(
CAST(
REVERSE(
LEFT(
REVERSE(LEFT(your_column,LEN(your_column)-1)),
CHARINDEX('/',REVERSE(LEFT(your_column,LEN(your_column)-1)) ,0) - 1
)
)
AS INT
)
)
FROM your_table
Note: Data has to be as your sample data
The creators of hierarchyid have anticipated your needs and have an officially supported solution for this.
declare #h table (h HIERARCHYID);
insert into #h (h)
values
('/1/1/1/6/1/'),
('/1/1/1/6/7/'),
('/1/1/1/6/3/'),
('/1/1/1/6/13/'),
('/1/1/1/6/4/');
declare #parent HIERARCHYID = '/1/1/1/6/';
declare #maxChild HIERARCHYID = (
select max(h)
from #h
where h.IsDescendantOf(#parent) = 1
);
-- ToString() added here for readability in the output;
-- it's not needed to be used as data.
select #parent.GetDescendant(#maxChild, null).ToString();
You can read more about this here.
Another way around this is to specify your own components to the hierarchyid yourself. I like to use the primary key values. For example, let's say that this data represents a company's org chart. If EmployeeID 1 is the CEO, 42 is the CFO (who reports to the CEO) and 306 is Accounting Manager (who reports to the CFO), the latter's hierarchyid would be /1/42/306/. Because the PK values are unique, the generated hierarchid is also unique.
To give some ideas, 2 solutions.
The first one is for Sql Server 2017 and beyond.
The second for most versions below that.
create table YourTable
(
ID int identity(101,1) primary key,
hierarcyIDs varchar(100)
)
GO
✓
insert into YourTable
(hierarcyIDs) values
('/1/2/3/4/5/')
,('/1/2/3/4/15/')
,('/1/2/3/4/10/')
,('/11/12/13/14/15/')
,('/11/12/13/14/42/')
;
GO
5 rows affected
SELECT
[1] as id1,
[2] as id2,
[3] as id3,
[4] as id4,
max([5]) as id5,
concat_ws('/','',[1],[2],[3],[4],max([5])+1,'') as nextHierarcyIDs
FROM YourTable
OUTER APPLY
(
select *
from
( select try_cast(value as int) as id
, row_number() over (order by (select 0)) rn
from string_split(trim('/' from hierarcyIDs),'/') s
) src
pivot (max(id)
for rn in ([1],[2],[3],[4],[5])
) pvt
) anarchy
group by [1],[2],[3],[4]
GO
id1 | id2 | id3 | id4 | id5 | nextHierarcyIDs
--: | --: | --: | --: | --: | :---------------
1 | 2 | 3 | 4 | 15 | /1/2/3/4/16/
11 | 12 | 13 | 14 | 42 | /11/12/13/14/43/
SELECT hierarcyIdLvl1to4
, MAX(hierarcyIDLvl5) AS maxHierarcyIDLvl5
FROM
(
SELECT id, hierarcyIDs
, substring(hierarcyIDs, 0, len(hierarcyIDs)-charindex('/',
reverse(hierarcyIDs),2)+2) AS hierarcyIdLvl1to4
, reverse(substring(reverse(hierarcyIDs),2,charindex('/',
reverse(hierarcyIDs),2)-2)) AS hierarcyIDLvl5
FROM YourTable
) q
GROUP BY hierarcyIdLvl1to4
GO
hierarcyIdLvl1to4 | maxHierarcyIDLvl5
:---------------- | :----------------
/1/2/3/4/ | 5
/11/12/13/14/ | 42
db<>fiddle here

SQL SELECT Convert Min/Max into Separate Rows

I have a table that has a min and max value that I'd like create a row for each valid number in a SELECT statement.
Original table:
| Foobar_ID | Min_Period | Max_Period |
---------------------------------------
| 1 | 0 | 2 |
| 2 | 1 | 4 |
I'd like to turn that into:
| Foobar_ID | Period_Num |
--------------------------
| 1 | 0 |
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 2 | 4 |
The SELECT results need to come out as one result-set, so I'm not sure if a WHILE loop would work in my case.
If you expect just a handful of rows per foobar, then this is a good opportunity to learn about recursive CTEs:
with cte as (
select foobar_id, min_period as period_num, max_period
from original t
union all
select foobar_id, min_period + 1 as period_num, max_period
from cte
where period_num < max_period
)
select foobar_id, period_num
from cte
order by foobar_id, period_num;
You can extend this to any number of periods by setting the MAXRECURSION option to 0.
One method would be to use a Tally table, ther's plenty of examples out there, but I'm going to create a very small one in this example. Then you can JOIN onto that and return your result set.
--Create the Tally Table
CREATE TABLE #Tally (I int);
WITH ints AS(
SELECT 0 AS i
UNION ALL
SELECT i + 1
FROM ints
WHERE i + 1 <= 10)
--And in the numbers go!
INSERT INTO #Tally
SELECT i
FROM ints;
GO
--Create the sample table
CREATE TABLE #Sample (ID int IDENTITY(1,1),
MinP int,
MaxP int);
--Sample data
INSERT INTO #Sample (Minp, MaxP)
VALUES (0,2),
(1,4);
GO
--And the solution
SELECT S.ID,
T.I AS P
FROM #Sample S
JOIN #Tally T ON T.I BETWEEN S.MinP AND S.MaxP
ORDER BY S.ID, T.I;
GO
--Clean up
DROP TABLE #Sample;
DROP TABLE #Tally;
Depending on the size of the data and the range of the period, the easiest way to do this is to use a dynamic number fact table, as follows:
WITH rn AS (SELECT ROW_NUMBER() OVER (ORDER BY object_id) -1 as period_num FROM sys.objects)
SELECT f.foobar_id, rn.period_num
FROM foobar f
INNER JOIN rn ON rn.period_num BETWEEN f.min_period AND f.max_period
However, if you're working with a larger volume of data, it will be worth creating a number fact table with an index. You can even use a TVV for this:
-- Declare the number fact table
DECLARE #rn TABLE (period_num INT IDENTITY(0, 1) primary key, dummy int)
-- Populate the fact table so that all periods are covered
WHILE (SELECT COUNT(1) FROM #rn) < (SELECT MAX(max_period) FROM foobar)
INSERT #rn select 1 from sys.objects
-- Select using a join to the fact table
SELECT f.foo_id, rn.period_num
FROM foobar f
inner join #rn rn on rn.period_num between f.min_period and f.max_period
Just Create a function sample date and use it
CREATE FUNCTION [dbo].[Ufn_GetMInToMaxVal] (#Min_Period INT,#Max_Period INT )
RETURNS #OutTable TABLE
(
DATA INT
)
AS
BEGIN
;WIth cte
AS
(
SELECT #Min_Period As Min_Period
UNION ALL
SELECT Min_Period+1 FRom
cte
WHERE Min_Period < #Max_Period
)
INSERT INTO #OutTable
SELECT * FROM cte
RETURN
END
Get the result by executing sql statement
DECLARE #Temp AS TABLE(
Foobar_ID INT,
Min_Period INT,
Max_Period INT
)
INSERT INTO #Temp
SELECT 1, 0,2 UNION ALL
SELECT 2, 1,4
SELECT Foobar_ID ,
DATA
FROM #Temp
CROSS APPLY
[dbo].[Ufn_GetMInToMaxVal] (Min_Period,Max_Period)
Result
Foobar_ID DATA
----------------
1 0
1 1
1 2
2 1
2 2
2 3
2 4

Tsql looping father-son relationship between tables

I have a table like this:
table item
(
id int,
quantity float,
father int, -- refer to item itself in case of subitem
)
I need to sum al quantity plus sons quantity like this way:
select i.id, max(i.quantity)+sum(ft.quantity) as quantity
from item i
left join item ft on ft.id=i.id
group by i.id
My trouble is because relationship between father-son is recursive so I would like to sum also his grandfather quantity and so on... and i don't know the maximum deepness, than I can not join many times.
What can i do?
Thank you.
You have to use a recursive CTE. Somthing like this:
;WITH FathersSonsTree
AS
(
SELECT Id, quantity, 0 AS Level
FROM Items WHERE fatherid IS NULL
UNION ALL
SELECT c.id, c.quantity, p.level+1
FROM FathersSonsTree p
INNER JOIN items c ON c.fatherid = p.id
), ItemsWithMaxQuantities
AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY level
ORDER BY quantity DESC) rownum
FROM FathersSonsTree
)
SELECT
ID,
(SELECT MAX(Quantity)
FROM FathersSonsTree t3
WHERE t3.level = t1.level
) +
ISNULL((SELECT SUM(t2.Quantity)
FROM FathersSonsTree t2
WHERE t1.level - t2.level = 1), 0)
FROM FathersSonsTree t1
ORDER BY ID;
SQL Fiddle Demo
This will give you something like:
| ID | QUANTITY |
-----------------
| 1 | 10 |
| 2 | 20 |
| 3 | 20 |
| 4 | 20 |
| 5 | 32 |
| 6 | 32 |
| 7 | 32 |
| 8 | 32 |
You might try building a recursive CTE (common table expression) as described in this article on SQLAuthority:
http://blog.sqlauthority.com/2012/04/24/sql-server-introduction-to-hierarchical-query-using-a-recursive-cte-a-primer/
The author, Pinal Dave, discusses using a recursive CTE on an employees table that has a self referencing foreign key for ManagerID to return a list of employees with a count of how many levels are between them and the top of the hierarchy where the employee has no manager (ManagerID = NULL). That's not exactly what you're wanting but it might get you started.
I did a little experimentation and ended up with something very similar to Mahmoud Gamal's solution but with a slight difference to include the not just the parent, grandparents, great-grandparents, etc. quantity but also the child quantity.
Here's the test table I used:
CREATE TABLE Items(ID int IDENTITY
CONSTRAINT PK_Items PRIMARY KEY,
Quantity int NOT NULL,
ParentID int NULL
CONSTRAINT FK_Item_Parents REFERENCES Items(ID));
And the data:
ID Quantity ParentID
------------------------------------------------------------
1 10 {NULL}
2 10 1
3 10 2
4 10 3
5 10 2
Here's my recursive query:
WITH cteRecursiveItems
AS (SELECT Id,
quantity,
0
AS Level
FROM Items
WHERE ParentID IS NULL
UNION ALL
SELECT i.id,
i.quantity,
cri.level + 1
FROM
cteRecursiveItems cri
INNER JOIN items i ON i.ParentID = cri.id)
SELECT ID,
Quantity + (
SELECT MAX(Quantity)
FROM cteRecursiveItems cri3
WHERE cri3.level = cri1.level) + (
SELECT SUM(cri2.Quantity)
FROM cteRecursiveItems cri2
WHERE cri1.level - cri2.level = 1) as Total
FROM cteRecursiveItems cri1
ORDER BY ID;
And here's the results I get from running it against the test table:
ID Total
----------------------------------------
1 {NULL}
2 30
3 30
4 40
5 30
It still needs a little tweaking because the first and 2nd row are off by 10. Row 1 should have a total of 10 and row 2 should have a total of 20. I'm making a note to try and fix that when I get home. Can't spend too much of my employer's time on this right now. :) The other rows have the value I was expecting.