SQL Server : connect by level for DATE type with union - sql

I've found basic answer for replacing the Oracle's "CONNECT BY LEVEL" in this question but my case is little bit more complicated:
Basically things that I want to replace looks like this:
...
UNION ALL
Select
adate, 'ROAD' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum
from dual
connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
Select
adate, 'PORTPACK' as TSERV_ID, 0 AS EQ_NBR
from
(SELECT
to_date(sysdate - 732,'dd/mm/yy') + rownum -1 as adate, rownum
FROM
(select rownum from dual connect by level <= 732)
WHERE rownum <= 732)
UNION ALL
....
Now, the single dual connect is easy, even if this is apparently not very efficient method
WITH CTE AS (
SELECT dateadd(day,-720,CONVERT (date, GETDATE())) as Datelist
UNION ALL
SELECT dateadd(day,1,Datelist)
FROM CTE
WHERE datelist < getdate() )
SELECT *,'ROAD' as Tserv_ID , 0 as EQ_NBR FROM CTE
option (maxrecursion 0)
repeating the union is hard because I get an error:
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
There are more parts of this union that I've provided here; I've tried to use the "WITH" only at start but no luck. Am I missing something obvious here?
EDIT: There is of course big question WHY I am even trying to do such thing: Personally, I wouldn't, but at the other end of the query there is a huge Crystal Report that runs once every month and which accepts data in this particular format. End of the FULL query's output is something like
3 columns of data
3 Columns of data
...
Currentdate-732,"ROAD",0
Currentdate -731,"ROAD",0
...
Currentdate, "ROAD,"0"
Currentdate -732, "PORTPAK", 0
Currentdate -731, "PORTPAK", 0
etc.

Are you trying to do:
WITH CTE1 AS (...),
CTE2 AS (...)
SELECT stuff FROM CTE1
UNION ALL
SELECT stuff FROM CTE2;
? This is a common challenge, I guess it is not very discoverable that in order to use more than one CTE, you just separate them with a comma.
That all said, it seems like you are just trying to generate a series of dates. A recursive CTE (never mind a series of many of them) is not the most efficient way to do this. Instead of telling us you want to replace CONNECT BY LEVEL and showing us the syntax you've tried, why don't you just show or describe the output you want? We've already got an appreciation that you've tried something on your own (thanks!) but we'd rather give you an efficient solution than bridging the gap to an inefficient one.
As an example, here is something that requires a lot less redundant code, and ( think gives you what you're after:
DECLARE #n INT = 722, #d DATE = CURRENT_TIMESTAMP;
;WITH v AS (SELECT v FROM (VALUES('ROAD'),('PORTPACK')) AS v(v)),
n AS (SELECT TOP (#n) n = ROW_NUMBER() OVER (ORDER BY number)
FROM master.dbo.spt_values ORDER BY n)
SELECT Datelist = DATEADD(DAY, 2-n.n, #d), Tserv_ID = v.v, EQ_NBR = 0
FROM n CROSS JOIN v
ORDER BY Tserv_ID, Datelist;

Related

Create View with option (maxrecursion)?

I get a "Incorrect syntax near keyword 'OPTION'" error when attempting to save a view in SQL Server 2012. I'm trying to add a MAXRECURSION option to the end of my common table expression. All the examples I've seen and working CTEs I've created before don't mind the "OPTION (MAXRECURSION 0)" at the end of the CTE.
Anyone see why I'm getting this error? The CTE works without the OPTION clause, although it reaches the maximum number of recursions (100).
WITH CTE1 AS (select
t.[ID_DIM_PRODUIT]
,t.[ID_FACTURE]
,t.[date_facture]
,t.[quantite]
,t.[quantite_enstock]
from [dbo].[Fact_Stock] t
cross apply (select sum([quantite_enstock]) as stockTotal
from [dbo].[Fact_Stock]
where [ID_DIM_PRODUIT] = t.[ID_DIM_PRODUIT]
and [date_facture] <= t.[date_facture]
) as st),
DateList (ID_FACTURE,ID_DIM_PRODUIT,stockTotal,date_facture,stockDateEnd )
AS (
SELECT ID_FACTURE,ID_DIM_PRODUIT,stockTotal,date_facture
, LEAD(date_facture, 1) OVER ( PARTITION BY ID_DIM_PRODUIT ORDER BY date_facture ) AS stockDateEnd
FROM CTE1
UNION ALL
SELECT ID_FACTURE,ID_DIM_PRODUIT,mvtStock,stockTotal,DATEADD(DAY,1,date_facture),stockDateEnd FROM DateList
WHERE DATEADD(DAY,1,date_facture) < stockDateEnd
)
SELECT d.id_facture,d.ID_DIM_PRODUIT,d.stockTotal ,d.date_facture as date_stock ,t.[ID_FACT_STOCK]
,t.[id_dim_fournisseur]
,t.[id_dim_depot]
,t.[poidnetGlobal]
,t.[poidsBrutGloba]
,t.[quantite]
,t.[montant_HT]
,t.[montant_TTC]
,t.[cout_moyen_unitaire]
,t.[mvtStock]
,t.[prix_vente_unitaire]
,t.[prix_achat]
FROM DateList d , CTE1 t where
d.id_facture=t.ID_FACTURE and d.ID_DIM_PRODUIT=t.ID_DIM_PRODUIT ORDER BY ID_DIM_PRODUIT,date_stock
option (maxrecursion 0)

MS SQL does not return the expected top row when ordering by DIFFERENCE()

I have noticed strange behaviour in some SQL code used for address matching at the company I work for & have created some test SQL to illustrate the issue.
; WITH Temp (Id, Diff) AS (
SELECT 9218, 0
UNION
SELECT 9219, 0
UNION
SELECT 9220, 0
)
SELECT TOP 1 * FROM Temp ORDER BY Diff DESC
Returns 9218 but
; WITH Temp (Id, Name) AS (
SELECT 9218, 'Sonnedal'
UNION
SELECT 9219, 'Lammermoor'
UNION
SELECT 9220, 'Honeydew'
)
SELECT TOP 1 *, DIFFERENCE(Name, '') FROM Temp ORDER BY DIFFERENCE(Name, '') DESC
returns 9219 even though the Difference() is 0 for all records as you can see here:
; WITH Temp (Id, Name) AS (
SELECT 9218, 'Sonnedal'
UNION
SELECT 9219, 'Lammermoor'
UNION
SELECT 9220, 'Honeydew'
)
SELECT *, DIFFERENCE(Name, '') FROM Temp ORDER BY DIFFERENCE(Name, '') DESC
which returns
9218 Sonnedal 0
9219 Lammermoor 0
9220 Honeydew 0
Does anyone know why this happens? I am writing C# to replace existing SQL & need to return the same results so I can test that my code produces the same results. But I can't see why the actual SQL used returns 9219 rather than 9218 & it doesn't seem to make sense. It seems it's down to the Difference() function but it returns 0 for all the record in question.
When you call:
SELECT TOP 1 *, DIFFERENCE(Name, '')
FROM Temp l
ORDER BY DIFFERENCE(Name, '') DESC
All three records have a DIFFERENCE value of zero, and hence SQL Server is free to choose from any of the three records for ordering. That is to say, there is no guarantee which order you will get. The same is true for your second query. Actually, it is possible that the ordering for the same query could even change over time. In practice, if you expect a certain ordering, you should provide exact logic for it, e.g.
SELECT TOP 1 *
FROM Temp
ORDER BY Id;

Can select using dblink, cannot insert using dblink

I have inherited a code which helps me to fetch data from external systems. It is a bit screwed up, but I have to use it as is.
SELECT NVL (
(
SELECT TRIM (alias.SERV_CD)
FROM schema.CX_SER#db_link alias
WHERE row_id =
(
SELECT mix.par_row_id
FROM schema.CX_SER_MI_XM#db_link mix
WHERE mix.bill = SA.BILL_AC
AND ROWNUM < 2
)
AND ROWNUM < 2
),
(
SELECT TRIM (ba.SERV_CD)
FROM schema.s_some_table#db_link ba
WHERE ba.row_id = sa.BILL_AC AND ROWNUM < 2
)
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa, schema.action_table#db_link ia, schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_AC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1);
The said code returns results when I run the select as is.
But when I try to insert these records in my schema(using simple insert into(columns) <this select statement>, I get the following error messages :
Error: ORA-00904:"SA"."BILL_ACC" :invalid identifier.
ORA - 02063 : preceding line from db_link.
A select statement like :
with c as(
SELECT NVL (
(
SELECT TRIM (alias.SERV_CD)
FROM schema.CX_SER#db_link alias
WHERE row_id =
(
SELECT mix.par_row_id
FROM schema.CX_SER_MI_XM#db_link mix
WHERE mix.bill = SA.BILL_AC
AND ROWNUM < 2
)
AND ROWNUM < 2
),
(
SELECT TRIM (ba.SERV_CD)
FROM schema.s_some_table#db_link ba
WHERE ba.row_id = sa.BILL_AC AND ROWNUM < 2
)
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa, schema.action_table#db_link ia, schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_AC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1))
select * from c where REQUIRED_CODE IS NOT NULL;
also fail with same error. However, I am able to obtain results when I query using some other column in above with clause statement gives results, for e.g where order_count>2 gives result. So the problem is in the REQUIRED_CODE section, and maybe, in the group by.
Please guide on the course of action. I need to insert the records flowing into my schema.
NOTE : All columns are either varchar2 or date
If my attempt to replicate the issue has ended up close to yours, you can avoid the error with the CTE by adding a driving_site hint:
with c as (
SELECT /*+ DRIVING_SITE (sa) */ NVL (
...
That prevents the query being written and distributed in a way that confuses the optimiser; I think it's tripping over the nested reference to SA in the subquery and it's ending up too many levels down to be recognised.
That hint doesn't have any effect on the insert though.
As mentioned in comments I've had a quick go at rewriting the query to avoid the subqueries. It's a bit rough and I'm not sure I understand everything you're currently doing, partly because of the table name changes etc. But you wanted to see it, and it might give you something to work from...
INSERT INTO t42
SELECT NVL (TRIM(MIN(t.SERV_CD) KEEP (DENSE_RANK FIRST ORDER BY NULL)),
TRIM(MIN(ba.SERV_CD) KEEP (DENSE_RANK FIRST ORDER BY NULL))
) REQUIRED_CODE, --NVL ends here
COUNT (*) order_count,
TRUNC (ia.CREATED_DATE) CREATED_DATE_date,
TRUNC (ia.CREATED_DATE + 1) inserted_date
FROM schema.s_some_table#db_link sa
LEFT JOIN (
SELECT mix.bill, alias.SERV_CD
FROM schema.CX_SER_MI_XM#db_link mix
JOIN schema.CX_SER#db_link alias
ON alias.row_id = mix.par_row_id
) t
ON t.bill = SA.BILL_ACC
LEFT JOIN schema.s_some_table#db_link ba
ON ba.row_id = sa.BILL_ACC
CROSS JOIN schema.action_table#db_link ia
CROSS JOIN schema.s_order#db_link ord
WHERE ia.CREATED_DATE >= TRUNC (SYSDATE - 1)
AND ia.CREATED_DATE < TRUNC (SYSDATE)
AND ord.status = 'Done'
GROUP BY SA.BILL_ACC, TRUNC (ia.CREATED_DATE), TRUNC (IA.CREATED_DATE + 1);
If you can't get it working then you could use your original query in a PL/SQL block, either as a cursor and doing row-by-row inserts, or preferably (particularly if it will return a lot of data) using a collection with bulk collect and a forall insert.
If you search My Oracle Support for ORA-02063 and ORA-00904 you'll see quite a few bugs, some of which seem to apply to 11g but ought to have been fixed by my version 11.2.0.4; I can't see anything that quite matches from a quick browse but it's possible you are hitting one of those, or indeed one that hasn't been reported. It may be worth raising an SR to investigate your specific scenario.

How to optimizing SQL Query with Cross Join

How can I make this SQL query more efficient? The CteFinal code shown below is a portion of my query which add up to 6 minutes to my query. The cteMonth is shown below. The cteDetail is another cte which pulls information directly from the database, and it takes less than a second to run.
What CteFinal is doing is creating missing fiscal period rows while including some of the column data from the row where f.FiscalPeriod=0.
I cannot add, delete, or change any of the indexes on the tables, as this is a ERP database and I'm not allowed to make those type of changes.
CteFinal:
SELECT Account,Month, CONVERT(DATETIME, CAST(#Year as varchar(4)) + '-' + CAST(Month as VARCHAR(2)) + '-' + '01', 102) JEDate
,accountdesc,'' Description,'' JournalCode,NULL JournalNum,NULL JournalLine
,'' LegalNumber,'' CurrencyCode,0.00 DebitAmount,0.00 CreditAmount,fiscalcalendarid,company,bookid,SegValue2,SegValue1,SegValue3,SegValue4
FROM cteDetail f
CROSS JOIN cteMonths m
WHERE f.FiscalPeriod=0 and not exists(select * from cteDetailADDCreatedZero x where x.Account=f.Account and x.FiscalPeriod=Month)
CteMonth:
cteMonths (Month) AS(
select 0 as Month
UNION select 1 as Month
UNION select 2 as Month
UNION select 3 as Month
UNION select 4 as Month
UNION select 5 as Month
UNION select 6 as Month
UNION select 7 as Month
UNION select 8 as Month
UNION select 9 as Month
UNION select 10 as Month
UNION select 11 as Month
UNION select 12 as Month)
Thank you!
Here's a slightly more efficient way to generate the 12 months of a given year (even more efficient if you have your own Numbers table):
DECLARE #year INT = 2013;
;WITH cteMonths([Month],AsDate) AS
(
SELECT n-1,DATEADD(YEAR, #Year-1900, DATEADD(MONTH,n-1,0)) FROM (
SELECT TOP (13) RANK() OVER (ORDER BY [object_id]) FROM sys.all_objects
) AS c(n)
)
SELECT [Month], AsDate FROM cteMonths;
So now, you can say:
;WITH cteMonths([Month],AsDate) AS
(
SELECT n,DATEADD(YEAR, #Year-1900, DATEADD(MONTH,n-1,0)) FROM (
SELECT TOP (13) RANK() OVER (ORDER BY [object_id]) FROM sys.all_objects
) AS c(n)
),
cteDetail AS
(
...no idea what is here...
),
cteDetailADDCreatedZero AS
(
...no idea what is here...
)
SELECT f.Account, m.[Month], JEDate = m.AsDate, f.accountdesc, Description = '',
JournalCode = '', JournalNum = NULL, JournalLine = NULL, LegalNumber = '',
CurrencyCode = '', DebitAmount = 0.00, CreditAmount = 0.00, f.fiscalcalendarid,
f.company, f.bookid, f.SegValue2, f.SegValue1, f.SegValue3, f.SegValue4
FROM cteMonths AS m
LEFT OUTER JOIN cteDetail AS f
ON ... some clause I am not clear on ...
WHERE f.FiscalPeriod = 0
AND NOT EXISTS
(
SELECT 1 FROM cteDetailADDCreatedZero AS x
WHERE x.Account = f.Account
AND x.FiscalPeriod = m.[Month]
);
I suspect this won't solve your problem though: it is likely that this is forcing an entire table scan on either whatever tables are mentioned in cteDetail or cteDetailADDCreatedZero or both. You should inspect the actual execution plan for this query and see if there are any scans or other expensive operations that could guide you towards better indexing. It also might just be that you have a bunch of inefficient CTEs stacked up together - we can't really help with that unless you show everything. CTEs are like views - if you start stacking them up on top of each other, you really limit the optimizer's ability to generate an efficient plan for you. At some point it will just throw its hands in the air...
One possibility is to physicalize the SQL View (if it the query is a view). Sometimes views with complex queries are slow.

Accessing aliased fields in T-SQL

In a query I create lots of fields using the CASE expression.
I need to reference these fields later on in the query but it seems I can't access the field using its alias - I have to repeat the CASE expression every time I want to reference its value.
Is there a simple way to access these fields?
You can use CTEs (assuming SQL Server 2005+), like this very basic example:
DECLARE #Val INT
SET #Val = 1
;WITH CTEExample AS
(
SELECT CASE #Val WHEN 1 THEN 'A' ELSE 'B' END AS MyCaseField1
)
SELECT * FROM CTEExample WHERE MyCaseField1 = 'A'
why not simply make a subquery
eg
SELECT foo.column
FROM (
SELECT
CASE WHEN yourcase THEN 'a'
ELSE 'b'
END AS 'column'
FROM yourtable) AS foo
but this can be done with CTEs either (look at this answer)
You can also use CROSS APPLY for this as in this tip by Itzik Ben Gan.
SELECT SalesOrderID, OrderDate, Week_Day
FROM Sales.SalesOrderHeader
CROSS APPLY
(SELECT DATEPART(weekday, DATEADD(day, ##DATEFIRST - 7, OrderDate)) AS Week_Day) AS A
WHERE Week_Day NOT IN (1, 7);
You should be aware that reusing aliases in a where clause will render the predicate unsargable however so should be used with caution (this applies regardless of whether you use APPLY or a CTE)