Custom report who lines is columns in SQL Server - sql

I need make one custom report in SQL Server. I have one table called ProductsTable, in this table have the products tables of prices.
| id | description |
| 1 | TABLE A |
| 2 | TABLE B |
| 3 | TABLE C |
| 4 | TABLE D |
Now, i have the table ProductsTablePrices with all products prices and his tables.
| id | idproduct | idtable | price |
| 1 | 1 | 1 | 1.00 |
| 1 | 1 | 2 | 1.50 |
| 1 | 1 | 3 | 2.00 |
| 1 | 1 | 4 | 5.00 |
And finally, i have the Products table.
| id | name |
| 1 | Paper |
I need create one select to get one result like this...
| name | TABLE A | TABLE B | TABLE C | TABLE D |
| Paper | 1.00 | 1.50 | 2.00 | 5.00 |
Thanks!

Try this:
SELECT
P.name,
SUM(CASE WHEN PT.description = 'TABLE A' THEN PP.price END) [TABLE A],
SUM(CASE WHEN PT.description = 'TABLE B' THEN PP.price END) [TABLE B],
SUM(CASE WHEN PT.description = 'TABLE C' THEN PP.price END) [TABLE C],
SUM(CASE WHEN PT.description = 'TABLE D' THEN PP.price END) [TABLE D]
FROM Products P
JOIN ProductsTablePrices PP
ON P.id = PP.idproduct
JOIN ProductsTable PT
ON PP.idtable = PT.id
GROUP BY P.name

I Use that to resolve my problem.
DECLARE #NameColumnTable VARCHAR(100)
DECLARE #CountColumns INT
DECLARE #ColumnsPivot varchar(6000)
DECLARE #Flag INT
SELECT #CountColumns = count(*) FROM ProductsTable
SET #Flag = 0
WHILE (#Flag <= #CountColumns)
BEGIN
SELECT #NameColumnTable = Name FROM ProductsTable
WHERE id = #Flag
ORDER BY Name ASC
SET #ColumnsPivot = ISNULL(#ColumnsPivot,'') + 'SUM(CASE WHEN PT.description = ''' + #NameColumnTable + ''' THEN PP.price END) [' + #NameColumnTable + ']'
IF #Flag <> #CountColumns
BEGIN
SET #ColumnsPivot = #ColumnsPivot + ','
END
SET #ColumnsPivot = #ColumnsPivot + CHAR(13)
SET #Flag = #Flag + 1
END
DECLARE #SQLFINAL VARCHAR(5000)
SET #SQLFINAL = 'SELECT
P.name,
' + #ColumnsPivot + '
FROM Products P
JOIN ProductsTablePrices PP
ON P.id = PP.idproduct
JOIN ProductsTable PT
ON PP.idtable = PT.id
GROUP BY P.name
ORDER BY P.name'
EXEC(#SQLFINAL)
I don't know if is a good practice, but works great and performance as well.
Thanks!

Related

SQL joining the same table multiple times on the same column with differing restrictions

I have 2 tables
| Categories | | Products |
:--------------------: :------------------------:
| Id int | | Id int |
| Name nvarchar(max) | | Name vnarchar(max) |
| ApprovedForRelease bit |
| ApprovedForRecall bit |
| CategoryId int |
I need to get the counts for products approvedForRealease, not approvedForRelease, ApprovedForRecall and not ApprovedForRecall for all Categories
Something like this
| Category | #Released | #NotReleased | #Recalled | #NotRecalled |
:----------------------------------------------------------------:
| Arts | 5 | 1 | 3 | 4 |
| Crafts | 13 | 7 | 7 | 8 |
My query looks like this
SELECT
Category = cat.Name,
#Releases = Count(released.Id),
#NotReleased = Count(notReleased.Id),
#Recalled = Count(recalled.Id),
#NotRecalled = Count(notRecalled.Id),
-- Selected product ids
releasedIds = STRING_AGG(released.Id, ', '),
notReleasedIds = STRING_AGG(notReleased.Id, ', '),
recalledIds = STRING_AGG(recalled.Id, ', '),
notRecalledIds = STRING_AGG(notRecalled.Id, ', ')
FROM
Categories as cat
LEFT JOIN Products as released ON released.CategoryId = cat.Id AND released.ApprovedForRelease = 1
LEFT JOIN Products as notReleased ON released.CategoryId = cat.Id AND notReleased.ApprovedForRelease = 0
LEFT JOIN Products as recalled ON released.CategoryId = cat.Id AND recalled.ApprovedForRecall = 1
LEFT JOIN Products as notRecalled ON released.CategoryId = cat.Id AND notRecalled.ApprovedForRecall = 0
GROUP BY
cat.Name
I noticed that product counts are a little too much, so I added the Selected product ids columns to check what actually gets joined and noticed that the joined tables have the same rows multiple times
Example of the result I would get:
| Category | #Released | #NotReleased | #Recalled | #NotRecalled | releasedIds | notReleasedIds | recalledIds | notRecalledIds |
:------------------------------------------------------------------------------------------------------------------------------:
| Arts | 3 | 3 | 3 | 3 | 1, 2, 3 | 4, 5, 6 | 10, 10, 10 | 6, 6, 6 |
| Crafts | 2 | 2 | 4 | 2 | 25, 26 | 96, 98 | 7, 8, 7, 8 | 9, 9 |
Could somebody explain to me what's happening and why do some products get joined multiple times?
And Is there a way to achieve my desired result without using subqueries like:
SELECT
Category = cat.Name,
#Releases = (SELECT COUNT (Id) FROM PRODUCTS WHERE CategoryId = cat.Id AND ApprovedForRelease = 1),
#NotReleased = (SELECT COUNT (Id) FROM PRODUCTS WHERE CategoryId = cat.Id AND ApprovedForRelease = 0),
#Recalled = (SELECT COUNT (Id) FROM PRODUCTS WHERE CategoryId = cat.Id AND ApprovedForRecall= 1),
#NotRecalled = (SELECT COUNT (Id) FROM PRODUCTS WHERE CategoryId = cat.Id AND ApprovedForRecall= 0)
FROM Categories
Use conditional aggregation:
SELECT c.name as category,
SUM(CASE WHEN p.ApprovedForRelease = 1 THEN 1 ELSE 0 END) as released,
SUM(CASE WHEN p.ApprovedForRelease = 0 THEN 1 ELSE 0 END) as not_released,
SUM(CASE WHEN p.ApprovedForRecall = 1 THEN 1 ELSE 0 END) as recalled,
SUM(CASE WHEN p.ApprovedForRecall = 0 THEN 1 ELSE 0 END) as not_recalled
FROM Categories c LEFT JOIN
Products p
ON p.CategoryId = cat.Id
released.ApprovedForRelease = 1
GROUP BY c.name;

Can I update many tables based on another table containing the table names on a column?

Current working on a project where I have to update the data on 85 tables replacing the current empty string to a NULL value.
it is a simple SQL query for this but since it is a sensitive environment, if anything goes wrong, we need to revert this.
The main idea was to create a table to save the data to roll back. but I am trying to avoid creating 85 tables.
I will give a smaller example:
there is 4 tables
------------------------------------
| airplane |
------------------------------------
| air_ID | color | tail_number |
------------------------------------
| 1 | red | |
| 2 | green | |
| 3 | black | 21AF |
------------------------------------
------------------------------------
| bus |
------------------------------------
| bus_ID | color | tag_number |
------------------------------------
| 1 | red | AAY-464 |
| 2 | green | |
| 3 | black | |
------------------------------------
------------------------------------
| train |
------------------------------------
| tr_ID | color | designated_name |
------------------------------------
| 1 | red | 99212 |
| 2 | green | |
| 3 | black | |
------------------------------------
------------------------------------
| Cruise_Ship |
------------------------------------
| sea_ID | color | hull_number |
------------------------------------
| 1 | red | |
| 2 | green | MAGDA |
| 3 | black | |
------------------------------------
So I created a temp table with the data
-------------------------------------------------
| update_table |
-------------------------------------------------
| table_name | ID_colname | ID | col_name |
-------------------------------------------------
| airplane | air_ID | 1 | tail_number |
| airplane | air_ID | 2 | tail_number |
| bus | bus_ID | 2 | tag_number |
| bus | bus_ID | 3 | tag_number |
| train | tr_ID | 2 |designated_name|
| train | tr_ID | 3 |designated_name|
|Cruise_Ship | sea_ID | 1 | hull_number |
|Cruise_Ship | sea_ID | 3 | hull_number |
-------------------------------------------------
With this table I was trying to generate a dynamic SQL to update all the tables with one call
SET #SQLString = N'UPDATE #table
SET #value = '+ #empty +'
where #key = #id';
SET #ParmDefinition = N'#table nvarchar(max),
#value nvarchar(max) ,
#key nvarchar(max) ,
#id int';
DECLARE #table nvarchar(255)
DECLARE #value nvarchar(255)
DECLARE #key nvarchar(255)
DECLARE #id int
select #table = table_name, #id = ID, #key = ID_colname , #value = col_name from update_table
EXECUTE sp_executesql
#SQLString
,#ParmDefinition
,#table
,#value
,#key
,#id
;
But this is not working, anyone has a idea on how to improve this query?
This is a high profile environment and the developers are not the ones executing the code, so it need to be customer proof.
The code is ran overnight to not disturb daytime operations.
According to SQL Server documentation:
If a SELECT statement returns more than one row and the variable references a non-scalar expression, the variable is set to the value returned for the expression in the last row of the result set.
That means your variables were assigned with the values from the last row only.
Therefore only [Cruise_Ship].[hull_number] will be pass to sp_executesql and that is the only column being updated with your script.
To store mutliple values, a table variable should be used.
sp_executesql does accept parameters and it is used for building dynamic queries.
However I don't think you can pass table-valued variable as parameters.
added: Check this for how to pass table-valued variable to sp_executesql.
This is where your code goes wrong.
select #table = table_name, #id = ID, #key = ID_colname , #value = col_name from update_table
I know this is not elegant but the following code should do the job.
And I whould suggest to wrap it in a TRANSACTION whether you did a backup or not.
DECLARE #empty NVARCHAR(10)
SET #empty = 'NULL'
SELECT
'UPDATE ' + s.name + '.' + t.name + '
SET [' + c.name + '] = ' + #empty + '
WHERE LTRIM([' + c.name + ']) = ''''
END;'
FROM sys.tables t
INNER JOIN sys.schemas s
ON t.schema_id = s.schema_id
INNER JOIN sys.columns c
ON t.object_id = c.object_id
ORDER BY
s.name
,t.name
,c.column_id
I agree with Adam Yam, I just expanded the answer to include the needed data from the update_tables.
This way the execution of the SQL will only happen for those 8 entries on the table.
the result is the update to the 4 tables from the example correctly.
DECLARE #currentId INT
SELECT #currentId = MIN(tabl.ID) from udpate_table tabl
DECLARE #sql NVARCHAR(MAX)
WHILE (1 = 1)
BEGIN
--- execute for the current pk
BEGIN
SELECT #sql =
'UPDATE ' + s.name + '.' + t.name + '
SET [' + c.name + '] = ''''
where ' + s.name + '.' + t.name + '.' + tab.ID_colname + ' = ' + convert (varchar(20), tab.ID) + ' '
FROM sys.tables t
INNER JOIN sys.schemas s
ON t.schema_id = s.schema_id
INNER JOIN sys.columns c
ON t.object_id = c.object_id
JOIN update_table tab
on t.name = tab.table_name
and c.name = tab.col_name
and tab.ID = #currentId
ORDER BY
tab.ID
,s.name
,t.name
,c.column_id
END
exec sp_executesql #sql
-- select the next id to handle
SELECT TOP 1 #currentId = tabl.ID
FROM update_table tabl
WHERE tabl.ID > #currentId
ORDER BY tabl.ID
IF ##ROWCOUNT = 0 BREAK;
END

how to convert multiple rows to single row?

How to get sql with Query
I have three tables in the form below
Table A
ID | Title |Count
--- |-------- |-----
1 |Mouse | 50
2 |pen | 60
Table B
ID | CompName|
--- |---------|
1 |Comp1 |
2 | Comp2 |
3 |Comp3 |
Table T
|---------------------|
|IDA | IDB | CountT|
|-------|-----|-------|
|1 | 1 | 5 |
|2 | 1 | 6 |
|1 | 2 | 7 |
+---------------------+
I want to make such a report
| object | Copm1 | Comp2 | Comp3 |Sum|remaining |
|--------|-------|-------|-------|---|--------- |
| Mouse | 5 | 7 | 0 | 12| 38 |
| pen | 6 | 0 | 0 | 6 | 54 |
My answer to my question
I was able to get the final answer using the PIVOT function
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') +
QUOTENAME(CompName) from B
set #SQLQuery=N'select pvt.title as object, ' + #PivotColumns + '
FROM
(select title, CountT,CompName
from T
inner join A on T.IDA = A.ID
inner join B on B.ID = T.IDA) AS src PIVOT
(
max(CountT)
FOR CompName IN (' + #PivotColumns + ')
) AS pvt;'
EXEC sp_executesql #SQLQuery
select a.title as object,
sum(case when b.id=1 then T.countT else 0 end) as Comp1,
sum(case when b.id=2 then T.countT else 0 end) as Comp2,
sum(case when b.id=3 then T.countT else 0 end) as Comp3,
sum(t.countt) as 'Sum',
max(a.count)-sum(t.countt) as Remaining
from TableT t
inner join tableA A on a.id=t.IDA
inner join TableB b on b.id=t.IDB
group by a.Title
http://rextester.com/l/sql_server_online_compiler

Using the crosstab() function while appending to the column names & using boolean logic

I'm using Postgresl 9.2
I need a crosstab table created from this:
select id, imp from sg_imp_id (There are A LOT more rows than this)
id | imp |
-------+-------+
1 | 111 |
2 | 111 |
2 | 121 |
2 | 122 |
3 | 131 |
4 | 154 |
.... ....
Like this:
id | x111 | x121 | x122 | x131 | x154 |
---------+------+------+------+------+------+
1 | 1 | 0 | 0 | 0 | 0 |
2 | 1 | 1 | 1 | 0 | 0 |
3 | 0 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 0 | 0 | 1 |
With a column for every imp row and whenever an id has that imp number, to place a 1. If it doesn't have that imp number
then a 0 should be in that spot. I have very limited knowledge of the crosstab() function. There are currently very many different rows of "x111,x112,x113" values so using the case clause won't really be probable.
Not sure about crosstab() function, but you can always pivot manually:
select
id,
max(case when imp = 111 then 1 else 0 end) as x111,
max(case when imp = 121 then 1 else 0 end) as x121,
max(case when imp = 122 then 1 else 0 end) as x122,
max(case when imp = 131 then 1 else 0 end) as x131,
max(case when imp = 154 then 1 else 0 end) as x154
from Table1
group by id
sql fiddle demo
Please Try it , It is some useful to you
DECLARE #SQL VARCHAR(MAX)
SELECT #SQL = ISNULL(#SQL, '') + ',
(
SELECT
id
FROM stacky.dbo.crosstab t' + Rownum + '
WHERE t' + Rownum + '.id = t.id AND t' + Rownum + '.imp = ''' + imp + '''
)
[x' + imp + ']'
FROM ( SELECT imp, CONVERT(VARCHAR, ROW_NUMBER() OVER (ORDER BY imp)) [RowNum] FROM stacky.dbo.crosstab GROUP BY imp) d
SET #SQL = 'SELECT id ' + #SQL + ' FROM (SELECT DISTINCT id FROM stacky.dbo.crosstab) t'
EXEC (#SQL)
Please Change DAtabase name
and (id int),(imp varchar(20)) type
Other then use casting

Transpose rows to column and join table

Hi I have two tables Attribute, Instance
Attribute Table
id | key_info | value_info
2 | amount | 1009
2 | currency | USD
2 | Level | Level 5
3 | amount | 2017
3 | currency | CAD
Instance Table
id | status
2 | Pending
3 | Approved
I want to join two tables like this-
New table
id | amount | currrency | level | status
2 | 1001 | USD | Level 5 | Pending
3 | 2017 | CAD | | Approved
All the fields in the Attribute and Instance are optional except id.
select
a.id,
max(case when a.key_info = 'amount' then a.value_info end) as amount,
max(case when a.key_info = 'currency' then a.value_info end) as currency,
max(case when a.key_info = 'level' then a.value_info end) as level,
i.status
from
attribute a
join instance i on a.id = i.id
group by
a.id,
i.status
Sql Fiddle
Starting in SQL Server 2005, the PIVOT function can perform this conversion from rows to columns:
select id,
amount,
currency,
level,
status
from
(
select i.id, i.status,
a.key_info,
a.value_info
from instance i
inner join attribute a
on i.id = a.id
) src
pivot
(
max(value_info)
for key_info in (amount, currency, level)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of key_values then you can use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(key_info)
from Attribute
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, ' + #cols + ', status from
(
select i.id, i.status,
a.key_info,
a.value_info
from instance i
inner join attribute a
on i.id = a.id
) x
pivot
(
max(value_info)
for key_info in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. Both give the result:
| ID | AMOUNT | CURRENCY | LEVEL | STATUS |
-----------------------------------------------
| 2 | 1009 | USD | Level 5 | Pending |
| 3 | 2017 | CAD | (null) | Approved |