Get unique value from lines else take default value in SQL - sql

I have a list of sales lines each having a currency field.
A sale line is represented by the table SALE_LINE which has a field, Currency, to store the currency of the sales line.
I want to do an SQL request which will take a list of sales line and if the currency of these sales lines is unique to them all,
then take that currency or else set the value to a default one like the dollar.
As in if I have 3 lines with currencies: EUR, GBP, ZAR, the request would return me the DOL.
if I have 3 lines with currencies: EUR, EUR, EUR, the request would return me the EUR.
My SQL request is as below:
DECLARE
#salesLineIds VARCHAR(MAX),
#defaultCurrency VARCHAR(10),
#uniqueCurrency VARCHAR(10)
SET #salesLineIds = '1,2,3';
SET defaultCurrency = 'DOL';
DECLARE
#ID_LIST table (Id BIGINT)
INSERT INTO #ID_LIST SELECT TRY_CAST(value AS BIGINT)
FROM STRING_SPLIT(#salesLineIds, ',')
SELECT #uniqueCurrency =(CASE WHEN 1 = (SELECT COUNT(DISTINCT SL.Currency))
THEN
(SELECT DISTINCT SL_CUR.Currency
from SALE_LINE SL_CUR
where SL_CUR.Id in (SELECT * FROM #ID_LIST)
)
ELSE
#defaultCurrency
END)
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST)
The above works but it might not be good performance. Is there any way where I can improve the performance of the above query?
The below query does not give the good currency:
SELECT #uniqueCurrency =(CASE WHEN 1 = (SELECT COUNT(DISTINCT SL.Currency))
THEN
(SELECT DISTINCT SL.Currency
)
ELSE
#defaultCurrency
END)
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST)
group by SL.Currency
Any help of how I can do better in the working SQL will be appreciated. Thanks

You need to group over the whole set, then simply check for distinct Currency values. If there is only one then you can just use any aggregation, such as MIN, to get it.
DECLARE
#salesLineIds VARCHAR(MAX),
#defaultCurrency VARCHAR(10),
#uniqueCurrency VARCHAR(10);
SET #salesLineIds = '1,2,3';
SET defaultCurrency = 'DOL';
DECLARE
#ID_LIST table (Id BIGINT);
INSERT INTO #ID_LIST SELECT TRY_CAST(value AS BIGINT)
FROM STRING_SPLIT(#salesLineIds, ',');
SELECT #uniqueCurrency =
CASE WHEN COUNT(DISTINCT SL.Currency) = 1
THEN MIN(SL.Currency)
ELSE #defaultCurrency
END
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST);
I suggest CHAR(3) as a suitable data type for currency

Assuming no nulls.
SELECT
CASE WHEN min(SL.Currency) = max(SL.Currency)
THEN min(SL.Currency)
ELSE #defaultCurrency
END
FROM SALE_LINE SL
WHERE SL.Id IN (SELECT * FROM #ID_LIST);

Related

Incorrect Syntax near With

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.

Find the specific number of specific value in SQL

I have to show the specific value in a table that I have to get that from user, for example user write 2 and 'toilet paper' than I have to show the second date of toilet paper in my table:
I wrote this but I know I doesn't work:
CREATE PROC BuyCount(#NInput INT,
#TitleInput nvarchar(50))
AS
BEGIN
DECLARE #RecordItemCount INT
SET #RecordItemCount = (SELECT COUNT(FID) FROM Buy_tbl WHERE Bname = #TitleInput)
IF (#Input <= #RecordItemCount)
BEGIN
SELECT BuyDate , #Input
FROM Buy_tbl
WHERE Bname = #TitleInput AND ...
END
ELSE
BEGIN
PRINT 'Out of range'
SELECT BuyDate , #Input
FROM Buy_tbl
WHERE Bname = #TitleInput
END
END
PS: Also I should mention, If the number was out of range it returns the last value of the buy items
Following query should work in both the scenarios
;WITH cte
AS (
SELECT BuyDate
,row_number() OVER (ORDER BY BuyDate) rn
,count(*) OVER () ct
FROM Buy_tbl
WHERE Bname = #TitleInput
)
SELECT TOP 1 BuyDate
,#Input
FROM cte
WHERE rn = #Input
OR rn = ct
ORDER BY BuyDate
Example Demo

How to write if exist statement that would run a different select depending if it exists or not

I am trying to convert a sql if exists statement into a SSRS valid format to run a report on CRM.
CRM report doesn't accept the report on upload if I have a if exists method, I'm having troubles figuring out what I can use in its place.
IF EXISTS(select * from dbo.FC where dbo.FC.ContactID in (select dbo.AV.so_contactid from dbo.AV))
begin
select [STATEMENT 1]
from dbo.AV CRMAF_so_AV join
dbo.FC c
on CRMAF_so_AV.so_contactid = c.ContactID;
end
else
begin
select [STATEMENT 2]
from dbo.AV CRMAF_so_AV join
dbo.FA c
on CRMAF_so_AV.so_contactid = c.AccountID;
end;
I want to be able to either run the select [STATEMENT 1] if the condition is true else I want to run select [STATEMENT 2]
I have managed to get this to work by doing a LEFT JOIN instead of a JOIN.
select [STATEMENT 1 + 2 all columns needed]
from dbo.AV CRMAF_so_AV
left join dbo.FC c on CRMAF_so_AV.so_contactid = c.ContactID;
left join dbo.FA a on CRMAF_so_AV.so_contactid = a.AccountID;
This now runs if its an account or a contact.
Try this -
You have to put your entire statement in #select1 and #select1.
declare #statement1 as varchar(max);
declare #statement2 as varchar(max);
SET #statement1 = 'SELECT 1'
SET #statement2 = 'SELECT 2'
IF EXISTS(select * from dbo.FC where dbo.FC.ContactID in (select dbo.AV.so_contactid from dbo.AV))
BEGIN
EXEC (#statement1)
END
ELSE
BEGIN
EXEC (#statement2)
END
Instead of using if exists can you not get a count of records that meet the criteria and then if its 1 or greater run a different query as apposed to if it was equal to 0.
let me know if I am missing something what you are trying to achieve.
sorry i am unable to put comments due to having a new account so my reputation is low.
I think you need something like this:
WITH PreSelection AS (
SELECT
AV.ID AS AVID,
(SELECT TOP(1) c.ContactID FROM dbo.FC c WHERE c.ContactID = AV.so_contactid) AS ContactID,
(SELECT TOP(1) c.ContactID FROM dbo.FA c WHERE c.AccountID = AV.so_contactid) AS AccountID
FROM dbo.AV
)
SELECT
AVID,
ISNULL(
CASE WHEN ContactID IS NULL
THEN (SELECT TOP(1) AccountName FROM dbo.FA WHERE FA.AccountID = AccountID)
ELSE (SELECT TOP(1) LTRIM(RTRIM(ISNULL(FirstName, '') + ' ' + ISNULL(LastName, ''))) FROM dbo.FC WHERE FC.ContactID = ContactID)
END, '') AS ContactName
FROM PreSelection
A few things to note:
When SSRS evaluates query it expects the resluts to always have the same structure in terms of column names and types.
So you CANNOT do something like this..
IF #x=#y
BEGIN
SELECT Name, Age FROM employees
END
ELSE
BEGIN
SELECT DeptID, DeptName, DeptEMpCOunt FROM departments
END
... as this will return different types and column names and column counts.
What you CAN DO is this..
DECLARE #t TABLE(resultType int, colA varchar(128), colB int, colC varchar(128), colD int)
IF #x=#y
BEGIN
INSERT INTO #t(resultType, colA, ColB)
SELECT 1 as resultType, Name, Age FROM employees
END
ELSE
BEGIN
INSERT INTO #t(resultType, colB, colC, colD)
SELECT 2 AS resultType, DeptID, DeptName, DeptEmpCount FROM departments
END
SELECT * FROM #t
Al we are doing is creating a table that can handle all variations of the data and putting the results into whatever columns can accommodate that data type.
This will always return the same data structure so SSRS will be happy, then you will need to handle which columns to display your data from based on what gets returned, hence why I added the result type to the results so you can test that from within the report.

SQL server update query to add totals

I have a table as below:
The first record Amount and TotalAmount are same
In the second record Amount is added from first row and current and TotalAmount is added
And so on....
Now if I update the second row from 1.25 to 2, then the TotalAmount for all subsequent records should be changed.
I need an update query for this.
I have seq_no and row no as reference and field Type is the reference
Ideally you should create a view or stored procedure that performs a running total, this is an example of one method using a subquery:
SELECT
Type,
Amount ,
Total =
(
SELECT SUM(Amount)
FROM SomeTable B
WHERE B.Type=A.Type AND B.RowNum <= A.RowNum
)
FROM SomeTable A
This is just one method (not necessarily the best). I would suggest you google 'Running totals in SQL' and familiarise yourself with the explanation of this and other methods their pros, cons and performance implications of each.
One question, what version of SQL server are you using?
If you can use row number as reference than you can try following query but storing totals in table is bad idea as mentioned in comment:
DECLARE #Temp TABLE
(
Amount float,
TotalAmount float,
Rownum int
)
INSERT INTO #Temp VALUES (1.25,1.25,1),(1.25,2.50,2),(10,12.50,3)
DECLARE #PreviousAmount AS FLOAT
SELECT #PreviousAmount = Amount FROM #Temp WHERE Rownum=1
DECLARE #NewAmount AS FLOAT = 2
UPDATE #Temp SET TotalAmount = TotalAmount - #PreviousAmount WHERE Rownum>=1
UPDATE #Temp SET Amount=#NewAmount, TotalAmount = TotalAmount + #NewAmount WHERE Rownum=1
UPDATE #Temp SET TotalAmount = TotalAmount + #NewAmount WHERE Rownum>1
SELECT * FROM #Temp
If you want to use triggers(which is not recommended).you can use this:
create trigger trigger_name
for update
as
declare #count int= (select count(*) from table)
declare #a int =1
while(#a<#count)
begin
update table
set total_amount=(select amount from table where row_number=#a) + (select amount from table where row_number=#a-1 )
where row_number!=1
set #a=#a+1
end
Go

Show 0 in count SQL

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];