SQL For Each - write to file - sql

So we have a Production Table with the following data (in simple terms)
ID, Item, QTY
1,AAA,3
2,BBB,4
so 2 production tasks, one for a quantity of 3, and one with a quantity of 4. I require an export file (txt) that would display the following
ID,Item
1,AAA
1,AAA
1,AAA
2,BBB
2,BBB
2,BBB
2,BBB
Basically, I need a file with a line for each of the quantity. This is because I use a 3rd party software that uses each line in the file to create a ticket/label for the task.
any help on the above would be gratefully appreciated.
Thanks,
Dean

Basically, you need a numbers table, so you can do:
select p.id, p.item
from production p join
numbers n
on n.n <= p.qty;
If your table has enough rows, then one ANSI-standard method that will work in many databases is:
select p.id, p.item
from production p join
(select row_number() over (order by p.id) as n
from production
) n
on n.n <= p.qty;
There are other database-specific ways of generating numbers.
Another ANSI compatible method is recursive CTEs:
with cte (id, item) as (
select id, item, qty
from production
union all
select id, item, qty - 1
from production
where qty > 0
)
select id, item
from cte;
(Note: sometimes the recursive keyword is needed.)

Related

SQL Server 2008: duplicate a row n-times, where n is a value in a field

In SQL Server 2018 I have three tables:
T1 (idService, dateStart, dateStop)
T2 (idService, totalCostOfService)
T3 (idService, companyName)
Using joins, I created a view:
V1 (idService, dateStart, dateStop, totalCostOfService, companyName)
And we are fine. I can do my selects on the view and obtain the list of services done.
What I would like to do now is to duplicate every row of the view n times, where n=dateStart-dateStop; every row should have a "new" totalCostOfService = totalCostOfService/n.
I can do that using a temporary table, declaring variables, insert in temp using some while etc. etc. Let's call it "the procedure"
But what I would like to understand is:
is it possibile to do that directly with a select on V1? If not, is it possible to save "the procedure" as a view so that I can have it as a easy select?
Sorry if my question looks somewhat stupid, but I'm totally new with SQL. I tried searching here and on google but I couldn't find what an answer to my questions.
Thank you!
Rather than an rCTE (which is RBAR), you could use a Tally Table:
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)) -1 AS I
FROM N N1
CROSS JOIN N N2 --100
CROSS JOIN N N3 --1000
CROSS JOIN N N4) --10000
SELECT *
FROM YourTable
JOIN Tally T ON T.I <= dateStart-dateStop --Assumes dateStart and DateStop are integer values, even though their name implies otherwise
--If they are dates, then use DATEDIFF(DAY, dateStart, dateEnd)
That tally will generate numbers up to 10000 (which over 27 years worth of days. That should be far more than enough).
I will assume the existence of a numbers table which has the column val for the individual value numbers. If you don't, you will find plenty by searching around.
Add this in the end of the FROM clause of your view:
cross apply (select datediff(day,T1.dateStart,T1.dateStop)+1 as n_days)q1 -- number of days INCLUDING start
cross apply (select dateadd(day,T1.dateStart,n.val) as day_of_charge)q2 from numbers n where n.val between 0 and n_days-1)
Then you will be able to have the following field on your SELECT:
T2.totalCostOfService/n_days as totalCostOfService
I'll add a numbers table solution shortly.
You can use a recursive CTE:
with cte as (
select idService, dateStart, dateStop,
totalCostOfService / (datediff(day, datestop, datestart) + 1) as dailyCostOfService,
companyName
from v1
union all
select idService,
dateadd(day, 1, dateStart),
dateStop,
dailyCostOfService
companyName
from cte
)
select idservice, dateStart as dateOfService,
dailyCostOfService, companyName
from cte;
Note that if there are more than 100 days in any row, then you will need to add OPTION (MAXRECURSION 0).

Modify my SQL Server query -- returns too many rows sometimes

I need to update the following query so that it only returns one child record (remittance) per parent (claim).
Table Remit_To_Activate contains exactly one date/timestamp per claim, which is what I wanted.
But when I join the full Remittance table to it, since some claims have multiple remittances with the same date/timestamps, the outermost query returns more than 1 row per claim for those claim IDs.
SELECT * FROM REMITTANCE
WHERE BILLED_AMOUNT>0 AND ACTIVE=0
AND REMITTANCE_UUID IN (
SELECT REMITTANCE_UUID FROM Claims_Group2 G2
INNER JOIN Remit_To_Activate t ON (
(t.ClaimID = G2.CLAIM_ID) AND
(t.DATE_OF_LATEST_REGULAR_REMIT = G2.CREATE_DATETIME)
)
where ACTIVE=0 and BILLED_AMOUNT>0
)
I believe the problem would be resolved if I included REMITTANCE_UUID as a column in Remit_To_Activate. That's the REAL issue. This is how I created the Remit_To_Activate table (trying to get the most recent remittance for a claim):
SELECT MAX(create_datetime) as DATE_OF_LATEST_REMIT,
MAX(claim_id) AS ClaimID,
INTO Latest_Remit_To_Activate
FROM Claims_Group2
WHERE BILLED_AMOUNT>0
GROUP BY Claim_ID
ORDER BY Claim_ID
Claims_Group2 contains these fields:
REMITTANCE_UUID,
CLAIM_ID,
BILLED_AMOUNT,
CREATE_DATETIME
Here are the 2 rows that are currently giving me the problem--they're both remitts for the SAME CLAIM, with the SAME TIMESTAMP. I only want one of them in the Remits_To_Activate table, so only ONE remittance will be "activated" per Claim:
enter image description here
You can change your query like this:
SELECT
p.*, latest_remit.DATE_OF_LATEST_REMIT
FROM
Remittance AS p inner join
(SELECT MAX(create_datetime) as DATE_OF_LATEST_REMIT,
claim_id,
FROM Claims_Group2
WHERE BILLED_AMOUNT>0
GROUP BY Claim_ID
ORDER BY Claim_ID) as latest_remit
on latest_remit.claim_id = p.claim_id;
This will give you only one row. Untested (so please run and make changes).
Without having more information on the structure of your database -- especially the structure of Claims_Group2 and REMITTANCE, and the relationship between them, it's not really possible to advise you on how to introduce a remittance UUID into DATE_OF_LATEST_REMIT.
Since you are using SQL Server, however, it is possible to use a window function to introduce a synthetic means to choose among remittances having the same timestamp. For example, it looks like you could approach the problem something like this:
select *
from (
select
r.*,
row_number() over (partition by cg2.claim_id order by cg2.create_datetime desc) as rn
from
remittance r
join claims_group2 cg2
on r.remittance_uuid = cg2.remittance_uuid
where
r.active = 0
and r.billed_amount > 0
and cg2.active = 0
and cg2.billed_amount > 0
) t
where t.rn = 1
Note that that that does not depend on your DATE_OF_LATEST_REMIT table at all, it having been subsumed into the inline view. Note also that this will introduce one extra column into your results, though you could avoid that by enumerating the columns of table remittance in the outer select clause.
It also seems odd to be filtering on two sets of active and billed_amount columns, but that appears to follow from what you were doing in your original queries. In that vein, I urge you to check the results carefully, as lifting the filter conditions on cg2 columns up to the level of the join to remittance yields a result that may return rows that the original query did not (but never more than one per claim_id).
A co-worker offered me this elegant demonstration of a solution. I'd never used "over" or "partition" before. Works great! Thank you John and Gaurasvsa for your input.
if OBJECT_ID('tempdb..#t') is not null
drop table #t
select *, ROW_NUMBER() over (partition by CLAIM_ID order by CLAIM_ID) as ROW_NUM
into #t
from
(
select '2018-08-15 13:07:50.933' as CREATE_DATE, 1 as CLAIM_ID, NEWID() as
REMIT_UUID
union select '2018-08-15 13:07:50.933', 1, NEWID()
union select '2017-12-31 10:00:00.000', 2, NEWID()
) x
select *
from #t
order by CLAIM_ID, ROW_NUM
select CREATE_DATE, MAX(CLAIM_ID), MAX(REMIT_UUID)
from #t
where ROW_NUM = 1
group by CREATE_DATE

DB2 getting QDT Array List maximum exceeded using CTE and sql recursion

I am using CTE to create a recursive query to merge multiple column data into one.
I have about 9 working CTE's (I need to merge columns a few times in one row per request, so I have the CTE helpers). When I add the 10th, I get an error. I am running the query on Visual Studio 2010 and here is the error:
And on the As400 system using the, WRKOBJLCK MyUserProfile *USRPRF command, I see:
I can't find any information on this.
I am using DB2 running on an AS400 system, and using: Operating system: i5/OS Version: V5R4M0
I repeat these same 3 CTE's but with different conditions to compare against:
t1A (ROWNUM, PARTNO, LOCNAM, LOCCODE, QTY) AS
(
SELECT rownumber() over(partition by s2.LOCPART), s2.LOCPART, s2.LOCNAM, s2.LOCCODE, s2.LOCQTY
FROM (
SELECT distinct s1.LOCPART, L.LOCNAM, L.LOCCODE, L.LOCQTY
FROM(
SELECT COUNT(LOCPART) AS counts, LOCPART
FROM LOCATIONS
WHERE LOCCODE = 'A'
GROUP BY LOCPART) S1, LOCATIONS L
WHERE S1.COUNTS > 1 AND S1.LOCPART = L.LOCPART AND L.LOCCODE = 'A'
)s2
),
t2A(PARTNO, LIST, QTY, CODE, CNT) AS
(
select PARTNO, LOCNAM, QTY, LOCCODE, 1
from t1A
where ROWNUM = 1
UNION ALL
select t2A.PARTNO, t2A.LIST || ', ' || t1A.LOCNAM, t1A.QTY, t1A.LOCCODE, t2A.CNT + 1
FROM t2A, t1A
where t2A.PARTNO = t1A.PARTNO
AND t2A.CNT + 1 = t1A.ROWNUM
),
t3A(PARTNO, LIST, QTY, CODE, CNT) AS
(
select t2.PARTNO, t2.LIST, q.SQTY, t2.CODE, t2.CNT
from(
select SUM(QTY) as SQTY, PARTNO
FROM t1A
GROUP BY PARTNO
) q, t2A t2
where t2.PARTNO = q.PARTNO
)
Using these, I just call a simple select on one of the CTE's just for testing, and I get the error each time when I have more than 9 CTE's (even if only one is being called).
In the AS400 error (green screen snapshot) what does QDT stand for, and when am I using an Array here?
This was a mess. Error after error. The only way I could get around this was to create views and piece them together.
When creating the view I was only able to get it to work with one CTE not multiple, then what worked fine as one recursive CTE, wouldn't work when trying to define as a view. I had to break apart the sub query into views, and I couldn't create a view out of SELECT rownumber() over(partition by COL1, Col2) that contained a sub query, I had to break it down into two views. If I called SELECT rownumber() over(partition by COL1, Col2) using a view as its subquery and threw that into the CTE it wouldn't work. I had to put the SELECT rownumber() over(partition by COL1, Col2) with its inner view into another view, and then I was able to use it in the CTE, and then create a main view out of all of that.
Also, Each error I got was a system error not SQL.
So in conclusion, I relied heavily on views to fix my issue if anyone ever runs across this same problem.

SQL - Concatenating values when the number of items is not known

I am currently refreshing my knowledge in SQL and encountered some difficulties with the following query.
The requirements were:
For each maker, list in the alphabetical order with "/" as delimiter all the types of products he produces.
Deduce: maker, product types' list
The following solution actually works but I don't exactly understand how..
;with
t1 as
(select maker, type, DENSE_RANK() over(partition by maker order by type) rn
from product
),
tr(maker, type,lev) as
(select distinct t1.maker, cast(t1.type as nvarchar) , 2 from t1 where t1.rn = 1
union all
select t1.maker, cast(tr.type +'/'+t1.type as nvarchar), lev + 1
from t1 join tr on (t1.maker = tr.maker and t1.rn = tr.lev
)
)
select maker, max(type) names from tr group by maker
These output:
1 | A | Laptop/PC/Printer
2 | B | Laptop/PC
3 | C | Laptop
4 | D | Printer
5 | E | PC/Printer
*second column is the maker and third is the dynamically concatenated list of types.
Now, I'm a bit confused on how exactly does the lev grows dynamically.. Is there some kind of loop I'm missing here?
Why does it start with 2?
Why it doesn't work without the "cast"?
I would be very grateful if someone could explain the logic behind this query.
Thanks a lot!
What you're looking at is a recursive CTE,a CTE that calls itself. It's what you think is the "looping" effect. Recursion put a kink in my brain when I first looked at it. It helps to look at some examples and try creating a few simple ones of your own. I'm still not the best at them, but I'm getting better. I added some comments to your code. Hope this helps.
;WITH t1
AS (
SELECT maker,
type,
--Partition says each maker is a group so restart at 1
--Order by type is alphabetic
--DENSE_RANK() means if there are two the of the same item, they get the same number
DENSE_RANK() OVER (PARTITION BY maker ORDER BY type) rn
FROM product
),
--This is a recursive CTE meaning a CTE that calls itself(what is doing the "looping"
tr (maker,type,lev)
AS (
--Grab only the distinct items from your ranked table
SELECT DISTINCT t1.maker,
cast(t1.type AS NVARCHAR),
2 --This is the start of your recursive loop
FROM t1
WHERE t1.rn = 1
UNION ALL
--Recursively loop through you data, adding each string at the end with the '/'
SELECT t1.maker,
cast(tr.type + '/' + t1.type AS NVARCHAR),
--Plus one to grab the next value
lev + 1
FROM t1
INNER JOIN tr ON (
--Only match the same makers
t1.maker = tr.maker
--Match to the next value
AND t1.rn = tr.lev
)
)
--I think you know what this does
SELECT maker,
max(type) names
FROM tr
GROUP BY maker

Logic to identify new line item for current month

I am trying to write a query to spot new line items appearing in my data set. So for example I have the following table structure.
The logic needs to identify if the line item is new since the previous billedmonth
TableA
So if I was to write it in English.
Select IF 'CLI' & 'Description' & 'UnitCost' doesn't exist for BilledMonth -1
I have managed to create a join showing if it exists for the previous billing month.
But I am really struggling with the negative logic (i.e. the line item is new for this month)
Any help greatly appreciated.
SELECT t.CLI, t.Description
FROM yourTable t
LEFT JOIN yourTable t2
ON t.CLI = t2.CLI
AND t.Description = t2.Description
AND t.UnitCost = t2.UnitCost
AND t.BilledMonth - 1 = t2.BilledMonth
WHERE t2.CLI is null
I think sql server supports analytic functions, so something like this should work:
select CLI, Description, UnitCost, billedMonth
from (
select CLI, Description, UnitCost, billedMonth
count(*) over (partition by CLI, Description, UnitCost order by billedMonth) cnt
from mytable
) where cnt = 1
Iff this works it is very likely to be way more efficient and faster than a join based select statement.