Looping in SQL Server from 1 to 60 - sql

I have a table T1 as below
I need to copy the data from T1 to another table called T2. T2 has an additional column called 'Month' and each record from T1 needs to be copied to T2 60 times, with Month value ranging from 1 to 60.
I have been trying something like this and need the MONTH value to be taken dynamically , like a loop from 1 to 60. Could someone help please? Thank you
INSERT INTO T2
SELECT PRODUCT, CUSTOMER, 1 as MONTH
FROM T1

We can use a cross join approach:
WITH months AS (
SELECT n = v2.n * 10 + v1.n
FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v1(n)
CROSS JOIN (VALUES (0), (1), (2), (3), (4), (5), (6)) v2(n)
)
INSERT INTO T2 (Product, Customer, Month)
SELECT t1.Product, t1.Customer, m.n
FROM table1 t1
CROSS JOIN months m
WHERE m.n BETWEEN 1 AND 60;

CROSS JOIN to a tally, with the values 1 to 60. -- Thanks #Larnu for the answer.

I used stored procedures in MySQL:
DELIMITER $$
CREATE PROCEDURE auto_insert()
BEGIN
DECLARE i1 INT DEFAULT 1;
WHILE i1 <= 60 DO
INSERT INTO T2 SELECT *, i1 FROM T1;
SET i1 = i1 + 1;
END WHILE;
END $$
DELIMITER ;
CALL auto_insert;

Related

Increment in the value of each row based on iterations in SQL

How can I have an increment on a column in each row based on the values of other column till specific iterations?
For example:
There are 2 columns Iterations and IncrementRatio. The value of Iterations is 5 and IncrementRatio is 5000. The query is supposed to generate 10 records only. Once the iteration number is reached the remaining rows should keep the last value. So based on this scenario I should be able to get something like this.
MyCount
---------
5000
10000
15000
20000
25000
25000
25000
25000
25000
25000
I assumed you always wanted fixed 10 rows result. This is achieve with CROSS JOIN to a derived table with 10 rows. In the solution, i am using Table Value Constructor to generate 10 rows. You can replace it with a tally table if you have one.
-- create the sample table for demonstration of the query
declare #table table
(
Id int identity,
Iterations int,
IncrementRatio int
)
-- insert original sample data plus one extra
insert into #table values (5, 5000), (3, 1000)
-- the query
select *,
MyCount = case when n < Iterations
then n * IncrementRatio
else Iterations * IncrementRatio
end
from #table
cross join
(
values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)
) num (n)
I would use a recursive cte:
with cte as (
select iterations, incrementratio, 1 as i, incrementratio as cnt
from t
union all
select iterations, incrementratio, i + 1,
(case when i < iterations then t.incrementratio + cnt
else cnt
end)
from t
where i < 10
)
select *
from cte;

Is it possible to multiply the results of two select statements?

If I have a select statement like this...
SELECT col1 FROM table WHERE col2=1
Which returns the results 1,2,3,4,5.
And another select statement like this...
SELECT col1 FROM table WHERE col2=2
Which return the results 6,7,8,9,10.
Is there a way to multiply the results of these two queries, so that a final result set of 6,14,24,36,50 is returned?
I've tried simple things like this.
SELECT (SELECT col1 FROM table WHERE col2=1) * (SELECT col1 FROM table WHERE col2=2)
But that didn't seem to work.
If it makes a difference, I'm using Sqlite, and the value types are REAL not INTEGER.
DECLARE #Table1 TABLE ( thing real)
DECLARE #Table2 TABLE (anotherThing real)
INSERT #Table1 (thing)
VALUES (1), (2), (3), (4), (5)
INSERT #Table2 (anotherThing)
VALUES (6), (7), (8), (9), (10)
;
SELECT T.thing * TT.anotherThing AS Multi
FROM (
SELECT T.thing
, (
SELECT COUNT(1) RN
FROM #Table1 AS T2
WHERE T2.thing <= T.Thing
) RN
FROM #Table1 AS T
)AS T
INNER JOIN (
SELECT TT.anotherThing
, (
SELECT COUNT(1) RN
FROM #Table2 AS T2
WHERE T2.anotherThing <= TT.anotherThing
) RN
FROM #Table2 AS TT
) TT
ON TT.RN = T.RN
Result:
+-------+
| Multi |
+-------+
| 6 |
| 14 |
| 24 |
| 36 |
| 50 |
+-------+
The example probably won't work in sqlite, I wrote it in sql server. This shows it works on sqlite.

Get records which are not in tables when passed in In clause?

I am sure that the title of the question need to get change , but not sure what to put from my end .
I am passing In clause in two Tables want to get the records which are not in both the tables .
Table A contains ID 1,2
Table B contains ID 3,4
I am passing in my In clause (1,2,3,4,5,6)
I am looking for Something
1 TableA
2 TableA
3 TableB
4 TableB
5 Not Found
6 Not Found
I am using union all to get items from Table A and Table B not sure how to get the not found records in both the table ?
Here is one method. It uses exists to check whether the tables contain the id:
select id,
(case when inA = 1 and inB = 1 then 'Both'
when inA = 1 then 'TableA'
when inB = 1 then 'TableB'
else 'Not Found'
end) as status
from (select id,
(case when exists (select 1 from tableA a where a.id = ids.id then 1 else 0 end) as inA,
(case when exists (select 1 from tableB b where b.id = ids.id then 1 else 0 end) as inB
from (values (1), (2), (3), (4), (5), (6)) as ids(id)
) i;
Of course, you can add where inA = 0 or inB = 0 if you don't want the 'Both' rows.
Try following query:
DECLARE #TableA TABLE (Col1 INT)
INSERT #TableA VALUES (1), (2),(5)
DECLARE #TableB TABLE (Col2 INT)
INSERT #TableB VALUES (3), (4),(5)
-- Query
SELECT t.Col3,
CONCAT(xa.ExistsInA, ',', xb.ExistsInB) AS SourceTables
--STUFF(CONCAT(',' + xa.ExistsInA, ',' + xb.ExistsInB), 1, 1, '') AS SourceTables
FROM (VALUES
(1),
(2),
(3),
(4),
(5),
(6)
) AS t(Col3)
OUTER APPLY (
SELECT 'TableA'
WHERE EXISTS(SELECT * FROM #TableA a WHERE a.Col1 = t.Col3)
) xa (ExistsInA)
OUTER APPLY (
SELECT 'TableB'
WHERE EXISTS(SELECT * FROM #TableB b WHERE b.Col2 = t.Col3)
) xb (ExistsInB)

SQL sectioning/averaging based on different timetag/timestamps and user-chosen input (T-SQL)

I've got the following problem that I would like to cope with - I have a SQL dataset that I would like to section (e.g. like this one):
OldTimetag OldValue
2012-05-03 12:47:00 5
2012-05-03 13:00:00 1.3
2012-05-03 13:21:00 7
2012-05-03 14:56:00 5
2012-05-03 14:57:00 0.3
.... ....
Now, I want to section (and/or average) the data based on a user-chosen interval - into new timetags, e.g. every 15 minutes with the first timetag as starting point, i.e.:
NewTimetag NewValue
2012-05-03 12:47:00 4.507
2012-05-03 13:02:00 1.3
.... ....
The main constraint is that the value next to the timetag is always valid, until the next timetag appears. So the value of 5 at timetag 2012-05-03 12:47:00 is valid for the next 13 minutes until 13:00:00. The value for the first 15 minutes from 12:47:00 would be (13*5+2*1.3)/15 = 4.507. In the next 15 minutes, at 13:02:00 the value is simply equal to 1.3... (and so on)
I've come so long, that it is a good idea to make an "artificial table" first, to later join it with the old table. I'm generating that table by:
DECLARE #intStart datetime, #intEnd datetime
SELECT #intStart =min(OldTimetag), #intEnd = MAX(OldTimetag)
FROM OldTable
where OldTimetag between '2012-05-03 12:47:00' and '2012-05-03 14:57:00'
Declare #ArtificalTable table (NewTimeTag datetime, NewValue Float)
Declare #MinuteSlicer Int
Set #MinuteSlicer = 15
Insert #Hallo Select #intStart, null
While ( #intStart < #intEnd ) BEGIN
Insert #ArtificalTable
Select DATEADD(mi,#MinuteSlicer, #intStart), Null
Set #intStart = DATEADD(mi,#MinuteSlicer,#intStart)
If #intEnd <= DATEADD(mi,#MinuteSlicer,#intStart)
Break
End
This gives me an output like:
NewTimetag NewValue
2012-05-03 12:47:00 Null
2012-05-03 13:02:00 Null
.... ....
However, I'm having problems with the next step, how to join the tables correctly - can anyone give me a hint?
Here is one way of doing it.
Sample Data:
declare #data table(OldTimetag datetime2, OldValue numeric(5,2));
Insert into #data(OldTimetag, OldValue) Values
('2012-05-03 12:47:00', 5)
, ('2012-05-03 13:00:00', 1.3)
, ('2012-05-03 13:21:00', 7)
, ('2012-05-03 14:56:00', 5)
, ('2012-05-03 14:57:00', 0.3);
Your custom range size in minutes:
declare #mins int = 15;
List is used to quickly compute an ordered list of number from 0 to n Where n <= to the number of minutes between the first and the last OldTimetag.
With list(n) as (
Select top(Select 1+DATEDIFF(minute, min(OldTimetag), max(OldTimetag)) From #data)
ROW_NUMBER() over(order by (select 1))-1
From (
Select 1 From (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x1(n)
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x2(n)
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x3(n)
) as x(n)
)
Select NewTimetag = DATEADD(minute, #mins*(l.n/#mins), MIN(r.startTime)), NewValue = AVG(d.oldValue)
From list l
Cross Join (Select startTime = min(OldTimetag) From #data) as r
Cross Apply (Select maxTimetag = MAX(OldTimetag) From #data Where OldTimetag <= DATEADD(minute, n, startTime)) as mx
Inner Join #data d on d.OldTimetag = mx.maxTimetag
Group By l.n/#mins
Cross Join is used to mix each number from the ordered list with the first OldTimetag from your data.
Cross Apply is used to get the nearest OldTimetag before each minute created with the Cross Join.
Inner Join then matches the nearest OldTimetag with your data in order to retrieved oldValue.
Select only have to calculate the average for each range on #mins minutes and its NewTimetag.
It works well for a range of up to 1000 minutes between the min and max OldTimetag. If you need to go beyond this limit, you can add a a 4th line in the list CTE:
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x4(n) => up to 10.000
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x5(n) => up to 100.000
...
One way is to determine the intervals (an interval is generated if it contains at least one timestamp), augment the time table with the next timestamp and then calculate the averages for each such interval by intersecting the intervals with the time table.
IF OBJECT_ID('tempdb..#values') IS NOT NULL DROP TABLE #values
CREATE TABLE #values (pk int identity, time datetime, value numeric(10,4))
INSERT INTO #values VALUES ('2012-05-03 12:47:00', 5)
INSERT INTO #values VALUES ('2012-05-03 13:00:00', 1.3)
INSERT INTO #values VALUES ('2012-05-03 13:21:00', 7)
INSERT INTO #values VALUES ('2012-05-03 14:56:00', 5)
INSERT INTO #values VALUES ('2012-05-03 14:57:00', 0.3)
DECLARE #timeSpanMinutes int SET #timeSpanMinutes=15
DECLARE #startTime datetime, #endTtime datetime
SELECT #startTime=MIN(time) FROM #values
SELECT #endTtime =DATEADD(MINUTE,(DATEDIFF(MINUTE,#startTime,MAX(time))
/#timeSpanMinutes+1)*#timeSpanMinutes, #startTime) FROM #values -- MAX(time) multiple
SELECT intervals.start
, SUM(value*(DATEDIFF(MINUTE -- minutes in intersection of [start,end] and [time,next]
, CASE WHEN time<start THEN start ELSE time END -- Maximum(time,start)
, CASE WHEN next<DATEADD(MINUTE,#timeSpanMinutes,intervals.start) THEN next
ELSE DATEADD(MINUTE,#timeSpanMinutes,intervals.start) END -- Minimum(next,end)
)*1.0/#timeSpanMinutes)) as average
FROM
(SELECT DISTINCT DATEADD(MINUTE, (DATEDIFF(MINUTE,#startTime,time)
/#timeSpanMinutes)*#timeSpanMinutes, #startTime) AS start
FROM #values -- round start to multiple of #timeSpanMinutes
UNION SELECT DISTINCT DATEADD(MINUTE,#timeSpanMinutes+(DATEDIFF(MINUTE,#startTime,time)
/#timeSpanMinutes)*#timeSpanMinutes, #startTime)
FROM #values -- union distinct with same as above but shifted with #timeSpanMinutes
) intervals -- intervals start time (end is calculated as start + #timeSpanMinutes)
INNER JOIN
(SELECT v.*,ISNULL((SELECT MIN(time) FROM #values WHERE time>v.time),#endTtime) as next
FROM #values v -- add next column to #values
) vals
ON vals.next>=intervals.start and vals.time<=DATEADD(MINUTE,#timeSpanMinutes,start)
WHERE intervals.start<>#endTtime
GROUP BY intervals.start
ORDER BY intervals.start

Insert multiple rows into SQL Server

I have two tables
Product (product_id, productName)
ProductSerialNumber (ProductSerialNumber_id, product_id, serialNumber, status)
I have serial numbers 12345679000 to 123456790100 (quantity: 90) for product : MILK
Is there way to do this without using multiple inserts ie
$Sn = 12345679000;
while ($Sn <= 123456790100 )
{
INSERT INTO ProductSerialNumber VALUES(...,...,$Sn,...)
$Sn++;
}
You can do this:
WITH Temp
AS
(
SELECT n
FROM(VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) AS Temp(n)
), nums
AS
(
SELECT id = t1.n * 10 + t2.n + 1
FROM temp AS T1
CROSS JOIN temp AS t2
)
INSERT INTO ProductSerialNumber(serialnumber)
SELECT 12345679000 + id AS Serialnumber -- You can insert into other columns too
FROM nums;
SQL Fiddle Demo.
Note that: This syntax FROM(VALUES(0), (1), ..., (9)) AS Temp(n) is new to SQL Server-2008, for old versions you can use SELECT ... UNION ALL SELECT ... instead.
However, if possible, you can alter this table and make this column SerialNumber an IDENTITY(12345679000, 1) and it will be auto incremental.
Update 1
For SQL Server 2005, try this:
WITH Temp
AS
(
SELECT 1 AS id
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY t1.id) AS id
FROM temp t1, temp t2, temp t3, temp t4
)
INSERT INTO ProductSerialNumber(serialnumber)
SELECT 12345679000 + id AS Serialnumber
FROM nums
WHERE id <= 100;
Updated SQL Fiddle Demo.
Update 2
*How does this query work? *
Firstly, I define a virtual table with only four values:
SELECT 1 AS id
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
I defined it inside a Common table expression(CTE), to reuse it later.
Then in the following CTE, I used:
FROM temp t1, temp t2, temp t3, temp t4
This will join the table temp four times with it self, therefore it will give you: 44 = 256 rows. Then I used the ranking function ROW_NUMBER() as a work a round to generate a sequence number of number from 1 to 265.
The last thing is the syntax of INSERT INTO ... SELECT ... to select the numbers <= 100 of the numbers that we already generated from the previous step and inserting them to the table.
Hope this makes sense.
here another method to insert multiple rows with procedure
CREATE PROCEDURE autoInsert
#SerialNumberStart bigint,
#SerialNumberEnd bigint,
#status int,
#productID int
AS
while #SerialNumberStart <= #SerialNumberEnd
begin
BEGIN TRAN
INSERT INTO ProductSerialNumber VALUES(#SerialNumberStart)
--print #SerialNumberStart
COMMIT TRAN;
set #SerialNumberStart=#SerialNumberStart+ 1
end