No matter where I place my With statement inside the SQL query, the keyword in the next line always shows an error, 'Incorrect syntax near keyword'. I also tried putting semi-colon.
; WITH Commercial_subset AS
(
SELECT DISTINCT
PRDID_Clean, Value, [Year]
FROM
Reporting_db_SPKPI.DBO.[tbl_RCCP_commercial]
WHERE
MEASURE = 'Unit Growth Rate'
)
--error appears at truncate
TRUNCATE TABLE Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup]
Example 1:
[Example 1][1]
Example 2:
[Example 2][2]
What am I missing?
[1]: https://i.stack.imgur.com/lkfVd.png
[2]: https://i.stack.imgur.com/tZRnG.png
My Final code after getting suggestions in the comments,
--Ensure the correct database is selected for creating the views
USE Reporting_db_SPKPI
--Create the table where new values will be appended
Insert into Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup]
Select *, Replace(productID,'-','') as ProductID_clean from Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR]
GO
--Create a subset as view which will be used for join later
Create or Alter View QRY_Commerical_Subset AS
Select Distinct PRDID_Clean, Value, [Year] From Reporting_db_SPKPI.DBO.[tbl_RCCP_commercial] where MEASURE = 'Unit Growth Rate'
Go
--Create a view with distinct list of all SKUs
CREATE OR ALTER VIEW QRY_RCCP_TEMP AS
SELECT
PRODUCTID, ROW_NUMBER() Over (ORDER BY ProductID) AS ID
FROM (
SELECT
DISTINCT A.ProductID_clean ProductID
FROM
Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup] A
LEFT JOIN
Reporting_db_SPKPI.DBO.QRY_Commerical_Subset B ON A.ProductID_clean = B.PRDID_Clean
WHERE
B.PRDID_Clean IS NOT NULL --and A.filename = 'Capacity Planning_INS_Springhill' --DYNAMIC VARIABLE HERE
and Cast(A.SnapshotDate as date) =
(SELECT Max(Cast(SnapshotDate as date)) FROM reporting_db_spkpi.dbo.tbl_RCCP_3_NR)
) T
GO
SET NOCOUNT ON
-- For every product id from the distinct list iterate the following the code
DECLARE #I INT = 1
WHILE #I <= (SELECT MAX(ID) FROM QRY_RCCP_TEMP)
BEGIN
DECLARE #PRODUCT NVARCHAR(50) = (SELECT PRODUCTID FROM QRY_RCCP_TEMP WHERE ID = #I)
DROP TABLE Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_temp]
--Retrieve last 12 months of value from NR and add it to a temp table in increasing order of their months. These 12 data points will be baseline
SELECT
Top 12 A.*,
Case When B.[Value] is Null then 0 else CAST(B.[Value] as float) End GROWTH
INTO
Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_temp]
FROM
Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup] A
LEFT JOIN
--using the view here
QRY_Commerical_Subset B ON B.PRDID_Clean = A.ProductID_clean AND B.[YEAR] = YEAR(A.[MONTH])+1
WHERE
A.PRODUCTID= #PRODUCT
AND Cast(A.SnapshotDate AS DATE) = (SELECT Max(Cast(SnapshotDate AS DATE)) FROM reporting_db_spkpi.dbo.[tbl_RCCP_3_NR_dup])
Order by
[Month] desc
-- Generate 3 years of data
DECLARE #J INT = 1
WHILE #J<=3
BEGIN
--Calculate next year's value
UPDATE Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_temp]
SET
[Value] = [Value]*(1+ GROWTH),
[MONTH] = DATEADD(YEAR,1,[Month]),
MonthCode= 'F' + CAST(CAST(SUBSTRING(MonthCode,2,LEN(MonthCode)) AS INT) + 12 AS NVARCHAR(10))
--Add it to the NR table.
Insert into Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup]
(ProductID, MonthCode, Value, Month, FileName,
LastModifiedDate, SnapshotDate, Quarter, IsError, ErrorDescription)
Select
ProductID, MonthCode, Value, Month, FileName,
LastModifiedDate, SnapshotDate, Quarter, IsError, ErrorDescription
from
Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_temp]
--Update growth rate for next year
UPDATE Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_temp]
SET GROWTH = Case When B.[Value] is Null then 0 else CAST(B.[Value] as float) End
FROM Reporting_db_SPKPI.DBO.QRY_Commerical_Subset B
WHERE B.PRDID_Clean = ProductID_clean AND [YEAR] = YEAR([MONTH])+1
SET #J=#J+1
END
SET #I=#I+1
END
DROP VIEW QRY_RCCP_TEMP
DROP VIEW QRY_Commerical_Subset
The WITH is a Common Table Expression, aka CTE.
And a CTE is like a template for a sub-query.
For example this join of the same sub-query:
SELECT *
FROM (
select distinct bar
from table1
where foo = 'baz'
) AS foo1
JOIN (
select distinct bar
from table1
where foo = 'baz'
) AS foo2
ON foo1.bar > foo2.bar
Can be written as
WITH CTE_FOO AS (
select distinct bar
from table1
where foo = 'baz'
)
SELECT *
FROM CTE_FOO AS foo1
JOIN CTE_FOO AS foo2
ON foo1.bar > foo2.bar
It's meant to be used with a SELECT.
Not with a TRUNCATE TABLE or DROP TABLE.
(It can be used with an UPDATE though)
As such, treat the TRUNCATE as a seperate statement.
TRUNCATE TABLE Reporting_db_SPKPI.DBO.[tbl_RCCP_3_NR_dup];
WITH Commercial_subset AS
(
SELECT DISTINCT
PRDID_Clean, Value, [Year]
FROM
Reporting_db_SPKPI.DBO.[tbl_RCCP_commercial]
WHERE
MEASURE = 'Unit Growth Rate'
)
SELECT *
FROM Commercial_subset;
Btw, the reason why many write a CTE with a leading ; is because the WITH clause raises an error if the previous statement wasn't ended with a ;. It's just a small trick to avoid that error.
Related
Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4 as well.
This is the code I am handling this problem. The code is working very well.
declare #checkValue int = (select top 1 checkvalue from #temp);
declare #counter int = 0, #sumValue int = 0;
while #sumValue < #checkValue
begin
set #counter = #counter + 1;
set #sumValue = #sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = #counter
) t
)
end
declare #sql nvarchar(255) = 'select top '+cast(#counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql #sql, N'#counter int', #counter = #counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN in your OVER clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE #RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE #CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE #Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (#RoughAverage) #Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF #Sum = #CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET #RoughAverage = #CheckValue - #Sum -- Most likely incomplete like this.
END
For SQL 2008 you can use recursive cte. Top 1 with ties limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)
I need to populate a new column in a table known as RowType, where if the ID column contains the same ID value as the one above RowType is populated with 'D', if the value is new then RowType is populate with 'H', how would the SQL code look to be able to do this?
I.e should look something like below:
RowType (to be populated), ID (already there)
H, 1
D, 1
D, 1
H, 2
D, 2
H, 3
D, 3
D, 3
Thanks
You can use Row_Number and case
select *, RowType = case when Row_Number() over (partition by id order by id) = 1 then 'H' else 'D' End from #yourid
Your input table:
create table #yourId (id int)
insert into #yourid (id) values
(1)
,(1)
,(1)
,(2)
,(2)
,(3)
,(3)
,(3)
Use ROW_NUMER concept :
CREATE TABLE #table(Id INT)
INSERT INTO #table(Id)
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 3 UNION ALL
SELECT 3
SELECT CASE WHEN RowType = 1 THEN 'H' ELSE 'D' END RowType , Id
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY Id ORDER BY id) RowType , Id
FROM #table
) A
Please try...
UPDATE tableName
SET RowType = CASE
WHEN ( ID = LAG( ID ) OVER ( ORDER BY ID ) ) THEN 'D'
ELSE 'H'
END
If you have any questions or comments, then please feel free to post a Comment accordingly.
Further Reading
https://learn.microsoft.com/en-us/sql/t-sql/functions/lag-transact-sql (for information on LAG()).
It may not be the best solution, however it can point you somewhere, and it works.
Go through the code carfuly and make sure you understand this.
create table yourTable (RowType char, id int)
insert into yourTable (RowType, id) values
('',1)
,('',1)
,('',1)
,('',2)
,('',2)
,('',3)
,('',3)
,('',3)
select
row_number() over (order by id) as rowNumber,
RowType,
id
into #tempTable
from yourTable
declare #maxRow int = (select max(rowNumber) from #tempTable)
declare #currentRow int = 1
while (#currentRow <= #maxRow)
begin
if (#currentRow = 1)
begin
update #tempTable
set RowType = 'H'
where rowNumber = #currentRow
end
else
begin
if (select id from #tempTable where rowNumber = #currentRow) = (select id from #tempTable where rowNumber = #currentRow - 1)
begin
update #tempTable
set RowType = 'D'
where rowNumber = #currentRow
end
else
begin
update #tempTable
set RowType = 'H'
where rowNumber = #currentRow
end
end
set #currentRow = #currentRow +1
end
-- update data in actual table, you can do below if only those two columns exist in table !!!
delete from yourTable
-- insert into table from updated temp table
insert into yourTable
select RowType, ID
from #tempTable
select * from yourTable
select * from #tempTable
-- drop temp table
drop table #tempTable
This is my result :
Year matches
2005 1
2008 2
and this is my expected result:
Year matches
2005 1
2006 0
2007 0
2008 2
This is what I have tried:
SELECT DATEPART(yy,A.match_date) AS [Year], COUNT(A.match_id) AS "matches"
FROM match_record A
INNER JOIN match_record B ON A.match_id = B.match_id
WHERE (score) IS NULL OR (score) = 0
GROUP BY DATEPART(yy,A.match_date);
I want to get zero as count in the years where score have some values(not null and zero, anything greater than 0) . Can someone help me?
This might do what you're looking for:
SELECT DATEPART(yy,A.match_date) AS [Year],
SUM(CASE WHEN score=0 or score is null THEN 1 ELSE 0 END) AS "matches"
FROM match_record A
INNER JOIN match_record B ON A.match_id = B.match_id
GROUP BY DATEPART(yy,A.match_date);
Assuming you have any data in the missing years, this should now produce your expected results.
If, instead, you need 0s for years where you have no data, you'll need to provide the list of years separately (say, via a numbers table) and then LEFT JOIN that source to your existing query.
Consider following is your table
SELECT * INTO #TEMP FROM
(
SELECT 2005 [YEARS],1 [MATCHES]
UNION ALL
SELECT 2008,2
)T
Declare two variables to get min and max date in your table
DECLARE #MINYEAR int;
DECLARE #MAXYEAR int;
SELECT #MINYEAR = MIN(YEARS) FROM #TEMP
SELECT #MAXYEAR = MAX(YEARS) FROM #TEMP
Do the following recursion to get years between the period in your table and LEFT JOIN with your table.
; WITH CTE as
(
select #MINYEAR as yr FROM #TEMP
UNION ALL
SELECT YR + 1
FROM CTE
WHERE yr < #MAXYEAR
)
SELECT DISTINCT C.YR,CASE WHEN T.MATCHES IS NULL THEN 0 ELSE T.MATCHES END MATCHES
FROM CTE C
LEFT JOIN #TEMP T ON C.yr=T.YEARS
DECLARE #t table(Year int, matches int)
DECLARE #i int=2005
WHILE #i <=2008
BEGIN
IF NOT exists (SELECT matches FROM tbl WHERE year=#i)
BEGIN
INSERT INTO #t
SELECT #i,'0'
SET #i=#i+1
END
else
BEGIN
INSERT INTO #t
SELECT year,[matches] from tbl
SET #i=#i+1
END
END
SELECT DISTINCT * FROM #t
how about,
SELECT
[year],
COUNT(*) [matches]
FROM (
SELECT
DATEPART(yy, [A].[match_date]) [year]
FROM
[match_record] [A]
LEFT JOIN
[match_record] [B]
ON [A].[match_id] = [B].[match_id]
WHERE
COALESCE([B].[score], 0) = 0) [Nils]
GROUP BY
[Year];
I have a table in sql server database in which records of transactions are stored. Table consists of user id of buyer and user id of seller of product. I have to find the circles in the table for example-
I have to get the records of type- A sells to B, B sells to C, C sells to D AND D sells to A.
Please help.
Use following function:
CREATE FUNCTION dbo.CheckIsCircular(#SellerId INT)
RETURNS BIT
AS BEGIN
DECLARE #IsCircular BIT = 0
DECLARE #Sellers TABLE(Id INT)
DECLARE #TempSellers TABLE(Id INT)
DECLARE #Buyers TABLE(Id INT)
INSERT INTO #TempSellers(Id)VALUES(#SellerId)
WHILE EXISTS(SELECT * FROM #TempSellers)BEGIN
IF EXISTS(SELECT *
FROM #Sellers s
INNER JOIN #TempSellers t ON t.Id = s.Id)BEGIN
SET #IsCircular = 1
BREAK;
END
INSERT INTO #Sellers(Id)
SELECT Id FROM #TempSellers
INSERT INTO #Buyers(Id) SELECT BuyerId FROM YourTable
DELETE #TempSellers
INSERT Into #TempSellers(Id)
SELECT YourTable.SellerId
FROM YourTable
INNER JOIN #Buyers ON [#Buyers].Id = YourTable.SellerId
END
RETURN #IsCircular
END
Your problem is a graph traversal challenge; this is not natively supported in TSQL, but you can simulate it.
This is a skeleton how I do it in Teradata, so syntax must be slightly modified for SQL Server:
WITH RECURSIVE cte (..., Path, isCycle) AS
(
SELECT
...
,',' || CAST(seller AS VARCHAR(1000)) || ',' AS path
,0 AS isCycle
FROM tab
UNION ALL
SELECT
...
,cte.Path || cte.buyer || ',',
,case when cte.Path LIKE '%,' || TRIM(tab.buyer) || ',%' then 1 else 0 end
FROM cte, tab
WHERE cte.buyer = tab.seller
AND cte.isCycle <> 1
)
SELECT ...
,Path || Destination
,isCycle
FROM cte
WHERE isCycle = 1
Build a materialized path of the graph while traversing and check if the next buyer is already in this path.
With a recursive cte
declare #trans table (seller int, buyer int)
insert #trans
values (1,2),(2,3),(3,4),(4,1),(1,5),(2,6),(3,5)
begin try
;with cte as
(
select *, convert(varchar(500),'') as route from #trans
union all
select cte.seller, t1.buyer, convert(varchar(500),route + CONVERT(varchar(5),t1.seller)) from cte
inner join #trans t1 on cte.buyer = t1.seller
)
select * from cte
where seller=buyer
option (maxrecursion 50)
end try
begin catch
print 'loops'
end catch
I have the following table layout. Each line value will always be unique. There will never be more than one instance of the same Id, Name, and Line.
Id Name Line
1 A Z
2 B Y
3 C X
3 C W
4 D W
I would like to query the data so that the Line field becomes a column. If the value exists, a 1 is applied in the field data, otherwise a 0. e.g.
Id Name Z Y X W
1 A 1 0 0 0
2 B 0 1 0 0
3 C 0 0 1 1
4 D 0 0 0 1
The field names W, X, Y, Z are just examples of field values, so I can't apply an operator to explicitly check, for example, 'X', 'Y', or 'Z'. These could change at any time and are not restricted to a finate set of values. The column names in the result-set should reflect the unique field values as columns.
Any idea how I can accomplish this?
It's a standard pivot query.
If 1 represents a boolean indicator - use:
SELECT t.id,
t.name,
MAX(CASE WHEN t.line = 'Z' THEN 1 ELSE 0 END) AS Z,
MAX(CASE WHEN t.line = 'Y' THEN 1 ELSE 0 END) AS Y,
MAX(CASE WHEN t.line = 'X' THEN 1 ELSE 0 END) AS X,
MAX(CASE WHEN t.line = 'W' THEN 1 ELSE 0 END) AS W
FROM TABLE t
GROUP BY t.id, t.name
If 1 represents the number of records with that value for the group, use:
SELECT t.id,
t.name,
SUM(CASE WHEN t.line = 'Z' THEN 1 ELSE 0 END) AS Z,
SUM(CASE WHEN t.line = 'Y' THEN 1 ELSE 0 END) AS Y,
SUM(CASE WHEN t.line = 'X' THEN 1 ELSE 0 END) AS X,
SUM(CASE WHEN t.line = 'W' THEN 1 ELSE 0 END) AS W
FROM TABLE t
GROUP BY t.id, t.name
Edited following update in question
SQL Server does not support dynamic pivoting.
To do this you could either use dynamic SQL to generate a query along the following lines.
SELECT
Id ,Name,
ISNULL(MAX(CASE WHEN Line='Z' THEN 1 END),0) AS Z,
ISNULL(MAX(CASE WHEN Line='Y' THEN 1 END),0) AS Y,
ISNULL(MAX(CASE WHEN Line='X' THEN 1 END),0) AS X,
ISNULL(MAX(CASE WHEN Line='W' THEN 1 END),0) AS W
FROM T
GROUP BY Id ,Name
Or an alternative which I have read about but not actually tried is to leverage the Access Transform function by setting up an Access database with a linked table pointing at the SQL Server table then query the Access database from SQL Server!
Here is the dynamic version
Test table
create table #test(id int,name char(1),line char(1))
insert #test values(1 , 'A','Z')
insert #test values(2 , 'B','Y')
insert #test values(3 , 'C','X')
insert #test values(4 , 'C','W')
insert #test values(5 , 'D','W')
insert #test values(5 , 'D','W')
insert #test values(5 , 'D','P')
Now run this
declare #names nvarchar(4000)
SELECT #names =''
SELECT #names = #names + line +', '
FROM (SELECT distinct line from #test) x
SELECT #names = LEFT(#names,(LEN(#names) -1))
exec('
SELECT *
FROM(
SELECT DISTINCT Id, Name,Line
FROM #test
) AS pivTemp
PIVOT
( COUNT(Line)
FOR Line IN (' + #names +' )
) AS pivTable ')
Now add one row to the table and run the query above again and you will see the B
insert #test values(5 , 'D','B')
Caution: Of course all the problems with dynamic SQL apply, if you can use sp_executeSQL but since parameters are not use like that in the query there really is no point
Assuming you have a finite number of values for Line that you could enumerate:
declare #MyTable table (
Id int,
Name char(1),
Line char(1)
)
insert into #MyTable
(Id, Name, Line)
select 1,'A','Z'
union all
select 2,'B','Y'
union all
select 3,'C','X'
union all
select 3,'C','W'
union all
select 4,'D','W'
SELECT Id, Name, Z, Y, X, W
FROM (SELECT Id, Name, Line
FROM #MyTable) up
PIVOT (count(Line) FOR Line IN (Z, Y, X, W)) AS pvt
ORDER BY Id
As you are using SQL Server, you could possibly use the PIVOT operator intended for this purpose.
If you're doing this for a SQL Server Reporting Services (SSRS) report, or could possibly switch to using one, then stop now and go throw a Matrix control onto your report. Poof! You're done! Happy as a clam with your data pivoted.
Here's a rather exotic approach (using sample data from the old Northwind database). It's adapted from the version here, which no longer worked due to the deprecation of DBCC RENAMECOLUMN and the addition of PIVOT as a keyword.
set nocount on
create table Sales (
AccountCode char(5),
Category varchar(10),
Amount decimal(8,2)
)
--Populate table with sample data
insert into Sales
select customerID, 'Emp'+CAST(EmployeeID as char), sum(Freight)
from Northwind.dbo.orders
group by customerID, EmployeeID
create unique clustered index Sales_AC_C
on Sales(AccountCode,Category)
--Create table to hold data column names and positions
select A.Category,
count(distinct B.Category) AS Position
into #columns
from Sales A join Sales B
on A.Category >= B.Category
group by A.Category
create unique clustered index #columns_P on #columns(Position)
create unique index #columns_C on #columns(Category)
--Generate first column of Pivot table
select distinct AccountCode into Pivoted from Sales
--Find number of data columns to be added to Pivoted table
declare #datacols int
select #datacols = max(Position) from #columns
--Add data columns one by one in the correct order
declare #i int
set #i = 0
while #i < #datacols begin
set #i = #i + 1
--Add next data column to Pivoted table
select P.*, isnull((
select Amount
from Sales S join #columns C
on C.Position = #i
and C.Category = S.Category
where P.AccountCode = S.AccountCode),0) AS X
into PivotedAugmented
from Pivoted P
--Name new data column correctly
declare #c sysname
select #c = Category
from #columns
where Position = #i
exec sp_rename '[dbo].[PivotedAugmented].[X]', #c, 'COLUMN'
--Replace Pivoted table with new table
drop table Pivoted
select * into Pivoted from PivotedAugmented
drop table PivotedAugmented
end
select * from Pivoted
go
drop table Pivoted
drop table #columns
drop table Sales