Data Selection SQL - sql

I have a scenario where I need to select data based on month-end.
Raw data looks like:
ID
Date
Cost
IS_REVERSED
Reverse_ID
1
2021-01-01
$1
No
NULL
2
2021-01-30
$2
YES
NULL
3
2021-02-01
$3
NULL
2
4
2021-02-03
$4
No
NULL
Please note the IS_REVERSED flag column and Reverse_ID column. If the transaction is successful in the first attempt the flag is NO, But if the transaction is successful in the second attempt the flag is NULL
My desired output if I run the report for January end it should bring all transactions that happened in Jan (even if it reversed is Yes but reversal has not yet happened)
ID 1, 2
For next month-end, I need to report data from Jan and Feb combined. and the desired output should be
ID 1, 3 and 4
Id 2 should not be reported in because that has a reverse flag of Yes
Any pointers to achieve this would be much appreciated.
Create table Test_Report
(ID Int
,[Date] date
,Cost varchar(100)
,Is_reversed varchar(100)
,Reversed_ID int)
insert into Test_Report values
(1 ,'2021-01-01', '$1' , 'No', NULL),
(2 ,'2021-01-30', '$2' , 'YES', NULL),
(3 ,'2021-02-01', '$3' , NULL, 2),
(4 ,'2021-02-03', '$4' , 'No', NULL)
I need a single query where i can pass [date] as a condition to filter out records. (date < '2021-01-31' should bring id 1,2 )
(date <'2021-02-28' should bring id 1,3,4) ID 2 should not come as the transaction is reversed and we have a new transaction (Id 3) with IS_REVERSE flag as NULL.
Thanks

I used a dynamic SQL query to achieve the results. Since your data is not clear and I see the datatypes are not correct, assumptions are used. So, DML and DDL are included with the answer
Create table Test_Report
(ID Int
,[Date] varchar(100)
,Cost varchar(100)
,Is_reversed varchar(100)
,Reversed_ID int)
insert into Test_Report values (1 ,'01 Jan', '$1' , 'No', NULL)
,(1 ,'30 Jan', '$2' , 'YES', NULL)
,(1 ,'01 Feb', '$3' , NULL, 2)
,(1 ,'03 Feb', '$4' , 'No', NULL)
declare #from varchar(100) = 'Jan'
, #to varchar(100) = 'Feb'
declare #where varchar(max)
declare #query varchar(max)
set #query = 'select * from Test_Report where '
if #from = #to
begin
set #where = ' [Date] like ''%'+#from+ '%'' and ( isnull(Is_reversed,'''') in (''No'', ''YES'', '''')) and Reversed_ID is null'
end
else
begin
set #where = ' ( [Date] like ''%'+#from+ '%'' or [Date] like ''%'+#to+ '%'' ) and ( isnull(Is_reversed,'''') in (''No'', ''''))'
end
Declare #Main varchar(max) = #query + #where
--This statement can be used to test the resulted query
--select #Main
exec(#main)

I am assuming you need to impute the date of the report you wish to run. In this case I would declare a variable for your WHERE clause.
This variable outputs the current date, but you can replace the GETDATE() syntax with a date value to make the query dynamic. Now the WHERE clause...
DECLARE #DATEVAR AS DATE = GETDATE()
SELECT *
,EOMONTH(#Test_Report.[Date],0) [EOMONTH for WHERE clause]
FROM #Test_Report
WHERE (Is_reversed != 'YES'
OR Is_reversed IS NULL)
AND #Test_Report.[Date] <= EOMONTH(#DATEVAR,0)
The EOMONTH() function can be used here to evaluate the end of the month selected, and select all records less than that month-end date. Since you want both records where Is_reversed is not YES and is NULL, we need to put an OR clause in parenthesis.
Desired Output:
A more clever way to deal with the nulls is the ISNULL(,) function. You can use it in your WHERE clause to make that constraint a 1-liner.
SELECT *
,EOMONTH(#Test_Report.[Date],0) [EOMONTH for WHERE clause]
FROM #Test_Report
WHERE ISNULL(Is_reversed,'No') != 'YES'
AND #Test_Report.[Date] <= EOMONTH(#DATEVAR,0)
Either method should do the trick!

Related

I am having the error with a Subquery returning more than one value. How do I reduce to one?

At the very end of the Stored procedure a SELECT statement is made to display the contents of the Table including function that will simultaneously populate fields in the table.
Here is the Select Statement:
IF #type = 'SH'
SELECT DISTINCT *
FROM #History
ORDER BY 1, 2, 3, 4, 5
ELSE
SELECT DISTINCT AmhazName
,Activity
,ServiceName
,Sarid
,PerformedDate
,UserRole
,Details
,dbo.ufn_SarHistoryActionText(sarid, status, performeddate) AS [ActionText]
,FullName
,CategoryDescription
,StatusDescription
,ActionPerformed
,Case
when Details like '%ProjManagerId%'
Then dbo.ufn_GetUserForHistoryReport (PerformedDate, SarId, '%ProjManagerId%')
Else
--when Details like '%UserId%'
dbo.ufn_GetUserForHistoryReport (PerformedDate, SarId, '%UserId%')
--(select 'no user') as [AssignedUser]
End as [AssignedUser]
--,dbo.ufn_GetPMForHistoryReport(PerformedDate, SarId) as [AssignedUser]
FROM #history
ORDER BY 1, 2, 3, 4, 5
DROP TABLE #Historyw
Here is the function I believe is causing problems:
ALTER FUNCTION [dbo].[ufn_SarHistoryActionText]
(
-- Add the parameters for the function here
#sarID int
, #status varchar(6)
, #statusDate datetime
)
RETURNS varchar(100)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar(100)
set #Result = (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
-- Return the result of the function
RETURN #Result
END
GO
As I debug and walk through loads of values, I haven't come across anything that resulted in multiple values. maybe I'm missing something.
Add TOP 1 in the select inside the function:
SELECT TOP 1 C.ActionText
Can you replace
set #Result = (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
as below:
#Result ***IN*** (
SELECT C.ActionText
from LuStatusChange as C
WHERE C.FromStatus = dbo.ufn_SarHistoryPriorStatus(#sarID,#status,#statusDate)
AND C.ToStatus = #status
)
If functionally your query should not written more than 1 row, something is wrong with your query.

Struggling with getting results (Views, Variables, StoredProcedures)

As I understand it, you cannot create a view with variables. The dilemma is I have to do some datediff on values. Our third party reporting software can only do basic Selects on Tables or views. I cannot call up any stored procedures or set variables there.
My original source data looks select * from tblquotestatuschangelog
results in this I would like to see these results
Quotenumber UpdatedOn Status UpdatedBy
----------------------------------------------
100001 04102019 Open domain/user
100001 04132019 Closed domain/user
I have done a pivot on this data to get the results in the desired format with this query. (There are more status types than in this example, and more can be added via the CRM we use) (Q1)
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(T1.STATUS)
FROM tblCglQuoteStatusChangeLog as T1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT quotenumber, ' + #cols + ' from
(
select quotenumber, status, updatedon, updatedby
from tblcglquotestatuschangelog
where updatedon > 2019-04-01
) x
pivot
(
max(updatedon)
for status in (' + #cols + ')
) p '
execute #query
To get these results
Quotenumber Open Closed
----------------------------------------------
100001 04102019 04132019
I would like to call (Q1) as a View to perform
Select
QuoteNumber,
case when datediff(day,Open, Closed) = 0 then Cast('SAMEDAY' AS NVARCHAR(20)) else cast(datediff(day,openedon,updatedon) as NVarCHAR(20)) end as TotalAge,
datediff(day,Open,SentToCustomer) as Stage1,
datediff(day,SentToCustomer,) as Stage2,
From V1
Any help or alternate direction to achieve results would be much appreciated.
You can use conditional aggregation to achieve your results - as long as you want to rely on the assumptions about the shape of your data. Those are your answers to my questions - that the tuple is unique and that there are only 2 status values. If you have more status values (and it smells like you do) you can extend the logic
use tempdb;
set nocount on;
go
if object_id('v1') is not null
drop view v1;
if object_id('chglog') is not null
drop table chglog;
go
create table chglog (IdNumber int not null, UpdatedOn date not null, Status varchar(10) not null, UpdatedBy varchar(20) not null
constraint chg_unique unique (IdNumber, Status),
constraint chg_check check (Status in ('Open', 'Closed'))
);
insert chglog(IdNumber, UpdatedOn, Status, UpdatedBy)
values (100001, '20190410', 'Open', 'domain/user'), (100001, '20190413', 'Closed', 'domain/user'),
(9999, '20190401', 'Open', 'zork'),
(99001, '20190402', 'Open', 'bob'),
(99001, '20190402', 'Closed', 'alice')
;
go
-- setup complete, now create a view to do a hard-coded pivot
create view v1 as
select IdNumber,
max(case Status when 'Open' then UpdatedOn else null end) as 'Open',
max(case Status when 'Closed' then UpdatedOn else null end) as 'Closed'
from chglog
group by IdNumber;
go
-- test the view
select * from v1
order by IdNumber;
-- now the query that you wanted to write/use
Select
IdNumber,
case when datediff(day, [Open], Closed) = 0 then N'SAMEDAY' else cast(datediff(day, [Open], Closed) as nvarchar(20)) end as TotalAge
-- datediff(day,Open,SentToCustomer) as Stage1,
-- datediff(day,SentToCustomer,) as Stage2,
from v1
order by IdNumber;
I'll point out that your "table" contained IdNumber but your last query referenced QuoteNumber (as well as other columns you did not mention). I just ignored some and guesssed some. This is just old-style pivoting based on hard-coded values. So long as you know ahead of time the values you need to consider when pivoting and you know that the domain is unchanging (or don't care about other values), this will what you asked.
The question you should consider is whether you should care about other status values that are added via your CRM after you create this view. But you're kinda stuck with the 3rd party reporting software anyways. A reporting tool that can't use anything but a table or view as a source of data seems primitive (to say the least).

SQL Query data based on a variable

I have a simple query that I am trying to adapt to a new situation. For this situation, if the variable locationID=1 then I need all records, regardless of its locationID.
However, if it is not equal to 1, I need to only provide records that match that of the #locationID.
Here is my setup example:
DECLARE #locationID INT = 1; -- All Results Regardless of locationID
--DECLARE #locationID INT = 2; -- Only results that match this locationID (2)
--DECLARE #locationID INT = 3; -- Only results that match this locationID (3)
-- Temp Data
DECLARE #temp AS TABLE (color VARCHAR(10), locationID INT, name VARCHAR(20))
INSERT INTO #temp( color, locationID, name )VALUES ( 'Blue', 1, 'Test 1' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 2, 'Test 2' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 1, 'Test 3' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 2, 'Test 4' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 3, 'Test 5' )
-- Query
SELECT *
FROM #temp
WHERE
locationID = ...
I am trying to figure out if I need to use a CASE WHEN or some other method for this.
WHERE (locationID = #LocationId OR #locationID = 1)
Try this:
...WHERE (#LocationId = 1 AND 1=1)
OR (#LocationId <> 1 AND LocationId = #LocationId)
This seems devilishly simple, perhaps I am not understanding your question. If you want to query by a variable value, then just use the variable value:
DECLARE #LocationId INT = 1
SELECT *
FROM #temp
WHERE (#LocationId = 1 AND 1=1)
OR (#LocationId <> 1 AND LocationId = #LocationId)
A couple of other ideas. Since using a constant of 1 is not exactly intuitive or self-documenting, you could default that to NULL. And then say:
WHERE LocationID = COALESCE(#LocationID, LocationID);
If LocationID column is nullable, then instead you could use some token value that is at least slightly more self-documenting than 1, like -1 (since I don't think anyone looking at the code will immediately know that there isn't really a valid row where LocationID = 1):
WHERE LocationID = COALESCE(NULLIF(#LocationID, -1), LocationID);
This is susceptible to parameter sniffing, so you might want to add OPTION (RECOMPILE) if you find you are often switching between "all rows" and "very specific rows." You can also use dynamic SQL to optionally build the WHERE clause, assuming your real scenario uses a real table:
DECLARE #sql nvarchar(max) = N'SELECT ... FROM dbo.RealTable'
IF #LocationID <> 1 -- or IS NOT NULL or <> -1 or what have you
BEGIN
SET #sql += N' WHERE LocationID = #LocationID';
END
EXEC sys.sp_executesql #sql, N'#LocationID int', #LocationID;
This way you get a different plan when the parameter is included vs. when it is not, which doesn't really make the scan (where you need all rows) any better, but it guards against the case where you use a seek + lookup against all rows, which can be really bad. More often, it prevents you from getting the dreadful full table/index scan when you really could have used a seek. But which way that goes is completely dependent on which case is more likely the first time the query runs.
And even here you may want to use OPTION (RECOMPILE) at the end of #sql if the distribution of data across different LocationID values is (or might later become) heavily skewed.
I call this type of query "the kitchen sink," and have written about it here:
#BackToBasics : An Updated "Kitchen Sink" Example
So the variable should be 1 or the locationid?
You could use an IN for that.
... WHERE #LocationID IN (1, LocationID)
Example snippet:
declare #locationID int;
-- Test data using a table variable
declare #varTable table (id int identity(1,1) primary key, locationID int, name varchar(20), color varchar(10));
insert into #varTable (locationID, name, color) values
(1,'Test 1','Blue'),
(2,'Test 2','Red'),
(1,'Test 3','Red'),
(2,'Test 4','Red'),
(3,'Test 5','Red');
-- Returning all records
set #locationID = 1;
SELECT t.* FROM #varTable t WHERE #locationID IN (1, t.locationID);
-- Only returning those with locationID = 2
set #locationID = 2;
SELECT t.* FROM #varTable t WHERE #locationID IN (1, t.locationID);

How to return function columns with null values

I have a function with about 40 columns with null values and these columns have never been used for the past 5 years. So I want to write a query that confirms that these fields have null values. I have used a select query below:
select col1, col2, col3, .....coln
from dbo.fn_functionName(DATEADD(YEAR, -5, DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)),'','','','')
The problem with this query is that, Its returning over 10 000 records and my clients are not satisfied.
Query to check if any column contains a null:
DECLARE #tb NVARCHAR(255), #sql NVARCHAR(MAX);
SET #tb = N'dbo.[table]';
SET #sql = N'SELECT * FROM ' + #tb + ' WHERE 1 = 0';
SELECT #sql = #sql + N' OR ' + QUOTENAME(name) + ' IS NULL'
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#tb);
EXEC sp_executesql #sql;
Might help. Also you can just check if important columns are null's and display only valid ones:
select *
from tabName
where col1 is not null and col6 is not null...
At the end You can think about creating second table 1 to 1 with old values, if they are important. All the tables need to be refreshed time to time :)
EDIT:
I think that is similar to the query you are looking for:
Select [TransactionID] , [ProductID] ,[ReferenceOrderID] ,[ReferenceOrderLineID], [TransactionDate] ,[TransactionType] ,[Quantity],[ActualCost] ,[ModifiedDate]
from tableName
where datediff(year,[TransactionDate], GETDATE()) < 5
and ([ProductID] is not null and [ProductID] != '')
and ([TransactionType] is not null and [TransactionType] != '')
and ([Quantity] is not null)
and [ActualCost] is not null
Ok, so what is going on in this query? After select you are listing columns you want to be displayed. If you need all of them just type "select *". Then you choose the table (by selecting it) and here comes the magic.
datediff(year, date1,date2) counts difference between two dates. To check todays date just type GETDATE(). Than other conditions. Unlike normal programming languages you can't type "something != null", you need to use "something is not null". Sometimes you'll have to check if it's null or empty string. So
([ProductID] is not null and [ProductID] != '')
I hope that helped :)
This is exactly what I needed to use:
Select [TransactionID] , [ProductID] ,[ReferenceOrderID] ,[ReferenceOrderLineID], [TransactionDate] ,[TransactionType] ,[Quantity],[ActualCost] ,[ModifiedDate]
from tableName
where datediff(year,[TransactionDate], GETDATE()) < 5
and ([ProductID] is not null and [ProductID] != '')
and ([TransactionType] is not null and [TransactionType] != '')
and ([Quantity] is not null)
and [ActualCost] is not null

Split column value to match yes or no

I have two tables named Retail and Activity and the data is as shown below:
Retail Table
Activity Table
My main concern is about Ok and Fault column of the table Retail, as you can see it contains comma separated value of ActivityId.
What i want is, if the Ok column has ActivityId the corresponding column will have Yes, if the Fault column has ActivityId then it should be marked as No
Note I have only four columns that is fixed, it means i have to check that either four of the columns has its value in Ok or Fault, if yes then only i have to print yes or no, otherwise null.
Desired result should be like :
If the value is in Ok then yes other wise No.
I guessing you want to store 'yes' or 'No' in some column. Below is the query to update that column :
UPDATE RetailTable
SET <Result_Column>=
CASE
WHEN Ok IS NOT NULL THEN 'Yes'
WHEN Fault IS NOT NULL THEN 'No'
END
You can use below code as staring point:
DECLARE #Retail TABLE
(
PhoneAuditID INT,
HandsetQuoteID INT,
Ok VARCHAR(50)
)
INSERT INTO #Retail VALUES (1, 1009228, '4,22,5')
INSERT INTO #Retail VALUES (2, 1009229, '1')
DECLARE #Activity TABLE
(
ID INT,
Activity VARCHAR(50)
)
INSERT INTO #Activity VALUES (1, 'BatteryOK?'), (4, 'PhonePowersUp?'), (22,'SomeOtherQuestion?'), (5,'LCD works OK?')
SELECT R.[PhoneAuditID], R.[HandsetQuoteID], A.[Activity], [Ok] = CASE WHEN A.[ID] IS NOT NULL THEN 'Yes' END
FROM #Retail R
CROSS APPLY dbo.Split(R.Ok, ',') S
LEFT JOIN #Activity A ON S.[items] = A.[ID]
I have used Split function provided here:
separate comma separated values and store in table in sql server
Try following query. i have used pivot to show row as columns. I have also used split function to split id values which you can find easily on net:
CREATE TABLE PhoneAudit
(
PhoneAuditRetailID INT,
HandsetQuoteID INT,
Ok VARCHAR(50),
Fault VARCHAR(50)
)
INSERT INTO PhoneAudit VALUES (1,10090,'1,2','3')
CREATE TABLE ActivityT
(
ID INT,
Activity VARCHAR(100)
)
INSERT INTO ActivityT VALUES (1,'Battery')
INSERT INTO ActivityT VALUES (2,'HasCharger')
INSERT INTO ActivityT VALUES (3,'HasMemoryCard')
INSERT INTO ActivityT VALUES (4,'Test')
DECLARE #SQL AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
SELECT #ColumnName= ISNULL(#ColumnName + ',','') + QUOTENAME(Activity) FROM (SELECT DISTINCT Activity FROM ActivityT) AS Activities
SET #SQL = 'SELECT PhoneAuditRetailID, HandsetQuoteID,
' + #ColumnName + '
FROM
(SELECT
t1.PhoneAuditRetailID,
t1.HandsetQuoteID,
TEMPOK.*
FROM
PhoneAudit t1
CROSS APPLY
(
SELECT
Activity,
(CASE WHEN ID IN (SELECT * FROM dbo.SplitIDs(t1.Ok,'',''))
THEN ''YES''
ELSE ''NO''
END) AS VALUE
FROM
ActivityT t2
) AS TEMPOK) AS t3
PIVOT
(
MIN(VALUE)
FOR Activity IN ('+ #ColumnName + ')
) AS PivotTable;'
EXEC sp_executesql #SQL
DROP TABLE PhoneAudit
DROP TABLE ActivityT
There are several ways to do this. If you are looking for a purely declarative approach, you could use a recursive CTE. The following example of this is presented as a generic solution with test data which should be adaptable to your needs:
Declare #Delimiter As Varchar(2)
Set #Delimiter = ','
Declare #Strings As Table
(
String Varchar(50)
)
Insert Into #Strings
Values
('12,345,6,78,9'),
(Null),
(''),
('123')
;With String_Columns As
(
Select
String,
Case
When String Is Null Then ''
When CharIndex(#Delimiter,String,0) = 0 Then ''
When Len(String) = 0 Then ''
Else Left(String,CharIndex(#Delimiter,String,0)-1)
End As String_Column,
Case
When String Is Null Then ''
When CharIndex(#Delimiter,String,0) = 0 Then ''
When Len(String) = 0 Then ''
When Len(Left(String,CharIndex(#Delimiter,String,0)-1)) = 0 Then ''
Else Right(String,Len(String)-Len(Left(String,CharIndex(#Delimiter,String,0)-1))-1)
End As Remainder,
1 As String_Column_Number
From
#Strings
Union All
Select
String,
Case
When CharIndex(#Delimiter,Remainder,0) = 0 Then Remainder
Else Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1)
End As Remainder,
Case
When CharIndex(#Delimiter,Remainder,0) = 0 Then ''
When Len(Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1)) = 0 Then ''
Else Right(Remainder,Len(Remainder)-Len(Left(Remainder,CharIndex(#Delimiter,Remainder,0)-1))-1)
End As Remainder,
String_Column_Number + 1
From
String_Columns
Where
(Remainder Is Not Null And Len(Remainder) > 1)
)
Select
String,
String_Column,
String_Column_Number
From
String_Columns