MS SQL Query in Access via ODBC connecting to SQL Server - sql-server

Due to a few limitations (which I won't go into here).. our architecture is using queries in Access running via ODBC SQL Server driver.
The following query produces 2 errors:
SELECT Tbl2.columnid,
Tbl2.label,
Tbl2.group1,
Tbl2.group2,
Count(Tbl2.columnid) AS Total
FROM (SELECT scanned AS Group1,
false AS Group2,
scanned AS Label,
scanned AS ColumnID
FROM (SELECT *,
( quantity - productqty ) AS Variance
FROM order_line
WHERE processed = false) AS Tbl1
WHERE wsid = 1 ) AS Tbl2
WHERE Tbl2.columnid = false
GROUP BY Tbl2.group1,
Tbl2.group2,
Tbl2.columnid,
Tbl2.label
ORDER BY Tbl2.group1 DESC,
Tbl2.group2
Error 1: Each GROUP BY Expression must contain at least one column that is an outer reference: (#164)
Error 2: The ORDER BY position number 0 is out of range of the number of items in the Select list (#108)
Its important to note that "scanned" is a BIT field in SQL Server (and therefore Group1, Label, ColumnId are also bits). I believe this is the reason why GROUP BY and ORDER BY are treating it as a constant (value=0), resulting in these errors.
But I do not know how to resolve these issues. Any suggestions would be great!
PS - The reason why 2 sub queries are being used is due to other constraints, where we are trying to get ID, Label, Counts for a column in Kanban.

Based on DRapp's comment and suggestion.. the following works:
SELECT Tbl2.columnid,
Tbl2.label,
Tbl2.group1,
Tbl2.group2,
Count(Tbl2.columnid) AS Total
FROM (SELECT IIf(scanned=True, 'Y', 'N') AS Group1,
'N' AS Group2,
IIf(scanned=True, 'Y', 'N') AS Label,
IIf(scanned=True, 'Y', 'N') AS ColumnID
FROM (SELECT *,
( quantity - productqty ) AS Variance
FROM order_line
WHERE processed = false) AS Tbl1
WHERE wsid = 1 ) AS Tbl2
WHERE Tbl2.columnid = 'N'
GROUP BY Tbl2.group1,
Tbl2.group2,
Tbl2.columnid,
Tbl2.label
ORDER BY Tbl2.group1 DESC,
Tbl2.group2
Not ideal (as the first subquery is generated dynamically, and now needs extra handling if group field is bit. But works! Still open to any other solutions.

Related

Is there a way you can produce an output like this in T-SQL

I have a column which I translate the values using a case statements and I get numbers like this below. There are multiple columns I need to produce the result like this and this is just one column.
How do you produce the output as a whole like this below.
The 12 is the total numbers counting from top to bottom
49 is the Average.
4.08 is the division 49/12.
1 is how many 1's are there in the output list above. As you can see there is only one 1 in the output above
8.33% is the division and percentage comes from 1/12 * 100
and so on. Is there a way to produce this output below?
drop table test111
create table test111
(
Q1 nvarchar(max)
);
INSERT INTO TEST111(Q1)
VALUES('Strongly Agree')
,('Agree')
,('Disagree')
,('Strongly Disagree')
,('Strongly Agree')
,('Agree')
,('Disagree')
,('Neutral');
SELECT
CASE WHEN [Q1] = 'Strongly Agree' THEN 5
WHEN [Q1] = 'Agree' THEN 4
WHEN [Q1] = 'Neutral' THEN 3
WHEN [Q1] = 'Disagree' THEN 2
WHEN [Q1] = 'Strongly Disagree' THEN 1
END AS 'Test Q1'
FROM test111
I have to make a few assumptions here, but it looks like you want to treat an output column like a column in a spreadsheet. You have 12 numbers. You then have a blank "separator" row. Then a row with the number 12 (which is the count of how many numbers you have). Then a row with the number 49, which is the sum of those 12 numbers. Then the 4.08 row, which is rougly the average, and so on.
Some of these outputs can be provided by cube or rollup, but neither is a complete solution.
If you wanted to produce this output directly from TSQL, you would need to have multiple select statements and combine the results of all of those statements using union all. First you would have a select just to get the numbers. Then you would have a second select which outputs a "blank". Then another select which is providing a count. Then another select which is providing a sum. And so on.
You would also no longer be able to output actual numbers, since a "blank" is not a number. Visually it's best represented as an empty string. But now your output column has to be of datatype char or varchar.
You also have to make sure rows come out in the correct order for presentation. So you need a column to order by. You would have to add some kind of ordering column "manually" to each of the select statements, so when you union them all together you can tell SQL in what order the output should be provided.
So the answer to "can it be done?" is technically "yes". But if you think seems like a whole lot of laborious and inefficient TSQL work, you'd be right.
The real solution here is to change your approach. SQL should not be concerned with "output formatting". What you should do is just return the actual data (your 12 numbers) from SQL, and then do all of the additional presentation (like adding a blank row, adding a count row, etc), in the code of the program that is calling SQL to get that data.
I must say, this is one of the strangest T-SQL requirements I've seen, and is really best left to the presentation layer.
It is possible using GROUPING SETS though. We can use it to get an extra rollup row that aggregates the whole table.
Once you have the rollup, you need to unpivot the totalled row (identified by GROUPING() = 1) to get your final result. We can do this using CROSS APPLY.
This is impossible without a row-identifier. I have added ROW_NUMBER, but any primary or unique key will do.
WITH YourTable AS (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rn,
CASE WHEN [Q1] = 'Strongly Agree' THEN 5
WHEN [Q1] = 'Agree' THEN 4
WHEN [Q1] = 'Neutral' THEN 3
WHEN [Q1] = 'Disagree' THEN 2
WHEN [Q1] = 'Strongly Disagree' THEN 1
END AS TestQ1
FROM test111
),
RolledUp AS (
SELECT
rn,
TestQ1,
grouping = GROUPING(TestQ1),
count = COUNT(*),
sum = SUM(TestQ1),
avg = AVG(TestQ1 * 1.0),
one = COUNT(CASE WHEN TestQ1 = 1 THEN 1 END),
onePct = COUNT(CASE WHEN TestQ1 = 1 THEN 1 END) * 1.0 / COUNT(*)
FROM YourTable
GROUP BY GROUPING SETS(
(rn, TestQ1),
()
)
)
SELECT v.TestQ1
FROM RolledUp r
CROSS APPLY (
SELECT r.TestQ1, 0 AS ordering
WHERE r.grouping = 0
UNION ALL
SELECT v.value, v.ordering
FROM (VALUES
(NULL , 1),
(r.count , 2),
(r.sum , 3),
(r.avg , 4),
(r.one , 5),
(r.onePct, 6)
) v(value, ordering)
WHERE r.grouping = 1
) v
ORDER BY
v.ordering,
r.rn;
db<>fiddle

Microsoft SQL Case statement not working in Order By statement

I have a database column which is used as a grouping for variations for products and it is not in the First Normal Form. We don't need the Width, Height and the measurements to be in separate columns.
We have various attributes in this one database column, for example, product sizes like 1200*400mm and sometimes meters like 1m or even colors like red, blue and green.
The issue is that the CASE statement is being ignored and SQL is trying to Convert Black to an Int in the Case statement.
Currently the code needs to Sort the sizes correctly which the SQL now does. The issue comes in when it fetches a product with only colour attributes and the CASE statement is ignored near the Order By section.
I have created this SQL Fiddle to showcase the DB column with Sizes and Colors.
http://sqlfiddle.com/#!18/4e58e/1
This is my current SQL:
DECLARE #StockID int = 684
DECLARE #VariationParent int = (SELECT TOP 1 StockParent_ParentId FROM
StockVariations SV INNER JOIN FinGoodsParent FGP ON FGP.Id =
SV.StockParent_ChildId WHERE StockParent_ChildId = #StockID AND
SV.IsDeleted = 0 AND FGP.IsDeleted = 0 AND FGP.Publish = 1)
SELECT AV.ID, AV.AttrValue, AV.AttributeTypes_Id 'AttributeTypeID',
CAST(CASE WHEN SA.StockParent_Id = #StockID THEN 1 ELSE 0 END as BIT)
'IsDefault'
FROM AttributeTypes AT
INNER JOIN AttributeValues AV ON AV.AttributeTypes_Id = AT.Id
INNER JOIN StockParent_AttributeValues SA on SA.AttributeValue_Id = AV.Id
INNER JOIN FinGoodsParent FGP ON FGP.Id = SA.StockParent_Id AND
FGP.IsDeleted = 0 AND FGP.Publish = 1
WHERE SA.StockParent_Id IN (SELECT SV.StockParent_ChildId FROM
StockVariations SV INNER JOIN FinGoodsParent FGP ON FGP.Id =
SV.StockParent_ChildId AND FGP.IsDeleted = 0 AND FGP.Publish = 1 WHERE
SV.StockParent_ParentId = #VariationParent AND SV.IsDeleted = 0)
AND SA.IsDeleted = 0 AND AT.IsDeleted = 0 AND AV.IsDeleted = 0
ORDER BY
CASE WHEN (CHARINDEX('*', AV.AttrValue) > 0
AND CHARINDEX('mm', AV.AttrValue) > 0)
THEN
CONVERT(int, (select top 1 value from STRING_SPLIT(AV.AttrValue, '*'))) *
CONVERT(int, (select top 1 LEFT(value, LEN(value) - 2) from
STRING_SPLIT(AV.AttrValue, '*') where value LIKE '%mm'))
ELSE
AV.AttrValue
END
ASC
This is the error I am getting when trying to fetch a product with only color attributes and sorting it by the Attribute name with the CASE Statement.
So basically I got this SQL code to do the sorting from How to match a width and height size with a Regex expression and use Sort By in SQL or C# to build a drop-down?
I have added the CASE statement to try and only do the sorting when there is an Asterisk * and mm in the column, otherwise, I would just like to use Order By attrValue normally when the column contains, for example, red or black instead of numerics.
Updated
I have updated the SQL Fiddle as the data Output should only be either Sizes or Colours. So I have added an additional column with a type for this example. The SQL retrieving the data near the WHERE Clause should be either 0 for sizes or 1 for just colors and the SQL should still be able to work and order the Output of the sizes correctly.
http://sqlfiddle.com/#!18/56b3e/1
Explanations:
Consider the following:
CASE evaluates a list of conditions and returns the highest precedence type from the set of types. In your case you have int and varchar as possible return values and this explains your Conversion failed when converting the varchar value 'Black' to data type int error.
do not use SELECT TOP 1 FROM STRING_SPLIT() because STRING_SPLIT() doesn't guarantee that the order of the splitted substrings matches their positions in the original string. In your case you may try to use simple string parsing with LEFT, RIGHT and CHARINDEX functions. After that you need to convert the result from calculations to varchar.
Example:
Table:
CREATE TABLE attributes
([AttrValue] varchar(13))
;
INSERT INTO attributes
([AttrValue])
VALUES
('900*900mm'),
('1200*900mm'),
('1200*1200mm'),
('1200*6000mm'),
('1500*3000mm'),
('Red'),
('Green'),
('Black'),
('Purple')
;
Statement:
SELECT
AttrValue
FROM
attributes
ORDER BY
CASE
WHEN (CHARINDEX('*', AttrValue) > 0 AND CHARINDEX('mm', AttrValue) > 0) THEN
RIGHT(
REPLICATE('0', 13) +
CONVERT(
varchar(13),
CONVERT(int, LEFT(REPLACE(AttrValue, 'mm', ''), CHARINDEX('*', REPLACE(AttrValue, 'mm', '')) - 1)) *
CONVERT(int, RIGHT(REPLACE(AttrValue, 'mm', ''), LEN(REPLACE(AttrValue, 'mm', '')) - CHARINDEX('*', REPLACE(AttrValue, 'mm', ''))))
),
13
)
ELSE AttrValue
END ASC
Output:
--------------
AttrValue
--------------
900*900mm
1200*900mm
1200*1200mm
1500*3000mm
1200*6000mm
Black
Green
Purple
Red

Missing Rows when running SELECT in SQL Server

I have a simple select statement. It's basically 2 CTE's, one includes a ROW_NUMBER() OVER (PARTITION BY, then a join from these into 4 other tables. No functions or anything unusual.
WITH Safety_Check_CTE AS
(
SELECT
Fact_Unit_Safety_Checks_Wkey,
ROW_NUMBER() OVER (PARTITION BY [Dim_Unit_Wkey], [Dim_Safety_Check_Type_Wkey]
ORDER BY [Dim_Safety_Check_Date_Wkey] DESC) AS Check_No
FROM
[Pitches].[Fact_Unit_Safety_Checks]
), Last_Safety_Check_CTE AS
(
SELECT
Fact_Unit_Safety_Checks_Wkey
FROM
Safety_Check_CTE
WHERE
Check_No = 1
)
SELECT
COUNT(*)
FROM
Last_Safety_Check_CTE lc
JOIN
Pitches.Fact_Unit_Safety_Checks f ON lc.Fact_Unit_Safety_Checks_Wkey = f.Fact_Unit_Safety_Checks_Wkey
JOIN
DIM.Dim_Unit u ON f.Dim_Unit_Wkey = u.Dim_Unit_Wkey
JOIN
DIM.Dim_Safety_Check_Type t ON f.Dim_Safety_Check_Type_Wkey = t.Dim_Safety_Check_Type_Wkey
JOIN
DIM.Dim_Date d ON f.Dim_Safety_Check_Date_Wkey = d.Dim_Date_Wkey
WHERE
f.Safety_Check_Certificate_No IN ('GP/KB11007') --option (maxdop 1)
Sometimes it returns 0, 1 or 2 rows. The result should obviously be consistent.
I have ran a profile trace whilst replicating the issue and my session was the only one in the database.
I have compared the Actual execution plans and they are both the same, except the final hash match returns the differing number of rows.
I cannot replicate if I use MAXDOP 0.
In case you use my comment as the answer.
My guess is ORDER BY [Dim_Safety_Check_Date_Wkey] is not deterministic.
In the CTE's you are finding the [Fact_Unit_Safety_Checks_Wkey] that's associated with the most resent row for any given [Dim_Unit_Wkey], [Dim_Safety_Check_Type_Wkey] combination... With no regard for weather or not [Safety_Check_Certificate_No] is equal to 'GP/KB11007'.
Then, in the outer query, you are filtering results based on [Safety_Check_Certificate_No] = 'GP/KB11007'.
So, unless the most recent [Fact_Unit_Safety_Checks_Wkey] happens to have [Safety_Check_Certificate_No] = 'GP/KB11007', the data is going to be filtered out.

Flag Records with highest version number within calculated field/calculated column as "true", rest as false

Environment: MS SQL Server 2016.
I have a table which contains (Jasper Reports) layout representations like this (only relevant fields shown for brevity):
ID Name Key Version
1 CoverLetter <guid1> 1.00.00
2 Contract <guid2> 1.00.00
3 CoverLetter <guid1> 1.00.01
Goal:
I need an additional calculated field which is set to true or false according to
whether the record is the highest version of any given Layout or not (Same layout but different versions have same key, different layouts have different key).
Like this:
ID: Name: Key: Version: isHighestVersion: (calculated field)
1 CoverLetter <guid1> 1.00.00 false
2 Contract <guid2> 1.00.00 true
3 CoverLetter <guid1> 1.00.01 true
The SQL query which shows only the highest versions of each Layout is like this:
( SELECT TACMasterlayouts.*
FROM
(SELECT
TACMasterLayoutKey, MAX(TACMasterLayoutVersion) as TACMasterLayoutVersion
FROM
TACMasterlayouts
GROUP BY
TACMasterLayoutKey) AS latest_TACMasterLayouts
INNER JOIN
TACMasterlayouts
ON
TACMasterlayouts.TACMasterLayoutKey = latest_TACMasterLayouts.TACMasterLayoutKey AND
TACMasterlayouts.TACMasterLayoutVersion = latest_TACMasterLayouts.TACMasterLayoutVersion
)
But I need all records - the ones with highest version number per same key flagged with true and the rest flagged with false.
What I already did:
Searched google and SO but didn't find anything similar which I could transform into what I need.
Just change your INNER JOIN To a LEFT OUTER JOIN
and use a case in your
Select
EG
CASE WHEN latest_TACMasterLayouts.TACMasterLayoutKey IS NOT NULL THEN 1 ELSE 0 END as isHighestVersion
Thanks John,
this has pointed me into the right direction.
It has to be a RIGHT OUTER JOIN - otherwise only the records with highest version are shown.
As reference here the fully working code:
SELECT TACMasterlayouts.*, CASE WHEN latest_TACMasterLayouts.TACMasterLayoutKey IS NOT NULL THEN 1 ELSE 0 END as isHighestVersion
FROM
(SELECT TACMasterLayoutKey, MAX(TACMasterLayoutVersion) as TACMasterLayoutVersion
FROM
TACMasterlayouts
GROUP BY
TACMasterLayoutKey) AS latest_TACMasterLayouts
RIGHT OUTER JOIN
TACMasterlayouts
ON
TACMasterlayouts.TACMasterLayoutKey = latest_TACMasterLayouts.TACMasterLayoutKey AND
TACMasterlayouts.TACMasterLayoutVersion = latest_TACMasterLayouts.TACMasterLayoutVersion
)
You need to do some parsing in order to get desired result.
First, you split your version numbers into separate ints, then assign row_number based on them, and then based on row number, you put 1-true or 0-false in a extra column, which I called IsLatest.
In SQL Server there is no true or false, you can use BIT datatype, which has two values (just like boolean): 1 and 0.
Try this query:
declare #tbl table(ID int,Name varchar(20),[Key] varchar(10),Version varchar(10));
insert into #tbl values
(1,'CoverLetter','<guid1>','1.00.00'),
(2,'Contract','<guid2>','1.00.00'),
(3,'CoverLetter','<guid1>','1.00.01');
select ID, [Key], [version],
case when rn = 1 then 1 else 0 end IsLatest
from (
select *,
row_number() over (order by
cast(substring([version], 1, FirstDot - 1) as int) desc,
cast(substring([version], FirstDot + 1, SecondDot - FirstDot - 1) as int) desc,
cast(substring([version], SecondDot + 1, 100) as int) desc) rn
from (
select ID, [Key], [version],
charindex('.', [version]) FirstDot,
charindex('.', [version], charindex('.', [version]) + 1) SecondDot
from #tbl
) a
) a

Return id where count of value associated with that id on other table is 0

I have been stuck at a fairly simple scenario but even after scratching my head around for sometime I haven't been able to find a solution... Here's what I have.
I have got 2 tables with following data:
Trip (ID, Status)
30063 SUBMITTED
30066 SUBMITTED
30067 ASSIGNED
30068 SUBMITTED
And
AgentTripAssignment(TripId, AgentId, IsRejected)
30063 5 1
30063 2 0
30066 3 0
30066 4 0
30067 1 0
30067 2 0
30067 3 0
What I want to do is:
Return the trip id from trip table where status is SUBMITTED and if entry for trip is present in other table, count of IsRejected = 1 is zero in the AgentTripAssignment table with 1 query (as new enquiries....30066,30068 in the given case) and
Return the trip id from trip table where status is SUBMITTED and entry is present in AgentTripAssignment table having count of IsRejected = 1 appearing at least once in the table with second query (as agent rejected....30063 in the given case)
Other point of note is that the status of trip stays SUBMITTED until 3 agents are not assigned to a trip request which is when the status changes to ASSIGNED as is the case with 30067.
Any help will be much appreciated!
Case 1: Trip Submitted, No 'Rejected' Assignments
This is using a left join on assignments to handle the case of no assignments for a given trip. If the count of assignments in rejected state is not 0, it's excluded.
SELECT t.ID
FROM Trip AS t
LEFT JOIN AgentTripAssignmentx AS at
ON at.TripId = t.ID
WHERE t.Status = 'SUBMITTED'
GROUP BY t.ID
HAVING COUNT(CASE WHEN at.IsRejected = 1 THEN 1 ELSE NULL END) = 0
Case 2: Trip Submitted, Has 'Rejected' Assignments
Similar to above, but using an inner join, and excluding trips that don't have assignments rejected.
SELECT t.ID
FROM Trip AS t
JOIN AgentTripAssignmentx AS at
ON at.TripId = t.ID
WHERE t.Status = 'SUBMITTED'
GROUP BY t.ID
HAVING COUNT(CASE WHEN at.IsRejected = 1 THEN 1 ELSE NULL END) > 0
select a.tripid
from agenttripassignment a
join trip b on a.tripid = b.tripid
where b.status = 'submitted'
group by a.tripid
having max(a.isrejected) = 0
and likewise with 1
This is an example (in SQL Server syntax) that satisfies your first bullet point.
I broke the query down to try to explain the logic. (first 2 table statements are your data table samples)
WITH T AS ( -- Sample data
SELECT 30063 AS ID, 'Submitted' as Status
UNION ALL SELECT 30066, 'Submitted'
UNION ALL SELECT 30067, 'Assigned'
UNION ALL SELECT 30068, 'Submitted'
), A AS ( -- Sample data
SELECT 30063 AS ID, 5 AS AgentID, 1 AS IsRejected
UNION ALL SELECT 30063, 2, 0
UNION ALL SELECT 30066, 3, 0
UNION ALL SELECT 30066, 4, 0
UNION ALL SELECT 30067, 1, 0
UNION ALL SELECT 30067, 2, 0
UNION ALL SELECT 30067, 3, 0
), TPres AS ( -- get list of all IDs present in A table.
SELECT DISTINCT A.ID
FROM A
), RejCnt AS ( -- get List of all items with a rejected status
SELECT DISTINCT A.ID
FROM A
WHERE IsRejected=1
)
SELECT T.ID
FROM T
JOIN TPres ON TPres.ID=T.ID -- Filter out where entry for trip is in the other table.
LEFT JOIN RejCnt ON RejCnt.ID=T.ID -- Used to Determine if IsRejected is Zero
WHERE T.Status='Submitted' -- Where status=Submitted
AND RejCnt.ID IS NULL -- Has no rejected entries
This is very simply done with semi-joins (joins that filter rows but do not provide access to data and do not cause duplication of rows). For your first request, here is an anti-semi-join:
SELECT T.*
FROM
dbo.Trip T
WHERE
T.Status = 'SUBMITTED'
AND NOT EXISTS (
SELECT *
FROM dbo.AgentTripAssignment AT
WHERE
T.ID = AT.TripId
AND AT.IsRejected = 1
)
;
See a Live Demo at SQL Fiddle
This is very simply read as, "show me all rows from table Trip where no matching row (based on ID) exists in table AgentTripAssignment that has IsRejected = 1".
For your second query, it is almost exactly the same, simply change AND NOT EXISTS to EXISTS: "show all rows from table Trip where at least one matching row (based on ID) exists in table AgentTripAssignment that has IsRejected = 1".
The reason I prefer the EXISTS semi-join syntax is that it can often be better performance, depending on the exact indexes. It also helps database developers think in ways that I think are beneficial. It also has the benefit of not having to tack on a DISTINCT to fix duplication, and it avoids the problems of an aggregate method by allowing you to return all rows from the desired table (when a GROUP BY would have to contain all the columns in the table).

Resources