Repeating Record Sequence using SQL - sql

This could easily be done using code, but I wondered if it could be done at the database level using SQL Server (2008).
I have a table similar to below:
CROP_ID YEAR_ PRODUCTION
1 1 0
1 2 300
1 3 500
2 1 100
2 2 700
I want to be able to run a query to repeat this for n number of years, per crop type e.g.
CROP_ID YEAR_ PRODUCTION
1 1 0
1 2 300
1 3 500
1 4 0
1 5 300
1 6 500
etc.
I'm not sure of the best approach, I presume I would need a SP and pass in a year variable, and use a loop construct? However the exact syntax escapes me. Any help appreciated.
Update
Sorry for not providing all the information in my original post. The table will allow for multiple crop types, and for Produciton values to be updated so Case statements with fixed variables are not really suitable. Apologies for not being clearer.
Update
With the TVF answer I used the following modified SQL to select by CropType for 20 years.
select top 20 b.CROP_ID,
YEAR_ = n.num * (select count() from MyBaseTable where CROP_ID = 3) + b.YEAR,
b.PRODUCTION from MyBaseTable b, dbo.fnMakeNRows(20) n
where CROP_ID = 3

You can do this in standard SQL without creating a stored procedure or using temp tables. The example below will do this for 12 years. You can extend it out to any number of years:
insert into CropYield
(CropID, Year_, Production)
Select 1, a.a + (10 * b.a),
case (a.a + (10 * b.a)) % 3
when 0 then 500
when 1 then 0
when 2 then 300
end
from (Select 0 as a union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) as a
cross join (Select 0 as a union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) as b
where a.a + (10 * b.a) between 1 and 12

You could use a table-valued-function instead of a stored proc, which gives a little more flexibility for what you do with the result (as it can be selected from directly, inserted into another table, joined to other tables, etc).
You could also make this more generic by having a TVF generate N rows (with a number from 0 to N-1 on each row) and then use some simple expressions to generate the columns you need from this. I have found such a TVF to be useful in a variety of situations.
If you need to generate more complicated data than you can with 0..N-1 and simple expressions, then you should create a TVF dedicated to your specific needs.
The following example shows how a generic TVF could be used to generate the data you ask for:
create function fnMakeNRows (#num as integer)
returns #result table (num integer not null) as
begin
if #num is null or #num = 0
begin
return
end
declare #n as integer
set #n = 0
while #n < #num
begin
insert into #result values (#n)
set #n = #n + 1
end
return
end
go
select
CROP_ID = 1,
YEAR_ = num,
PRODUCTION = case num % 3 when 0 then 0 when 1 then 300 else 500 end
from dbo.fnMakeNRows(100000)
You can also use this to duplicate rows in an existing table (which I think is more like what you want). For example, assuming base_table contains the three rows at the beginning of your question, you can turn the 3 rows into 60 rows using the following:
select
b.CROP_ID,
YEAR_ = n.num * (select count(*) from base_table) + b.YEAR_,
b.PRODUCTION
from base_table b, dbo.fnMakeNRows(20) n
This (hopefully) shows the utility of a generic fnMakeNRows function.

A common trick to produce this kind of data without the need of a stored procedure is with the use of a table of constants. Because such a table can be of generic use, it can be created with say all the integers between 1 and 100 or even 1 and 1,000 depending on usage.
For exmaple
CREATE TABLE tblConstNums
( I INT )
INSERT INTO tblConstNums VALUES (1)
INSERT INTO tblConstNums VALUES (2)
INSERT INTO tblConstNums VALUES (3)
INSERT INTO tblConstNums VALUES (4)
INSERT INTO tblConstNums VALUES (5)
-- ...
INSERT INTO tblConstNums VALUES (1000)
The the solution can be written declaratively (without requiring Stored Procedure or more generally procedural statements:
SELECT CROP_ID, YEAR_ * I, PRODUCTION
FROM myCropTable T
JOIN tblConstNums C on 1=1
WHERE I in (1, 2, 3)
order by CROP_ID, YEAR_ * I, PRODUCTION
Note that the table of constants may include several columns for commonly used cases. For example, and even though many of these can be expressed as mathematical expressions of numbers in a basic 0 to n sequence, one can have a column with only even number, another one with odd numbers, another one with multiples of 5 etc. Also if it small enough, no indexes are needed on a table of constants but these may become useful on a bigger on.

use this one:
WITH tn (n) as
(
SELECT 0
UNION ALL
SELECT n+1
FROM tn
WHERE tn.n < 10
)
SELECT DISTINCT t.CROP_ID, t.YEAR_ + (3*tn.n), t.PRODUCTION
FROM table t, tn
/*
WHERE tn.n < 10 ==> you will get 1 -> (10*3) + 3 = 33
*/

Related

SQL Server - Manipulate data and return new results

Is there any way to do something in SQL Server to manipulate data like manipulating arrays in any other programming language?
I have one SQL query that returns 3 columns, "dt_ref" (date), "vlr_venda" (float) and "qt_parcelas" (int)
Basically, I need to do something like this:
- When field "qt_parcelas" is higher than 1, I need to do a "loop" with this row and generate 3 rows.
So, I need to divide the field "vlr_venda" by field "qt_parcelas", and use the field "dt_ref" as reference for the date start and increment month in the date field for the value of "qt_parcelas"
For example, if my query returns these structure:
| dt_ref | vlr_venda | qt_parcelas |
-------------------------------------
|20180901 | 3000 | 3 |
I need to do something to return this:
| dt_ref | vlr_venda |
----------------------
|20180901 | 1000 |
|20181001 | 1000 |
|20181101 | 1000 |
Is it possible to do it in SQL Server?
I've searched for something like this but haven't found anything useful...
Any ideas?
You can use a recursive CTE: Sql Fiddle
with cte as (
select dt_ref, vlr_venda / qt_parcelas as new_val, qt_parcelas, 1 as num
from t
union all
select dateadd(month, 1, dt_ref), new_val, qt_parcelas, num + 1
from cte
where num < qt_parcelas
)
select dt_ref, new_val
from cte;
As written, this will work for up to 100 months. You need to add OPTION (MAXRECURSION 0) for longer periods.
Instead of using a rCTE you could use a tally table. If you have much larger numbers than 3 this'll probably be far be efficient:
WITH N AS(
SELECT n
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(n)),
Tally AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) I
FROM N N1 --10 rows
CROSS JOIN N N2 --100 rows
--CROSS JOIN N N3 --Keep adding more CROSS JOINs to create more rows
),
VTE AS (
SELECT CONVERT(date,V.dt_ref) AS dt_ref,
V.vlr_venda,
V.qt_parcelas
FROM (VALUES('20180901',3000,3),
('20181001',12000,6)) V(dt_ref,vlr_venda ,qt_parcelas))
SELECT DATEADD(MONTH,T.I,V.dt_ref),
CONVERT(decimal(10,4),V.vlr_venda / (V.qt_parcelas * 1.0)) --incase you need decimal points
FROM VTE V
JOIN Tally T ON V.qt_parcelas >= T.I;
I developed a software to generate tickets and i had a similar experience than you. I have tried CURSORS and recursive CTE and they all took something like 50 minutes when creating tickets for clients
I used this function to replicate my clients and generate my tickets
/****** Object: UserDefinedFunction [dbo].[NumbersTable] Script Date: 28/09/2018 10:51:25 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[NumbersTable] (
#fromNumber int,
#toNumber int,
#byStep int
)
RETURNS #NumbersTable TABLE (i int)
AS
BEGIN
WITH CTE_NumbersTable AS (
SELECT #fromNumber AS i
UNION ALL
SELECT i + #byStep
FROM CTE_NumbersTable
WHERE
(i + #byStep) <= #toNumber
)
INSERT INTO #NumbersTable
SELECT i FROM CTE_NumbersTable OPTION (MAXRECURSION 0)
RETURN;
END
GO
Then you can use a
CROSS APPLY dbo.NumbersTable(1,qt_parcelas ,1);
To generate your rows
Believe me this way is more efficient and when dealing with large amount of data (something like 8 to 10 million rows) it takes something like 2 minutes instead of 40

SQL: create sequential list of numbers from various starting points

I'm stuck on this SQL problem.
I have a column that is a list of starting points (prevdoc), and anther column that lists how many sequential numbers I need after the starting point (exdiff).
For example, here are the first several rows:
prevdoc | exdiff
----------------
1 | 3
21 | 2
126 | 2
So I need an output to look something like:
2
3
4
22
23
127
128
I'm lost as to where even to start. Can anyone advise me on the SQL code for this solution?
Thanks!
;with a as
(
select prevdoc + 1 col, exdiff
from <table> where exdiff > 0
union all
select col + 1, exdiff - 1
from a
where exdiff > 1
)
select col
If your exdiff is going to be a small number, you can make up a virtual table of numbers using SELECT..UNION ALL as shown here and join to it:
select prevdoc+number
from doc
join (select 1 number union all
select 2 union all
select 3 union all
select 4 union all
select 5) x on x.number <= doc.exdiff
order by 1;
I have provided for 5 but you can expand as required. You haven't specified your DBMS, but in each one there will be a source of sequential numbers, for example in SQL Server, you could use:
select prevdoc+number
from doc
join master..spt_values v on
v.number <= doc.exdiff and
v.number >= 1 and
v.type = 'p'
order by 1;
The master..spt_values table contains numbers between 0-2047 (when filtered by type='p').
If the numbers are not too large, then you can use the following trick in most databases:
select t.exdiff + seqnum
from t join
(select row_number() over (order by column_name) as seqnum
from INFORMATION_SCHEMA.columns
) nums
on t.exdiff <= seqnum
The use of INFORMATION_SCHEMA columns in the subquery is arbitrary. The only purpose is to generate a sequence of numbers at least as long as the maximum exdiff number.
This approach will work in any database that supports the ranking functions. Most databases have a database-specific way of generating a sequence (such as recursie CTEs in SQL Server and CONNECT BY in Oracle).

SQL server count and Sum Query?

I have a Question table with the fields (QuestionID,QuestionMarks), and with the data fields look like -
QuestionID QuestionMarks
1 1
2 4
5 1
9 1
12 2
which means at the moment Question table has 5 Question of Total 9 marks, Now my problem is that i want to know that a combination of 4 Questions with 8 marks is possible and fetch out that combination ( in general a combination of "x" questions of "y" marks is possible) ?
I was thinking to use CTE, but was afraid that it will take lot of time to execute the query if i have tens of thousand question.
please suggest some idea how to get the data. I am using SQL Server version 2008
This is a start. It's going to have poor performance:
declare #Qs table (QuestionID int not null, QuestionMarks int not null)
insert into #Qs (QuestionID,QuestionMarks) values
(1,1), (2,4), (5,1), (9,1), (12,2)
declare #TargetMarks int = 8
declare #TargetCount int = 4
;with Build as (
select QuestionID as MinID,QuestionID as MaxID,QuestionMarks as Total,1 as Cnt
,'/' + CONVERT(varchar(max),QuestionID) + '/' as QPath
from #Qs
union all
select MinID,q.QuestionID,Total+q.QuestionMarks,Cnt+1,QPath + CONVERT(varchar(max),q.QuestionID) + '/'
from
Build b
inner join
#Qs q
on
b.MaxID < q.QuestionID and
b.Total + q.QuestionMarks <= #TargetMarks and
b.Cnt < #TargetCount
)
select * from Build where Cnt = #TargetCount and Total = #TargetMarks
Result set:
MinID MaxID Total Cnt QPath
--------------------------------------------------------------------------------
2 12 8 4 /2/5/9/12/
1 12 8 4 /1/2/9/12/
1 12 8 4 /1/2/5/12/
The tricky part is that the QPath value isn't exactly the greatest way of storing ID values.
I think you're right in that tens of thousands of questions can slow down execution, so I'd start by limiting the potential rows being queried. You already know for certain that even with millions of rows, you never need more than four with identical QuestionMarks and you can reduce this further, e.g. (sorry for not knowing whether SQL Server accepts this syntax)
WITH LimitPotentialRows AS
(SELECT m1.QuestionID, m1.QuestionMarks,
(SELECT SUM(m2.QuestionMarks)
FROM MyTable m2
WHERE m1.QuestionMarks = m2.QuestionMarks
AND m1.PrimaryKeyID <= m2.PrimaryKeyID) CurrentMarks,
(SELECT COUNT(*)
FROM MyTable m3
WHERE m1.QuestionMarks = m3.QuestionMarks
AND m1.PrimaryKeyID <= m3.PrimaryKeyID) TotalQuestions
FROM MyTable m1
WHERE m1.QuestionMarks <= :DesiredTotalQuestionMarks - :TotalNoOfQuestions + 1
HAVING CurrentMarks <= :DesiredTotalQuestionMarks
AND TotalQuestions <= :TotalNoOfQuestions)
Desiring 4 questions with a total of 8 marks, the result of this CTE will leave you with only
QuestionMarks NumberOfQuestions
1 4
2 4
3 2
4 1
5 1
Having limited the number of rows from tens of thousands to maximum 12, you're unlikely to have performance problems in your further calculations.

SQL return multiple rows from one record

This is the opposite of reducing repeating records.
SQL query to create physical inventory checklists
If widget-xyz has a qty of 1 item return 1 row, but if it has 5, return 5 rows etc.
For all widgets in a particular warehouse.
Previously this was handled with a macro working through a range in excel, checking the qty column. Is there a way to make a single query instead?
The tables are FoxPro dbf files generated by an application and I am outputting this into html
Instead of generating an xml string and using xml parsing functions to generate a counter as Nestor has suggested, you might consider joining on a recursive CTE as a counter, as LukLed has hinted to:
WITH Counter AS
(
SELECT 0 i
UNION ALL
SELECT i + 1
FROM Counter
WHERE i < 100
),
Data AS
(
SELECT 'A' sku, 1 qty
UNION
SELECT 'B', 2
UNION
SELECT 'C', 3
)
SELECT *
FROM Data
INNER JOIN Counter ON i < qty
According to query analyzer, this query is much faster than the xml pseudo-table. This approach also gives you a recordset with a natural key (sku, i).
There is a default recursion limit of 100 in MSSQL that will restrict your counter. If you have quantities > 100, you can either increase this limit, use nested counters, or create a physical table for counting.
For SQL 2005/2008, take a look at
CROSS APPLY
What I would do is CROSS APPLY each row with a sub table with as many rows as qty has. A secondary question is how to create that sub table (I'd suggest to create an xml string and then parse it with the xml operators)
I hope this gives you a starting pointer....
Starting with
declare #table table (sku int, qty int);
insert into #table values (1, 5), (2,4), (3,2);
select * from #table;
sku qty
----------- -----------
1 5
2 4
3 2
You can generate:
with MainT as (
select *, convert(xml,'<table>'+REPLICATE('<r></r>',qty)+'</table>') as pseudo_table
from #table
)
select p.sku, p.qty
from MainT p
CROSS APPLY
(
select p.sku from p.pseudo_table.nodes('/table/r') T(row)
) crossT
sku qty
----------- -----------
1 5
1 5
1 5
1 5
1 5
2 4
2 4
2 4
2 4
3 2
3 2
Is that what you want?
Seriously dude... next time put more effort writing your question. It's impossible to know exactly what you are looking for.
You can use table with number from 1 to max(quantity) and join your table by quantity <= number. You can do it in many ways, but it depends on sql engine.
You can do this using dynamic sql.

Generating Random Number In Each Row In Oracle Query

I want to select all rows of a table followed by a random number between 1 to 9:
select t.*, (select dbms_random.value(1,9) num from dual) as RandomNumber
from myTable t
But the random number is the same from row to row, only different from each run of the query. How do I make the number different from row to row in the same execution?
Something like?
select t.*, round(dbms_random.value() * 8) + 1 from foo t;
Edit:
David has pointed out this gives uneven distribution for 1 and 9.
As he points out, the following gives a better distribution:
select t.*, floor(dbms_random.value(1, 10)) from foo t;
At first I thought that this would work:
select DBMS_Random.Value(1,9) output
from ...
However, this does not generate an even distribution of output values:
select output,
count(*)
from (
select round(dbms_random.value(1,9)) output
from dual
connect by level <= 1000000)
group by output
order by 1
1 62423
2 125302
3 125038
4 125207
5 124892
6 124235
7 124832
8 125514
9 62557
The reasons are pretty obvious I think.
I'd suggest using something like:
floor(dbms_random.value(1,10))
Hence:
select output,
count(*)
from (
select floor(dbms_random.value(1,10)) output
from dual
connect by level <= 1000000)
group by output
order by 1
1 111038
2 110912
3 111155
4 111125
5 111084
6 111328
7 110873
8 111532
9 110953
you don’t need a select … from dual, just write:
SELECT t.*, dbms_random.value(1,9) RandomNumber
FROM myTable t
If you just use round then the two end numbers (1 and 9) will occur less frequently, to get an even distribution of integers between 1 and 9 then:
SELECT MOD(Round(DBMS_RANDOM.Value(1, 99)), 9) + 1 FROM DUAL