Update: Had the order of the two queries wrong. First one works, the second one does not.
I'm experiencing some odd SQL Server behavior that has me stumped, and I'm hoping someone can help me figure out what's going.
My query needs to pull data from a number of tables, including the products table. The schema we work with is pretty rigid, and recently we added some new products that in their name contain a "recurrence" value (i.e. we bill the customer every 3, 6, or 12 months, and that number is only available in text, in the product name. This bites, but that's how it is).
I add a WHERE clause to filter out the 3 specific products, and then run the following piece of code to obtain the quantity recurrence-factor:
CAST(RTRIM(SUBSTRING(e.NAME, CHARINDEX('-', e.NAME) +2 , 2)) as int)
Nothing overly fancy, and with the WHERE clause specifically filtering only those 3 products, we ought to be good. I then cast the retrieved value to an integer to be used in a calculation.
However, seemingly random, the query will complain about two letter combinations not being possible to convert to an int value.
It was my assumption that by using the WHERE clause to remove unwanted products, other products would never be evaluated to begin with. If the above code is the only thing in the query, the query runs. If we add e.NAME as the only other field, I get an error:
Conversion failed when converting the nvarchar value 'ni' to data type int.
I attempted to also filter out unwanted products by hard-coding the products into the JOIN (which shouldn't really matter), but it did not help.
Execution plans and updating statistics on the table did not reveal anything worthwhile.
This throws the error:
SELECT
CAST(RTRIM(SUBSTRING(e.NAME, CHARINDEX('-', e.NAME) +2 , 2)) AS int) AS BillingFrequency
FROM
orders a
JOIN order_items b ON a.ORDER_ID = b.ORDER_ID
JOIN order_item_options c ON b.ORDER_ITEM_ID = c.ORDER_ITEM_ID
JOIN prices d ON c.PRICE_ID = d.PRICE_ID AND d.TO_OPTION_ID IN (189, 190, 191)
JOIN product_options_vw e ON d.TO_OPTION_ID = e.OPTION_ID AND e.OPTION_ID IN (189, 190, 191)
WHERE
e.OPTION_ID IN (189, 190, 191)
This works:
SELECT
e.NAME
, CAST(RTRIM(SUBSTRING(e.NAME, CHARINDEX('-', e.NAME) +2 , 2)) AS int) AS BillingFrequency
FROM
orders a
JOIN order_items b ON a.ORDER_ID = b.ORDER_ID
JOIN order_item_options c ON b.ORDER_ITEM_ID = c.ORDER_ITEM_ID
JOIN prices d ON c.PRICE_ID = d.PRICE_ID AND d.TO_OPTION_ID IN (189, 190, 191)
JOIN product_options_vw e ON d.TO_OPTION_ID = e.OPTION_ID AND e.OPTION_ID IN (189, 190, 191)
WHERE
e.OPTION_ID IN (189, 190, 191)
I see both queries are same with exception of adding one more column in second query and it is strange that the second one works for you which shouldn't be.i recommend using try cast and try below steps
Try cast throws null,if conversion fails and you can see
Try_CAST(RTRIM(SUBSTRING(e.NAME, CHARINDEX('-', e.NAME) +2 , 2)) AS int)
You can see all columns with null which might be causing the issue.further error message clearly says ..
Conversion failed when converting the nvarchar value 'ni' to data type int.
So you can try using only below part and see which one gives you an output of ni
RTRIM(SUBSTRING(e.NAME, CHARINDEX('-', e.NAME) +2 , 2))
Related
I'm trying to build my own custome pivot view where i can use multiple aggregations and more than one column to filter values on. To do that i tried to get an simple example up and running to understand how to do that. Unfortunately i'm running in an limitation i don't know how to move around.
I have to admin that im pretty new to Snowflake and this might be solvable by the existing pivot function that i just don't understand right to use.
First of all let my give you an example what i want to achive:
given a Table like so:
create temporary table test_table(PLACE varchar, TYPE varchar, METER Int, PRICE Int);
insert overwrite into test_table values
('Place_A', 'flat', 1, 10),
('Place_A', 'flat', 2, 11),
('Place_A', 'house', 3, 12),
('Place_A', 'house', 4, 13),
('Place_B', 'flat', 5, 14),
('Place_B', 'flat', 6, 15),
('Place_B', 'flat', 7, 16),
('Place_B', 'house', 8, 17),
('Place_B', 'house', 9, 18),
('Place_B', 'house', 10, 19);
i want to produce the following:
PLACE
FLAT_AVG
FLAT_MAX
HOUSE_AVG
HOUSE_MAX
And More ...
Place_A
1.5
11
3.5
13
...
Place_B
6.0
16
9.0
19
...
By using something like that
With
"AGG" As (Select PLACE, TYPE, AVG(METER) AS "AVG(METER)", MAX(PRICE) AS "MAX(PRICE)", COUNT(PRICE) From test_table Group By PLACE, TYPE)
Select
PLACE,
(Select "AVG(METER)" FROM "AGG" Where PLACE = "O".PLACE AND TYPE = 'flat') AS "FLAT_AVG",
(Select "MAX(PRICE)" FROM "AGG" Where PLACE = "O".PLACE AND TYPE = 'flat') AS "FLAT_MAX",
(Select "AVG(METER)" FROM "AGG" Where PLACE = "O".PLACE AND TYPE = 'house') AS "HOUSE_AVG",
(Select "MAX(PRICE)" FROM "AGG" Where PLACE = "O".PLACE AND TYPE = 'house') AS "HOUSE_MAX"
FROM test_table as "O" Group By PLACE
The problem is that "O".PLACE seams not to work there. I just get an Error saying:
SQL compilation error: Unsupported subquery type cannot be evaluated
I tried something similar in mariadb where a subquery like that would to the trick. I wonder what i have to do in snowflake to get something like that running?
I also tried the pivot function like so:
With
"P1" as (select *
from (Select PLACE, TYPE, METER From test_table)
pivot(AVG(METER) for TYPE in ('flat', 'house'))
as p1 (PLACE, FLAT_AVG, HOUSE_AVG)),
"P2" as (select *
from (Select PLACE, TYPE, PRICE From test_table)
pivot(MAX(PRICE) for TYPE in ('flat', 'house'))
as p1 (PLACE, FLAT_MAX, HOUSE_MAX))
Select "P1".PLACE,
FLAT_AVG, FLAT_MAX,
HOUSE_AVG, HOUSE_MAX
From "P1"
inner join "P2" on "P1".PLACE = "P2".PLACE
This did the trick for my example but like i sad in the beginning i want more than on column to filter on. How would i expand that if there was a second Column like TYPE? I have a clear idea how to do that with my first approach but not with the pivot function.
Let me know what you think i can try next.
Thanks for reading and trying to help!
SQL compilation error: Unsupported subquery type cannot be evaluated
I tried something similar in mariadb where a subquery like that would to the trick. I wonder what i have to do in snowflake to get something like that running?
Resolving correlated subquery with conditional aggregation:
With AGG As (
Select PLACE
,TYPE
,AVG(METER) AS "AVG(METER)"
,MAX(PRICE) AS "MAX(PRICE)"
,COUNT(PRICE)
From test_table
Group By PLACE, TYPE
)
Select
O.PLACE
,MAX(IFF(A.TYPE='flat', "AVG(METER)", NULL)) AS FLAT_AVG
,MAX(IFF(A.TYPE='flat', "MAX(PRICE)", NULL)) AS FLAT_MAX
,MAX(IFF(A.TYPE='house', "AVG(METER)", NULL)) AS HOUSE_AVG
,MAX(IFF(A.TYPE='house', "MAX(PRICE)", NULL)) AS HOUSE_MAX
FROM test_table as O
LEFT JOIN AGG A
ON A.PLACE = O.PLACE
GROUP BY O.PLACE;
db<>fiddle demo MariaDB
In my table country :
name|gdp|city
-------------
S.A |60 |amr
S.A |60 |amb
US |200|ken
US |70 |mas
aus |80 |po
aus |90 |tr
I want to get the country whose gdp is lower than 100.
when I use (2) it doesnt work and gives error because it returns multiple values which have to be compared to where condition.when I use (1) it works even though the sub query still gives back multiple values which are compared to 100.
What is the logic behind it please explain because I am new to sql. how is subquery in (1) different from (2)
(1)
SELECT DISTINCT
name
FROM country a
WHERE 100 > (SELECT SUM(gdp)FROM country b WHERE a.name = b.name);
(2)
SELECT DISTINCT
name
FROM country a
WHERE 100 > (SELECT SUM(gdp)FROM country b GROUP BY name);
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
When you are using (2): You are getting multiple values in the Subquery, as you are not having WHERE condition. You are getting multiple countries and corresponding sum. So, you are getting error.
When you are using (1): You are getting single value in Subquery, as you are getting single gdp as you are applying filter at country level. For each country, you are getting single sum only. So, you are not getting error.
The sub-query in query number 1 does not return multiple values. What you have there is called a "correlated sub-query". The sub-query has a WHERE clause that relates the results of the sub-query (the "inner" query) to the main query (the "outer" query). It's this bit WHERE a.name = b.name. Functionally, that query is run on a row-by-row basis where the name values match, and the sub-query only returns the single result for that name value. You'll notice that you can't run the sub-query by itself, because it needs to get the name value from the outer query in order to work.
In query number 2, if you run the sub-query by itself, it will return a list of summed gdp values. One column, with several rows. The GROUP BY clause is telling the query to SUM the results by name, but the result set doesn't contain the name value, so it's just a list of numbers. The outer query has no way of knowing which row of that result set you want to compare to 100, and so it throws the error that you received.
Seems like you're after a HAVING here:
CREATE TABLE dbo.Country ([name] varchar(3),
gdp smallint,
city varchar(3));
INSERT INTO dbo.Country (name,
gdp,
city)
VALUES ('S.A', 60, 'amr'),
('S.A', 60, 'amb'),
('US ', 200, 'ken'),
('US ', 70, 'mas'),
('aus', 80, 'po '),
('aus', 90, 'tr ');
GO
SELECT C.name
FROM dbo.Country C
GROUP BY C.name
HAVING SUM(C.gdp) < 100
GO
DROP TABLE dbo.Country;
As you want rows where "the country gdp is lower than 100" this returns no rows, as there are no countries where the SUM of the gdp is lower than 100. (S.A has 120, US has 270, and aus has 170).
If the gbp of a Country (not the City) is worked out differently, you may need to use a different aggregate function (AVG, MAX?) or a completely different method. if so, you should explain which rows you are expecting in your question and why.
So I'm trying to convert an Excel table into SQL and I'm having difficulty coming up with the last 2 columns. Below, find my Excel table that is fully functional (in green) and a table for the code that I have in SQL so far (in yellow). I need help replicating columns C and D, I pasted the Excel formula I'm using so you can understand what I'm trying to do:
Here's the code that I have so far:
WITH
cte_DistinctScheduling AS (
SELECT DISTINCT
s.JobNo
FROM
dbo.Scheduling s
WHERE
s.WorkCntr = 'Framing')
SELECT
o.OrderNo,
o.Priority AS [P],
SUM(r.TotEstHrs)/ROUND((8*w.CapacityFactor*(w.UtilizationPct/100)),2) AS
[Work Days Left],
Cast(GetDate()+ROUND(SUM(r.TotEstHrs)/ROUND((8*w.CapacityFactor*
(w.UtilizationPct/100)),2),3) AS DATE) AS DueDate
FROM OrderDet o JOIN cte_DistinctScheduling ds ON o.JobNo = ds.JobNo
JOIN OrderRouting r ON o.JobNo = r.JobNo
JOIN WorkCntr w ON r.WorkCntr = w.ShortName
WHERE r.WorkCntr = 'Framing'
AND o.OrderNo NOT IN ('44444', '77777')
GROUP BY o.OrderNo, o.Priority, ROUND((8*w.CapacityFactor*
(w.UtilizationPct/100)),2)
ORDER BY o.Priority DESC;
My work days left column in SQL gets the right amount for that particular row, but I need it to sum itself and everything with a P value above it and then add that to today's date, while taking workdays into account. I don't see a Workday function in SQL from what I've been reading, so I'm wondering what are some creative solutions? Could perhaps a CASE statement be the answer to both of my questions? Thanks in advance
Took me a while to understand how is the Excel helpful, and I'm still having a hard time absorbing the rest, can't tell if it's a me thing or a you thing, in any case...
First, I've mocked up something to test SUM per your rationale, the idea is doing a self-JOIN and summing everything from that JOIN side, relying on the fact that NULLs will come up for anything that shouldn't be summed:
DECLARE #TABLE TABLE(P int, [Value] int)
INSERT INTO #TABLE SELECT 1, 5
INSERT INTO #TABLE SELECT 2, 6
INSERT INTO #TABLE SELECT 3, 2
INSERT INTO #TABLE SELECT 4, 4
INSERT INTO #TABLE SELECT 5, 9
SELECT T1.P, [SUM] = SUM(ISNULL(T2.[Value], 0))
FROM #TABLE AS T1
LEFT JOIN #TABLE AS T2 ON T2.P <= T1.P
GROUP BY T1.P
ORDER BY P DESC
Second, workdays is a topic that comes up regularly. In case you didn't, consider reading a little about it from previous questions, I even posted an answer on one question last week, and the thread as a whole had several references.
Thirdly, we could use table definitions and sample data loaded on SQL itself, something like I did above.
Lastly, could you please check result of UtilizationPct / 100? If that's an integer-like data type, you're probably getting a bad result on it.
I am working on a SELECT statement.
USE SCRUMAPI2
DECLARE #userParam VARCHAR(100)
,#statusParam VARCHAR(100)
SET #userParam = '%'
SET #statusParam = '%'
SELECT ROW_NUMBER() OVER (
ORDER BY PDT.[Name] DESC
) AS 'RowNumber'
,PDT.[Name] AS Project
,(
CASE WHEN (
STY.KanBanProductId IS NOT NULL
AND STY.SprintId IS NULL
) THEN 'KanBan' WHEN (
STY.KanBanProductId IS NULL
AND STY.SprintId IS NOT NULL
) THEN 'Sprint' END
) AS ProjectType
,STY.[Number] StoryNumber
,STY.Title AS StoryTitle
,TSK.[Name] AS Task
,CONVERT(VARCHAR(20), STY.Effort) AS Effort
,CONVERT(VARCHAR(20), TSK.OriginalEstimateHours) AS OriginalEstimateHours
,TSK.STATUS AS STATUS
FROM Task TSK
LEFT JOIN Story STY ON TSK.StoryId = STY.PK_Story
LEFT JOIN Sprint SPT ON STY.SprintId = SPT.PK_Sprint
LEFT JOIN Product PDT ON STY.ProductId = PDT.PK_Product
WHERE TSK.PointPerson LIKE #userParam
AND TSK.STATUS LIKE #statusParam
GROUP BY STY.[Number]
,TSK.STATUS
,STY.Title
,PDT.[Name]
,TSK.CreateDate
,TSK.[Name]
,STY.KanBanProductId
,STY.SprintId
,TSK.OriginalEstimateHours
,STY.Effort
My issue that that although I have the ORDER BY sorting by story number first it is not returning as expected (below is column STY.[Number]):
As you can see it foes from 33 to 4 to 42, I want it in numerical order so that 4 would be between 3 and 5 not 33 and 42. How do I achieve this?
Given the structure of your data (with a constant prefix), probably the easiest way to get what you want is:
order by len(STY.[Number]), STY.[Number]
This orders first by the length and then by the number itself.
Those are strings. Do you really expect SQL Server to be able to identify that there is a number at character 6 in every single row in the result, and instead of ordering by character 6, they pretend that, say, SUPP-5 is actually SUPP-05? If that worked for you, people who expect the opposite behavior (to treat the whole string as a string) would be complaining. The real fix is to store this information in two separate columns, since it is clearly two separate pieces of data.
In the meantime, you can hack something, like:
ORDER BY LEFT(col, 4), CONVERT(INT, SUBSTRING(col, 6, 255)));
As Martin explained, this should be on the outer query, not just used to generate a ROW_NUMBER() - generating a row number alone doesn't guarantee the results will be ordered by that value. And this will only work with additional checks to ensure that every single row has a value following the dash that can be converted to an int. As soon as you have SUPP-5X this will break.
It's sorting by the string in lexicography order. To get numerical ordering you need to extract the number from the string (with substring()) and cast it to integer.
I want to select values from table in range.
Something like this:
SELECT
date_values.date_from,
date_values.date_to,
sum(values.value)
FROM values
inner join date_values on values.id_date = date_values.id
inner join date_units on date_values.id_unit = date_units.id
WHERE
date_values.date_from >= '14.1.2012' AND
date_values.date_to <= '30.1.2012' AND
date_units.id = 4
GROUP BY
date_values.date_from,
date_values.date_to
ORDER BY
date_values.date_from,
date_values.date_to;
But this query give me back only range of days, where is any value. Like this:
14.01.12 15.01.12 66
15.01.12 16.01.12 4
17.01.12 18.01.12 8
...etc
(Here missing 16.01.12 to 17.01.12)
But I want to select missing value too, like this:
14.01.12 15.01.12 66
15.01.12 16.01.12 4
16.01.12 17.01.12 0
17.01.12 18.01.12 8
...etc
I can't use PL/SQL and if can you advise more general solution which can I expand for use on Hours, Months, Years; will be great.
I'm going to assume you're providing date_from and date_to. If so, you can generate your list of dates first and then join to it to get the remainder of your result. Alternatively, you can union this query to your date_values table as union does a distinct this will remove any extra data.
If this is how the list of dates is generated:
select to_date('14.1.2012','dd.mm.yyyy') + level - 1 as date_from
, to_date('14.1.2012','dd.mm.yyyy') + level as date_to
from dual
connect by level <= to_date('30.1.2012','dd.mm.yyyy')
- to_date('14.1.2012','dd.mm.yyyy')
Your query might become
with the_dates as (
select to_date('14.1.2012','dd.mm.yyyy') + level - 1 as date_from
, to_date('14.1.2012','dd.mm.yyyy') + level as date_to
from dual
connect by level <= to_date('30.1.2012','dd.mm.yyyy')
- to_date('14.1.2012','dd.mm.yyyy')
)
SELECT
dv.date_from,
dv.date_to,
sum(values.value)
FROM values
inner join ( select the_dates.date_from, the_dates.date_to, date_values.id
from the_dates
left outer join date_values
on the_dates.date_from = date_values.date_from ) dv
on values.id_date = dv.id
inner join date_units on date_values.id_unit = date_units.id
WHERE
date_units.id = 4
GROUP BY
dv.date_from,
dv.date_to
ORDER BY
dv.date_from,
dv.date_to;
The with syntax is known as sub-query factoring and isn't really needed in this case but it makes the code cleaner.
I've also assumed that the date columns in date_values are, well, dates. It isn't obvious as you're doing a string comparison. You should always explicitly convert to a date where applicable and you should always store a date as a date. It saves a lot of hassle in the long run as it's impossible for things to be input incorrectly or to be incorrectly compared.