SQL Server - Dynamic multi column pivot - sql-server-2012

I have results like this, the Annual is dynamic and may have X amount of ranges
+-----------+---------+-----------+
| Annual | Revenue | Completed |
+-----------+---------+-----------+
| 2020/2021 | 1000 | Yes |
+-----------+---------+-----------+
| 2021/2022 | 2000 | Yes |
+-----------+---------+-----------+
| 2022/2023 | 2500 | No |
+-----------+---------+-----------+
I'm looking to have results like this:
+-----------+-----------+-----------+-----------+
| Annual | 2020/2021 | 2021/2022 | 2022/2023 |
+-----------+-----------+-----------+-----------+
| Revenue | 1000 | 2000 | 2500 |
+-----------+-----------+-----------+-----------+
| Completed | Yes | Yes | No |
+-----------+-----------+-----------+-----------+
Here's what I got so far, I can execute on my local but can't seem to get it to execute on sqlfiddle:
http://sqlfiddle.com/#!18/a4c61/4
I've looked at examples all over stackoverflow but couldn't seem to find an exact case like mine.
Please help if you can, thank you!
Sql version: Microsoft SQL Azure (RTM) - 12.0.2000.8 May 15 2020 00:47:08

Dynamic data recommendations
You can get count and use StringBuilder to generate strSQL.
StringBuilder strSQL = new StringBuilder();
int DataAmount = 3;// a,b,c,d,e
Dictionary<int, string> dic = new Dictionary<int, string>();
dic.Add(0 ,"a");
dic.Add(1, "b");
dic.Add(2, "c");
strSQL.Append("select ");
strSQL.Append(" isnull(a.Column2,b.Column2) Column0,isnull(a.Column1,0) Column1 ");
//dic.Count > 2
for (int i = 1; i < dic.Count; i++)
{
strSQL.Append(",isnull("+dic[i]+".Column1,0) Column"+i+" ");
}
strSQL.Append(" from (SELECT ID,Column1,Column2 FROM [dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed]))b where ID=1) a ");
for (int j = 1; j < dic.Count; j++)
{
strSQL.Append("FULL JOIN (SELECT ID, Column1, Column2 FROM[dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed])) " + dic[j] + " where ID = " + (j+1)+ ") " + dic[j] + " on a.Column2 = " + dic[j]+".Column2 ");
}
// SqlHelper.ExcuteNoQuery(strSQL.ToString());
SQL Query Part
Data in my azure sqlserver.
When run SELECT *FROM [dbo].[20200618] .
Run SELECT ID,Column1,Column2 FROM [dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed]))b where ID=2.
Finally, run the code.
select isnull(a.Column2,b.Column2) Column0,isnull(a.Column1,0) Column1,isnull(b.Column1,0) Column2,isnull(c.Column1,0) Column3 from (SELECT ID,Column1,Column2 FROM [dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed]))b where ID=1) a FULL JOIN (SELECT ID,Column1,Column2 FROM [dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed]))b where ID=2) b on a.Column2=b.Column2 FULL JOIN (SELECT ID,Column1,Column2 FROM [dbo].[20200618] UNPIVOT(Column1 for Column2 in ([Annual],[Revenue],[Completed]))b where ID=3) c on a.Column2=c.Column2

Related

SQL: SUM on Aggregate columns

I have this query:
SELECT
spend_codes.spend_code,
SUM(orders.shipping_cost + orders.sales_tax + orders.manual_total + orders.fees) as order_total,
SUM(ordered_items.fees + ordered_items.price * ordered_items.quantity) as item_total
FROM spend_codeables
LEFT JOIN spend_codes
ON spend_codeables.spend_code_id = spend_codes.id
LEFT JOIN orders
ON spend_codeables.spend_codeable_id = orders.id AND spend_codeables.spend_codeable_type = 'App\Order'
LEFT JOIN ordered_items
ON spend_codeables.spend_codeable_id = ordered_items.id AND spend_codeables.spend_codeable_type = 'App\OrderedItem'
GROUP BY
spend_codes.spend_code;
Which has this result:
+--------------+---------------+--------------+
| spend_code | order_total | item_total |
|--------------+---------------+--------------|
| 1230131391 | $362.00 | <null> |
| A12345 | <null> | <null> |
| B29393 | <null> | $374.28 |
+--------------+---------------+--------------+
However, I'd like to add order_total and item_total in order to get just total.
So I'd expect this:
+--------------+---------------+
| spend_code | total |
|--------------+---------------+
| 1230131391 | $362.00 |
| A12345 | <null> |
| B29393 | $374.28 |
+--------------+---------------+
Doing this did not work:
SUM(orders.shipping_cost + orders.sales_tax + orders.manual_total + orders.fees) +
SUM(ordered_items.fees + ordered_items.price * ordered_items.quantity) as total
Another monkeywrench is that the type of the numbers is money not integer.
Anyone would be able to help?
If any of the 2 sums returns NULL then the result of the sum of the sums will also be NULL because NULL + anything returns NULL.
Use SUM() only once:
SUM(
orders.shipping_cost +
orders.sales_tax +
orders.manual_total +
orders.fees +
ordered_items.fees +
ordered_items.price * ordered_items.quantity
) as total
If any of the columns involved may also be NULL use COALESCE() like COALESCE(orders.shipping_cost, 0::money)
you do it in next step using sub query and I used coalesce() for avoid null
with cte as ( SELECT
spend_codes.spend_code,
SUM(orders.shipping_cost + orders.sales_tax + orders.manual_total + orders.fees) as order_total,
SUM(ordered_items.fees + ordered_items.price * ordered_items.quantity) as item_total
FROM spend_codeables
LEFT JOIN spend_codes
ON spend_codeables.spend_code_id = spend_codes.id
LEFT JOIN orders
ON spend_codeables.spend_codeable_id = orders.id AND spend_codeables.spend_codeable_type = 'App\Order'
LEFT JOIN ordered_items
ON spend_codeables.spend_codeable_id = ordered_items.id AND spend_codeables.spend_codeable_type = 'App\OrderedItem'
GROUP BY
spend_codes.spend_code
) select spend_code,coalesce(order_total,0)+coalesce(item_total,0) as total from cte

Create a flag in a left join

I am trying to create a new column (a sort of identifier flag) for the "Null" rows resulting of my following left join :
with CTE (...) as (
... unrelated code
) select * from CTE
left join (select columnID from table1) Pu
on CTE.columnID = Pu.columnID
left join (select case when bz.column2 is null then 'null test is working' else columnID2, column2 end FROM table2) Bz
ON CTE.columnID2 = Bz.columnID2
This code is working properly when I don't try to use a 'case when'. Actually, you could very well ignore the first left join.
My purpose would be to be able test the left join result while doing it, and act depending on the result :
If the left join result give a null row : creation of a flag column for the row,
If the left join result give a normal row : the left join is done normally, and the flag column is empty (as I suspect it cant be un existent).
I'd be glad if you could give me a hand!
EDIT : tables example:
CTE
| columnID | columnID2 | InformationsCTE |
| ab | mp | randominfo1 |
| ac | ma | randominfo2 |
| ae | me | randominfo3 |
| ad | mb | randominfo4 |
table2
| columnID2 | InformationsTable2 |
| mp | randominfo5 |
| ma | randominfo6 |
| me | randominfo7 |
Result after the second left join :
new CTE
| columnID | columnID2 | InformationsCTE | InformationsTable2| FLAG |
| ab | mp | randominfo1 | randominfo5 | OK |
| ac | ma | randominfo2 | randominfo6 | OK |
| ae | me | randominfo3 | randominfo7 | OK |
| ad | mb | randominfo4 | NULL | NOK |
Just use
T-SQL:
SELECT ISNULL(Column_to_check,'flag') FROM SomeTable
PL/SQL:
SELECT NVL(Column_to_check,'flag') FROM SomeTable
Also use NVL2 as below if you want to return other value from the Column_to_check:
NVL2(Column_to_check, value_if_NOT_null, value_if_null )
Would it not be more practical to SELECT this column, use the ISNULL operator and just use a straightforward LEFT JOIN? I feel like you're over-complicating it a bit.
Something like:
with CTE (...) as (
... unrelated code
)
SELECT CTE.*, NVL(bz.InformationsTable2, 'TEST OK')
FROM CTE
LEFT JOIN table2 Bz ON CTE.columnID2 = Bz.columnID2
EDIT: Based on your example table, if you join on the ID, then use NVL on the other column, it should work for you.
Here is an example I prepared for a previous question: SQL Fiddle
Example was build in mysql, so beware syntax, but logically it works the same way
Why the joins? It seems you only want to look up data in other table, for which you'd use EXISTS or IN:
with cte (...) as (
... unrelated code
)
select
cte.*,
case when columnid in (select columnid from table1) then 'okay' else 'fail' end as test1,
case when columnid2 in (select columnid2 from table2) then 'okay' else 'fail' end as test2
from cte;

SQL Server: How to calculate the percentage of a table through a join using multiple tables?

I need to find the percentage of assets that have work order tickets that are open. The following shows the structure of the tables with some dummy data.
Assets Table
+---------------------+
| Asset_Number_PK |
+---------------------+
| 56412 |
| 56413 |
| 56414 |
| 56415 |
+---------------------+
Statuses Table
+-------------------++-------------------+
| Open_Number || Closed_Number |
+-------------------++-------------------+
| 5 || 8 |
| 2 || 7 |
| 9 || 11 |
+-------------------++-------------------+
Work_Orders Table
+-------------------++-------------------++-------------------+
| WO_Status_Number || WO_Description || Asset_number |
+-------------------++-------------------++-------------------+
| 5 || Fix air handler || 56415 |
| 5 || Replace chiller || 56412 |
| 5 || 2 fans 2nd fl || 56414 |
| 7 || 4 fans 2nd fl || 56414 |
| 7 || Fix Air duct || 56413 |
+-------------------++-------------------++-------------------+
I've tried the following two queries to calculate the percentage but my output is incorrect:
sql = "SELECT DISTINCT COUNT(Asset_Number_PK) AS tol_open_assets "
sql = sql & "FROM Assets WHERE Asset_Number_PK IN (SELECT a.Asset_Number "
sql = sql & "FROM Work_Orders a, Statuses b, Assets c "
sql = sql & "WHERE a.WO_Status_Number = b.Open_Number AND a.Asset_Number = c.Asset_Number_PK)"
myRs.Open sql,con,1,2
if myRs.eof then
tol_open_assets = 0
else
tol_open_assets = myRs("tol_open_assets")
end if
myRs.Close
sql = "SELECT COUNT(Asset_Number_PK) AS tol_assets FROM Assets"
myRs.Open sql,con,1,2
tol_assets = myRs("tol_assets")
myRs.Close
if tol_assets = 0 then
tol_asset_percentage = 0
else
tol_asset_percentage = round(100*tol_open_assets/tol_assets,0)
end if
response.write(tol_asset_percentage)
Your result (75 based on your data) appears correct.
You could determine the percentage of open assets in a single query as well:
SELECT
PercentOpen = SUM(COALESCE(openOrder.IsOpen, 0)) / COUNT(*) * 100
FROM Assets a
OUTER APPLY (
SELECT TOP 1 IsOpen = CAST(1 AS DECIMAL(10,2))
FROM Work_Orders wo
JOIN Statuses s ON wo.WO_Status_Number = s.Open_Number
WHERE wo.Asset_Number = a.Asset_Number_PK
) openOrder
SQL Fiddle

T-sql using values from one table and displaying them in different columns

My database Table looks like:
ID | INDEX | Value |
1 | 0 | 3 |
1 | 1 | 5 |
1 | 2 | 7 |
2 | 0 | 4 |
2 | 1 | 6 |
2 | 2 | 2 |
What I want my output to look like is the difference of the values column based on their index
i.e. value(id=2,index = i) - value(id = 1, index = i) so the output table will look like
INDEX | Delta Value |
0 | 1 |
1 | 1 |
2 | -5 |
My attempt at solving this problem is as follows:
SELECT Top 6
col1.value column1,
col2.value column2,
col2.value - col1.value
FROM My_Table col1
INNER JOIN My_Table col2
ON col1.index = col2.index
WHERE col1.id = 1
OR col2.id = 2
I know there are problems with this query. But I just haven't been able to produce the output that I want. Any help is appreciated.
You can do this by join
select
t1.ind, t1.value - t2.value as delta
from My_Table as t1
inner join My_Table as t2 on t2.id = 2 and t2.ind = t1.ind
where t1.id = 1
Or by simple aggregate:
select
ind, sum(case id when 1 then 1 when 2 then -1 end * value) as delta
from My_Table
group by ind
Do you want something like this?
select
col1.value column1,
col2.value column2,
col2.value - col1.value AS delta
From My_Table col1
INNER JOIN My_Table col2
ON col1.index = col2.index
AND col2.id = 2
where col1.id = 1
have a look at mine
select t1.[index],
t2.value-t1.value
from my_table t1 inner join my_table t2
on t1.[index] = t2.[index]
where t1.id = 1 and t2.id = 2
order by t1.[index]

Opposite of SELECT TOP

Transact-SQL has a handy SELECT TOP 4 [whatever] FROM.........
I want to make a SELECT query returning the last "n" entries from a table instead of the first ones.
This is the query I would use to return the first four items entered at the table, using SELECT TOP:
sql = "SELECT TOP 4 [news_title], [news_date_added], [news_short_description],
[news_ID] FROM [Web_Xtr_News] WHERE ([news_type] = 2 OR [news_type] = 3) AND
[news_language] = '" + Language + "' ORDER BY [news_ID] ASC"
I need to return the last four.
Change the order of the table from ASC to DESC.
It's exactly this: http://www.sqlfiddle.com/#!3/6c813/1
with bottom as(
select top 4 *
from tbl
order by n desc
)
select *
from bottom
order by n
Data source:
| N |
|----|
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
Output:
| N |
|----|
| 7 |
| 8 |
| 9 |
| 10 |
Continue to use TOP, and reverse the order:
SELECT TOP 4 [news_title],
[news_date_added],
[news_short_description],
[news_ID]
FROM [Web_Xtr_News]
WHERE ([news_type] = 2
OR [news_type] = 3)
AND [news_language] = #Language
ORDER BY [news_ID] DESC
(It was rewritten to use parameters of course. Your original is vulnerable to SQL injection.)
You can reverse the ordering by using DESC instead of ASC at the end of your query.
A quick way to select batches from a list is to create a unique identifier like a (ROW ID) as (1, 2, 3, etc.) and create a nested query.
Select *
from
(select
row_number() as id
,column1
,column2
from
table1
)as D
where D.id (<>=! between)