Powerquery - reference Excel cell value within SQL query? - excel

I am trying to modify an excel worksheet that was given to me, with a connection to a SQL database that looks as follows:
select * from DB.AccountAssignments where Company_Code = '102'
How can I replace the static 102 value to reference a specific cell in Excel? For example, cell A1? The objective of course, being that when I change the 102 for a different value, the query will re-run without having to enter PowerQuery to edit the query it self each time.
NOTE: I found examples that involve filtering AFTER the query runs and pulls the data. However, the data source is HUGE so I need the Company Code parameter to be embedded within the query, so that the data brought into Powerquery is already filtered. That makes the difference between the query taking 5 seconds to run to 5 minutes to run.
I spent a couple hours attempting instructions on older posts, but have not been successful so far.

I had the same problem and it took me some time to understand how to implement Sharif's answer. So here are more detailed instructions based on the same idea:
Convert your cell into a named range (Formulas > Define Name)
Select the cell and go to Data > Get Data > From Other Sources > From Table/Range, which opens the Power Query Editor
Right-click on your cell value in the little preview table in the editor and select 'Drill Down'
Go to Home/File > Close & Load To (the 'To' is important here!). An 'Import Data' window pops up. Select 'Only create connection'.
Now you have a query parameter, that can be used in any other query.
Open the power query editor for your SQL query (Edit)
User the parameter in your query:
Source = Odbc.Query("dsn=SQLDSN", "select * from DB.AccountAssignments where Company_Code = " & NamedRangeCompanyCodeFromCell )
I got the error Expression.Error: We cannot apply operator & to types Text and Number. I solved that by changing the type of my query parameter: In the query of my parameter under Applied Steps > Changed Type, I changed Int64.Type to type text
Most likely, you will also need to give permission to run this query
Instead of creating a separate query parameter, it can also be created on the fly within the SQL query:
Open the Power Query Editor of your SQL query (Edit)
Open the advanced editor (Home > Advanced Editor)
Do all the necessary steps here, e.g.:
let
Params = Excel.CurrentWorkbook(){[Name="myNamedRange"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Params,{{"Column1", type text}}),
CodeFromCell = #"Changed Type"{0}[Column1],
Source = Odbc.Query("dsn=SQLDSN", "select * from DB.AccountAssignments where Company_Code = " & CodeFromCell)
in
Source

Convert cell A1 to Table from Range (Insert -> Table)
Name the table as param (or anything you like)
Use the below lines to use this table in your power query:
params = Excel.CurrentWorkbook(){[Name="params"]}[Content],
code = params{0}[value],
Use this in your SQL query:
select * from DB.AccountAssignments where Company_Code = code
Most likely you will have something similar to below line in your power query editor:
Source = Odbc.Query("dsn=SQLDSN", "select * from DB.AccountAssignments where Company_Code = " & code ),

Related

Getting "Please rebuild this data combination" on a computer but not on another one

This is my first try at using the Power Query... I've build a "dynamic" query in which I can change the retrieved fields as well as the filtering fields and values to be used by the query.
It's working perfectly on my computer but as soon as I try to execute it on another computer, I get the "Please rebuild this data combination" error. I saw some post saying I'll have to kind of split my query but I have not been able to figure it out.
Here is what my 2 tables look like:
Condition and fields selection
and here is my Query with the error:
Query
This might not be very elegant, but it allow me, thru a VBA script, to generate the list of fields to be retrieved and to generate the condition to be used by the SQL.
Any idea why it's not working on the other computers or how to improved the solution I'm using?
Thank you!
Notes:
Hi, all my Privacy Level are already set to 'None'.
I've tried to parametrize my code but I can't figure how. The Where condition is dynamic: it could be Where Number = "1234" but in other condition, the where might be like: 'Where Assignee = "xyz"'.
Here is a simplified example of my code:
let
Source = Sql.Database("xxxx", "yyyy", [Query=
"Select network, testid
from CM3T1M1 "
& paramConditions[Conditions]{0} &
" "])
in
Source
rebuild query, Formula.Firewall
That's a feature to prevent prevent accidentally leaking data. You can change the privacy level to ignore it
See also: docs.microsoft/dataprivacyfirewall
Is the dynamic query inserting those cells into the SQL query ? Report Parameters are nice for letting the user change variables without having to re-edit the query.
Parameterized native SQL queries
from: https://blog.crossjoin.co.uk/2016/12/11/passing-parameters-to-sql-queries-with-value-nativequery-in-power-query-and-power-bi/
let
Source = Sql.Database("localhost", "Adventure Works DW"),
Test = Value.NativeQuery(
Source,
"SELECT * FROM DimDate
WHERE EnglishMonthName=#MonthName AND
EnglishDayNameOfWeek=#DayName",
[
MonthName = "March",
DayName = "Tuesday"
]
)
in
Test
Dynamic Power Query version of SQL Query
To dynamically generate this SQL Query
select NUMBER, REQUESTED_BY from SourceTable
where NUMBER = 404115
Table.SelectRows is your Where.
SelectColumns is your select
let
Source = ...,
filterByNum = 404115,
columnNames = {"NUMBER", "REQUESTED_BY"},
removedColumns = Table.SelectColumns(
Source, columnNames, MissingField.Error
),
// I used 'MissingField.Error' so you know right away
// if there's a typo or bug
// assuming you are comparing Source[NUMBER]
filteredTable = Table.SelectRows(
Source, each [NUMBER] = filterByNum
)
in
filteredTable

Excel Power Query: Variables for Table Name

I'm trying to achieve something that seems like it should be fairly simple but I can't find an answer for... replace the name of a table or power query with a variable.
Currently trying to do this with a merge query so it would look something like this:
Table.NestedJoin(VARIABLE1,key1,VARIABLE2,key2,"Append",JoinKind.Inner)
Currently getting all sorts of errors no matter what I try...
Thank you!
// Edit:
Not really looking to do a function - hoping for users to utilize as easy as possible so they would be able to update a named table in the workbook, refresh, and then get a table as an output. Here is my current code - hopefully that'll help. My Region code replacements worked fine, but the Days replacements don't - I need each day (Monday-Thursday) to be replaced with my day variables (StartDay, Day2, etc.). Each of those has a separate text query referring back to the excel workbook inputs, and each of them should pull up a query based on the text (ex: StartDay = Monday so should pull the Monday query). This is the error I get, assuming that it is reading it as text "Monday" and not query Monday.
Expression.Error: We cannot convert the value "Monday" to type Table.
Details:
Value=Monday
Type=Type
let
ANDOriginCode = OriginRegion,
ANDDestinationCode = DestinationRegion,
ANDStartDay = StartDay,
ANDDay2 = Day2,
ANDDay3 = Day3,
ANDDay4 = Day4,
ANDDay5 = Day5,
Source = Table.NestedJoin(Monday,{"Tuesday Destination Region Code"},Tuesday,{"Tuesday Origin Region Code"},"Append1 (3)",JoinKind.Inner),
#"Filtered Rows1" = Table.SelectRows(Source, each [Monday Origin Region Code] = OriginRegion),
#"Removed Columns" = Table.RemoveColumns(#"Filtered Rows1",{"ID", "Pickup Day of Week", "Delivery Day of Week"}),
#"Expanded Append1 (3)" = Table.ExpandTableColumn(#"Removed Columns", "Append1 (3)", {"Tuesday Origin Region Code", "Wednesday Destination Region Code", "Tuesday Projected Number of Loads"}, {"Tuesday Origin Region Code", "Wednesday Destination Region Code", "Tuesday Projected Number of Loads"}),
#"Merged Queries" = Table.NestedJoin(#"Expanded Append1 (3)",{"Wednesday Destination Region Code"},Wednesday,{"Wednesday Origin Region Code"},"Append1 (4)",JoinKind.Inner),
#"Expanded Append1 (4)" = Table.ExpandTableColumn(#"Merged Queries", "Append1 (4)", {"Wednesday Origin Region Code", "Thursday Destination Region Code", "Wednesday Projected Number of Loads"}, {"Wednesday Origin Region Code", "Thursday Destination Region Code", "Wednesday Projected Number of Loads"})
#"Merged Queries1" = Table.NestedJoin(#"Expanded Append1 (4)",{"Thursday Destination Region Code"},Thursday,{"Thursday Origin Region Code"},"Append1 (5)",JoinKind.Inner)
in
#"Merged Queries1"
This might help:
let
Source = (VARIABLE1 as table, VARIABLE2 as table) => Table.NestedJoin(VARIABLE1, Key1, VARIABLE2, Key1, "Append", JoinKind.Inner)
in
Source
You can use parameters for Key1 and Key2. The function will prompt you to select your tables.
You can invoke it from any other query with:
Function.Invoke(Merge,{Table1,Table2})
Replace Merge with whatever you named the first query above and replace Table1 and Table2 with your target tables.
In case you're thinking of it, I have not been able to figure out how to pass tables from parameters. When you do that, the value you enter is recognized as text--for instance, "Table" versus Table--so it won't work. I could not find any information on how to pass a table value, like Table, in a variable. Anyhow, I hope this helps at least a little.
I was searching for this, too!
I finally found it, thanks to Chris Webb at https://blog.crossjoin.co.uk/2015/02/06/expression-evaluate-in-power-querym/
The key is using Expression.Evaluate with #shared as the second argument.
If you define Query1 as
let
Source = 1 + 1
in
Source
Query2 as
let
Source = 15 * 10
in
Source
define pIndex as a parameter that is "1" or "2", and
define QuerySwitch as
Expression.Evaluate("Query" & pIndex, #shared)
then QuerySwitch will return
2 when pIndex is "1"
150 when pIndex is "2"
My example:
I have a query QueryThatTakesFiveMinutes that
other queries use, and
writes to an Excel table (also named "QueryThatTakesFiveMinutes")
If I define a query "QueryThatTakesFiveMinutes Cached" by moving my cursor to the output QueryThatTakesFiveMinutes table in Excel and creating a new query from that table then, when I'm testing, I can change all the queries that use QueryThatTakesFiveMinutes to instead use #"QueryThatTakesFiveMinutes cached" and test downstream computation without waiting five minutes every time. Then I just need to remember to change it back when I'm ready.
But that was annoying.
I created a named range in Excel called "ProductionMode" that pointed to a specific cell that holds a value of either TRUE or FALSE
In Power-Query, I defined a very handy power query function called fNamedCellValue as
(rangeName as text) => Excel.CurrentWorkbook(){[Name=rangeName ]}[Content]{0}[Column1]
so that I can define a "ProductionMode" query as
fGetNamedCellValue("ProductionMode")
I use this in a way that's similar to the Index parameter above, but this way I can edit it via Excel.
When I defined "modeQueryThatTakesFiveMinutes" as
if ProductionMode then QueryThatTakesFiveMinutes else #"QueryThatTakesFiveMinutes Cached"
and changed all queries that use QueryThatTakesFiveMinutes to use modeQueryThatTakesFiveMinutes instead, I was very surprised to find that both QueryThatTakesFiveMinutes and #"QueryThatTakesFiveMinutes Cached" were evaluated and it didn't save any time at all!
So then after searching, being overjoyed to find your question only to realize it wasn't answered, then finding Chris Webb's article, I tried redefining modeQueryThatTakesFiveMinutes as
Expression.Evaluate(
if ProductionMode then
"QueryThatTakesFiveMinutes"
else
"#""QueryThatTakesFiveMinutes Cached""",
#shared
)
Unfortunately, instead of working, I got an error of
Formula.Firewall: Query 'modeQueryThatTakesFiveMinutes' references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
However, I found a way around this, too, by putting the offending code within a function that the consuming query executes.
Deleting ProductionMode and defining a new query fProductionMode of
() => fGetNamedCellValue("ProductionMode") as logical
now doesn't return true or false, it returns a function that will return true or false when evaluated. Why is one legal and the other isn't? I don't know, but it is! Change the definition of modeQueryThatTakesFiveMinutes to
Expression.Evaluate(
if fProductionMode() then
"QueryThatTakesFiveMinutes"
else
"#""QueryThatTakesFiveMinutes Cached""",
#shared
)
and it works!

How to pass parameterized value to OData feed in Excel Power Query

I have created a OData service which requires a filter.
This OData service is to be accessed from OData Feed option in Micorsoft Excel - Power Query.
OData URL -:
http://176.0.11.79:8000/sap/opu/odata/sap/Z_SALES_REPORT_TUBES_SRV/et_sales_report_tubesSet?$filter=
Spmon eq '20161101'
Now I need to pass the filter value of Spmon '20161101' as a parameter. This value is present in a different sheet in the same excel.
How to change the Query to allow the data to be passed from sheet rather than changing the URL every time.
Power Query will fold filters for OData, so you can use the autofilter or add the filter step yourself by adding a new step and adding the following formula through the formula bar:
= Table.SelectRows(PreviousStep, each [Spmon] = '20161101')
If it's from a different sheet that you loaded in a query SheetQuery, it will look like:
= Table.SelectRows(PreviousStep, each [Spmon] = SheetQuery{row_index}[column_name])
You will likely need to set the privacy levels for the OData source and worksheet, or you need to disable Privacy Levels through the Options dialog.
You can reference any cell in excel using the Excel.CurrentWorkbook function.
I prefer to name the cells and refer to them like below.
Then I build the uri.
I usually prefer to specify and edit the filters in excel as its easier to change that the mquery code. Technically the below should check if columns_1 and filters_1 are blank and not include in the URI.
company = Excel.CurrentWorkbook(){[Name="company_1"]}[Content][Column1]{0}
, service = Excel.CurrentWorkbook(){[Name="report_1"]}[Content][Column1]{0}
, columns_value = Excel.CurrentWorkbook(){[Name="columns_1"]}[Content][Column1]{0}
, filter_value = Excel.CurrentWorkbook(){[Name="filter_1"]}[Content][Column1]{0}
, selected_columns = "$select=" & columns_value
, filters = "&$filter=" & filter_value
, uri = "https://api.businesscentral.dynamics.com/v2.0/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/Production/ODataV4/Company('" & company & "')/"& service &"?" & selected_columns & filters
, Source = OData.Feed(uri, null, [Implementation="2.0"] )
I have found an alternate way of passing filters in Power Query (not directly from any sheet)
This was achieved by adding one more source in the Query and using this as variable in the OData Feed.
Spmon = "20161001" ,
Source = OData.Feed("http://176.0.11.79:8000/sap/opu/odata/sap/Z_SALES_REPORT_TUBES_SRV/
et_sales_report_tubesSet?$filter= Spmon eq '" & Spmon & "'")
This variable (Spmon) can be edited directly in the Advance Editor (Power Query) directly.

Query referencing another query

I can't execute a query in Power Query and the error that throws me is like:
Formula.Firewall: Query XXX references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
The code within this query is as below:
let
CallToFunction = myFunction,
#"Invoked Function" = CallToFunction(),
Source = Oracle.Database("myServer", [Query="SELECT * FROM myTable WHERE CustomerPK IN (" & #"Invoked Function" & ")"])
in
Source
myFunction is a function that uses a couple of other queries and eventually returns a string of primary keys that I can use to fill in the parenthesis of the WHERE clause of my SQL statement.
When I invoke the function alone it works correctly, so this must be an issue of how to call the function within the last query.
Any ideas?
You need to set the privacy settings of your data sources & workbook.
See https://support.office.com/en-ca/article/Privacy-levels-Power-Query-cc3ede4d-359e-4b28-bc72-9bee7900b540?ui=en-US&rs=en-CA&ad=CA

Excel Will not Import a Table From Access

I'm Using Excel/Access 2007. I made an Access query in Access, and I tried to import it into Excel using The Data Tab -> Get External Data Subtab -> From Access. I chose my Database, and chose the Query I wanted to import. However, only the headers of the table as well as 2 blank lines show up (as evidenced by their being formatted as a table). There is definitely data that is returned in the query, that I can verify by checking Access. Any help would be greatly appreciated
As an aside, can this be done programatically?
EDIT: Here is the SQL query in all it's glory
SELECT [Meter#], [LDC#], [ESCO#], [Brand], [LDCName], [RateClass], [RateSubClass], [CustName],
[DemandZone], [Type], dbo_Forecasts.Name AS ForecasForecastType,
Min(IntervalMeterConsumption.[DateFrom]) AS ConsumptionStart, IntermediateLog.[MaxOfDateRead] AS ConsumptionEnd,
Sum([kWh])/(Sum([Interval])/365) AS AverageAnnual
FROM (IntermediateLog
INNER JOIN (
(Premise INNER JOIN Meters ON Premise.PremiseCt = Meters.PremiseCt)
INNER JOIN IntervalMeterConsumption
ON Meters.Meterid = IntervalMeterConsumption.MeterID)
ON IntermediateLog.[LDC#] = Premise.CustomerPremiseNo)
INNER JOIN dbo_Forecasts ON Meters.ForecastID = dbo_Forecasts.ForecastID
WHERE ((([MaxOfDateRead]-[DateFrom])<=380))
GROUP BY IntermediateLog.[Meter#], IntermediateLog.[LDC#], IntermediateLog.[ESCO#],
IntermediateLog.Brand, IntermediateLog.LDCName, IntermediateLog.RateClass,
IntermediateLog.RateSubClass, IntermediateLog.CustName, IntermediateLog.DemandZone,
IntermediateLog.Type, dbo_Forecasts.Name, IntermediateLog.MaxOfDateRead;
You could try removing the where clause just temporarily to see if that makes a difference.
When you run the query in Access does it ask for a parameter?
The code to export a query to Excel from Access is as follows:
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12, "Query/Table Name", "c:\export.xls"
thanks Mark

Resources